mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-25 22:36:17 +03:00
Merge branch '814-normalize-database' into master
This commit is contained in:
commit
4e53d7ff93
73 changed files with 1231 additions and 284 deletions
15
.travis.yml
15
.travis.yml
|
@ -1,15 +0,0 @@
|
|||
language: android
|
||||
|
||||
jdk: oraclejdk8
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-29.0.2
|
||||
- android-29
|
||||
- extra-android-m2repository
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-29"
|
||||
|
||||
script: ./gradlew testDebug
|
|
@ -1,4 +1,4 @@
|
|||
# Nextcloud Notes
|
||||
# Nextcloud Notes for Android
|
||||
An android client for [Nextcloud Notes App](https://github.com/nextcloud/notes/).
|
||||
|
||||
[![Android CI](https://github.com/stefan-niedermann/nextcloud-notes/workflows/Android%20CI/badge.svg)](https://github.com/stefan-niedermann/nextcloud-notes/actions)
|
||||
|
@ -59,7 +59,7 @@ An android client for [Nextcloud Notes App](https://github.com/nextcloud/notes/)
|
|||
|
||||
## :link: Requirements
|
||||
* [Nextcloud](https://nextcloud.com/) instance running
|
||||
* [Nextcloud Files](https://github.com/nextcloud/android) app enabled (> 3.9.0)
|
||||
* [Nextcloud Android](https://github.com/nextcloud/android) app installed (> 3.9.0)
|
||||
* [Nextcloud Notes](https://github.com/nextcloud/notes) app enabled
|
||||
|
||||
## :notebook: License
|
||||
|
|
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId "it.niedermann.owncloud.notes"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 2011013
|
||||
versionName "2.11.13"
|
||||
versionCode 2012000
|
||||
versionName "2.12.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
|
17
app/src/dev/res/xml/shortcuts.xml
Normal file
17
app/src/dev/res/xml/shortcuts.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_add_blue_24dp"
|
||||
android:shortcutId="it.niedermann.owncloud.notes"
|
||||
android:shortcutLongLabel="@string/shortcut_create_long"
|
||||
android:shortcutShortLabel="@string/action_create"
|
||||
tools:targetApi="n_mr1">
|
||||
<intent
|
||||
android:action="android.intent.action.CREATE_DOCUMENT"
|
||||
android:targetClass="it.niedermann.owncloud.notes.android.activity.EditNoteActivity"
|
||||
android:targetPackage="it.niedermann.owncloud.notes.dev" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
|
@ -11,7 +11,7 @@
|
|||
android:allowBackup="true"
|
||||
android:fullBackupContent="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:label="@string/app_name_long"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
|
@ -20,6 +20,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".android.activity.SplashscreenActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -46,6 +47,17 @@
|
|||
android:value=".android.activity.NotesListViewActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.activity.AppendToNoteActivity"
|
||||
android:label="@string/append_to_note">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.activity.ExceptionActivity"
|
||||
android:label="@string/app_name" />
|
||||
|
|
|
@ -13,7 +13,7 @@ import it.niedermann.owncloud.notes.android.activity.ExceptionActivity;
|
|||
|
||||
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final String TAG = ExceptionHandler.class.getCanonicalName();
|
||||
private static final String TAG = ExceptionHandler.class.getSimpleName();
|
||||
private Context context;
|
||||
private Class<? extends Activity> errorActivity;
|
||||
public static final String KEY_THROWABLE = "T";
|
||||
|
|
|
@ -107,7 +107,7 @@ public class MultiSelectedActionModeCallback implements Callback {
|
|||
}
|
||||
return true;
|
||||
case R.id.menu_move:
|
||||
AccountChooserDialogFragment.newInstance().show(fragmentManager, NotesListViewActivity.class.getCanonicalName());
|
||||
AccountChooserDialogFragment.newInstance().show(fragmentManager, NotesListViewActivity.class.getSimpleName());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -24,7 +24,7 @@ import it.niedermann.owncloud.notes.persistence.NotesDatabase;
|
|||
|
||||
public class NotesListViewItemTouchHelper extends ItemTouchHelper {
|
||||
|
||||
private static final String TAG = NotesListViewItemTouchHelper.class.getCanonicalName();
|
||||
private static final String TAG = NotesListViewItemTouchHelper.class.getSimpleName();
|
||||
|
||||
public NotesListViewItemTouchHelper(
|
||||
@NonNull SingleSignOnAccount ssoAccount,
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package it.niedermann.owncloud.notes.android.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import it.niedermann.owncloud.notes.R;
|
||||
import it.niedermann.owncloud.notes.model.DBNote;
|
||||
|
||||
public class AppendToNoteActivity extends NotesListViewActivity {
|
||||
|
||||
private static final String TAG = AppendToNoteActivity.class.getSimpleName();
|
||||
|
||||
String receivedText = "";
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Intent receivedIntent = getIntent();
|
||||
receivedText = receivedIntent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
binding.activityNotesListView.toolbar.setTitle(R.string.append_to_note);
|
||||
binding.activityNotesListView.toolbar.setSubtitle(receivedText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoteClick(int position, View v) {
|
||||
if (receivedText != null && receivedText.length() > 0) {
|
||||
final DBNote note = db.getNote(localAccount.getId(), ((DBNote) adapter.getItem(position)).getId());
|
||||
final String oldContent = note.getContent();
|
||||
String newContent;
|
||||
if (oldContent != null && oldContent.length() > 0) {
|
||||
newContent = oldContent + "\n\n" + receivedText;
|
||||
} else {
|
||||
newContent = receivedText;
|
||||
}
|
||||
db.updateNoteAndSync(ssoAccount, localAccount.getId(), note, newContent, () -> Toast.makeText(this, getString(R.string.added_content, receivedText), Toast.LENGTH_SHORT).show());
|
||||
} else {
|
||||
Toast.makeText(this, R.string.shared_text_empty, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNoteLongClick(int position, View v) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ import it.niedermann.owncloud.notes.util.Notes;
|
|||
|
||||
public abstract class LockedActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = LockedActivity.class.getCanonicalName();
|
||||
private static final String TAG = LockedActivity.class.getSimpleName();
|
||||
|
||||
private static final int REQUEST_CODE_UNLOCK = 100;
|
||||
|
||||
|
|
|
@ -37,10 +37,12 @@ import com.google.android.material.snackbar.Snackbar;
|
|||
import com.nextcloud.android.sso.AccountImporter;
|
||||
import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
|
||||
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
||||
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -49,7 +51,9 @@ import it.niedermann.owncloud.notes.R;
|
|||
import it.niedermann.owncloud.notes.android.MultiSelectedActionModeCallback;
|
||||
import it.niedermann.owncloud.notes.android.NotesListViewItemTouchHelper;
|
||||
import it.niedermann.owncloud.notes.android.fragment.AccountChooserAdapter.AccountChooserListener;
|
||||
import it.niedermann.owncloud.notes.android.fragment.ExceptionDialogFragment;
|
||||
import it.niedermann.owncloud.notes.databinding.DrawerLayoutBinding;
|
||||
import it.niedermann.owncloud.notes.model.Capabilities;
|
||||
import it.niedermann.owncloud.notes.model.Category;
|
||||
import it.niedermann.owncloud.notes.model.DBNote;
|
||||
import it.niedermann.owncloud.notes.model.ISyncCallback;
|
||||
|
@ -58,6 +62,8 @@ import it.niedermann.owncloud.notes.model.ItemAdapter;
|
|||
import it.niedermann.owncloud.notes.model.LocalAccount;
|
||||
import it.niedermann.owncloud.notes.model.NavigationAdapter;
|
||||
import it.niedermann.owncloud.notes.model.NavigationAdapter.NavigationItem;
|
||||
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
|
||||
import it.niedermann.owncloud.notes.persistence.CapabilitiesWorker;
|
||||
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask;
|
||||
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask.NotesLoadedListener;
|
||||
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper;
|
||||
|
@ -91,8 +97,8 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
*/
|
||||
private boolean notAuthorizedAccountHandled = false;
|
||||
|
||||
private SingleSignOnAccount ssoAccount;
|
||||
private LocalAccount localAccount;
|
||||
protected SingleSignOnAccount ssoAccount;
|
||||
protected LocalAccount localAccount;
|
||||
|
||||
protected DrawerLayoutBinding binding;
|
||||
|
||||
|
@ -103,6 +109,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
|
||||
protected ItemAdapter adapter = null;
|
||||
|
||||
protected NotesDatabase db = null;
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
private NavigationAdapter adapterCategories;
|
||||
private NavigationItem itemRecent;
|
||||
|
@ -111,7 +118,6 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
private Category navigationSelection = new Category(null, null);
|
||||
private String navigationOpen = "";
|
||||
private ActionMode mActionMode;
|
||||
private NotesDatabase db = null;
|
||||
private SearchView searchView = null;
|
||||
private final ISyncCallback syncCallBack = () -> {
|
||||
adapter.clearSelection(listView);
|
||||
|
@ -127,6 +133,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
CapabilitiesWorker.update(this);
|
||||
binding = DrawerLayoutBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
this.coordinatorLayout = binding.activityNotesListView.activityNotesListView;
|
||||
|
@ -211,19 +218,25 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
fabCreate.hide();
|
||||
SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName);
|
||||
localAccount = db.getLocalAccountByAccountName(accountName);
|
||||
try {
|
||||
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
|
||||
new NotesListViewItemTouchHelper(ssoAccount, this, db, adapter, syncCallBack, this::refreshLists, swipeRefreshLayout, this).attachToRecyclerView(listView);
|
||||
synchronize();
|
||||
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
|
||||
Log.i(TAG, "Tried to select account, but got an " + e.getClass().getSimpleName() + ". Asking for importing an account...");
|
||||
handleNotAuthorizedAccount();
|
||||
if (localAccount != null) {
|
||||
try {
|
||||
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
|
||||
new NotesListViewItemTouchHelper(ssoAccount, this, db, adapter, syncCallBack, this::refreshLists, swipeRefreshLayout, this).attachToRecyclerView(listView);
|
||||
synchronize();
|
||||
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
|
||||
Log.i(TAG, "Tried to select account, but got an " + e.getClass().getSimpleName() + ". Asking for importing an account...");
|
||||
handleNotAuthorizedAccount();
|
||||
}
|
||||
refreshLists();
|
||||
fabCreate.show();
|
||||
setupHeader();
|
||||
setupNavigationList(ADAPTER_KEY_RECENT);
|
||||
updateUsernameInDrawer();
|
||||
} else {
|
||||
if (!notAuthorizedAccountHandled) {
|
||||
handleNotAuthorizedAccount();
|
||||
}
|
||||
}
|
||||
refreshLists();
|
||||
fabCreate.show();
|
||||
setupHeader();
|
||||
setupNavigationList(ADAPTER_KEY_RECENT);
|
||||
updateUsernameInDrawer();
|
||||
}
|
||||
|
||||
private void handleNotAuthorizedAccount() {
|
||||
|
@ -277,7 +290,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
}
|
||||
|
||||
private void setupActionBar() {
|
||||
Toolbar toolbar = binding.activityNotesListView.notesListActivityActionBar;
|
||||
Toolbar toolbar = binding.activityNotesListView.toolbar;
|
||||
setSupportActionBar(toolbar);
|
||||
drawerToggle = new ActionBarDrawerToggle(this, binding.drawerLayout, toolbar, R.string.action_drawer_open, R.string.action_drawer_close);
|
||||
drawerToggle.setDrawerIndicatorEnabled(true);
|
||||
|
@ -308,7 +321,27 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
Log.i(TAG, "Clearing Glide disk cache");
|
||||
Glide.get(getApplicationContext()).clearDiskCache();
|
||||
}).start();
|
||||
synchronize();
|
||||
new Thread(() -> {
|
||||
Log.i(TAG, "Refreshing capabilities for " + ssoAccount.name);
|
||||
final Capabilities capabilities;
|
||||
try {
|
||||
capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, localAccount.getCapabilitiesETag());
|
||||
db.updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
|
||||
db.updateBrand(localAccount.getId(), capabilities);
|
||||
db.updateBrand(localAccount.getId(), capabilities);
|
||||
db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
|
||||
Log.i(TAG, capabilities.toString());
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
Log.i(TAG, "Capabilities not modified.");
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
// Even if the capabilities endpoint makes trouble, we can still try to synchronize the notes
|
||||
synchronize();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -657,38 +690,63 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
|
|||
//not need because of db.synchronisation in createActivity
|
||||
|
||||
Bundle bundle = data.getExtras();
|
||||
if (bundle != null) {
|
||||
DBNote createdNote = (DBNote) data.getExtras().getSerializable(CREATED_NOTE);
|
||||
if (bundle != null && bundle.containsKey(CREATED_NOTE)) {
|
||||
DBNote createdNote = (DBNote) bundle.getSerializable(CREATED_NOTE);
|
||||
if (createdNote != null) {
|
||||
adapter.add(createdNote);
|
||||
} else {
|
||||
Log.w(TAG, "createdNote is null");
|
||||
Log.w(TAG, "createdNote must not be null");
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "bundle is null");
|
||||
Log.w(TAG, "Provide at least " + CREATED_NOTE);
|
||||
}
|
||||
}
|
||||
listView.scrollToPosition(0);
|
||||
} else if (requestCode == server_settings) {
|
||||
// Recreate activity completely, because theme switchting makes problems when only invalidating the views.
|
||||
// Recreate activity completely, because theme switching makes problems when only invalidating the views.
|
||||
// @see https://github.com/stefan-niedermann/nextcloud-notes/issues/529
|
||||
recreate();
|
||||
} else {
|
||||
try {
|
||||
AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> {
|
||||
Log.v(TAG, "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId);
|
||||
try {
|
||||
db.addAccount(account.url, account.userId, account.name);
|
||||
} catch (SQLiteConstraintException e) {
|
||||
if (db.getAccounts().size() > 1) { // TODO ideally only show snackbar when this is a not migrated account
|
||||
Snackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show();
|
||||
AccountImporter.onActivityResult(requestCode, resultCode, data, this, (ssoAccount) -> {
|
||||
CapabilitiesWorker.update(this);
|
||||
new Thread(() -> {
|
||||
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);
|
||||
db.addAccount(ssoAccount.url, ssoAccount.userId, ssoAccount.name, capabilities);
|
||||
Log.i(TAG, capabilities.toString());
|
||||
runOnUiThread(() -> {
|
||||
selectAccount(ssoAccount.name);
|
||||
this.accountChooserActive = false;
|
||||
binding.accountChooser.setVisibility(View.GONE);
|
||||
binding.accountNavigation.setVisibility(View.VISIBLE);
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START);
|
||||
});
|
||||
} catch (SQLiteConstraintException e) {
|
||||
if (db.getAccounts().size() > 1) { // TODO ideally only show snackbar when this is a not migrated account
|
||||
runOnUiThread(() -> {
|
||||
Snackbar.make(coordinatorLayout, R.string.account_already_imported, Snackbar.LENGTH_LONG).show();
|
||||
selectAccount(ssoAccount.name);
|
||||
this.accountChooserActive = false;
|
||||
binding.accountChooser.setVisibility(View.GONE);
|
||||
binding.accountNavigation.setVisibility(View.VISIBLE);
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START);
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
runOnUiThread(() -> {
|
||||
this.accountChooserActive = true;
|
||||
binding.accountChooser.setVisibility(View.VISIBLE);
|
||||
binding.accountNavigation.setVisibility(View.GONE);
|
||||
binding.drawerLayout.openDrawer(GravityCompat.START);
|
||||
binding.activityNotesListView.progressCircular.setVisibility(View.GONE);
|
||||
ExceptionDialogFragment.newInstance(e).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
|
||||
});
|
||||
}
|
||||
}
|
||||
selectAccount(account.name);
|
||||
this.accountChooserActive = false;
|
||||
binding.accountChooser.setVisibility(View.GONE);
|
||||
binding.accountNavigation.setVisibility(View.VISIBLE);
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START);
|
||||
}).start();
|
||||
});
|
||||
} catch (AccountImportCancelledException e) {
|
||||
Log.i(TAG, "AccountImport has been cancelled.");
|
||||
|
|
|
@ -29,7 +29,7 @@ public class SelectSingleNoteActivity extends NotesListViewActivity {
|
|||
setResult(Activity.RESULT_CANCELED);
|
||||
|
||||
fabCreate.setVisibility(View.GONE);
|
||||
Toolbar toolbar = binding.activityNotesListView.notesListActivityActionBar;
|
||||
Toolbar toolbar = binding.activityNotesListView.toolbar;
|
||||
SwipeRefreshLayout swipeRefreshLayout = binding.activityNotesListView.swiperefreshlayout;
|
||||
toolbar.setTitle(R.string.activity_select_single_note);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
|
|
|
@ -24,7 +24,7 @@ import it.niedermann.owncloud.notes.util.Notes;
|
|||
import static it.niedermann.owncloud.notes.android.appwidget.NoteListWidget.DARK_THEME_KEY;
|
||||
|
||||
public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
|
||||
private static final String TAG = NoteListWidgetFactory.class.getCanonicalName();
|
||||
private static final String TAG = NoteListWidgetFactory.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final int displayMode;
|
||||
|
|
|
@ -38,7 +38,7 @@ public class AccountChooserDialogFragment extends AppCompatDialogFragment implem
|
|||
if (context instanceof AccountChooserListener) {
|
||||
this.accountChooserListener = (AccountChooserListener) context;
|
||||
} else {
|
||||
throw new ClassCastException("Caller must implement " + AccountChooserListener.class.getCanonicalName());
|
||||
throw new ClassCastException("Caller must implement " + AccountChooserListener.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
|||
showCategorySelector();
|
||||
return true;
|
||||
case R.id.menu_move:
|
||||
AccountChooserDialogFragment.newInstance().show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getCanonicalName());
|
||||
AccountChooserDialogFragment.newInstance().show(requireActivity().getSupportFragmentManager(), BaseNoteFragment.class.getSimpleName());
|
||||
return true;
|
||||
case R.id.menu_share:
|
||||
Intent shareIntent = new Intent();
|
||||
|
|
|
@ -72,7 +72,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
|
|||
} else if (getActivity() instanceof CategoryDialogListener) {
|
||||
listener = (CategoryDialogListener) getActivity();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getCanonicalName());
|
||||
throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getSimpleName());
|
||||
}
|
||||
db = NotesDatabase.getInstance(getActivity());
|
||||
}
|
||||
|
|
|
@ -85,8 +85,13 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment {
|
|||
} else if (t instanceof NextcloudHttpRequestFailedException) {
|
||||
int statusCode = ((NextcloudHttpRequestFailedException) t).getStatusCode();
|
||||
switch (statusCode) {
|
||||
case 302:
|
||||
adapter.add(R.string.error_dialog_server_app_enabled);
|
||||
adapter.add(R.string.error_dialog_redirect);
|
||||
break;
|
||||
case 500:
|
||||
adapter.add(R.string.error_dialog_check_server_logs);
|
||||
break;
|
||||
case 503:
|
||||
adapter.add(R.string.error_dialog_check_maintenance);
|
||||
break;
|
||||
|
@ -100,10 +105,7 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment {
|
|||
return new AlertDialog.Builder(requireActivity())
|
||||
.setView(binding.getRoot())
|
||||
.setTitle(R.string.error_dialog_title)
|
||||
.setPositiveButton(android.R.string.copy, (a, b) -> {
|
||||
copyToClipboard(requireContext(), getString(R.string.simple_exception), "```\n" + debugInfos + "\n```");
|
||||
a.dismiss();
|
||||
})
|
||||
.setPositiveButton(android.R.string.copy, (a, b) -> copyToClipboard(requireContext(), getString(R.string.simple_exception), "```\n" + debugInfos + "\n```"))
|
||||
.setNegativeButton(R.string.simple_close, null)
|
||||
.create();
|
||||
}
|
||||
|
@ -116,6 +118,16 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment {
|
|||
return fragment;
|
||||
}
|
||||
|
||||
public static DialogFragment newInstance(Throwable exception) {
|
||||
final Bundle args = new Bundle();
|
||||
final ArrayList<Throwable> list = new ArrayList<>(1);
|
||||
list.add(exception);
|
||||
args.putSerializable(KEY_THROWABLES, list);
|
||||
final DialogFragment fragment = new ExceptionDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private static class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -203,6 +203,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
|
|||
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext());
|
||||
db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, () -> {
|
||||
note = db.getNote(note.getAccountId(), note.getId());
|
||||
changedText = note.getContent();
|
||||
binding.singleNoteContent.setText(parseCompat(markdownProcessor, replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
|
||||
binding.swiperefreshlayout.setRefreshing(false);
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ import it.niedermann.owncloud.notes.R;
|
|||
|
||||
public abstract class SearchableBaseNoteFragment extends BaseNoteFragment {
|
||||
|
||||
private static final String TAG = SearchableBaseNoteFragment.class.getCanonicalName();
|
||||
private static final String TAG = SearchableBaseNoteFragment.class.getSimpleName();
|
||||
private static final String saved_instance_key_searchQuery = "searchQuery";
|
||||
private static final String saved_instance_key_currentOccurrence = "currentOccurrence";
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.io.InputStream;
|
|||
@GlideModule
|
||||
public final class SingleSignOnLibraryGlideModule extends LibraryGlideModule {
|
||||
|
||||
private static final String TAG = SingleSignOnLibraryGlideModule.class.getCanonicalName();
|
||||
private static final String TAG = SingleSignOnLibraryGlideModule.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void registerComponents(
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class SingleSignOnUrlLoader implements ModelLoader<GlideUrl, InputStream> {
|
||||
|
||||
private static final String TAG = SingleSignOnUrlLoader.class.getCanonicalName();
|
||||
private static final String TAG = SingleSignOnUrlLoader.class.getSimpleName();
|
||||
private final NextcloudAPI client;
|
||||
|
||||
// Public API.
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package it.niedermann.owncloud.notes.model;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class ApiVersion implements Comparable<ApiVersion> {
|
||||
private static final Pattern NUMBER_EXTRACTION_PATTERN = Pattern.compile("[0-9]+");
|
||||
|
||||
private String originalVersion = "?";
|
||||
private int major = 0;
|
||||
private int minor = 0;
|
||||
|
||||
public ApiVersion(String originalVersion, int major, int minor) {
|
||||
this(major, minor);
|
||||
this.originalVersion = originalVersion;
|
||||
}
|
||||
|
||||
public ApiVersion(int major, int minor) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public String getOriginalVersion() {
|
||||
return originalVersion;
|
||||
}
|
||||
|
||||
public static ApiVersion of(String versionString) {
|
||||
int major = 0, minor = 0;
|
||||
if (versionString != null) {
|
||||
String[] split = versionString.split("\\.");
|
||||
if (split.length > 0) {
|
||||
major = extractNumber(split[0]);
|
||||
if (split.length > 1) {
|
||||
minor = extractNumber(split[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ApiVersion(versionString, major, minor);
|
||||
}
|
||||
|
||||
private static int extractNumber(String containsNumbers) {
|
||||
final Matcher matcher = NUMBER_EXTRACTION_PATTERN.matcher(containsNumbers);
|
||||
if (matcher.find()) {
|
||||
return Integer.parseInt(matcher.group());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param compare another version object
|
||||
* @return -1 if the compared major version is <strong>higher</strong> than the current major version
|
||||
* 0 if the compared major version is equal to the current major version
|
||||
* 1 if the compared major version is <strong>lower</strong> than the current major version
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(ApiVersion compare) {
|
||||
if (compare.getMajor() > getMajor()) {
|
||||
return -1;
|
||||
} else if (compare.getMajor() < getMajor()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Version{" +
|
||||
"originalVersion='" + originalVersion + '\'' +
|
||||
", major=" + major +
|
||||
", minor=" + minor +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package it.niedermann.owncloud.notes.model;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.load.HttpException;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* This entity class is used to return relevant data of the HTTP reponse.
|
||||
*/
|
||||
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;
|
||||
private String color = null;
|
||||
private String textColor = null;
|
||||
@Nullable
|
||||
private String eTag;
|
||||
|
||||
public Capabilities(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)) {
|
||||
this.color = theming.getString(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR);
|
||||
}
|
||||
if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT)) {
|
||||
this.textColor = theming.getString(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String getApiVersion() {
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public String getTextColor() {
|
||||
return textColor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getETag() {
|
||||
return eTag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Capabilities{" +
|
||||
"apiVersion='" + apiVersion + '\'' +
|
||||
", color='" + color + '\'' +
|
||||
", textColor='" + textColor + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -25,8 +25,6 @@ public class CloudNote implements Serializable {
|
|||
|
||||
public CloudNote(long remoteId, Calendar modified, String title, String content, boolean favorite, String category, String etag) {
|
||||
this.remoteId = remoteId;
|
||||
if (title != null)
|
||||
setTitle(title);
|
||||
setTitle(title);
|
||||
setContent(content);
|
||||
setFavorite(favorite);
|
||||
|
|
|
@ -22,7 +22,7 @@ import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
|||
|
||||
public class ItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private static final String TAG = ItemAdapter.class.getCanonicalName();
|
||||
private static final String TAG = ItemAdapter.class.getSimpleName();
|
||||
|
||||
private static final int section_type = 0;
|
||||
private static final int note_type = 1;
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
package it.niedermann.owncloud.notes.model;
|
||||
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import it.niedermann.owncloud.notes.persistence.NotesClient;
|
||||
|
||||
public class LocalAccount {
|
||||
|
||||
|
@ -9,7 +22,13 @@ public class LocalAccount {
|
|||
private String accountName;
|
||||
private String url;
|
||||
private String etag;
|
||||
private String capabilitiesETag;
|
||||
private long modified;
|
||||
private ApiVersion preferredApiVersion;
|
||||
@ColorInt
|
||||
private int color;
|
||||
@ColorInt
|
||||
private int textColor;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
|
@ -59,6 +78,62 @@ public class LocalAccount {
|
|||
this.modified = modified;
|
||||
}
|
||||
|
||||
public ApiVersion getPreferredApiVersion() {
|
||||
return preferredApiVersion;
|
||||
}
|
||||
|
||||
public String getCapabilitiesETag() {
|
||||
return capabilitiesETag;
|
||||
}
|
||||
|
||||
public void setCapabilitiesETag(String capabilitiesETag) {
|
||||
this.capabilitiesETag = capabilitiesETag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param availableApiVersions <code>["0.2", "1.0", ...]</code>
|
||||
*/
|
||||
public void setPreferredApiVersion(@Nullable String availableApiVersions) {
|
||||
// TODO move this logic to NotesClient?
|
||||
try {
|
||||
if (availableApiVersions == null) {
|
||||
this.preferredApiVersion = null;
|
||||
return;
|
||||
}
|
||||
JSONArray versionsArray = new JSONArray(availableApiVersions);
|
||||
Collection<ApiVersion> supportedApiVersions = new HashSet<>(versionsArray.length());
|
||||
for (int i = 0; i < versionsArray.length(); i++) {
|
||||
ApiVersion parsedApiVersion = ApiVersion.of(versionsArray.getString(i));
|
||||
for (ApiVersion temp : NotesClient.SUPPORTED_API_VERSIONS) {
|
||||
if (temp.compareTo(parsedApiVersion) == 0) {
|
||||
supportedApiVersions.add(parsedApiVersion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.preferredApiVersion = Collections.max(supportedApiVersions);
|
||||
} catch (JSONException | NoSuchElementException e) {
|
||||
e.printStackTrace();
|
||||
this.preferredApiVersion = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(@ColorInt int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getTextColor() {
|
||||
return textColor;
|
||||
}
|
||||
|
||||
public void setTextColor(@ColorInt int textColor) {
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -68,7 +143,11 @@ public class LocalAccount {
|
|||
", accountName='" + accountName + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
", etag='" + etag + '\'' +
|
||||
", modified='" + modified + '\'' +
|
||||
", modified=" + modified +
|
||||
", preferredApiVersion='" + preferredApiVersion + '\'' +
|
||||
", color=" + color +
|
||||
", textColor=" + textColor +
|
||||
", capabilitiesETag=" + capabilitiesETag +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.util.Log;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
@ -32,7 +33,7 @@ import it.niedermann.owncloud.notes.util.NoteUtil;
|
|||
abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
||||
private static final String TAG = AbstractNotesDatabase.class.getSimpleName();
|
||||
|
||||
private static final int database_version = 13;
|
||||
private static final int database_version = 14;
|
||||
@NonNull
|
||||
private final Context context;
|
||||
|
||||
|
@ -57,6 +58,7 @@ abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
|||
protected static final String key_favorite = "FAVORITE";
|
||||
protected static final String key_category = "CATEGORY";
|
||||
protected static final String key_etag = "ETAG";
|
||||
protected static final String key_capabilities_etag = "CAPABILITIES_ETAG";
|
||||
protected static final String key_color = "COLOR";
|
||||
protected static final String key_text_color = "TEXT_COLOR";
|
||||
protected static final String key_api_version = "API_VERSION";
|
||||
|
@ -116,7 +118,9 @@ abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
|||
key_api_version + " TEXT, " +
|
||||
key_color + " VARCHAR(6) NOT NULL DEFAULT '000000', " +
|
||||
key_text_color + " VARCHAR(6) NOT NULL DEFAULT '0082C9', " +
|
||||
"FOREIGN KEY(" + key_id + ") REFERENCES " + table_category + "(" + key_category_account_id + "));");
|
||||
key_capabilities_etag + " TEXT, " +
|
||||
" FOREIGN KEY(" + key_id + ") REFERENCES " + table_category + "(" + key_category_account_id + "));");
|
||||
|
||||
createAccountIndexes(db);
|
||||
}
|
||||
|
||||
|
@ -173,7 +177,17 @@ abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
|||
}
|
||||
if (oldVersion < 9) {
|
||||
// Create accounts table
|
||||
createAccountTable(db);
|
||||
db.execSQL("CREATE TABLE " + table_accounts + " ( " +
|
||||
key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
key_url + " TEXT, " +
|
||||
key_username + " TEXT, " +
|
||||
key_account_name + " TEXT UNIQUE, " +
|
||||
key_etag + " TEXT, " +
|
||||
key_modified + " INTEGER, " +
|
||||
key_color + " VARCHAR(6) NOT NULL DEFAULT '000000', " +
|
||||
key_text_color + " VARCHAR(6) NOT NULL DEFAULT '0082C9', " +
|
||||
key_capabilities_etag + " TEXT)");
|
||||
createAccountIndexes(db);
|
||||
|
||||
// Add accountId to notes table
|
||||
db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0");
|
||||
|
@ -291,6 +305,11 @@ abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
|||
CapabilitiesWorker.update(context);
|
||||
}
|
||||
if (oldVersion < 13) {
|
||||
db.execSQL("ALTER TABLE " + table_accounts + " ADD COLUMN " + key_capabilities_etag + " TEXT");
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelUniqueWork("it.niedermann.owncloud.notes.persistence.SyncWorker");
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelUniqueWork("SyncWorker");
|
||||
}
|
||||
if (oldVersion < 14) {
|
||||
// Rename a tmp_NOTES table.
|
||||
String tmpTableNotes = String.format("tmp_%s", table_notes);
|
||||
db.execSQL("ALTER TABLE " + table_notes + " RENAME TO " + tmpTableNotes);
|
||||
|
@ -337,7 +356,6 @@ abstract class AbstractNotesDatabase extends SQLiteOpenHelper {
|
|||
createCategoryIndexes(db);
|
||||
createNotesIndexes(db);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package it.niedermann.owncloud.notes.persistence;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||
import com.nextcloud.android.sso.api.AidlNetworkRequest;
|
||||
import com.nextcloud.android.sso.api.Response;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.Capabilities;
|
||||
|
||||
@WorkerThread
|
||||
public class CapabilitiesClient {
|
||||
|
||||
private static final String TAG = CapabilitiesClient.class.getSimpleName();
|
||||
|
||||
private static final int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30090000;
|
||||
|
||||
protected static final String HEADER_KEY_IF_NONE_MATCH = "If-None-Match";
|
||||
protected static final String HEADER_KEY_ETAG = "ETag";
|
||||
|
||||
private static final String API_PATH = "/ocs/v2.php/cloud/capabilities";
|
||||
private static final String METHOD_GET = "GET";
|
||||
private static final String PARAM_KEY_FORMAT = "format";
|
||||
private static final String PARAM_VALUE_JSON = "json";
|
||||
|
||||
private static final Map<String, String> parameters = new HashMap<>();
|
||||
|
||||
static {
|
||||
parameters.put(PARAM_KEY_FORMAT, PARAM_VALUE_JSON);
|
||||
}
|
||||
|
||||
public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag) throws Exception {
|
||||
final NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
|
||||
.setMethod(METHOD_GET)
|
||||
.setUrl(API_PATH)
|
||||
.setParameter(parameters);
|
||||
|
||||
final Map<String, List<String>> header = new HashMap<>();
|
||||
if (lastETag != null && !lastETag.isEmpty()) {
|
||||
header.put(HEADER_KEY_IF_NONE_MATCH, Collections.singletonList('"' + lastETag + '"'));
|
||||
requestBuilder.setHeader(header);
|
||||
}
|
||||
|
||||
final NextcloudRequest nextcloudRequest = requestBuilder.build();
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
try {
|
||||
Log.v(TAG, ssoAccount.name + " → " + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
|
||||
final Response response = SSOClient.requestFilesApp(context.getApplicationContext(), ssoAccount, nextcloudRequest);
|
||||
Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
|
||||
|
||||
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
response.getBody().close();
|
||||
|
||||
String etag = null;
|
||||
final AidlNetworkRequest.PlainHeader eTagHeader = response.getPlainHeader(HEADER_KEY_ETAG);
|
||||
if (eTagHeader != null) {
|
||||
etag = eTagHeader.getValue().replace("\"", "");
|
||||
}
|
||||
|
||||
return new Capabilities(result.toString(), etag);
|
||||
} catch (NullPointerException e) {
|
||||
final PackageInfo pInfo = context.getApplicationContext().getPackageManager().getPackageInfo("com.nextcloud.client", 0);
|
||||
if (pInfo.versionCode < MIN_NEXTCLOUD_FILES_APP_VERSION_CODE) {
|
||||
throw new NextcloudFilesAppNotSupportedException();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,17 +13,19 @@ import androidx.work.Worker;
|
|||
import androidx.work.WorkerParameters;
|
||||
|
||||
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.model.SingleSignOnAccount;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.Capabilities;
|
||||
import it.niedermann.owncloud.notes.model.LocalAccount;
|
||||
|
||||
public class CapabilitiesWorker extends Worker {
|
||||
|
||||
private static final String TAG = Objects.requireNonNull(CapabilitiesWorker.class.getCanonicalName());
|
||||
private static final String TAG = Objects.requireNonNull(CapabilitiesWorker.class.getSimpleName());
|
||||
private static final String WORKER_TAG = "capabilities";
|
||||
|
||||
private static final Constraints constraints = new Constraints.Builder()
|
||||
|
@ -40,13 +42,25 @@ public class CapabilitiesWorker extends Worker {
|
|||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
NotesDatabase db = NotesDatabase.getInstance(getApplicationContext());
|
||||
final NotesDatabase db = NotesDatabase.getInstance(getApplicationContext());
|
||||
for (LocalAccount account : db.getAccounts()) {
|
||||
try {
|
||||
SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());
|
||||
final SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());
|
||||
Log.i(TAG, "Refreshing capabilities for " + ssoAccount.name);
|
||||
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||
final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, account.getCapabilitiesETag());
|
||||
db.updateCapabilitiesETag(account.getId(), capabilities.getETag());
|
||||
db.updateBrand(account.getId(), capabilities);
|
||||
db.updateApiVersion(account.getId(), capabilities.getApiVersion());
|
||||
Log.i(TAG, capabilities.toString());
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NextcloudHttpRequestFailedException) {
|
||||
if (((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
Log.i(TAG, "Capabilities not modified.");
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
e.printStackTrace();
|
||||
return Result.failure();
|
||||
}
|
||||
}
|
||||
return Result.success();
|
||||
|
@ -54,12 +68,12 @@ public class CapabilitiesWorker extends Worker {
|
|||
|
||||
public static void update(@NonNull Context context) {
|
||||
deregister(context);
|
||||
Log.i(TAG, "Registering worker running each 24 hours.");
|
||||
Log.i(TAG, "Registering capabilities worker running each 24 hours.");
|
||||
WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
|
||||
}
|
||||
|
||||
private static void deregister(@NonNull Context context) {
|
||||
Log.i(TAG, "Deregistering all workers with tag \"" + WORKER_TAG + "\"");
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelAllWorkByTag(WORKER_TAG);
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelUniqueWork(WORKER_TAG);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ public class NoteServerSyncHelper {
|
|||
// current state of the synchronization
|
||||
private final Map<String, Boolean> syncActive = new HashMap<>();
|
||||
private final Map<String, Boolean> syncScheduled = new HashMap<>();
|
||||
private final NotesClient notesClient;
|
||||
|
||||
// list of callbacks for both parts of synchronziation
|
||||
private final Map<String, List<ISyncCallback>> callbacksPush = new HashMap<>();
|
||||
|
@ -101,7 +100,6 @@ public class NoteServerSyncHelper {
|
|||
private NoteServerSyncHelper(NotesDatabase db) {
|
||||
this.db = db;
|
||||
this.context = db.getContext();
|
||||
notesClient = new NotesClient(context.getApplicationContext());
|
||||
this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
|
||||
|
||||
// Registers BroadcastReceiver to track network connection changes.
|
||||
|
@ -199,7 +197,6 @@ public class NoteServerSyncHelper {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a synchronization and start it directly, if the network is connected and no
|
||||
* synchronization is currently running.
|
||||
|
@ -208,7 +205,7 @@ public class NoteServerSyncHelper {
|
|||
*/
|
||||
public void scheduleSync(SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
|
||||
if (ssoAccount == null) {
|
||||
Log.i(TAG, "ssoAccount is null. Is this a local account?");
|
||||
Log.i(TAG, SingleSignOnAccount.class.getSimpleName() + " is null. Is this a local account?");
|
||||
} else {
|
||||
if (syncActive.get(ssoAccount.name) == null) {
|
||||
syncActive.put(ssoAccount.name, false);
|
||||
|
@ -216,12 +213,13 @@ public class NoteServerSyncHelper {
|
|||
Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (Boolean.TRUE.equals(syncActive.get(ssoAccount.name)) ? "sync active" : "sync NOT active") + ") ...");
|
||||
if (isSyncPossible() && (!Boolean.TRUE.equals(syncActive.get(ssoAccount.name)) || onlyLocalChanges)) {
|
||||
Log.d(TAG, "... starting now");
|
||||
final LocalAccount account = db.getLocalAccountByAccountName(ssoAccount.name);
|
||||
if (account == null) {
|
||||
Log.e(TAG, "LocalAccount for ssoAccount \"" + ssoAccount.name + "\" is null. Cannot synchronize.", new IllegalStateException());
|
||||
final LocalAccount localAccount = db.getLocalAccountByAccountName(ssoAccount.name);
|
||||
if (localAccount == null) {
|
||||
Log.e(TAG, LocalAccount.class.getSimpleName() + " for ssoAccount \"" + ssoAccount.name + "\" is null. Cannot synchronize.", new IllegalStateException());
|
||||
return;
|
||||
}
|
||||
SyncTask syncTask = new SyncTask(account, ssoAccount, onlyLocalChanges);
|
||||
final NotesClient notesClient = NotesClient.newInstance(localAccount.getPreferredApiVersion(), context);
|
||||
final SyncTask syncTask = new SyncTask(notesClient, localAccount, ssoAccount, onlyLocalChanges);
|
||||
syncTask.addCallbacks(ssoAccount, callbacksPush.get(ssoAccount.name));
|
||||
callbacksPush.put(ssoAccount.name, new ArrayList<>());
|
||||
if (!onlyLocalChanges) {
|
||||
|
@ -260,13 +258,13 @@ public class NoteServerSyncHelper {
|
|||
|
||||
private void updateNetworkStatus() {
|
||||
try {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
final ConnectivityManager connMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
if (connMgr == null) {
|
||||
throw new NetworkErrorException("ConnectivityManager is null");
|
||||
}
|
||||
|
||||
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
|
||||
final NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
|
||||
|
||||
if (activeInfo == null) {
|
||||
throw new NetworkErrorException("NetworkInfo is null");
|
||||
|
@ -275,7 +273,7 @@ public class NoteServerSyncHelper {
|
|||
if (activeInfo.isConnected()) {
|
||||
networkConnected = true;
|
||||
|
||||
NetworkInfo networkInfo = connMgr.getNetworkInfo((ConnectivityManager.TYPE_WIFI));
|
||||
final NetworkInfo networkInfo = connMgr.getNetworkInfo((ConnectivityManager.TYPE_WIFI));
|
||||
|
||||
if (networkInfo == null) {
|
||||
throw new NetworkErrorException("connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) is null");
|
||||
|
@ -305,8 +303,11 @@ public class NoteServerSyncHelper {
|
|||
* Synchronization consists of two parts: pushLocalChanges and pullRemoteChanges.
|
||||
*/
|
||||
private class SyncTask extends AsyncTask<Void, Void, SyncResultStatus> {
|
||||
@NonNull
|
||||
private final NotesClient notesClient;
|
||||
@NonNull
|
||||
private final LocalAccount localAccount;
|
||||
@NonNull
|
||||
private final SingleSignOnAccount ssoAccount;
|
||||
private final boolean onlyLocalChanges;
|
||||
@NonNull
|
||||
|
@ -314,7 +315,8 @@ public class NoteServerSyncHelper {
|
|||
@NonNull
|
||||
private final ArrayList<Throwable> exceptions = new ArrayList<>();
|
||||
|
||||
SyncTask(@NonNull LocalAccount localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
|
||||
SyncTask(@NonNull NotesClient notesClient, @NonNull LocalAccount localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
|
||||
this.notesClient = notesClient;
|
||||
this.localAccount = localAccount;
|
||||
this.ssoAccount = ssoAccount;
|
||||
this.onlyLocalChanges = onlyLocalChanges;
|
||||
|
@ -364,19 +366,19 @@ public class NoteServerSyncHelper {
|
|||
case LOCAL_EDITED:
|
||||
Log.v(TAG, " ...create/edit");
|
||||
if (note.getRemoteId() > 0) {
|
||||
Log.v(TAG, " ...Note has remoteId -> try to edit");
|
||||
Log.v(TAG, " ...Note has remoteId → try to edit");
|
||||
try {
|
||||
remoteNote = notesClient.editNote(ssoAccount, note).getNote();
|
||||
} catch (NextcloudHttpRequestFailedException e) {
|
||||
if (e.getStatusCode() == HTTP_NOT_FOUND) {
|
||||
Log.v(TAG, " ...Note does no longer exist on server -> recreate");
|
||||
Log.v(TAG, " ...Note does no longer exist on server → recreate");
|
||||
remoteNote = notesClient.createNote(ssoAccount, note).getNote();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.v(TAG, " ...Note does not have a remoteId yet -> create");
|
||||
Log.v(TAG, " ...Note does not have a remoteId yet → create");
|
||||
remoteNote = notesClient.createNote(ssoAccount, note).getNote();
|
||||
}
|
||||
// Please note, that db.updateNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
|
||||
|
@ -412,7 +414,7 @@ public class NoteServerSyncHelper {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof TokenMismatchException) {
|
||||
NotesClient.invalidateAPICache(ssoAccount);
|
||||
SSOClient.invalidateAPICache(ssoAccount);
|
||||
}
|
||||
exceptions.add(e);
|
||||
success = false;
|
||||
|
@ -427,8 +429,8 @@ public class NoteServerSyncHelper {
|
|||
private boolean pullRemoteChanges() {
|
||||
Log.d(TAG, "pullRemoteChanges() for account " + localAccount.getAccountName());
|
||||
try {
|
||||
Map<Long, Long> idMap = db.getIdMap(localAccount.getId());
|
||||
ServerResponse.NotesResponse response = notesClient.getNotes(ssoAccount, localAccount.getModified(), localAccount.getEtag());
|
||||
final Map<Long, Long> idMap = db.getIdMap(localAccount.getId());
|
||||
final ServerResponse.NotesResponse response = notesClient.getNotes(ssoAccount, localAccount.getModified(), localAccount.getEtag());
|
||||
List<CloudNote> remoteNotes = response.getNotes();
|
||||
Set<Long> remoteIDs = new HashSet<>();
|
||||
// pull remote changes: update or create each remote note
|
||||
|
@ -438,7 +440,7 @@ public class NoteServerSyncHelper {
|
|||
if (remoteNote.getModified() == null) {
|
||||
Log.v(TAG, " ... unchanged");
|
||||
} else if (idMap.containsKey(remoteNote.getRemoteId())) {
|
||||
Log.v(TAG, " ... found -> Update");
|
||||
Log.v(TAG, " ... found → Update");
|
||||
Long remoteId = idMap.get(remoteNote.getRemoteId());
|
||||
if (remoteId != null) {
|
||||
db.updateNote(localAccount, remoteId, remoteNote, null);
|
||||
|
@ -464,6 +466,13 @@ public class NoteServerSyncHelper {
|
|||
localAccount.setModified(response.getLastModified());
|
||||
db.updateETag(localAccount.getId(), localAccount.getEtag());
|
||||
db.updateModified(localAccount.getId(), localAccount.getModified());
|
||||
try {
|
||||
if (db.updateApiVersion(localAccount.getId(), response.getSupportedApiVersions())) {
|
||||
localAccount.setPreferredApiVersion(response.getSupportedApiVersions());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
return true;
|
||||
} catch (NextcloudHttpRequestFailedException e) {
|
||||
Log.d(TAG, "Server returned HTTP Status Code " + e.getStatusCode() + " - " + e.getMessage());
|
||||
|
@ -475,7 +484,7 @@ public class NoteServerSyncHelper {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof TokenMismatchException) {
|
||||
NotesClient.invalidateAPICache(ssoAccount);
|
||||
SSOClient.invalidateAPICache(ssoAccount);
|
||||
}
|
||||
exceptions.add(e);
|
||||
return false;
|
||||
|
@ -492,7 +501,7 @@ public class NoteServerSyncHelper {
|
|||
if (context instanceof ViewProvider && context instanceof AppCompatActivity) {
|
||||
Snackbar.make(((ViewProvider) context).getView(), R.string.error_synchronization, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(exceptions)
|
||||
.show(((AppCompatActivity) context).getSupportFragmentManager(), ExceptionDialogFragment.class.getCanonicalName()))
|
||||
.show(((AppCompatActivity) context).getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,11 @@ import android.content.pm.PackageInfo;
|
|||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||
import com.nextcloud.android.sso.api.AidlNetworkRequest;
|
||||
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||
import com.nextcloud.android.sso.api.Response;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
@ -26,17 +25,76 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.ApiVersion;
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@WorkerThread
|
||||
public class NotesClient {
|
||||
public abstract class NotesClient {
|
||||
|
||||
final static int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30090000;
|
||||
private static final String TAG = NotesClient.class.getSimpleName();
|
||||
|
||||
private final Context appContext;
|
||||
private static final Map<String, NextcloudAPI> mNextcloudAPIs = new HashMap<>();
|
||||
protected final Context appContext;
|
||||
|
||||
protected static final String GET_PARAM_KEY_PRUNE_BEFORE = "pruneBefore";
|
||||
|
||||
protected static final String HEADER_KEY_ETAG = "ETag";
|
||||
protected static final String HEADER_KEY_LAST_MODIFIED = "Last-Modified";
|
||||
protected static final String HEADER_KEY_CONTENT_TYPE = "Content-Type";
|
||||
protected static final String HEADER_KEY_IF_NONE_MATCH = "If-None-Match";
|
||||
protected static final String HEADER_KEY_X_NOTES_API_VERSIONS = "X-Notes-API-Versions";
|
||||
|
||||
protected static final String HEADER_VALUE_APPLICATION_JSON = "application/json";
|
||||
|
||||
protected static final String METHOD_GET = "GET";
|
||||
protected static final String METHOD_PUT = "PUT";
|
||||
protected static final String METHOD_POST = "POST";
|
||||
protected static final String METHOD_DELETE = "DELETE";
|
||||
|
||||
public static final String JSON_ID = "id";
|
||||
public static final String JSON_TITLE = "title";
|
||||
public static final String JSON_CONTENT = "content";
|
||||
public static final String JSON_FAVORITE = "favorite";
|
||||
public static final String JSON_CATEGORY = "category";
|
||||
public static final String JSON_ETAG = "etag";
|
||||
public static final String JSON_MODIFIED = "modified";
|
||||
|
||||
public static final ApiVersion[] SUPPORTED_API_VERSIONS = new ApiVersion[]{
|
||||
new ApiVersion(1, 0),
|
||||
new ApiVersion(0, 2)
|
||||
};
|
||||
|
||||
public static NotesClient newInstance(@Nullable ApiVersion preferredApiVersion,
|
||||
@NonNull Context appContext) {
|
||||
if (preferredApiVersion == null) {
|
||||
Log.i(TAG, "apiVersion is null, using " + NotesClientV02.class.getSimpleName());
|
||||
return new NotesClientV02(appContext);
|
||||
// } else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[0]) == 0) {
|
||||
// Log.i(TAG, "Using " + NotesClient_1_0.class.getSimpleName());
|
||||
// return new NotesClient_1_0(appContext);
|
||||
} else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[1]) == 0) {
|
||||
Log.i(TAG, "Using " + NotesClientV02.class.getSimpleName());
|
||||
return new NotesClientV02(appContext);
|
||||
}
|
||||
Log.w(TAG, "Unsupported API version " + preferredApiVersion + " - try using " + NotesClientV02.class.getSimpleName());
|
||||
return new NotesClientV02(appContext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public NotesClient(@NonNull Context appContext) {
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
abstract NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception;
|
||||
|
||||
abstract NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception;
|
||||
|
||||
abstract NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception;
|
||||
|
||||
abstract void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception;
|
||||
|
||||
/**
|
||||
* This entity class is used to return relevant data of the HTTP reponse.
|
||||
|
@ -44,12 +102,14 @@ public class NotesClient {
|
|||
public static class ResponseData {
|
||||
private final String content;
|
||||
private final String etag;
|
||||
private final String supportedApiVersions;
|
||||
private final long lastModified;
|
||||
|
||||
ResponseData(String content, String etag, long lastModified) {
|
||||
ResponseData(@NonNull String content, String etag, long lastModified, @Nullable String supportedApiVersions) {
|
||||
this.content = content;
|
||||
this.etag = etag;
|
||||
this.lastModified = lastModified;
|
||||
this.supportedApiVersions = supportedApiVersions;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
|
@ -63,68 +123,13 @@ public class NotesClient {
|
|||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
public String getSupportedApiVersions() {
|
||||
return this.supportedApiVersions;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String API_PATH = "/index.php/apps/notes/api/v0.2/";
|
||||
|
||||
private static final String GET_PARAM_KEY_PRUNE_BEFORE = "pruneBefore";
|
||||
|
||||
private static final String HEADER_KEY_ETAG = "ETag";
|
||||
private static final String HEADER_KEY_LAST_MODIFIED = "Last-Modified";
|
||||
private static final String HEADER_KEY_CONTENT_TYPE = "Content-Type";
|
||||
private static final String HEADER_KEY_IF_NONE_MATCH = "If-None-Match";
|
||||
|
||||
private static final String HEADER_VALUE_APPLICATION_JSON = "application/json";
|
||||
|
||||
private static final String METHOD_GET = "GET";
|
||||
private static final String METHOD_PUT = "PUT";
|
||||
private static final String METHOD_POST = "POST";
|
||||
private static final String METHOD_DELETE = "DELETE";
|
||||
|
||||
public static final String JSON_ID = "id";
|
||||
public static final String JSON_TITLE = "title";
|
||||
public static final String JSON_CONTENT = "content";
|
||||
public static final String JSON_FAVORITE = "favorite";
|
||||
public static final String JSON_CATEGORY = "category";
|
||||
public static final String JSON_ETAG = "etag";
|
||||
public static final String JSON_MODIFIED = "modified";
|
||||
|
||||
NotesClient(Context appContext) {
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception {
|
||||
Map<String, String> parameter = new HashMap<>();
|
||||
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified));
|
||||
return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
|
||||
}
|
||||
|
||||
private NoteResponse putNote(SingleSignOnAccount ssoAccount, CloudNote note, String path, String method) throws Exception {
|
||||
JSONObject paramObject = new JSONObject();
|
||||
paramObject.accumulate(JSON_CONTENT, note.getContent());
|
||||
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000);
|
||||
paramObject.accumulate(JSON_FAVORITE, note.isFavorite());
|
||||
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
|
||||
return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Note on the Server
|
||||
*
|
||||
* @param note {@link CloudNote} - the new Note
|
||||
* @return Created Note including generated Title, ID and lastModified-Date
|
||||
*/
|
||||
NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
return putNote(ssoAccount, note, "notes", METHOD_POST);
|
||||
}
|
||||
|
||||
NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
|
||||
}
|
||||
|
||||
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
|
||||
this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
|
||||
}
|
||||
abstract protected String getApiPath();
|
||||
|
||||
/**
|
||||
* Request-Method for POST, PUT with or without JSON-Object-Parameter
|
||||
|
@ -136,15 +141,15 @@ public class NotesClient {
|
|||
* @param lastETag optional ETag of last response
|
||||
* @return Body of answer
|
||||
*/
|
||||
private ResponseData requestServer(SingleSignOnAccount ssoAccount, String target, String method, Map<String, String> parameter, JSONObject requestBody, String lastETag) throws Exception {
|
||||
NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
|
||||
protected ResponseData requestServer(SingleSignOnAccount ssoAccount, String target, String method, Map<String, String> parameter, JSONObject requestBody, String lastETag) throws Exception {
|
||||
final NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
|
||||
.setMethod(method)
|
||||
.setUrl(API_PATH + target);
|
||||
.setUrl(getApiPath() + target);
|
||||
if (parameter != null) {
|
||||
requestBuilder.setParameter(parameter);
|
||||
}
|
||||
|
||||
Map<String, List<String>> header = new HashMap<>();
|
||||
final Map<String, List<String>> header = new HashMap<>();
|
||||
if (requestBody != null) {
|
||||
header.put(HEADER_KEY_CONTENT_TYPE, Collections.singletonList(HEADER_VALUE_APPLICATION_JSON));
|
||||
requestBuilder.setRequestBody(requestBody.toString());
|
||||
|
@ -154,15 +159,15 @@ public class NotesClient {
|
|||
requestBuilder.setHeader(header);
|
||||
}
|
||||
|
||||
NextcloudRequest nextcloudRequest = requestBuilder.build();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
final NextcloudRequest nextcloudRequest = requestBuilder.build();
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
try {
|
||||
Log.v(TAG, ssoAccount.name + " => " + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
|
||||
Response response = getNextcloudAPI(appContext, ssoAccount).performNetworkRequestV2(nextcloudRequest);
|
||||
Log.v(TAG, ssoAccount.name + " → " + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
|
||||
final Response response = SSOClient.requestFilesApp(appContext, ssoAccount, nextcloudRequest);
|
||||
Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
|
||||
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
|
||||
|
||||
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
|
||||
String line;
|
||||
while ((line = rd.readLine()) != null) {
|
||||
result.append(line);
|
||||
|
@ -170,21 +175,27 @@ public class NotesClient {
|
|||
response.getBody().close();
|
||||
|
||||
String etag = "";
|
||||
AidlNetworkRequest.PlainHeader eTagHeader = response.getPlainHeader(HEADER_KEY_ETAG);
|
||||
final AidlNetworkRequest.PlainHeader eTagHeader = response.getPlainHeader(HEADER_KEY_ETAG);
|
||||
if (eTagHeader != null) {
|
||||
etag = Objects.requireNonNull(eTagHeader.getValue()).replace("\"", "");
|
||||
}
|
||||
|
||||
long lastModified = 0;
|
||||
AidlNetworkRequest.PlainHeader lastModifiedHeader = response.getPlainHeader(HEADER_KEY_LAST_MODIFIED);
|
||||
final AidlNetworkRequest.PlainHeader lastModifiedHeader = response.getPlainHeader(HEADER_KEY_LAST_MODIFIED);
|
||||
if (lastModifiedHeader != null)
|
||||
lastModified = new Date(lastModifiedHeader.getValue()).getTime() / 1000;
|
||||
Log.d(TAG, "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + lastModified + ")");
|
||||
|
||||
String supportedApiVersions = null;
|
||||
final AidlNetworkRequest.PlainHeader supportedApiVersionsHeader = response.getPlainHeader(HEADER_KEY_X_NOTES_API_VERSIONS);
|
||||
if (supportedApiVersionsHeader != null) {
|
||||
supportedApiVersions = Objects.requireNonNull(supportedApiVersionsHeader.getValue()).replace("\"", "");
|
||||
}
|
||||
|
||||
// return these header fields since they should only be saved after successful processing the result!
|
||||
return new ResponseData(result.toString(), etag, lastModified);
|
||||
return new ResponseData(result.toString(), etag, lastModified, supportedApiVersions);
|
||||
} catch (NullPointerException e) {
|
||||
int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30090000;
|
||||
PackageInfo pInfo = appContext.getPackageManager().getPackageInfo("com.nextcloud.client", 0);
|
||||
final PackageInfo pInfo = appContext.getPackageManager().getPackageInfo("com.nextcloud.client", 0);
|
||||
if (pInfo.versionCode < MIN_NEXTCLOUD_FILES_APP_VERSION_CODE) {
|
||||
throw new NextcloudFilesAppNotSupportedException();
|
||||
} else {
|
||||
|
@ -192,57 +203,4 @@ public class NotesClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NextcloudAPI getNextcloudAPI(Context appContext, SingleSignOnAccount ssoAccount) {
|
||||
if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
|
||||
return mNextcloudAPIs.get(ssoAccount.name);
|
||||
} else {
|
||||
Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name);
|
||||
NextcloudAPI nextcloudAPI = new NextcloudAPI(appContext, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.v(TAG, "SSO API connected for " + ssoAccount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
mNextcloudAPIs.put(ssoAccount.name, nextcloudAPI);
|
||||
return nextcloudAPI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates thes API cache for the given ssoAccount
|
||||
*
|
||||
* @param ssoAccount the ssoAccount for which the API cache should be cleared.
|
||||
*/
|
||||
public static void invalidateAPICache(@NonNull SingleSignOnAccount ssoAccount) {
|
||||
Log.v(TAG, "Invalidating API cache for " + ssoAccount.name);
|
||||
if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
|
||||
final NextcloudAPI nextcloudAPI = mNextcloudAPIs.get(ssoAccount.name);
|
||||
if (nextcloudAPI != null) {
|
||||
nextcloudAPI.stop();
|
||||
}
|
||||
mNextcloudAPIs.remove(ssoAccount.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the whole API cache for all accounts
|
||||
*/
|
||||
public static void invalidateAPICache() {
|
||||
for (String key : mNextcloudAPIs.keySet()) {
|
||||
Log.v(TAG, "Invalidating API cache for " + key);
|
||||
if (mNextcloudAPIs.containsKey(key)) {
|
||||
final NextcloudAPI nextcloudAPI = mNextcloudAPIs.get(key);
|
||||
if (nextcloudAPI != null) {
|
||||
nextcloudAPI.stop();
|
||||
}
|
||||
mNextcloudAPIs.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package it.niedermann.owncloud.notes.persistence;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse;
|
||||
|
||||
@WorkerThread
|
||||
public class NotesClientV02 extends NotesClient {
|
||||
|
||||
private static final String API_PATH = "/index.php/apps/notes/api/v0.2/";
|
||||
|
||||
NotesClientV02(@NonNull Context appContext) {
|
||||
super(appContext);
|
||||
}
|
||||
|
||||
NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception {
|
||||
Map<String, String> parameter = new HashMap<>();
|
||||
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified));
|
||||
return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
|
||||
}
|
||||
|
||||
private NoteResponse putNote(SingleSignOnAccount ssoAccount, CloudNote note, String path, String method) throws Exception {
|
||||
JSONObject paramObject = new JSONObject();
|
||||
paramObject.accumulate(JSON_CONTENT, note.getContent());
|
||||
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000);
|
||||
paramObject.accumulate(JSON_FAVORITE, note.isFavorite());
|
||||
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
|
||||
return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
|
||||
}
|
||||
|
||||
NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
return putNote(ssoAccount, note, "notes", METHOD_POST);
|
||||
}
|
||||
|
||||
NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
|
||||
}
|
||||
|
||||
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
|
||||
this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiPath() {
|
||||
return API_PATH;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package it.niedermann.owncloud.notes.persistence;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse;
|
||||
import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse;
|
||||
|
||||
@WorkerThread
|
||||
public class NotesClientV1 extends NotesClient {
|
||||
|
||||
private static final String API_PATH = "/index.php/apps/notes/api/v1/";
|
||||
|
||||
NotesClientV1(@NonNull Context appContext) {
|
||||
super(appContext);
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
|
||||
throw new UnsupportedOperationException("Not implemented yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiPath() {
|
||||
return API_PATH;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import android.database.Cursor;
|
|||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
@ -23,6 +24,9 @@ import com.nextcloud.android.sso.AccountImporter;
|
|||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
@ -37,6 +41,8 @@ import it.niedermann.owncloud.notes.R;
|
|||
import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
|
||||
import it.niedermann.owncloud.notes.android.appwidget.NoteListWidget;
|
||||
import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget;
|
||||
import it.niedermann.owncloud.notes.model.ApiVersion;
|
||||
import it.niedermann.owncloud.notes.model.Capabilities;
|
||||
import it.niedermann.owncloud.notes.model.CloudNote;
|
||||
import it.niedermann.owncloud.notes.model.DBNote;
|
||||
import it.niedermann.owncloud.notes.model.DBStatus;
|
||||
|
@ -665,12 +671,13 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param url URL to the root of the used Nextcloud instance without trailing slash
|
||||
* @param username Username of the account
|
||||
* @param accountName Composed by the username and the host of the URL, separated by @-sign
|
||||
* @param url URL to the root of the used Nextcloud instance without trailing slash
|
||||
* @param username Username of the account
|
||||
* @param accountName Composed by the username and the host of the URL, separated by @-sign
|
||||
* @param capabilities
|
||||
* @throws SQLiteConstraintException in case accountName already exists
|
||||
*/
|
||||
public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName) throws SQLiteConstraintException {
|
||||
public void addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) throws SQLiteConstraintException {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(key_url, url);
|
||||
|
@ -685,9 +692,9 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
*/
|
||||
public LocalAccount getAccount(long accountId) {
|
||||
validateAccountId(accountId);
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, key_id + " = ?", new String[]{accountId + ""}, null, null, null, null);
|
||||
LocalAccount account = new LocalAccount();
|
||||
final SQLiteDatabase db = getReadableDatabase();
|
||||
final Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified, key_api_version, key_color, key_text_color, key_capabilities_etag}, key_id + " = ?", new String[]{String.valueOf(accountId)}, null, null, null, null);
|
||||
final LocalAccount account = new LocalAccount();
|
||||
while (cursor.moveToNext()) {
|
||||
account.setId(cursor.getLong(0));
|
||||
account.setUrl(cursor.getString(1));
|
||||
|
@ -695,15 +702,19 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
account.setUserName(cursor.getString(3));
|
||||
account.setETag(cursor.getString(4));
|
||||
account.setModified(cursor.getLong(5));
|
||||
account.setPreferredApiVersion(cursor.getString(6));
|
||||
account.setColor(Color.parseColor('#' + cursor.getString(7)));
|
||||
account.setTextColor(Color.parseColor('#' + cursor.getString(8)));
|
||||
account.setCapabilitiesETag(cursor.getString(9));
|
||||
}
|
||||
cursor.close();
|
||||
return account;
|
||||
}
|
||||
|
||||
public List<LocalAccount> getAccounts() {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, null, null, null, null, null);
|
||||
List<LocalAccount> accounts = new ArrayList<>();
|
||||
final SQLiteDatabase db = getReadableDatabase();
|
||||
final Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified, key_api_version, key_color, key_text_color, key_capabilities_etag}, null, null, null, null, null);
|
||||
final List<LocalAccount> accounts = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
LocalAccount account = new LocalAccount();
|
||||
account.setId(cursor.getLong(0));
|
||||
|
@ -712,6 +723,10 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
account.setUserName(cursor.getString(3));
|
||||
account.setETag(cursor.getString(4));
|
||||
account.setModified(cursor.getLong(5));
|
||||
account.setPreferredApiVersion(cursor.getString(6));
|
||||
account.setColor(Color.parseColor('#' + cursor.getString(7)));
|
||||
account.setTextColor(Color.parseColor('#' + cursor.getString(8)));
|
||||
account.setCapabilitiesETag(cursor.getString(9));
|
||||
accounts.add(account);
|
||||
}
|
||||
cursor.close();
|
||||
|
@ -719,24 +734,97 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public LocalAccount getLocalAccountByAccountName(String accountName) {
|
||||
public LocalAccount getLocalAccountByAccountName(String accountName) throws IllegalArgumentException {
|
||||
if (accountName == null) {
|
||||
Log.e(TAG, "accountName is null");
|
||||
return null;
|
||||
}
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified}, key_account_name + " = ?", new String[]{accountName}, null, null, null, null);
|
||||
LocalAccount account = new LocalAccount();
|
||||
final SQLiteDatabase db = getReadableDatabase();
|
||||
final Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_etag, key_modified, key_api_version, key_color, key_text_color, key_capabilities_etag}, key_account_name + " = ?", new String[]{accountName}, null, null, null, null);
|
||||
final LocalAccount account = new LocalAccount();
|
||||
int numberEntries = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
numberEntries++;
|
||||
account.setId(cursor.getLong(0));
|
||||
account.setUrl(cursor.getString(1));
|
||||
account.setAccountName(cursor.getString(2));
|
||||
account.setUserName(cursor.getString(3));
|
||||
account.setETag(cursor.getString(4));
|
||||
account.setModified(cursor.getLong(5));
|
||||
account.setPreferredApiVersion(cursor.getString(6));
|
||||
account.setColor(Color.parseColor('#' + cursor.getString(7)));
|
||||
account.setTextColor(Color.parseColor('#' + cursor.getString(8)));
|
||||
account.setCapabilitiesETag(cursor.getString(9));
|
||||
}
|
||||
cursor.close();
|
||||
return account;
|
||||
switch (numberEntries) {
|
||||
case 0:
|
||||
Log.w(TAG, "Could not find any account for \"" + accountName + "\". Returning null.");
|
||||
return null;
|
||||
case 1:
|
||||
return account;
|
||||
default:
|
||||
Log.e(TAG, "", new IllegalArgumentException("Expected to find 1 account for name \"" + accountName + "\", but found " + numberEntries + "."));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBrand(long accountId, @NonNull Capabilities capabilities) throws IllegalArgumentException {
|
||||
validateAccountId(accountId);
|
||||
// Validate color format
|
||||
Color.parseColor(capabilities.getColor());
|
||||
Color.parseColor(capabilities.getTextColor());
|
||||
|
||||
final SQLiteDatabase db = this.getWritableDatabase();
|
||||
final ContentValues values = new ContentValues();
|
||||
|
||||
values.put(key_color, capabilities.getColor().substring(1));
|
||||
values.put(key_text_color, capabilities.getTextColor().substring(1));
|
||||
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{String.valueOf(accountId)});
|
||||
if (updatedRows == 1) {
|
||||
Log.v(TAG, "Updated " + key_color + " to " + capabilities.getColor() + " and " + key_text_color + " to " + capabilities.getTextColor() + " for " + key_account_id + " = " + accountId);
|
||||
} else {
|
||||
Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and " + key_color + " = " + capabilities.getColor() + " and " + key_text_color + " = " + capabilities.getTextColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param apiVersion has to be a JSON array as a string <code>["0.2", "1.0", ...]</code>
|
||||
* @return whether or not the given apiVersion have been written to the database
|
||||
* @throws IllegalArgumentException if the apiVersion does not match the expected format
|
||||
*/
|
||||
public boolean updateApiVersion(long accountId, @Nullable String apiVersion) throws IllegalArgumentException {
|
||||
validateAccountId(accountId);
|
||||
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 SQLiteDatabase db = this.getWritableDatabase();
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(key_api_version, apiVersion);
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{String.valueOf(accountId)});
|
||||
if (updatedRows == 1) {
|
||||
Log.i(TAG, "Updated " + key_api_version + " to \"" + apiVersion + "\" for accountId = " + accountId);
|
||||
} else {
|
||||
Log.e(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.");
|
||||
} catch (JSONException e) {
|
||||
throw new IllegalArgumentException("API version must contain be a JSON array.");
|
||||
}
|
||||
} else {
|
||||
Log.v(TAG, "Given API version is null. Do not update database");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -755,10 +843,10 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
}
|
||||
|
||||
try {
|
||||
NotesClient.invalidateAPICache(AccountImporter.getSingleSignOnAccount(getContext(), localAccount.getAccountName()));
|
||||
SSOClient.invalidateAPICache(AccountImporter.getSingleSignOnAccount(getContext(), localAccount.getAccountName()));
|
||||
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
NotesClient.invalidateAPICache();
|
||||
SSOClient.invalidateAPICache();
|
||||
}
|
||||
|
||||
final int deletedNotes = db.delete(table_notes, key_account_id + " = ?", new String[]{String.valueOf(localAccount.getId())});
|
||||
|
@ -770,7 +858,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(key_etag, etag);
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{accountId + ""});
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{String.valueOf(accountId)});
|
||||
if (updatedRows == 1) {
|
||||
Log.v(TAG, "Updated etag to " + etag + " for accountId = " + accountId);
|
||||
} else {
|
||||
|
@ -778,6 +866,19 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
public void updateCapabilitiesETag(long accountId, String capabilitiesETag) {
|
||||
validateAccountId(accountId);
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(key_capabilities_etag, capabilitiesETag);
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{String.valueOf(accountId)});
|
||||
if (updatedRows == 1) {
|
||||
Log.v(TAG, "Updated etag to " + capabilitiesETag + " for accountId = " + accountId);
|
||||
} else {
|
||||
Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and capabilitiesETag = " + capabilitiesETag);
|
||||
}
|
||||
}
|
||||
|
||||
void updateModified(long accountId, long modified) {
|
||||
validateAccountId(accountId);
|
||||
if (modified < 0) {
|
||||
|
@ -786,7 +887,7 @@ public class NotesDatabase extends AbstractNotesDatabase {
|
|||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(key_modified, modified);
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{accountId + ""});
|
||||
final int updatedRows = db.update(table_accounts, values, key_id + " = ?", new String[]{String.valueOf(accountId)});
|
||||
if (updatedRows == 1) {
|
||||
Log.v(TAG, "Updated modified to " + modified + " for accountId = " + accountId);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package it.niedermann.owncloud.notes.persistence;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||
import com.nextcloud.android.sso.api.Response;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@WorkerThread
|
||||
public class SSOClient {
|
||||
|
||||
private static final String TAG = SSOClient.class.getSimpleName();
|
||||
|
||||
private static final Map<String, NextcloudAPI> mNextcloudAPIs = new HashMap<>();
|
||||
|
||||
public static Response requestFilesApp(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @NonNull NextcloudRequest nextcloudRequest) throws Exception {
|
||||
return getNextcloudAPI(context.getApplicationContext(), ssoAccount).performNetworkRequestV2(nextcloudRequest);
|
||||
}
|
||||
|
||||
private static NextcloudAPI getNextcloudAPI(Context appContext, SingleSignOnAccount ssoAccount) {
|
||||
if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
|
||||
return mNextcloudAPIs.get(ssoAccount.name);
|
||||
} else {
|
||||
Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name);
|
||||
final NextcloudAPI nextcloudAPI = new NextcloudAPI(appContext, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.i(TAG, "SSO API connected for " + ssoAccount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
mNextcloudAPIs.put(ssoAccount.name, nextcloudAPI);
|
||||
return nextcloudAPI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates thes API cache for the given ssoAccount
|
||||
*
|
||||
* @param ssoAccount the ssoAccount for which the API cache should be cleared.
|
||||
*/
|
||||
public static void invalidateAPICache(@NonNull SingleSignOnAccount ssoAccount) {
|
||||
Log.v(TAG, "Invalidating API cache for " + ssoAccount.name);
|
||||
if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
|
||||
final NextcloudAPI nextcloudAPI = mNextcloudAPIs.get(ssoAccount.name);
|
||||
if (nextcloudAPI != null) {
|
||||
nextcloudAPI.stop();
|
||||
}
|
||||
mNextcloudAPIs.remove(ssoAccount.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the whole API cache for all accounts
|
||||
*/
|
||||
public static void invalidateAPICache() {
|
||||
for (String key : mNextcloudAPIs.keySet()) {
|
||||
Log.v(TAG, "Invalidating API cache for " + key);
|
||||
if (mNextcloudAPIs.containsKey(key)) {
|
||||
final NextcloudAPI nextcloudAPI = mNextcloudAPIs.get(key);
|
||||
if (nextcloudAPI != null) {
|
||||
nextcloudAPI.stop();
|
||||
}
|
||||
mNextcloudAPIs.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ import it.niedermann.owncloud.notes.model.LocalAccount;
|
|||
|
||||
public class SyncWorker extends Worker {
|
||||
|
||||
private static final String TAG = Objects.requireNonNull(SyncWorker.class.getCanonicalName());
|
||||
private static final String TAG = Objects.requireNonNull(SyncWorker.class.getSimpleName());
|
||||
private static final String WORKER_TAG = "background_synchronization";
|
||||
|
||||
private static final Constraints constraints = new Constraints.Builder()
|
||||
|
@ -67,13 +67,13 @@ public class SyncWorker extends Worker {
|
|||
}
|
||||
PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, repeatInterval, unit)
|
||||
.setConstraints(constraints).build();
|
||||
WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
|
||||
WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
|
||||
Log.i(TAG, "Registering worker running each " + repeatInterval + " " + unit);
|
||||
}
|
||||
}
|
||||
|
||||
private static void deregister(@NonNull Context context) {
|
||||
Log.i(TAG, "Deregistering all workers with tag \"" + WORKER_TAG + "\"");
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelAllWorkByTag(WORKER_TAG);
|
||||
WorkManager.getInstance(context.getApplicationContext()).cancelUniqueWork(WORKER_TAG);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import static android.content.Context.CLIPBOARD_SERVICE;
|
|||
|
||||
public class ClipboardUtil {
|
||||
|
||||
private static final String TAG = ClipboardUtil.class.getCanonicalName();
|
||||
private static final String TAG = ClipboardUtil.class.getSimpleName();
|
||||
|
||||
private ClipboardUtil() {
|
||||
// Util class
|
||||
|
|
|
@ -10,7 +10,7 @@ import android.util.Log;
|
|||
*/
|
||||
public class DeviceCredentialUtil {
|
||||
|
||||
private static final String TAG = DeviceCredentialUtil.class.getCanonicalName();
|
||||
private static final String TAG = DeviceCredentialUtil.class.getSimpleName();
|
||||
|
||||
private DeviceCredentialUtil() {
|
||||
// utility class -> private constructor
|
||||
|
|
|
@ -25,7 +25,7 @@ import it.niedermann.owncloud.notes.R;
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
public class MarkDownUtil {
|
||||
|
||||
private static final String TAG = MarkDownUtil.class.getCanonicalName();
|
||||
private static final String TAG = MarkDownUtil.class.getSimpleName();
|
||||
|
||||
public static final String CHECKBOX_UNCHECKED_MINUS = "- [ ]";
|
||||
public static final String CHECKBOX_UNCHECKED_MINUS_TRAILING_SPACE = CHECKBOX_UNCHECKED_MINUS + " ";
|
||||
|
|
|
@ -13,7 +13,7 @@ import it.niedermann.owncloud.notes.R;
|
|||
import it.niedermann.owncloud.notes.android.DarkModeSetting;
|
||||
|
||||
public class Notes extends Application {
|
||||
private static final String TAG = Notes.class.getCanonicalName();
|
||||
private static final String TAG = Notes.class.getSimpleName();
|
||||
|
||||
private static final long LOCK_TIME = 30 * 1000;
|
||||
private static boolean lockedPreference = false;
|
||||
|
|
|
@ -15,7 +15,7 @@ import static it.niedermann.owncloud.notes.util.MarkDownUtil.CHECKBOX_UNCHECKED_
|
|||
*/
|
||||
public abstract class NotesTextWatcher implements TextWatcher {
|
||||
|
||||
private static final String TAG = NotesTextWatcher.class.getCanonicalName();
|
||||
private static final String TAG = NotesTextWatcher.class.getSimpleName();
|
||||
|
||||
private static final String codeBlock = "```";
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package it.niedermann.owncloud.notes.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -62,6 +64,11 @@ public class ServerResponse {
|
|||
return response.getLastModified();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSupportedApiVersions() {
|
||||
return response.getSupportedApiVersions();
|
||||
}
|
||||
|
||||
CloudNote getNoteFromJSON(JSONObject json) throws JSONException {
|
||||
long id = 0;
|
||||
String title = "";
|
||||
|
|
|
@ -20,7 +20,7 @@ import static it.niedermann.owncloud.notes.util.MarkDownUtil.getStartOfLine;
|
|||
|
||||
public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
||||
|
||||
private static final String TAG = ContextBasedFormattingCallback.class.getCanonicalName();
|
||||
private static final String TAG = ContextBasedFormattingCallback.class.getSimpleName();
|
||||
|
||||
private final EditText editText;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import static it.niedermann.owncloud.notes.util.ClipboardUtil.getClipboardURLorN
|
|||
|
||||
public class ContextBasedRangeFormattingCallback implements ActionMode.Callback {
|
||||
|
||||
private static final String TAG = ContextBasedRangeFormattingCallback.class.getCanonicalName();
|
||||
private static final String TAG = ContextBasedRangeFormattingCallback.class.getSimpleName();
|
||||
|
||||
private final EditText editText;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/notesListActivityActionBar"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Na vámi využívané instanci Nexcloud nezbývá žádné volné místo na úložišti. Aby bylo možné synchronizovat vaše místní změny do vámi využívaného cloudu, je třeba nejprve smazat nějaké soubory.</string>
|
||||
<string name="error_dialog_contact_us">Pokud problémy přetrvávají, neváhejte se na nás obrátit. Kontaktní údaje naleznete v sekci „O aplikaci“ v postranním panelu.</string>
|
||||
<string name="error_dialog_we_need_info">Abychom vám mohli pomoci, potřebujeme následující technické údaje:</string>
|
||||
<string name="error_dialog_redirect">Vámi využívaný server odpověděl HTTP stavovým kódem 302, což ukazuje na to, že na serveru buď není nainstalovaná aplikace Poznámky, nebo je něco nesprávně nastavené. Toto může být způsobeno uživatelsky určenými přepsáními v souboru .htaccess nebo Nextcloud aplikacemi jako je OID klient.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -50,9 +50,11 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">Synkronisering fejlede: %1$s</string>
|
||||
<string name="error_synchronization">Synkroniseringen mislykkedes</string>
|
||||
<string name="error_json">Er Noter appen aktiveret på serveren?</string>
|
||||
<string name="error_no_network">Ingen netværksforbindelse</string>
|
||||
<string name="error_files_app">Har du installeret fil appen?</string>
|
||||
<string name="error_token_mismatch">Kan ikke tilslutte til app\'en files</string>
|
||||
<string name="error_insufficient_storage">Din server lagerplads er fuld.</string>
|
||||
<string name="error_unknown">En ukendt fejl er opstået</string>
|
||||
|
||||
|
@ -127,6 +129,22 @@
|
|||
<string name="simple_checkbox">Afkrydsningsfeldt</string>
|
||||
<string name="unlock_notes">Lås noter op</string>
|
||||
<string name="simple_beta">Beta</string>
|
||||
<string name="could_not_copy_to_clipboard">Kunne ikke kopiere til udklipsholderen</string>
|
||||
<string name="error_dialog_title">Åh nej - hvad nu? 🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Forsøg venligst at tvinge app\'en til at lukke og start den påny igen. Der kan have været en ukorrekt forbindelse til Nextcloud-app\'en.</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">Hvis problemet ikke løses, så forsøg at rydde lageret for begge apps: Nextcloud og Nextcloud Notes, for at løse problemet.</string>
|
||||
<string name="error_dialog_tip_clear_storage">Du kan rydde lageret ved at åbne åbne Android-indstillinger → Apps → Nextcloud / Nextcloud Notes → Lager → Ryd lagerplads.</string>
|
||||
<string name="error_dialog_tip_files_outdated">Din Nextcloud-app ser ud til at være forældet. Gå venligst til Play Butik eller F-Droid for at få seneste version.</string>
|
||||
<string name="error_dialog_tip_files_force_stop">Det lader til at være noget galt med din Nextcloud-app. Forsøg venligst at gennemtvinge stop af både Nextcloud-app\'en og Nextcloud Notes-app\'en. </string>
|
||||
<string name="error_dialog_tip_files_delete_storage">Hvis det ikke hjælper at stoppe begge, så kan du forsøge at rydde lagerpladsen for begge apps.</string>
|
||||
<string name="error_dialog_timeout_instance">Der var intet svar fra din server på nuværende tidspunkt. Sørg venligst for at sikre, at din instans kører rigtigt. </string>
|
||||
<string name="error_dialog_timeout_toggle">Tjek din netværksforbindelse. Undertiden kan det hjælpe at slukke og tænde for mobildata eller wifi.</string>
|
||||
<string name="error_dialog_check_server">Svaret fra din server var ikke korrekt. Tjek venligst om du kan tilgå dine noter via webgrænsefladen.</string>
|
||||
<string name="error_dialog_check_server_logs">Der er et problem med din opsætning af Nextcloud. Kig venligst i serverens logfiler. </string>
|
||||
<string name="error_dialog_check_maintenance">Sørg venligst for at din Nextcloud-instans ikke er i vedligeholdelsestilstand. </string>
|
||||
<string name="error_dialog_insufficient_storage">Din Nextcloud-instans har ikke mere ledig lagerplads. Slet venligst nogle filer for at synkronisere dine lokale ændringer ind i skyen.</string>
|
||||
<string name="error_dialog_contact_us">Du er altid velkommen til at kontakte os, hvis problemet varer ved. Du kan finde vores kontaktoplysninger i sektionen Om i sidebjælken.</string>
|
||||
<string name="error_dialog_we_need_info">Vi har brug for følgende tekniske information for at hjælpe dig:</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Åbn i redigeringstilstand</item>
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Ihre Nextcloud-Installation hat keinen freien Speicherplatz mehr. Bitte löschen Sie einige Dateien, um Ihre lokalen Änderungen mit Ihrer Cloud synchronisieren zu können.</string>
|
||||
<string name="error_dialog_contact_us">Bitte zögern Sie nicht, uns zu kontaktieren, wenn das Problem weiterhin besteht. Sie finden unsere Kontaktinformationen im Abschnitt \"Info\" in der Seitenleiste.</string>
|
||||
<string name="error_dialog_we_need_info">Wir benötigen die folgenden technischen Informationen, um Ihnen helfen zu können:</string>
|
||||
<string name="error_dialog_redirect">Ihr Server hat mit einem HTTP 302-Statuscode geantwortet, was bedeutet, dass Sie die Notes-App nicht auf Ihrem Server installiert haben oder etwas falsch konfiguriert ist. Dies kann durch benutzerdefinierte Überschreibungen in einer .htaccess-Datei oder durch Nextcloud-Apps wie OID-Client verursacht werden.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Δεν υπάρχει επαρκής χώρος αποθήκευσης στο Nextcloud. Διαγράψτε ορισμένα αρχεία για να συγχρονίσετε τις αλλαγές σας.</string>
|
||||
<string name="error_dialog_contact_us">Μην διστάσετε να επικοινωνήσετε μαζί μας εάν τα προβλήματα παραμένουν. Μπορείτε να βρείτε τα στοιχεία επικοινωνίας μας στην ενότητα σχετικά στην πλευρική στήλη.</string>
|
||||
<string name="error_dialog_we_need_info">Χρειαζόμαστε τις ακόλουθες τεχνικές πληροφορίες για να σας βοηθήσουμε:</string>
|
||||
<string name="error_dialog_redirect">Ο διακομιστής απάντησε με κωδικό κατάστασης HTTP 302, που σημαίνει, πως δεν έχετε εγκατεστημένη την εφαρμογή Notes στον διακομιστή σας ή υπάρχει λάθος ρύθμιση. Αυτό μπορεί να προκλήθηκε από λάθος καταχώρηση στο αρχείο .htaccess-file ή σε εφαρμογές του Nextcloud όπως την OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Su instancia de Nextcloud no tiene espacio libre de almacenamiento. Por favor elimine algunos archivos para sincronizar sus cambios locales con su nube.</string>
|
||||
<string name="error_dialog_contact_us">Por favor, no dude en contactar con nosotros si el problema persiste. Puede encontrar nuestra información de contacto en la sección acerca de en la barra lateral.</string>
|
||||
<string name="error_dialog_we_need_info">Necesitamos la siguiente información técnica para ayudarle:</string>
|
||||
<string name="error_dialog_redirect">Tu servidor respondió con el código de estado HTTP 302, lo que implica que no está instalada la aplicación Deck en su servidor o que algo está mal configurado. Esto puede estar causado por anulaciones personalizadas en un archivo .htaccess o por aplicaciones de Nexcloud como OID Client. </string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -140,7 +140,12 @@
|
|||
<string name="error_dialog_timeout_instance">Ez da zerbitzariaren erantzunik jaso emandako denboran. Ziurta ezazu zure instantzia ondo dabilela.</string>
|
||||
<string name="error_dialog_timeout_toggle">Egiaztatu sarera konexiorik duzun. Batzuetan, mugikorreko datuak edo Wi-Fia kendu eta berriro jartzeak laguntzen du.</string>
|
||||
<string name="error_dialog_check_server">Zerbitzariaren erantzuna ez da zuzena izan. Egiazta ezazu ea zure oharrak atzitu ditzakezun web interfazearen bidez.</string>
|
||||
<string name="error_dialog_check_server_logs">Arazo bat dago zure Nextcloud ezarpenekin. Eman begiratu bat zerbitzariko egunkari-fitxategiei.</string>
|
||||
<string name="error_dialog_check_maintenance">Egiazta ezazu zure Nextcloud instantzia ez dagoela mantentze moduan une honetan.</string>
|
||||
<string name="error_dialog_insufficient_storage">Zure Nextcloud instantziak ez du biltegiratze leku libre gehiagorik. Ezabatu fitxategi batzuk, aldaketa lokalak hodeiarekin sinkroniza daitezen.</string>
|
||||
<string name="error_dialog_contact_us">Mesedez, arazoak jarraitzen baldin badu ez izan zalantzarik gurekin harremanetan jartzeko. Alboko barran aurki dezakezu gurekin kontaktatzeko informazioa.</string>
|
||||
<string name="error_dialog_we_need_info">Zuri laguntzeko hurrengo informazio teknikoa behar dugu:</string>
|
||||
<string name="error_dialog_redirect">Zure zerbitzariak HTTP 302 egoera kodearekin erantzun du, honek esan nahi du ez duzula Notak aplikazioa ez duzula zure zerbitzarian instalatu edo zerbait txarto konfiguratuta dagoela. Hau .htaccess fitxategi batean arau pertsonalizatuak daudelako edo OID Bezeroa izeneko aplikazioarengatik sortua izan daiteke. </string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Votre instance Nextcloud n\'a plus d\'espace disponible. Veuillez supprimer certains fichiers pour pouvoir synchroniser vos modifications locales dans votre cloud.</string>
|
||||
<string name="error_dialog_contact_us">N\'hésitez pas à nous contacter si les problèmes persistent. Vous trouverez nos coordonnées dans la section : À propos, de la barre latérale.</string>
|
||||
<string name="error_dialog_we_need_info">Nous avons besoin des informations techniques suivantes pour pouvoir vous aider :</string>
|
||||
<string name="error_dialog_redirect">Votre serveur a répondu avec le code HTTP 302, ce qui implique que vous n\'avez pas installé l\'application Notes sur votre serveur or que quelque chose est mal configuré. Ça peut être dû par des modifications du fichier .htaccess or par une autre application Nextcloud comme OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -139,12 +139,13 @@
|
|||
<string name="error_dialog_tip_files_delete_storage">Se detelos pola forza non axuda, pode tentar limpar o almacenamento de ambas as aplicacións.</string>
|
||||
<string name="error_dialog_timeout_instance">Non houbo resposta do seu servidor no tempo dado. Asegúrese de que a súa instancia funciona correctamente.</string>
|
||||
<string name="error_dialog_timeout_toggle">Comprobe a súa conexión de rede. Ás veces apagar e volver acender os datos móbiles ou a Wi-Fi pode axudar.</string>
|
||||
<string name="error_dialog_check_server">A resposta do servidor non foi correcta. Comprobe se pode acceder ás súas notas a través de interface web.</string>
|
||||
<string name="error_dialog_check_server">A resposta do servidor non foi correcta. Comprobe se pode acceder a Notas a través de interface web.</string>
|
||||
<string name="error_dialog_check_server_logs">Hai un problema coa configuración do Nextcloud. Bótelle un ollo aos ficheiros de rexistro do servidor.</string>
|
||||
<string name="error_dialog_check_maintenance">Comprobe que a súa instancia Nextcloud non se atope en modo de mantemento.</string>
|
||||
<string name="error_dialog_insufficient_storage">A súa instancia Nextcloud non dispón de espazo de almacenamento libre. Elimine algúns ficheiros para sincronizar os seus cambios locais na súa nube.</string>
|
||||
<string name="error_dialog_contact_us">Non dubide en poñerse en contacto con nós se os problemas persisten. Pode atopar a nosa información de contacto na sección sobre na barra lateral.</string>
|
||||
<string name="error_dialog_we_need_info">Necesitamos a seguinte información técnica para axudarlle:</string>
|
||||
<string name="error_dialog_redirect">O seu servidor respondeu cun código de estado HTTP 302, o que implica que non ten instalada a aplicación Notas no servidor ou que algo está mal configurado. Isto pode ser causado por substitucións personalizadas nun ficheiro .htaccess ou por aplicacións Nextcloud como Client OID.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<resources>
|
||||
|
||||
<string name="app_name">פתקים</string>
|
||||
<string name="app_name_long">פתקים של Nextcloud</string>
|
||||
<string name="label_all_notes">כל הפתקים</string>
|
||||
<string name="label_favorites">מועדפים</string>
|
||||
<string name="action_create">פתק חדש</string>
|
||||
|
@ -30,7 +31,10 @@
|
|||
<string name="listview_updated_today">היום</string>
|
||||
<string name="listview_updated_yesterday">אתמול</string>
|
||||
<string name="listview_updated_this_week">השבוע</string>
|
||||
<string name="listview_updated_last_week">שבוע שעבר</string>
|
||||
<string name="listview_updated_this_month">החודש</string>
|
||||
<string name="listview_updated_last_month">חודש שעבר</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_note_mode">מצב תצוגת פתקים</string>
|
||||
<string name="settings_theme_title">ערכת עיצוב כהה</string>
|
||||
|
@ -38,13 +42,20 @@
|
|||
<string name="settings_font_size">גודל גופן</string>
|
||||
<string name="settings_wifi_only">סנכרון רק דרך רשת אלחוטית</string>
|
||||
<string name="settings_lock">Password protection</string>
|
||||
<!-- Certificates -->
|
||||
<string name="settings_background_sync">סנכרון ברקע</string>
|
||||
|
||||
<!-- Network -->
|
||||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">הסנכרון נכשל: %1$s</string>
|
||||
<string name="error_synchronization">הסנכרון נכשל</string>
|
||||
<string name="error_json">האם יישומון הפתקים מופעל בשרת?</string>
|
||||
<string name="error_no_network">אין חיבור לאינטרנט</string>
|
||||
<string name="error_files_app">האם התקנת את יישומון הקבצים?</string>
|
||||
<string name="error_token_mismatch">לא ניתן להתחבר ליישומון הקבצים.</string>
|
||||
<string name="error_insufficient_storage">נפח האחסון בשרת שלך נוצל במלואו</string>
|
||||
<string name="error_unknown">אירעה שגיאה בלתי ידועה.</string>
|
||||
|
||||
<!-- About -->
|
||||
<string name="about_version_title">גרסה</string>
|
||||
<string name="about_version">הניצולת עומדת על <strong>%1$s</strong></string>
|
||||
|
@ -91,18 +102,34 @@
|
|||
<string name="simple_error">שגיאה</string>
|
||||
<string name="simple_close">סגור</string>
|
||||
<string name="simple_copy">העתק</string>
|
||||
<string name="simple_exception">חריגה</string>
|
||||
<string name="copied_to_clipboard">הועתק ללוח הגזירים</string>
|
||||
<string name="pin_to_homescreen">הוסף למסך הבית</string>
|
||||
<string name="note_has_been_deleted">פתק זה נמחק</string>
|
||||
<string name="add_account">הוספת חשבון</string>
|
||||
<string name="category_music">מוזיקה</string>
|
||||
<string name="category_movies">סרטים</string>
|
||||
<string name="category_movie">סרט</string>
|
||||
<string name="category_work">עבודה</string>
|
||||
<string name="account_already_imported">החשבון הזה כבר עבר ייבוא</string>
|
||||
<string name="no_notes_yet">אין פתקים עדיין</string>
|
||||
<string name="no_notes_yet_description">לחיצה + כפתור כדי ליצור פתק חדש</string>
|
||||
<string name="could_not_load_preview_two_digit_numbered_list">לא ניתן לטעון תצוגה מקדימה. נא לבדוק האם יש פריט ברשימה עם שתי ספרות שאין לו תוכן.</string>
|
||||
<string name="simple_more">עוד</string>
|
||||
<string name="simple_move">העברה</string>
|
||||
<string name="error_files_app_version_too_old">האם גרסת יישומון הקבצים שלך מעודכנת?</string>
|
||||
<string name="checkbox_could_not_be_toggled">לא ניתן להחליף את מצב תיבת הבחירה.</string>
|
||||
<string name="bulk_notes_deleted">נמחקו %1$d פתקים</string>
|
||||
<string name="bulk_notes_restored">שוחזרו %1$d פתקים</string>
|
||||
<string name="category_readonly">קריאה בלבד</string>
|
||||
<string name="no_category">אין קטגוריה</string>
|
||||
<string name="add_category">הוספת %1$s</string>
|
||||
<string name="simple_checkbox">תיבת סימון</string>
|
||||
<string name="unlock_notes">שחרור פתקים</string>
|
||||
<string name="simple_beta">בטא</string>
|
||||
<string name="could_not_copy_to_clipboard">לא ניתן להעתיק ללוח הגזירים</string>
|
||||
<string name="error_dialog_title">שוד ושבר - מה עכשיו? 🙁</string>
|
||||
<string name="error_dialog_we_need_info">אנו זקוקים לפירוט הטכני הבא כדי לסייע לך:</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>פתיחה במצב עריכה</item>
|
||||
|
|
|
@ -50,9 +50,11 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">Sinkronizacija nije uspjela: %1$s</string>
|
||||
<string name="error_synchronization">Sinkronizacija nije uspjela</string>
|
||||
<string name="error_json">Je li aplikacija Notes aktivirana na poslužitelju?</string>
|
||||
<string name="error_no_network">Nema veze s mrežom</string>
|
||||
<string name="error_files_app">Je li instalirana aplikacija za upravljanje datotekama?</string>
|
||||
<string name="error_token_mismatch">Neuspješno povezivanje s aplikacijom za upravljanje datotekama.</string>
|
||||
<string name="error_insufficient_storage">Prostor za pohranu poslužitelja je pun.</string>
|
||||
<string name="error_unknown">Došlo je do nepoznate pogreške.</string>
|
||||
|
||||
|
@ -127,6 +129,22 @@
|
|||
<string name="simple_checkbox">Potvrdni okvir</string>
|
||||
<string name="unlock_notes">Otključaj bilješke</string>
|
||||
<string name="simple_beta">Beta</string>
|
||||
<string name="could_not_copy_to_clipboard">Kopiranje u međuspremnik nije uspjelo</string>
|
||||
<string name="error_dialog_title">Ups, što sada? 🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Pokušajte prisilno zatvoriti aplikaciju i ponovo je pokrenuti. Možda se radi o nepravilnoj vezi s aplikacijom Nextcloud.</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">Ako problem i dalje nije otklonjen, pokušajte izbrisati pohranjene podatke iz obje aplikacije: Nextcloud i Nextcloud Notes.</string>
|
||||
<string name="error_dialog_tip_clear_storage">Pohranjene podatke možete izbrisati tako da otvorite postavke operacijskog sustava Android i odaberete → Aplikacije → Nextcloud / Nextcloud Notes → Pohrana → Brisanje pohrane podataka.</string>
|
||||
<string name="error_dialog_tip_files_outdated">Čini se da je vaša aplikacija Nextcloud zastarjela. Posjetite trgovinu Play Store ili F-Droid kako biste dohvatili najnoviju inačicu.</string>
|
||||
<string name="error_dialog_tip_files_force_stop">Čini se da nešto nije u redu s vašom aplikacijom Nextcloud. Pokušajte prisilno zatvoriti aplikaciju Nextcloud i aplikaciju Nextcloud Notes.</string>
|
||||
<string name="error_dialog_tip_files_delete_storage">Ako prisilno zatvaranje nije otklonilo problem, pokušajte izbrisati pohranjene podatke iz obje aplikacije.</string>
|
||||
<string name="error_dialog_timeout_instance">Nije stigao odgovor poslužitelja u zadanom vremenskom razdoblju. Provjerite radi li vaša instanca.</string>
|
||||
<string name="error_dialog_timeout_toggle">Provjerite mrežnu vezu. Ponekad može pomoći isključivanje i ponovno uključivanje mobilnih podataka ili bežične (Wi-Fi) mreže.</string>
|
||||
<string name="error_dialog_check_server">Odgovor poslužitelja nije točan. Provjerite možete li svojim bilješkama pristupiti putem web-sučelja.</string>
|
||||
<string name="error_dialog_check_server_logs">Postoji određeni problem s vašim Nextcloudom. Provjerite datoteke zapisa poslužitelja.</string>
|
||||
<string name="error_dialog_check_maintenance">Provjerite je li vaša instanca Nextclouda trenutno u načinu održavanja.</string>
|
||||
<string name="error_dialog_insufficient_storage">Nema slobodnog prostora za pohranu u vašoj instanci Nextclouda. Izbrišite dio datoteka kako biste sinkronizirali lokalne promjene u oblak.</string>
|
||||
<string name="error_dialog_contact_us">Obratite nam se bez odlaganja ako problemi i dalje postoje. Naše podatke za kontakt možete pronaći u odjeljku s informacijama na bočnoj traci.</string>
|
||||
<string name="error_dialog_we_need_info">Potrebne su nam sljedeće tehničke informacije kako bismo vam pomogli:</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Otvori u načinu uređivanja</item>
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">sikertelen szinkronizálás: %1$s</string>
|
||||
<string name="error_synchronization">Szinkronizálás sikertelen</string>
|
||||
<string name="error_json">Be van kapcsolva a Jegyzetek alkalmazás a kiszolgálón?</string>
|
||||
<string name="error_no_network">Nincs hálózati kapcsolat</string>
|
||||
<string name="error_files_app">Telepítette a Fájlok alkalmazást?</string>
|
||||
|
@ -126,6 +127,18 @@
|
|||
<string name="add_category">%1$s hozzáadása</string>
|
||||
<string name="simple_checkbox">Jelölőmező</string>
|
||||
<string name="simple_beta">Béta</string>
|
||||
<string name="could_not_copy_to_clipboard">Nem sikerült a vágólapra másolni</string>
|
||||
<string name="error_dialog_title">Jaj ne – Most mi legyen? 🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Próbálja kényszeríteni az alkalmazás bezárását, és indítsa újra. Lehet, hogy hibás volt a kapcsolat a többi Nextcloud alkalmazással.</string>
|
||||
<string name="error_dialog_tip_files_outdated">A Nextcloud alkalmazása elavultnak tűnik. Keresse fel a Play Áruházat vagy az F-Droidot, hogy beszerezze a legfrissebb verziót.</string>
|
||||
<string name="error_dialog_tip_files_delete_storage">Ha a kényszerített leállítás nem segít, akkor megpróbálhatja mindkét alkalmazás tárolóját kiüríteni.</string>
|
||||
<string name="error_dialog_timeout_instance">A megadott időn belül nem érkezett válasz a kiszolgálótól. Győződjön meg róla, hogy a példánya helyesen fut.</string>
|
||||
<string name="error_dialog_timeout_toggle">Ellenőrizze a hálózati kapcsolatát. Néha a mobil adathasználat vagy a Wi-Fi ki- és bekapcsolása is segíthet.</string>
|
||||
<string name="error_dialog_check_server_logs">Hiba van a Nextcloudja beállításaiban. Nézzen bele a kiszolgáló naplófájljaiba.</string>
|
||||
<string name="error_dialog_check_maintenance">Ellenőrizze, hogy a Nextcloud példánya nincs-e karbantartási módban.</string>
|
||||
<string name="error_dialog_insufficient_storage">A Nextcloud példányán elfogyott a szabad hely. Töröljön pár fájlt, hogy szinkronizálhassa a helyi változtatásait a felhővel.</string>
|
||||
<string name="error_dialog_contact_us">Nyugodtan lépjen velünk kapcsolatba, ha a probléma továbbra is fennáll. A kapcsolati információkat az oldalsáv névjegy részében találja.</string>
|
||||
<string name="error_dialog_we_need_info">A következő műszaki információkra van szükségünk, hogy segíthessünk:</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Megnyitás szerkesztésre</item>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<string name="settings_note_mode">Modalità di visualizzazione delle note</string>
|
||||
<string name="settings_theme_title">Tema scuro</string>
|
||||
<string name="settings_font_title">Carattere a spaziatura fissa</string>
|
||||
<string name="settings_font_size">Dimensiine carattere</string>
|
||||
<string name="settings_font_size">Dimensione carattere</string>
|
||||
<string name="settings_wifi_only">Sincronizza solo con Wi-Fi</string>
|
||||
<string name="settings_lock">Protezione con password</string>
|
||||
<string name="settings_background_sync">Sincronizzazione in background</string>
|
||||
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">La tua istanza di Nextcloud non ha più spazio libero. Elimina alcuni file per sincronizzare le modifiche locali con il tuo cloud.</string>
|
||||
<string name="error_dialog_contact_us">Non esitare a contattarci se il problema persiste. Puoi trovare le nostre informazioni di contatto nella sezione Informazioni della barra laterale.</string>
|
||||
<string name="error_dialog_we_need_info">Ci servono le seguenti informazioni tecniche per aiutarti:</string>
|
||||
<string name="error_dialog_redirect">Il tuo server ha risposto con un codice di stato HTTP 302, che implica che non hai installato l\'applicazione Note sul tuo server o qualcosa non è configurato correttamente. Questo può essere causato da configurazioni personalizzate nel file .htaccess o da applicazioni di Nextcloud come OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -50,9 +50,11 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">同期に失敗しました: %1$s</string>
|
||||
<string name="error_synchronization">同期に失敗</string>
|
||||
<string name="error_json">ノートアプリはサーバー上で有効になっていますか?</string>
|
||||
<string name="error_no_network">ネットワークに接続されていません</string>
|
||||
<string name="error_files_app">ファイルアプリはインストールしましたか?</string>
|
||||
<string name="error_token_mismatch">ファイルアプリに接続できません。</string>
|
||||
<string name="error_insufficient_storage">サーバストレージに空きがありません</string>
|
||||
<string name="error_unknown">不明なエラーが発生しました</string>
|
||||
|
||||
|
@ -114,6 +116,7 @@
|
|||
<string name="account_already_imported">アカウントはすでにインポートされています</string>
|
||||
<string name="no_notes_yet">まだノートはありません</string>
|
||||
<string name="no_notes_yet_description">+ ボタンを押して新しいノートを作成</string>
|
||||
<string name="could_not_load_preview_two_digit_numbered_list">プレビューをロードできませんでした。中身のない二桁番号のリストアイテムがないかチェックしてください。</string>
|
||||
<string name="simple_more">さらに表示</string>
|
||||
<string name="simple_move">移動</string>
|
||||
<string name="error_files_app_version_too_old">ファイルアプリは最新版ですか?</string>
|
||||
|
@ -124,7 +127,26 @@
|
|||
<string name="no_category">カテゴリ無し</string>
|
||||
<string name="add_category">%1$s を追加</string>
|
||||
<string name="simple_checkbox">チェックボックス</string>
|
||||
<string name="unlock_notes">ノートをアンロック</string>
|
||||
<string name="simple_beta">ベータ</string>
|
||||
<string name="could_not_copy_to_clipboard">クリップボードにコピーできませんでした</string>
|
||||
<string name="error_dialog_title">おやまあ、どうしたことでしょう?🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">アプリの強制終了と再起動を試みてください。Nextcloudアプリへの接続が正しくない接続があったかも知れません。</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">この問題が続くようでしたら、下記両方のアプリでストレージをクリアして問題が解決できるか試してください:Nextcloud と Nextcloud Notes</string>
|
||||
<string name="error_dialog_tip_clear_storage">以下の方法でストレージをクリアできます:アンドロイドの設定を開く → アプリ → Nextcloud / Nextcloud Notes → ストレージ → ストレージをクリア.</string>
|
||||
<string name="error_dialog_tip_files_outdated">Nextcloudアプリが古いようです。プレイストアかF-Droidから最新版を入手してください。</string>
|
||||
<string name="error_dialog_tip_files_force_stop">Nextcloudアプリで何か不具合が発生しました。NextcloudアプリとNextcloud Notesアプリの両方を強制終了してください。</string>
|
||||
<string name="error_dialog_tip_files_delete_storage">強制終了で改善されない場合は、両方のアプリのストレージをクリアしてみてください。</string>
|
||||
<string name="error_dialog_timeout_instance">一定時間内にサーバからの反応がありませんでした。サーバが正常に動いているか確認してください。</string>
|
||||
<string name="error_dialog_timeout_toggle">ネットワーク接続を確認してください。モバイルデータやWi-Fiのオフ/オンを繰り返してみるとうまくいく場合があります。</string>
|
||||
<string name="error_dialog_check_server">サーバーからの反応が不正です。Webインターフェースからnotesにアクセス出来るか確認してください。</string>
|
||||
<string name="error_dialog_check_server_logs">Nextcloudのセットアップに問題があります。サーバーのログファイルを確認してください。</string>
|
||||
<string name="error_dialog_check_maintenance">あなたのNextcloudサーバがメンテナンスモード中かどうか確認してください。</string>
|
||||
<string name="error_dialog_insufficient_storage">あなたのNextcloudサーバのストレージに空き容量がありません。ローカルの変更をクラウドに同期するにはいくつかファイルを削除してください。</string>
|
||||
<string name="error_dialog_contact_us">問題が継続する場合はお問い合わせください。我々の連絡先はサイドバーのaboutセクションにかかれています。</string>
|
||||
<string name="error_dialog_we_need_info">サポートには下記技術情報が必要です:</string>
|
||||
<string name="error_dialog_redirect">サーバがHTTPステータスコード 302を返しました。これはNotesアプリがインストールされていないか、構成が誤っていることを意味します。.htaccessファイルのカスタムオーバーライドやOIDクライアントのようなNextcloudアプリが原因の可能性があります。</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>編集モードで開く</item>
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
<string name="could_not_copy_to_clipboard">Nepavyko nukopijuoti į iškarpinę</string>
|
||||
<string name="error_dialog_title">O, ne - Kas dabar? 🙁</string>
|
||||
<string name="error_dialog_we_need_info">Norint jums padėti, mums reikia techninės informacijos:</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Atverti redagavimo veiksenoje</item>
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
<string name="simple_beta">Bèta</string>
|
||||
<string name="could_not_copy_to_clipboard">Kon niet kopiëren naar het klembord</string>
|
||||
<string name="error_dialog_title">Oh nee - wat nu? 🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Probeer de app te sluiten en opnieuw te starten. Er was misschien een onjuiste verbinding met de Nextcloud-app.</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">Als het probleem blijft aanhouden, probeer dan de gegevensopslag van beide apps te wissen: van Nextcloud én van Nextcloud Notes.</string>
|
||||
<string name="error_dialog_tip_clear_storage">Je kan de gegevensopslag wissen door in Android te gaan naar Instellingen → Apps → Nextcloud / Nextcloud Notes → Geheugen → Ruimte beheren → Gegevens verwijderen</string>
|
||||
<string name="error_dialog_tip_files_outdated">Je Nextcloud app lijkt te zijn verouderd. Download alstublieft de nieuwste versie in de Google Play Store of in F-Droid.</string>
|
||||
|
@ -145,6 +146,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Je Nextcloud instantie heeft geen vrije ruimte meer. Verwijder alstublieft enkele bestanden om je lokale gegevens te kunnen synchroniseren.</string>
|
||||
<string name="error_dialog_contact_us">Schroom alstublieft niet om contact met ons op te nemen als het probleem blijft aanhouden. Onze contactgegevens zijn te vinden in de About sectie in de zijbalk.</string>
|
||||
<string name="error_dialog_we_need_info">We hebben de volgende technische informatie nodig om je te kunnen helpen: </string>
|
||||
<string name="error_dialog_redirect">Je server heeft gereageerd met een HTTP 302-statuscode, wat inhoudt dat je de Deck-app niet op je server hebt geïnstalleerd of dat er iets verkeerd is geconfigureerd. Dit kan worden veroorzaakt door maatwerk aanpassingen in een .htaccess-bestand of door Nextcloud-apps zoals de OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">W Twojej instancji Nextcloud nie ma już wolnego miejsca. Usuń niektóre pliki, aby zsynchronizować lokalne zmiany w chmurze.</string>
|
||||
<string name="error_dialog_contact_us">Jeśli problem będzie się powtarzał, skontaktuj się z nami. Nasze informacje kontaktowe można znaleźć w sekcji \"Informacje\" na pasku bocznym.</string>
|
||||
<string name="error_dialog_we_need_info">Potrzebujemy następujących informacji technicznych, aby Ci pomóc:</string>
|
||||
<string name="error_dialog_redirect">Twój serwer odpowiedział kodem stanu HTTP 302, co oznacza, że nie masz zainstalowanej aplikacji Notes na serwerze lub coś jest źle skonfigurowane. Może to być spowodowane niestandardowymi przekierowaniami pliku .htaccess lub aplikacjami Nextcloud, takimi jak OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Sua instância do Nextcloud não possui mais espaço livre. Exclua alguns arquivos para sincronizar suas alterações locais com a nuvem.</string>
|
||||
<string name="error_dialog_contact_us">Não hesite em nos contactar se os problemas persistirem. Você pode encontrar informações de contato na seção \"sobre\" na barra lateral.</string>
|
||||
<string name="error_dialog_we_need_info">Precisamos das seguintes informações técnicas para ajudá-lo:</string>
|
||||
<string name="error_dialog_redirect">Seu servidor respondeu com um código de status HTTP 302, o que implica que você não instalou o aplicativo Notes no servidor ou que algo está configurado incorretamente. Isso pode ser causado por alterações no arquivo .htaccess ou por aplicativos como o Client OID.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="simple_bold">Жирный</string>
|
||||
<string name="simple_link">Ссылка</string>
|
||||
<string name="simple_italic">Курсив</string>
|
||||
<string name="action_note_deleted">%1$s удалена</string>
|
||||
<string name="action_note_restored">%1$s восстановлена</string>
|
||||
<string name="action_note_deleted">Заметка %1$s удалена</string>
|
||||
<string name="action_note_restored">Заметка %1$s восстановлена</string>
|
||||
<string name="action_undo">Отменить</string>
|
||||
<string name="action_drawer_open">открыть навигацию</string>
|
||||
<string name="action_drawer_close">закрыть навигацию</string>
|
||||
|
@ -50,9 +50,11 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">Синхронизация не удалась: %1$s</string>
|
||||
<string name="error_synchronization">Сбой синхронизации</string>
|
||||
<string name="error_json">Включено ли приложение «Заметки» на сервере?</string>
|
||||
<string name="error_no_network">Нет подключения к сети</string>
|
||||
<string name="error_files_app">Установлено ли приложение Файлы?</string>
|
||||
<string name="error_token_mismatch">Не могу подключиться к файлам приложения.</string>
|
||||
<string name="error_insufficient_storage">Закончилось свободное место в хранилище на сервере.</string>
|
||||
<string name="error_unknown">Произошла неизвестная ошибка.</string>
|
||||
|
||||
|
@ -128,6 +130,23 @@
|
|||
<string name="unlock_notes">Разблокировать заметки</string>
|
||||
<string name="simple_beta">Beta</string>
|
||||
<string name="could_not_copy_to_clipboard">Не удалось скопировать в буфер обмена.</string>
|
||||
<string name="error_dialog_title">О нет, что теперь? 🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Попробуйте принудительно закрыть приложение и запустить его снова. Это могло быть связано с некорректным подключением к приложению Nextcloud.</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">Если проблема повторилась, попробуйте для ее решения очистить хранилище обоих приложений: Nextcloud и Nextcloud Notes</string>
|
||||
<string name="error_dialog_tip_clear_storage">Вы можете очистить хранилище открыв настройки Android → Приложения → Nextcloud / Nextcloud Notes → Хранилище → Очистить хранилище</string>
|
||||
<string name="error_dialog_tip_files_outdated">Ваше приложение Nextcloud устарело. Установите новую версию с Play Store или F-Droid.</string>
|
||||
<string name="error_dialog_tip_files_force_stop">Похоже что с вашим приложением Nextcloud что-то не так. Попробуйте принудительно остановить оба приложения Nextcloud и Nextcloud Notes. </string>
|
||||
<string name="error_dialog_tip_files_delete_storage">Если принудительная остановка не поможет, попробуйте очистить хранилище обоих приложений.</string>
|
||||
<string name="error_dialog_timeout_instance">Ответ сервера не был получен в отведённое на это время. Проверьте работоспособность сервера.</string>
|
||||
<string name="error_dialog_timeout_toggle">Проверьте ваше сетевое соединение. В некоторых случаях может помочь отключение Wi-Fi и включение его вновь.</string>
|
||||
<string name="error_dialog_check_server">Ответ вашего сервера неверный. Проверьте работает ли вход через веб-интерфейс.</string>
|
||||
<string name="error_dialog_check_server_logs">В конфигурации сервера обнаружены ошибки, проверьте файлы журналов сервера.</string>
|
||||
<string name="error_dialog_check_maintenance">Убедитесь что ваш экземпляр Nextcloud не находится в режиме обслуживания.</string>
|
||||
<string name="error_dialog_insufficient_storage">На вашем экземпляре Nextcloud нет свободного места. Удалите ненужные файлы для синхронизации изменений в облако.</string>
|
||||
<string name="error_dialog_contact_us">Не стесняйтесь сообщать разработчикам о повторяющихся ошибках. Контактная информация приведена в боковой панели в разделе «О программе».</string>
|
||||
<string name="error_dialog_we_need_info">Требуется следующая техническая информация от вас:</string>
|
||||
<string name="error_dialog_redirect">На запрос сервер вернул код состояния HTTP 302, что означает, что приложение Заметки либо не установлено, либо сервер настроен неверно. Такое поведение может являться следствием замены стандартного файла .htaccess администратором сервера или приложением Nextcloud, таким как OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Открывать в режиме редактирования</item>
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Vo vašej inštancii Nexcloudu nie je na úložisku voľné miesto. Aby bolo možné synchronizovať vaše miestne zmeny s vaším cloudom, je potrebné najprv zmazať niektoré súbory.</string>
|
||||
<string name="error_dialog_contact_us">Ak problémy pretrvávajú, neváhajte nás kontaktovať. Kontaktné informácie nájdete v časti \"O nás\" v bočnom paneli.</string>
|
||||
<string name="error_dialog_we_need_info">Aby sme vám pomohli, potrebujeme následovné technické informácie:</string>
|
||||
<string name="error_dialog_redirect">Váš server odpovedal stavovým kódom HTTP 302, čo znamená, že na svojom serveri nemáte nainštalovanú aplikáciu Notes alebo je niečo nesprávne nastavené. Môže to byť spôsobené vlastnými zmenami v súbore .htaccess alebo aplikáciami Nextcloud, ako je OID Client.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">Usklajevanje je spodletelo: %1$s</string>
|
||||
<string name="error_synchronization">Usklajevanje je spodletelo</string>
|
||||
<string name="error_json">Ali je program Notes na strežniku omogočen?</string>
|
||||
<string name="error_no_network">Ni omrežne povezave</string>
|
||||
<string name="error_files_app">Ali je program za upravljanje datotek »files« nameščen?</string>
|
||||
|
|
|
@ -50,9 +50,11 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">Synkronisering misslyckades: %1$s</string>
|
||||
<string name="error_synchronization">Synkroniseringen misslyckades</string>
|
||||
<string name="error_json">Är appen anteckningar aktiverad på servern?</string>
|
||||
<string name="error_no_network">Ingen nätverksanslutning</string>
|
||||
<string name="error_files_app">Har du installerat filer-appen?</string>
|
||||
<string name="error_token_mismatch">Det gick inte att ansluta till fil-applikationen</string>
|
||||
<string name="error_insufficient_storage">Din serverlagring är full.</string>
|
||||
<string name="error_unknown">Ett okänt fel uppstod.</string>
|
||||
|
||||
|
@ -128,6 +130,9 @@
|
|||
<string name="unlock_notes">Lås upp anteckningar</string>
|
||||
<string name="simple_beta">Beta</string>
|
||||
<string name="could_not_copy_to_clipboard">Kunde inte kopiera till urklipp</string>
|
||||
<string name="error_dialog_title">Å nej - Vad händer nu?🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">Försök med att Tvinga appen att stänga och återstarta. En felaktig anslutning till Nextcloud-appen kan ha förekommit.</string>
|
||||
<string name="error_dialog_tip_token_mismatch_clear_storage">Om problemet kvarstår, prova med att rensa lagringen på både appen Nextcloud och Nextcloud Notes.</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>Öppna i redigera-läge</item>
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<string name="error_dialog_insufficient_storage">Nextcloud kopyanızda boş depolama alanı kalmamış. Lütfen yerel değişikliklerinizi bulut ile eşitlemek için bazı dosyaları silerek yer açın.</string>
|
||||
<string name="error_dialog_contact_us">Sorun sürerse lütfen bize sormaktan çekinmeyin. İletişim bilgilerimizi yan çubuktaki hakkında bölümünde bulabilirsiniz.</string>
|
||||
<string name="error_dialog_we_need_info">Size yardımcı olabilmemiz için şu teknik bilgilere gerek duyacağız:</string>
|
||||
<string name="error_dialog_redirect">Sunucunuz, üzerine Notlar uygulamasının kurulmadığı ya da bir şeyin yanlış yapılandırıldığı anlamına gelen HTTP 302 durum kodu ile yanıt verdi. Bu durum, bir .htaccess dosyasındaki özel kısıtlamalardan ya da OID İstemcisi gibi Nextcloud uygulamalarından kaynaklanabilir.</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
<!-- Error -->
|
||||
<string name="error_sync">同步失败:%1$s</string>
|
||||
<string name="error_synchronization">同步失败</string>
|
||||
<string name="error_json">是否已在服务器上启用了 笔记 应用?</string>
|
||||
<string name="error_no_network">没有网络连接</string>
|
||||
<string name="error_files_app">您是否已安装 文件 应用程序?</string>
|
||||
|
@ -128,6 +129,17 @@
|
|||
<string name="unlock_notes">解锁笔记</string>
|
||||
<string name="simple_beta">Beta</string>
|
||||
<string name="could_not_copy_to_clipboard">无法复制到剪贴板</string>
|
||||
<string name="error_dialog_title">哦,不。所以会怎么样?🙁</string>
|
||||
<string name="error_dialog_tip_token_mismatch_retry">请尝试强行关闭应用并重新打开。可能是与 Nextcloud 应用的连接不正确。</string>
|
||||
<string name="error_dialog_tip_files_outdated">您的 Nextcloud 应用似乎已经过时了。请访问 Play 商店或 F-Droid 以获取最新版本。</string>
|
||||
<string name="error_dialog_tip_files_delete_storage">如果强行停止没有效果,您可以尝试清除两个应用的存储空间。</string>
|
||||
<string name="error_dialog_timeout_instance">服务器没有在规定的时间内回答。请确保您的服务器实例在正常运行。</string>
|
||||
<string name="error_dialog_timeout_toggle">检查您的网络连接。有时候关闭并重新开启移动数据或 Wi-Fi 有帮助。</string>
|
||||
<string name="error_dialog_check_server_logs">您的 Nextcloud 配置有一些问题。请查看服务器日志。</string>
|
||||
<string name="error_dialog_check_maintenance">请检查您的 Nextcloud 实例是否处于维护模式。</string>
|
||||
<string name="error_dialog_insufficient_storage">您的 Nextcloud 实例已经没有可用的存储空间了。请删除一些文件来允许本地的变更同步到云。</string>
|
||||
<string name="error_dialog_contact_us">如果问题持续存在,请不要犹豫来联系我们。您可以在侧边栏的 “关于” 部分找到我们的联系信息。</string>
|
||||
<string name="error_dialog_we_need_info">我们需要以下技术信息来帮助您:</string>
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
<item>以编辑模式打开</item>
|
||||
|
|
|
@ -179,6 +179,11 @@
|
|||
<string name="error_dialog_insufficient_storage">Your Nextcloud instance has no free storage left. Please delete some files to sync your local changes into your cloud.</string>
|
||||
<string name="error_dialog_contact_us">Please do not hesitate to contact us if the issues persist. You can find our contact information in the about section in the sidebar.</string>
|
||||
<string name="error_dialog_we_need_info">We need the following technical information to help you:</string>
|
||||
<string name="error_dialog_server_app_enabled">Please make sure you have installed and enabled the "Notes" app on your server.</string>
|
||||
<string name="error_dialog_redirect">Your server did respond with a HTTP 302 status code, which implies, that you do not have installed the Notes app on your server or something is misconfigured. This can be caused by custom overrides in a .htaccess-file or by Nextcloud apps like OID Client.</string>
|
||||
<string name="added_content">Added "%1$s"</string>
|
||||
<string name="shared_text_empty">Shared text was empty</string>
|
||||
<string name="append_to_note">Append to note</string>
|
||||
|
||||
<!-- Array: note modes -->
|
||||
<string-array name="noteMode_entries">
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<shortcut
|
||||
android:shortcutId="it.niedermann.owncloud.notes"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_add_blue_24dp"
|
||||
android:shortcutId="it.niedermann.owncloud.notes"
|
||||
android:shortcutLongLabel="@string/shortcut_create_long"
|
||||
android:shortcutShortLabel="@string/action_create"
|
||||
android:shortcutLongLabel="@string/shortcut_create_long">
|
||||
tools:targetApi="n_mr1">
|
||||
<intent
|
||||
android:action="android.intent.action.CREATE_DOCUMENT"
|
||||
android:targetPackage="it.niedermann.owncloud.notes"
|
||||
android:targetClass="it.niedermann.owncloud.notes.android.activity.EditNoteActivity" />
|
||||
android:targetClass="it.niedermann.owncloud.notes.android.activity.EditNoteActivity"
|
||||
android:targetPackage="it.niedermann.owncloud.notes" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
|
@ -1 +1 @@
|
|||
Nextcloud Notes
|
||||
Nextcloud Notes für Android
|
||||
|
|
3
fastlane/metadata/android/en-US/changelogs/2012000.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/2012000.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
- ⚡️ Speed up synchronization by supporting ETags for capabilities endpoint
|
||||
- ➕ Allow to share into exiting note (#174)
|
||||
- 🐞 Fix upgrading from version <= 1.0.1 to a current version
|
|
@ -1 +1 @@
|
|||
Nextcloud Notes
|
||||
Nextcloud Notes for Android
|
||||
|
|
Loading…
Reference in a new issue