diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e0cd7fbbac0..a40d7cf05e7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+3.9.0 (November, 12, 2019)
+
+- preview Markdown with syntax highlighting @AndyScherzinger
+- improved DavX5 integration @bitfireAT
+- AutoUpload: allow files to upload into subfolder
+- new media player service @ezaquarii
+- Remote wipe integration
+- Print from within Collabora
+- enhanced SingleSignOn
+- outdated server warning set to NC14
+
+For a full list, please see https://github.com/nextcloud/android/milestone/38
+
## 3.8.1 (October, 11, 2019)
- upload images into subfolder, if source folder also has subfolder
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a57f5b423d30..45c48f796137 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,6 +23,8 @@
1. Backport pull request
1. Pull requests that also need changes on library
1. Adding new files
+ 1. File naming
+ 1. Menu files
1. Translations
1. Releases
1. Types
@@ -236,6 +238,24 @@ Source code of app:
-->
```
+## File naming
+
+The file naming patterns are inspired and based on [Ribot's Android Project And Code Guidelines](https://github.com/ribot/android-guidelines/blob/c1d8c9c904eb31bf01fe24aadb963b74281fe79a/project_and_code_guidelines.md).
+
+### Menu files
+
+Similar to layout files, menu files should match the name of the component. For example, if we are defining a menu file that is going to be used in the `UserProfileActivity`, then the name of the file should be `activity_user_profile.xml`. Same pattern applies for menus used in adapter view items, dialogs, etc.
+
+| Component | Class Name | Menu Name |
+| ---------------- | ---------------------- | ----------------------------- |
+| Activity | `UserProfileActivity` | `activity_user_profile.xml` |
+| Fragment | `SignUpFragment` | `fragment_sign_up.xml` |
+| Dialog | `ChangePasswordDialog` | `dialog_change_password.xml` |
+| AdapterView item | --- | `item_person.xml` |
+| Partial layout | --- | `partial_stats_bar.xml` |
+
+A good practice is to not include the word `menu` as part of the name because these files are already located in the `menu` directory. In case a component uses several menus in different places (via popup menus) then the resource name would be extended. For example, if the user profile activity has two popup menus for configuring the users settings and one for the handling group assignments then the file names for the menus would be: `activity_user_profile_user_settings.xml` and `activity_user_profile_group_assignments.xml`.
+
## Translations
We manage translations via [Transifex](https://www.transifex.com/nextcloud/nextcloud/android/). So just request joining the translation team for Android on the site and start translating. All translations will then be automatically pushed to this repository, there is no need for any pull request for translations.
diff --git a/build.gradle b/build.gradle
index a8a862e339ef..aeb25b865553 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@
import com.github.spotbugs.SpotBugsTask
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()
@@ -20,13 +20,13 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.1'
+ classpath 'com.android.tools.build:gradle:3.5.2'
classpath('com.dicedmelon.gradle:jacoco-android:0.1.4') {
exclude group: 'org.codehaus.groovy', module: 'groovy-all'
}
classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.6'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.1.1"
+ classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.2.0"
}
}
@@ -58,7 +58,7 @@ configurations {
ext {
jacocoVersion = "0.8.2"
daggerVersion = "2.25.2"
- markwonVersion = "4.1.2"
+ markwonVersion = "4.2.0"
prismVersion = "2.0.0"
androidLibraryVersion = "master-SNAPSHOT"
@@ -105,7 +105,12 @@ android {
abortOnError false
htmlReport true
htmlOutput file("$project.buildDir/reports/lint/lint.html")
- disable 'MissingTranslation', 'GradleDependency', 'VectorPath', 'IconMissingDensityFolder', 'IconDensities'
+ disable 'MissingTranslation',
+ 'GradleDependency',
+ 'VectorPath',
+ 'IconMissingDensityFolder',
+ 'IconDensities',
+ 'GoogleAppIndexingWarning'
}
dexOptions {
@@ -162,8 +167,8 @@ android {
versionDev {
applicationId "com.nextcloud.android.beta"
dimension "default"
- versionCode 20191102
- versionName "20191102"
+ versionCode 20191127
+ versionName "20191127"
}
qa {
@@ -273,7 +278,7 @@ dependencies {
implementation 'com.jakewharton:disklrucache:2.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
- implementation 'androidx.exifinterface:exifinterface:1.0.0'
+ implementation 'androidx.exifinterface:exifinterface:1.1.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation "androidx.work:work-runtime:2.2.0"
implementation "androidx.work:work-runtime-ktx:2.2.0"
@@ -292,7 +297,7 @@ dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
implementation 'com.github.tobiaskaminsky:qrcodescanner:0.1.2.2' // 'com.github.blikoon:QRCodeScanner:0.1.2'
- implementation 'com.google.android:flexbox:1.1.1'
+ implementation 'com.google.android:flexbox:2.0.0'
implementation 'org.parceler:parceler-api:1.1.12'
kapt 'org.parceler:parceler:1.1.12'
implementation('com.github.bumptech.glide:glide:3.8.0') {
@@ -301,7 +306,7 @@ dependencies {
implementation 'com.caverock:androidsvg:1.4'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
- implementation 'org.jetbrains:annotations:17.0.0'
+ implementation 'org.jetbrains:annotations:18.0.0'
implementation 'com.github.cotechde.hwsecurity:hwsecurity-fido:2.5.1'
diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt
index 57214136334b..7b89b2202c95 100644
--- a/scripts/analysis/findbugs-results.txt
+++ b/scripts/analysis/findbugs-results.txt
@@ -1 +1 @@
-423
+406
\ No newline at end of file
diff --git a/spotbugs-filter.xml b/spotbugs-filter.xml
index 05c145793143..1b374ca4fed1 100644
--- a/spotbugs-filter.xml
+++ b/spotbugs-filter.xml
@@ -17,7 +17,8 @@
-
+
+
@@ -30,7 +31,7 @@
-
+
@@ -39,6 +40,10 @@
+
+
+
+
diff --git a/src/androidTest/java/com/owncloud/android/UploadIT.java b/src/androidTest/java/com/owncloud/android/UploadIT.java
index 1d468e7162aa..90df24d6daa2 100644
--- a/src/androidTest/java/com/owncloud/android/UploadIT.java
+++ b/src/androidTest/java/com/owncloud/android/UploadIT.java
@@ -120,7 +120,7 @@ public RemoteOperationResult testUpload(OCUpload ocUpload) {
account,
null,
ocUpload,
- false,
+ FileUploader.NameCollisionPolicy.DEFAULT,
FileUploader.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
@@ -140,17 +140,17 @@ public void testUploadInNonExistingFolder() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
"/testUpload/2/3/4/1.txt", account.name);
UploadFileOperation newUpload = new UploadFileOperation(
- storageManager,
- connectivityServiceMock,
- powerManagementServiceMock,
- account,
- null,
- ocUpload,
- false,
- FileUploader.LOCAL_BEHAVIOUR_COPY,
- targetContext,
- false,
- false
+ storageManager,
+ connectivityServiceMock,
+ powerManagementServiceMock,
+ account,
+ null,
+ ocUpload,
+ FileUploader.NameCollisionPolicy.DEFAULT,
+ FileUploader.LOCAL_BEHAVIOUR_COPY,
+ targetContext,
+ false,
+ false
);
newUpload.addRenameUploadListener(() -> {
// dummy
diff --git a/src/generic/fastlane/metadata/android/cs-CZ/full_description.txt b/src/generic/fastlane/metadata/android/cs-CZ/full_description.txt
index 5c7c441946a3..21f3f63f367b 100644
--- a/src/generic/fastlane/metadata/android/cs-CZ/full_description.txt
+++ b/src/generic/fastlane/metadata/android/cs-CZ/full_description.txt
@@ -1,19 +1,17 @@
-Svobodně licencovaná Nexcloud Android aplikace, dávající vám mobilní svobodu.
-
+Software, který můžete nasadit na vlastní server a ten organizuje ukládání a synchronizaci souborů. Díky doplňkům jde o kompletní groupwarové řešení.
Funkce:
-* Snadno použitelné moderní rozhraní, přizpůsobené vzhledu, používaném na vašem serveru
-* Nahrávejte soubory na svůj Nextcloud server
-* Sdílejte je s ostatními
-* Udržujte své oblíbené soubory a složky synchronizované
-* Hledejte napříč všemi složkami na svém serveru
-* Automaticky nahrávejte své fotky a videa, pořízená zařízením
-* Udržujte si přehled pomocí oznámení
-* Podporuje vícero účtů
-* Zabezpečený přístup k vašim datům pomocí otisku prstu nebo PIN kódu
-* Napojení na DAVx5 (dříve známé pod názvem DAVdroid) pro snadné nastavení synchronizace kalendáře a kontaktů
-
- Veškeré nalezené problémy hlaste na https://github.com/nextcloud/android/issues a diskutujte o aplikaci na https://help.nextcloud.com/c/clients/android
+* Soubory nahrané do nexcloudu lze snadno sdílet s ostatními.
+* Přístup odkudkoli přes webové a mobilní rozhraní.
+* Synchronizace s počítačem umožňující offline práci.
+* Vaše data jsou na vašem serveru a máte nad nimi plnou kontrolu.
+* Jde o svobodný software, který snadno můžete sami hostovat a volně si ho přizplsobit. V případě potřeby je ale k dispozici komerční podpora.
+* Vyhledávání napříč soubory.
+* Synchronizace kontaktů a zpráv.
+* Sdílení kalendářů do mobilu (DAVx5/DAVdroid)
+* Neomezený počet uživatelů, autoupload fotek přímo z mobilu, oznámení,
+* Klienti pro WIndows, MacOS, iOS, Android, web.
+* Mnoho dalších doplňků s dalšími funkcemi.
-Noví v Nextcloud? Nextcloud je soukromý server pro synchronizaci souborů a jejich sdílení, dále pro komunikaci. Jedná se o svobodný software a můžete ho hostovat svými silami nebo za to zaplatit nějaké společnosti. Tím máte pod kontrolou své fotky, kalendář a kontakty, dokumenty a vše ostatní.
+Díky Nextcloudu budete mít kompletní privátní řešení pro všechy vaše soubory, fotky, kalendáře a kontakty.
-Podívejte se na Nextcloud na https://nextcloud.com
\ No newline at end of file
+Připomínky zasílejte na https://github.com/nextcloud/android/issues, diskutovat lze na https://help.nextcloud.com/c/clients a vše ostatní najdete na https://nextcloud.com
\ No newline at end of file
diff --git a/src/generic/fastlane/metadata/android/cs-CZ/short_description.txt b/src/generic/fastlane/metadata/android/cs-CZ/short_description.txt
index 056cf107f752..01a56a453767 100644
--- a/src/generic/fastlane/metadata/android/cs-CZ/short_description.txt
+++ b/src/generic/fastlane/metadata/android/cs-CZ/short_description.txt
@@ -1 +1 @@
-Produktivní platforma hostovaná u vás, kde vaše data jsou opravdu vaše
\ No newline at end of file
+Platforma pro produktivitu hostovaná u vás, kde vaše data jsou opravdu vaše
\ No newline at end of file
diff --git a/src/main/java/com/nextcloud/client/account/AnonymousUser.kt b/src/main/java/com/nextcloud/client/account/AnonymousUser.kt
new file mode 100644
index 000000000000..ec2a284e2cbc
--- /dev/null
+++ b/src/main/java/com/nextcloud/client/account/AnonymousUser.kt
@@ -0,0 +1,58 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.nextcloud.client.account
+
+import android.accounts.Account
+import android.content.Context
+import android.net.Uri
+import com.owncloud.android.MainApp
+import com.owncloud.android.R
+import com.owncloud.android.lib.common.OwnCloudAccount
+import com.owncloud.android.lib.common.OwnCloudBasicCredentials
+import java.net.URI
+
+/**
+ * This object represents anonymous user, ie. user that did not log in the Nextcloud server.
+ * It serves as a semantically correct "empty value", allowing simplification of logic
+ * in various components requiring user data, such as DB queries.
+ */
+internal class AnonymousUser(private val accountType: String) : User {
+
+ companion object {
+ @JvmStatic
+ fun fromContext(context: Context): AnonymousUser {
+ val type = context.getString(R.string.account_type)
+ return AnonymousUser(type)
+ }
+ }
+
+ override val accountName: String = "anonymous"
+ override val server = Server(URI.create(""), MainApp.MINIMUM_SUPPORTED_SERVER_VERSION)
+ override val isAnonymous = true
+
+ override fun toPlatformAccount(): Account {
+ return Account(accountName, accountType)
+ }
+
+ override fun toOwnCloudAccount(): OwnCloudAccount {
+ return OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", ""))
+ }
+}
diff --git a/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java b/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java
index f37624933b4d..ab6484599a5f 100644
--- a/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java
+++ b/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java
@@ -2,19 +2,31 @@
import android.accounts.Account;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
- * This interface provides access to currently selected user Account.
+ * This interface provides access to currently selected user.
+ *
* @see UserAccountManager
*/
-@FunctionalInterface
public interface CurrentAccountProvider {
/**
- * Get currently active account.
+ * Get currently active account.
*
* @return Currently selected {@link Account} or first valid {@link Account} registered in OS or null, if not available at all.
*/
+ @Deprecated
@Nullable
Account getCurrentAccount();
+
+ /**
+ * Get currently active user profile. If there is no actice user, anonymous user is returned.
+ *
+ * @return User profile. Profile is never null.
+ */
+ @NonNull
+ default User getUser() {
+ return new AnonymousUser("dummy");
+ }
}
diff --git a/src/main/java/com/nextcloud/client/account/RegisteredUser.kt b/src/main/java/com/nextcloud/client/account/RegisteredUser.kt
new file mode 100644
index 000000000000..4ba956552137
--- /dev/null
+++ b/src/main/java/com/nextcloud/client/account/RegisteredUser.kt
@@ -0,0 +1,47 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.nextcloud.client.account
+
+import android.accounts.Account
+import com.owncloud.android.lib.common.OwnCloudAccount
+
+/**
+ * This class represents normal user logged into the Nextcloud server.
+ */
+internal class RegisteredUser(
+ private val account: Account,
+ private val ownCloudAccount: OwnCloudAccount,
+ override val server: Server
+) : User {
+ override val isAnonymous = false
+
+ override val accountName: String get() {
+ return account.name
+ }
+
+ override fun toPlatformAccount(): Account {
+ return account
+ }
+
+ override fun toOwnCloudAccount(): OwnCloudAccount {
+ return ownCloudAccount
+ }
+}
diff --git a/src/main/java/com/nextcloud/client/account/Server.kt b/src/main/java/com/nextcloud/client/account/Server.kt
new file mode 100644
index 000000000000..0a44dfb4ecbd
--- /dev/null
+++ b/src/main/java/com/nextcloud/client/account/Server.kt
@@ -0,0 +1,30 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.nextcloud.client.account
+
+import com.owncloud.android.lib.resources.status.OwnCloudVersion
+import java.net.URI
+
+/**
+ * This object provides all information necessary to interact
+ * with backend server.
+ */
+data class Server(val uri: URI, val version: OwnCloudVersion)
diff --git a/src/main/java/com/nextcloud/client/account/User.kt b/src/main/java/com/nextcloud/client/account/User.kt
new file mode 100644
index 000000000000..0500d8358a9a
--- /dev/null
+++ b/src/main/java/com/nextcloud/client/account/User.kt
@@ -0,0 +1,54 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.nextcloud.client.account
+
+import android.accounts.Account
+import com.owncloud.android.lib.common.OwnCloudAccount
+
+interface User {
+ val accountName: String
+ val server: Server
+ val isAnonymous: Boolean
+
+ /**
+ * This is temporary helper method created to facilitate incremental refactoring.
+ * Code using legacy platform Account can be partially converted to instantiate User
+ * object and use account instance when required.
+ *
+ * This method calls will allow tracing code awaiting further refactoring.
+ *
+ * @return Account instance that is associated with this User object.
+ */
+ @Deprecated("Temporary workaround")
+ fun toPlatformAccount(): Account
+
+ /**
+ * This is temporary helper method created to facilitate incremental refactoring.
+ * Code using legacy ownCloud account can be partially converted to instantiate User
+ * object and use account instance when required.
+ *
+ * This method calls will allow tracing code awaiting further refactoring.
+ *
+ * @return OwnCloudAccount instance that is associated with this User object.
+ */
+ @Deprecated("Temporary workaround")
+ fun toOwnCloudAccount(): OwnCloudAccount
+}
diff --git a/src/main/java/com/nextcloud/client/account/UserAccountManager.java b/src/main/java/com/nextcloud/client/account/UserAccountManager.java
index 5584541c49e2..d4394423a564 100644
--- a/src/main/java/com/nextcloud/client/account/UserAccountManager.java
+++ b/src/main/java/com/nextcloud/client/account/UserAccountManager.java
@@ -21,10 +21,13 @@
import android.accounts.Account;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
+import java.util.List;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -51,6 +54,22 @@ public interface UserAccountManager extends CurrentAccountProvider {
@NonNull
Account[] getAccounts();
+ /**
+ * Get configured nextcloud user accounts
+ * @return List of users or empty list, if users are not registered.
+ */
+ @NonNull
+ List getAllUsers();
+
+ /**
+ * Get user with a specific account name.
+ *
+ * @param accountName Account name of the requested user
+ * @return User or empty optional if user does not exist.
+ */
+ @NonNull
+ Optional getUser(CharSequence accountName);
+
/**
* Check if Nextcloud account is registered in {@link android.accounts.AccountManager}
*
@@ -78,10 +97,24 @@ public interface UserAccountManager extends CurrentAccountProvider {
* @return Version of the OC server corresponding to account, according to the data saved
* in the system AccountManager
*/
+ @Deprecated
@NonNull
OwnCloudVersion getServerVersion(Account account);
+ @Deprecated
boolean isSearchSupported(@Nullable Account account);
+
+ /**
+ * Check if user's account supports media streaming. This is a property of server where user has his account.
+ *
+ * @deprecated Please use {@link OwnCloudVersion#isMediaStreamingSupported()} directly,
+ * obtainable from {@link User#getServer()} and {@link Server#getVersion()}
+ *
+ * @param account Account used to perform {@link android.accounts.AccountManager} lookup.
+ *
+ * @return true is server supports media streaming, false otherwise
+ */
+ @Deprecated
boolean isMediaStreamingSupported(@Nullable Account account);
void resetOwnCloudAccount();
diff --git a/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java b/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java
index 575580f04956..4ea8246872eb 100644
--- a/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java
+++ b/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java
@@ -27,6 +27,7 @@
import android.preference.PreferenceManager;
import android.text.TextUtils;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
@@ -35,11 +36,18 @@
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.UserInfo;
+import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.inject.Inject;
import androidx.annotation.NonNull;
@@ -80,6 +88,20 @@ public Account[] getAccounts() {
return accountManager.getAccountsByType(getAccountType());
}
+ @Override
+ @NonNull
+ public List getAllUsers() {
+ Account[] accounts = getAccounts();
+ List users = new ArrayList<>(accounts.length);
+ for (Account account : accounts) {
+ User user = createUserFromAccount(account);
+ if (user != null) {
+ users.add(user);
+ }
+ }
+ return users;
+ }
+
@Override
public boolean exists(Account account) {
Account[] nextcloudAccounts = getAccounts();
@@ -103,6 +125,7 @@ public boolean exists(Account account) {
return false;
}
+ @Override
@Nullable
public Account getCurrentAccount() {
Account[] ocAccounts = getAccounts();
@@ -139,6 +162,76 @@ public Account getCurrentAccount() {
return defaultAccount;
}
+ /**
+ * Temporary solution to convert platform account to user instance.
+ * It takes null and returns null on error to ease error handling
+ * in legacy code.
+ *
+ * @param account Account instance
+ * @return User instance or null, if conversion failed
+ */
+ @Nullable
+ private User createUserFromAccount(@Nullable Account account) {
+ if (account == null) {
+ return null;
+ }
+
+ OwnCloudAccount ownCloudAccount = null;
+ try {
+ ownCloudAccount = new OwnCloudAccount(account, context);
+ } catch (AccountUtils.AccountNotFoundException ex) {
+ return null;
+ }
+
+ /*
+ * Server version
+ */
+ String serverVersionStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_VERSION);
+ OwnCloudVersion serverVersion;
+ if (serverVersionStr != null) {
+ serverVersion = new OwnCloudVersion(serverVersionStr);
+ } else {
+ serverVersion = MainApp.MINIMUM_SUPPORTED_SERVER_VERSION;
+ }
+
+ /*
+ * Server address
+ */
+ String serverAddressStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);
+ if (serverAddressStr == null || serverAddressStr.isEmpty()) {
+ return AnonymousUser.fromContext(context);
+ }
+ URI serverUri = URI.create(serverAddressStr); // TODO: validate
+
+ return new RegisteredUser(
+ account,
+ ownCloudAccount,
+ new Server(serverUri, serverVersion)
+ );
+ }
+
+ /**
+ * Get user. If user cannot be retrieved due to data error, anonymous user is returned instead.
+ *
+ *
+ * @return User instance
+ */
+ @NotNull
+ @Override
+ public User getUser() {
+ Account account = getCurrentAccount();
+ User user = createUserFromAccount(account);
+ return user != null ? user : AnonymousUser.fromContext(context);
+ }
+
+ @Override
+ @NonNull
+ public Optional getUser(CharSequence accountName) {
+ Account account = getAccountByName(accountName.toString());
+ User user = createUserFromAccount(account);
+ return Optional.of(user);
+ }
+
@Override
@Nullable
public OwnCloudAccount getCurrentOwnCloudAccount() {
@@ -197,6 +290,7 @@ public boolean setCurrentOwnCloudAccount(int hashCode) {
return result;
}
+ @Deprecated
@Override
@NonNull
public OwnCloudVersion getServerVersion(Account account) {
diff --git a/src/main/java/com/nextcloud/client/di/AppModule.java b/src/main/java/com/nextcloud/client/di/AppModule.java
index f9199705240c..64c96123b448 100644
--- a/src/main/java/com/nextcloud/client/di/AppModule.java
+++ b/src/main/java/com/nextcloud/client/di/AppModule.java
@@ -41,6 +41,7 @@
import com.nextcloud.client.logger.Logger;
import com.nextcloud.client.logger.LoggerImpl;
import com.nextcloud.client.logger.LogsRepository;
+import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository;
@@ -106,8 +107,8 @@ ActivitiesRepository activitiesRepository(ActivitiesServiceApi api) {
}
@Provides
- FilesRepository filesRepository(UserAccountManager accountManager) {
- return new RemoteFilesRepository(new FilesServiceApiImpl(accountManager));
+ FilesRepository filesRepository(UserAccountManager accountManager, ClientFactory clientFactory) {
+ return new RemoteFilesRepository(new FilesServiceApiImpl(accountManager, clientFactory));
}
@Provides
diff --git a/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index a8eadc36411a..558ef0afa9a5 100644
--- a/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -32,6 +32,7 @@
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.NotificationJob;
import com.owncloud.android.providers.DiskLruImageCacheFileProvider;
+import com.owncloud.android.providers.FileContentProvider;
import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
import com.owncloud.android.services.AccountManagerService;
import com.owncloud.android.services.OperationsService;
@@ -148,6 +149,7 @@ abstract class ComponentsModule {
@ContributesAndroidInjector abstract BootupBroadcastReceiver bootupBroadcastReceiver();
@ContributesAndroidInjector abstract NotificationJob.NotificationReceiver notificationJobBroadcastReceiver();
+ @ContributesAndroidInjector abstract FileContentProvider fileContentProvider();
@ContributesAndroidInjector abstract UsersAndGroupsSearchProvider usersAndGroupsSearchProvider();
@ContributesAndroidInjector abstract DiskLruImageCacheFileProvider diskLruImageCacheFileProvider();
diff --git a/src/main/java/com/nextcloud/client/errorhandling/ShowErrorActivity.kt b/src/main/java/com/nextcloud/client/errorhandling/ShowErrorActivity.kt
index f8f4b5405324..d115e298c34d 100644
--- a/src/main/java/com/nextcloud/client/errorhandling/ShowErrorActivity.kt
+++ b/src/main/java/com/nextcloud/client/errorhandling/ShowErrorActivity.kt
@@ -69,7 +69,7 @@ class ShowErrorActivity : AppCompatActivity() {
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.activity_error_show, menu)
+ menuInflater.inflate(R.menu.activity_show_error, menu)
return super.onCreateOptionsMenu(menu)
}
diff --git a/src/main/java/com/nextcloud/client/etm/pages/EtmPreferencesFragment.kt b/src/main/java/com/nextcloud/client/etm/pages/EtmPreferencesFragment.kt
index 68866d1c1d3b..a14506feffbf 100644
--- a/src/main/java/com/nextcloud/client/etm/pages/EtmPreferencesFragment.kt
+++ b/src/main/java/com/nextcloud/client/etm/pages/EtmPreferencesFragment.kt
@@ -51,7 +51,7 @@ class EtmPreferencesFragment : EtmBaseFragment() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
- inflater.inflate(R.menu.etm_preferences, menu)
+ inflater.inflate(R.menu.fragment_etm_preferences, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
diff --git a/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
index 3ddd9971144b..38a6a57c818d 100644
--- a/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
+++ b/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
@@ -26,6 +26,7 @@ import androidx.annotation.RequiresApi
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
+import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences
@@ -40,6 +41,7 @@ import javax.inject.Provider
class BackgroundJobFactory @Inject constructor(
private val preferences: AppPreferences,
private val contentResolver: ContentResolver,
+ private val clock: Clock,
private val powerManagerService: PowerManagementService,
private val backgroundJobManager: Provider,
private val deviceInfo: DeviceInfo
@@ -58,13 +60,14 @@ class BackgroundJobFactory @Inject constructor(
}
return when (workerClass) {
- ContentObserverWork::class -> createContentObserverJob(context, workerParameters)
+ ContentObserverWork::class -> createContentObserverJob(context, workerParameters, clock)
else -> null // falls back to default factory
}
}
- private fun createContentObserverJob(context: Context, workerParameters: WorkerParameters): ListenableWorker? {
- val folderResolver = SyncedFolderProvider(contentResolver, preferences)
+ private fun createContentObserverJob(context: Context, workerParameters: WorkerParameters, clock: Clock):
+ ListenableWorker? {
+ val folderResolver = SyncedFolderProvider(contentResolver, preferences, clock)
@RequiresApi(Build.VERSION_CODES.N)
if (deviceInfo.apiLevel >= Build.VERSION_CODES.N) {
return ContentObserverWork(
diff --git a/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt b/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
index 7d3342bc2a4f..4c791716f9a4 100644
--- a/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
+++ b/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
@@ -84,7 +84,7 @@ class LogsActivity : ToolbarActivity() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.logs_menu, menu)
+ menuInflater.inflate(R.menu.activity_logs, menu)
(menu.findItem(R.id.action_search).actionView as SearchView).apply {
setOnQueryTextListener(searchBoxListener)
diff --git a/src/main/java/com/nextcloud/client/network/ClientFactory.java b/src/main/java/com/nextcloud/client/network/ClientFactory.java
index affbb34c79bc..9f8694bab25f 100644
--- a/src/main/java/com/nextcloud/client/network/ClientFactory.java
+++ b/src/main/java/com/nextcloud/client/network/ClientFactory.java
@@ -26,6 +26,7 @@
import android.app.Activity;
import android.net.Uri;
+import com.nextcloud.client.account.User;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.accounts.AccountUtils;
@@ -35,10 +36,31 @@
public interface ClientFactory {
+ /**
+ * This exception wraps all possible errors thrown by trigger-happy
+ * OwnCloudClient constructor, making try-catch blocks manageable.
+ *
+ * This is a temporary refactoring measure, until a better
+ * error handling method can be procured.
+ */
+ @Deprecated
+ class CreationException extends Exception {
+
+ private static final long serialVersionUID = 0L;
+
+ CreationException(Throwable t) {
+ super(t);
+ }
+ }
+
+ OwnCloudClient create(User user) throws CreationException;
+
+ @Deprecated
OwnCloudClient create(Account account)
throws OperationCanceledException, AuthenticatorException, IOException,
AccountUtils.AccountNotFoundException;
+ @Deprecated
OwnCloudClient create(Account account, Activity currentActivity)
throws OperationCanceledException, AuthenticatorException, IOException,
AccountUtils.AccountNotFoundException;
diff --git a/src/main/java/com/nextcloud/client/network/ClientFactoryImpl.java b/src/main/java/com/nextcloud/client/network/ClientFactoryImpl.java
index 467d98ebc0b6..ff88a8487f9b 100644
--- a/src/main/java/com/nextcloud/client/network/ClientFactoryImpl.java
+++ b/src/main/java/com/nextcloud/client/network/ClientFactoryImpl.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.net.Uri;
+import com.nextcloud.client.account.User;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils;
@@ -41,6 +42,18 @@ class ClientFactoryImpl implements ClientFactory {
this.context = context;
}
+ @Override
+ public OwnCloudClient create(User user) throws CreationException {
+ try {
+ return OwnCloudClientFactory.createOwnCloudClient(user.toPlatformAccount(), context);
+ } catch (OperationCanceledException|
+ AuthenticatorException|
+ IOException|
+ AccountUtils.AccountNotFoundException e) {
+ throw new CreationException(e);
+ }
+ }
+
@Override
public OwnCloudClient create(Account account)
throws OperationCanceledException, AuthenticatorException, IOException,
diff --git a/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java b/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
index 9060b7922ad3..14f2a54cc4cf 100644
--- a/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
+++ b/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
@@ -20,14 +20,13 @@
package com.nextcloud.client.network;
-import android.accounts.Account;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.evernote.android.job.JobRequest;
+import com.nextcloud.client.account.Server;
import com.nextcloud.client.account.UserAccountManager;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.utils.Log_OC;
+import com.nextcloud.client.logger.Logger;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import org.apache.commons.httpclient.HttpClient;
@@ -44,10 +43,11 @@ class ConnectivityServiceImpl implements ConnectivityService {
private final static String TAG = ConnectivityServiceImpl.class.getName();
- private ConnectivityManager connectivityManager;
- private UserAccountManager accountManager;
- private ClientFactory clientFactory;
- private GetRequestBuilder requestBuilder;
+ private final ConnectivityManager connectivityManager;
+ private final UserAccountManager accountManager;
+ private final ClientFactory clientFactory;
+ private final GetRequestBuilder requestBuilder;
+ private final Logger logger;
static class GetRequestBuilder implements Function1 {
@Override
@@ -59,55 +59,55 @@ public GetMethod invoke(String url) {
ConnectivityServiceImpl(ConnectivityManager connectivityManager,
UserAccountManager accountManager,
ClientFactory clientFactory,
- GetRequestBuilder requestBuilder) {
+ GetRequestBuilder requestBuilder,
+ Logger logger) {
this.connectivityManager = connectivityManager;
this.accountManager = accountManager;
this.clientFactory = clientFactory;
this.requestBuilder = requestBuilder;
+ this.logger = logger;
}
@Override
public boolean isInternetWalled() {
if (isOnlineWithWifi()) {
try {
- Account account = accountManager.getCurrentAccount();
- OwnCloudAccount ocAccount = accountManager.getCurrentOwnCloudAccount();
- if (account != null && ocAccount != null) {
- OwnCloudVersion serverVersion = accountManager.getServerVersion(account);
-
- String url;
- if (serverVersion.compareTo(OwnCloudVersion.nextcloud_13) > 0) {
- url = ocAccount.getBaseUri() + "/index.php/204";
- } else {
- url = ocAccount.getBaseUri() + "/status.php";
- }
-
- GetMethod get = requestBuilder.invoke(url);
- HttpClient client = clientFactory.createPlainClient();
-
- int status = client.executeMethod(get);
+ Server server = accountManager.getUser().getServer();
+ String baseServerAddress = server.getUri().toString();
+ if (baseServerAddress.isEmpty()) {
+ return true;
+ }
+ String url;
+ if (server.getVersion().compareTo(OwnCloudVersion.nextcloud_13) > 0) {
+ url = baseServerAddress + "/index.php/204";
+ } else {
+ url = baseServerAddress + "/status.php";
+ }
- if (serverVersion.compareTo(OwnCloudVersion.nextcloud_13) > 0) {
- return !(status == HttpStatus.SC_NO_CONTENT &&
- (get.getResponseContentLength() == -1 || get.getResponseContentLength() == 0));
- } else {
- if (status == HttpStatus.SC_OK) {
- try {
- // try parsing json to verify response
- // check if json contains maintenance and it should be false
-
- String json = get.getResponseBodyAsString();
- return new JSONObject(json).getBoolean("maintenance");
- } catch (Exception e) {
- return true;
- }
- } else {
+ GetMethod get = requestBuilder.invoke(url);
+ HttpClient client = clientFactory.createPlainClient();
+
+ int status = client.executeMethod(get);
+
+ if (server.getVersion().compareTo(OwnCloudVersion.nextcloud_13) > 0) {
+ return !(status == HttpStatus.SC_NO_CONTENT &&
+ (get.getResponseContentLength() == -1 || get.getResponseContentLength() == 0));
+ } else {
+ if (status == HttpStatus.SC_OK) {
+ try {
+ // try parsing json to verify response
+ // check if json contains maintenance and it should be false
+ String json = get.getResponseBodyAsString();
+ return new JSONObject(json).getBoolean("maintenance");
+ } catch (Exception e) {
return true;
}
+ } else {
+ return true;
}
}
} catch (IOException e) {
- Log_OC.e(TAG, "Error checking internet connection", e);
+ logger.e(TAG, "Error checking internet connection", e);
}
} else {
return getActiveNetworkType() == JobRequest.NetworkType.ANY;
diff --git a/src/main/java/com/nextcloud/client/network/NetworkModule.java b/src/main/java/com/nextcloud/client/network/NetworkModule.java
index 61634af7a964..2a7edf8a7dd1 100644
--- a/src/main/java/com/nextcloud/client/network/NetworkModule.java
+++ b/src/main/java/com/nextcloud/client/network/NetworkModule.java
@@ -24,6 +24,7 @@
import android.net.ConnectivityManager;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.logger.Logger;
import javax.inject.Singleton;
@@ -36,11 +37,13 @@ public class NetworkModule {
@Provides
ConnectivityService connectivityService(ConnectivityManager connectivityManager,
UserAccountManager accountManager,
- ClientFactory clientFactory) {
+ ClientFactory clientFactory,
+ Logger logger) {
return new ConnectivityServiceImpl(connectivityManager,
accountManager,
clientFactory,
- new ConnectivityServiceImpl.GetRequestBuilder());
+ new ConnectivityServiceImpl.GetRequestBuilder(),
+ logger);
}
@Provides
diff --git a/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java b/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java
index eb978cda1bc1..8790115ebe73 100644
--- a/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java
+++ b/src/main/java/com/nextcloud/client/onboarding/FirstRunActivity.java
@@ -217,17 +217,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
return;
}
- setAccount(account);
userAccountManager.setCurrentOwnCloudAccount(account.name);
- onAccountSet();
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
+
+ finish();
}
}
+
+
public static FeatureItem[] getFirstRun() {
return new FeatureItem[]{
new FeatureItem(R.drawable.logo, R.string.first_run_1_text, R.string.empty, true, false),
diff --git a/src/main/java/com/nextcloud/client/preferences/AppPreferences.java b/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
index aad701982c7a..5c6d9107a6ef 100644
--- a/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
+++ b/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
@@ -23,8 +23,42 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.utils.FileSortOrder;
+import androidx.annotation.Nullable;
+
+/**
+ * This interface provides single point of entry for access to all application
+ * preferences and allows clients to subscribe for specific configuration
+ * changes.
+ */
public interface AppPreferences {
+ /**
+ * Preferences listener. Callbacks should be invoked on main thread.
+ *
+ * Miantainers should extend this interface with callbacks for specific
+ * events.
+ */
+ interface Listener {
+ default void onDarkThemeEnabledChanged(boolean enabled) {
+ /* default empty implementation */
+ };
+ }
+
+ /**
+ * Registers preferences listener. It no-ops if listener
+ * is already registered.
+ *
+ * @param listener application preferences listener
+ */
+ void addListener(@Nullable Listener listener);
+
+ /**
+ * Unregister listener. It no-ops if listener is not registered.
+ *
+ * @param listener application preferences listener
+ */
+ void removeListener(@Nullable Listener listener);
+
void setKeysReInitEnabled();
boolean isKeysReInitEnabled();
@@ -239,6 +273,18 @@ public interface AppPreferences {
*/
int getUploaderBehaviour();
+ /**
+ * Enable dark theme.
+ *
+ * This is reactive property. Listeners will be invoked if registered.
+ *
+ * @param enabled true to turn dark theme on, false to turn it off
+ */
+ void setDarkThemeEnabled(boolean enabled);
+
+ /**
+ * @return true if application uses dark UI theme, false otherwise
+ */
boolean isDarkThemeEnabled();
/**
diff --git a/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
index a78ee9aa2f6c..60867fabf047 100644
--- a/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
+++ b/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
@@ -27,6 +27,7 @@
import android.content.SharedPreferences;
import com.nextcloud.client.account.CurrentAccountProvider;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -35,12 +36,21 @@
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.utils.FileSortOrder;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import androidx.annotation.Nullable;
+
import static com.owncloud.android.ui.fragment.OCFileListFragment.FOLDER_LAYOUT_LIST;
/**
- * Helper to simplify reading of Preferences all around the app
+ * Implementation of application-wide preferences using {@link SharedPreferences}.
+ *
+ * Users should not use this class directly. Please use {@link AppPreferences} interafce
+ * instead.
*/
public final class AppPreferencesImpl implements AppPreferences {
+
/**
* Constant to access value of last path selected by the user to upload a file shared from other app.
* Value handled by the app without direct access in the UI.
@@ -48,6 +58,7 @@ public final class AppPreferencesImpl implements AppPreferences {
public static final String AUTO_PREF__LAST_SEEN_VERSION_CODE = "lastSeenVersionCode";
public static final String STORAGE_PATH = "storage_path";
public static final float DEFAULT_GRID_COLUMN = 4.0f;
+
private static final String AUTO_PREF__LAST_UPLOAD_PATH = "last_upload_path";
private static final String AUTO_PREF__UPLOAD_FROM_LOCAL_LAST_PATH = "upload_from_local_last_path";
private static final String AUTO_PREF__UPLOAD_FILE_EXTENSION_MAP_URL = "prefs_upload_file_extension_map_url";
@@ -68,7 +79,7 @@ public final class AppPreferencesImpl implements AppPreferences {
private static final String PREF__AUTO_UPLOAD_INIT = "autoUploadInit";
private static final String PREF__FOLDER_SORT_ORDER = "folder_sort_order";
private static final String PREF__FOLDER_LAYOUT = "folder_layout";
- public static final String PREF__THEME = "darkTheme";
+ static final String PREF__DARK_THEME_ENABLED = "dark_theme_enabled";
private static final String PREF__LOCK_TIMESTAMP = "lock_timestamp";
private static final String PREF__SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications";
@@ -81,7 +92,54 @@ public final class AppPreferencesImpl implements AppPreferences {
private final Context context;
private final SharedPreferences preferences;
private final CurrentAccountProvider currentAccountProvider;
+ private final ListenerRegistry listeners;
+
+ /**
+ * Adapter delegating raw {@link SharedPreferences.OnSharedPreferenceChangeListener} calls
+ * with key-value pairs to respective {@link com.nextcloud.client.preferences.AppPreferences.Listener} method.
+ */
+ static class ListenerRegistry implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private final AppPreferences preferences;
+ private final Set listeners;
+
+ ListenerRegistry(AppPreferences preferences) {
+ this.preferences = preferences;
+ this.listeners = new CopyOnWriteArraySet<>();
+ }
+
+ void add(@Nullable final Listener listener) {
+ if (listener != null) {
+ listeners.add(listener);
+ }
+ }
+
+ void remove(@Nullable final Listener listener) {
+ if (listener != null) {
+ listeners.remove(listener);
+ }
+ }
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(PREF__DARK_THEME_ENABLED.equals(key)) {
+ boolean enabled = preferences.isDarkThemeEnabled();
+ for(Listener l : listeners) {
+ l.onDarkThemeEnabledChanged(enabled);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is a temporary workaround to access app preferences in places that cannot use
+ * dependency injection yet. Use injected component via {@link AppPreferences} interface.
+ *
+ * WARNING: this creates new instance! it does not return app-wide singleton
+ *
+ * @param context Context used to create shared preferences
+ * @return New instance of app preferences component
+ */
+ @Deprecated
public static AppPreferences fromContext(Context context) {
final CurrentAccountProvider currentAccountProvider = UserAccountManagerImpl.fromContext(context);
final SharedPreferences prefs = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
@@ -92,6 +150,18 @@ public static AppPreferences fromContext(Context context) {
this.context = appContext;
this.preferences = preferences;
this.currentAccountProvider = currentAccountProvider;
+ this.listeners = new ListenerRegistry(this);
+ this.preferences.registerOnSharedPreferenceChangeListener(listeners);
+ }
+
+ @Override
+ public void addListener(@Nullable AppPreferences.Listener listener) {
+ this.listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(@Nullable AppPreferences.Listener listener) {
+ this.listeners.remove(listener);
}
@Override
@@ -213,7 +283,7 @@ public boolean isFingerprintUnlockEnabled() {
@Override
public String getFolderLayout(OCFile folder) {
return getFolderPreference(context,
- currentAccountProvider.getCurrentAccount(),
+ currentAccountProvider.getUser(),
PREF__FOLDER_LAYOUT,
folder,
FOLDER_LAYOUT_LIST);
@@ -222,7 +292,7 @@ public String getFolderLayout(OCFile folder) {
@Override
public void setFolderLayout(OCFile folder, String layout_name) {
setFolderPreference(context,
- currentAccountProvider.getCurrentAccount(),
+ currentAccountProvider.getUser(),
PREF__FOLDER_LAYOUT,
folder,
layout_name);
@@ -231,7 +301,7 @@ public void setFolderLayout(OCFile folder, String layout_name) {
@Override
public FileSortOrder getSortOrderByFolder(OCFile folder) {
return FileSortOrder.sortOrders.get(getFolderPreference(context,
- currentAccountProvider.getCurrentAccount(),
+ currentAccountProvider.getUser(),
PREF__FOLDER_SORT_ORDER,
folder,
FileSortOrder.sort_a_to_z.name));
@@ -240,7 +310,7 @@ public FileSortOrder getSortOrderByFolder(OCFile folder) {
@Override
public void setSortOrder(OCFile folder, FileSortOrder sortOrder) {
setFolderPreference(context,
- currentAccountProvider.getCurrentAccount(),
+ currentAccountProvider.getUser(),
PREF__FOLDER_SORT_ORDER,
folder,
sortOrder.name);
@@ -253,28 +323,23 @@ public FileSortOrder getSortOrderByType(FileSortOrder.Type type) {
@Override
public FileSortOrder getSortOrderByType(FileSortOrder.Type type, FileSortOrder defaultOrder) {
- Account account = currentAccountProvider.getCurrentAccount();
- if (account == null) {
+ User user = currentAccountProvider.getUser();
+ if (user.isAnonymous()) {
return defaultOrder;
}
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
- String value = dataProvider.getValue(account.name, PREF__FOLDER_SORT_ORDER + "_" + type);
+ String value = dataProvider.getValue(user.getAccountName(), PREF__FOLDER_SORT_ORDER + "_" + type);
return value.isEmpty() ? defaultOrder : FileSortOrder.sortOrders.get(value);
}
@Override
public void setSortOrder(FileSortOrder.Type type, FileSortOrder sortOrder) {
- Account account = currentAccountProvider.getCurrentAccount();
-
- if (account == null) {
- throw new IllegalArgumentException("Account may not be null!");
- }
-
+ User user = currentAccountProvider.getUser();
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
- dataProvider.storeOrUpdateKeyValue(account.name, PREF__FOLDER_SORT_ORDER + "_" + type, sortOrder.name);
+ dataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREF__FOLDER_SORT_ORDER + "_" + type, sortOrder.name);
}
@Override
@@ -342,9 +407,14 @@ public int getUploaderBehaviour() {
return preferences.getInt(AUTO_PREF__UPLOADER_BEHAVIOR, 1);
}
+ @Override
+ public void setDarkThemeEnabled(boolean enabled) {
+ preferences.edit().putBoolean(PREF__DARK_THEME_ENABLED, enabled).apply();
+ }
+
@Override
public boolean isDarkThemeEnabled() {
- return preferences.getBoolean(PREF__THEME, false);
+ return preferences.getBoolean(PREF__DARK_THEME_ENABLED, false);
}
@Override
@@ -502,22 +572,22 @@ public long getPhotoSearchTimestamp() {
* @return Preference value
*/
private static String getFolderPreference(final Context context,
- final Account account,
+ final User user,
final String preferenceName,
final OCFile folder,
final String defaultValue) {
- if (account == null) {
+ if (user.isAnonymous()) {
return defaultValue;
}
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
- FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
+ FileDataStorageManager storageManager = new FileDataStorageManager(user.toPlatformAccount(), context.getContentResolver());
- String value = dataProvider.getValue(account.name, getKeyFromFolder(preferenceName, folder));
+ String value = dataProvider.getValue(user.getAccountName(), getKeyFromFolder(preferenceName, folder));
OCFile prefFolder = folder;
while (prefFolder != null && value.isEmpty()) {
prefFolder = storageManager.getFileById(prefFolder.getParentId());
- value = dataProvider.getValue(account.name, getKeyFromFolder(preferenceName, prefFolder));
+ value = dataProvider.getValue(user.getAccountName(), getKeyFromFolder(preferenceName, prefFolder));
}
return value.isEmpty() ? defaultValue : value;
}
@@ -531,16 +601,12 @@ private static String getFolderPreference(final Context context,
* @param value Preference value to set.
*/
private static void setFolderPreference(final Context context,
- final Account account,
+ final User user,
final String preferenceName,
final OCFile folder,
final String value) {
- if (account == null) {
- throw new IllegalArgumentException("Account may not be null!");
- }
-
ArbitraryDataProvider dataProvider = new ArbitraryDataProvider(context.getContentResolver());
- dataProvider.storeOrUpdateKeyValue(account.name, getKeyFromFolder(preferenceName, folder), value);
+ dataProvider.storeOrUpdateKeyValue(user.getAccountName(), getKeyFromFolder(preferenceName, folder), value);
}
private static String getKeyFromFolder(String preferenceName, OCFile folder) {
diff --git a/src/main/java/com/nextcloud/java/util/Optional.java b/src/main/java/com/nextcloud/java/util/Optional.java
new file mode 100644
index 000000000000..e3a31e5c2a84
--- /dev/null
+++ b/src/main/java/com/nextcloud/java/util/Optional.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.nextcloud.java.util;
+
+import com.nextcloud.java.util.function.Predicate;
+
+import java.util.NoSuchElementException;
+
+import androidx.core.util.Consumer;
+import androidx.core.util.ObjectsCompat;
+import androidx.core.util.Supplier;
+import kotlin.jvm.functions.Function1;
+
+/**
+ * This class is backported from Java 8 to be used on older Android API levels.
+ * It uses available interfaces from Kotlin and androidx. It is semantically
+ * identical with Java 8 API, allowing smooth migration when those APIs become
+ * available.
+ *
+ * A container object which may or may not contain a non-null value.
+ * If a value is present, {@code isPresent()} will return {@code true} and
+ * {@code get()} will return the value.
+ *
+ *
Additional methods that depend on the presence or absence of a contained
+ * value are provided, such as {@link #orElse(java.lang.Object) orElse()}
+ * (return a default value if value not present) and
+ * {@link #ifPresent(Consumer) ifPresent()} (execute a block
+ * of code if the value is present).
+ */
+public final class Optional {
+ /**
+ * Common instance for {@code empty()}.
+ */
+ private static final Optional> EMPTY = new Optional<>();
+
+ /**
+ * If non-null, the value; if null, indicates no value is present
+ */
+ private final T value;
+
+ /**
+ * Constructs an empty instance.
+ *
+ * @implNote Generally only one empty instance, {@link Optional#EMPTY},
+ * should exist per VM.
+ */
+ private Optional() {
+ this.value = null;
+ }
+
+ /**
+ * Returns an empty {@code Optional} instance. No value is present for this
+ * Optional.
+ *
+ * @apiNote Though it may be tempting to do so, avoid testing if an object
+ * is empty by comparing with {@code ==} against instances returned by
+ * {@code Option.empty()}. There is no guarantee that it is a singleton.
+ * Instead, use {@link #isPresent()}.
+ *
+ * @param Type of the non-existent value
+ * @return an empty {@code Optional}
+ */
+ public static Optional empty() {
+ @SuppressWarnings("unchecked")
+ Optional t = (Optional) EMPTY;
+ return t;
+ }
+
+ /**
+ * Constructs an instance with the value present.
+ *
+ * @param value the non-null value to be present
+ * @throws NullPointerException if value is null
+ */
+ private Optional(T value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ this.value = value;
+ }
+
+ /**
+ * Returns an {@code Optional} with the specified present non-null value.
+ *
+ * @param the class of the value
+ * @param value the value to be present, which must be non-null
+ * @return an {@code Optional} with the value present
+ * @throws NullPointerException if value is null
+ */
+ public static Optional of(T value) {
+ return new Optional<>(value);
+ }
+
+ /**
+ * Returns an {@code Optional} describing the specified value, if non-null,
+ * otherwise returns an empty {@code Optional}.
+ *
+ * @param the class of the value
+ * @param value the possibly-null value to describe
+ * @return an {@code Optional} with a present value if the specified value
+ * is non-null, otherwise an empty {@code Optional}
+ */
+ public static Optional ofNullable(T value) {
+ return value == null ? empty() : of(value);
+ }
+
+ /**
+ * If a value is present in this {@code Optional}, returns the value,
+ * otherwise throws {@code NoSuchElementException}.
+ *
+ * @return the non-null value held by this {@code Optional}
+ * @throws NoSuchElementException if there is no value present
+ *
+ * @see Optional#isPresent()
+ */
+ public T get() {
+ if (value == null) {
+ throw new NoSuchElementException("No value present");
+ }
+ return value;
+ }
+
+ /**
+ * Return {@code true} if there is a value present, otherwise {@code false}.
+ *
+ * @return {@code true} if there is a value present, otherwise {@code false}
+ */
+ public boolean isPresent() {
+ return value != null;
+ }
+
+ /**
+ * If a value is present, invoke the specified consumer with the value,
+ * otherwise do nothing.
+ *
+ * @param consumer block to be executed if a value is present
+ * @throws NullPointerException if value is present and {@code consumer} is
+ * null
+ */
+ public void ifPresent(Consumer super T> consumer) {
+ if (value != null) {
+ consumer.accept(value);
+ }
+ }
+
+ /**
+ * If a value is present, and the value matches the given predicate,
+ * return an {@code Optional} describing the value, otherwise return an
+ * empty {@code Optional}.
+ *
+ * @param predicate a predicate to apply to the value, if present
+ * @return an {@code Optional} describing the value of this {@code Optional}
+ * if a value is present and the value matches the given predicate,
+ * otherwise an empty {@code Optional}
+ * @throws NullPointerException if the predicate is null
+ */
+ public Optional filter(Predicate super T> predicate) {
+ if (predicate == null) {
+ throw new NullPointerException();
+ }
+ if (!isPresent()) {
+ return this;
+ } else {
+ return predicate.test(value) ? this : empty();
+ }
+ }
+
+ /**
+ * If a value is present, apply the provided mapping function to it,
+ * and if the result is non-null, return an {@code Optional} describing the
+ * result. Otherwise return an empty {@code Optional}.
+ *
+ * @apiNote This method supports post-processing on optional values, without
+ * the need to explicitly check for a return status. For example, the
+ * following code traverses a stream of file names, selects one that has
+ * not yet been processed, and then opens that file, returning an
+ * {@code Optional}:
+ *
+ *
+ *
+ * Here, {@code findFirst} returns an {@code Optional}, and then
+ * {@code map} returns an {@code Optional} for the desired
+ * file if one exists.
+ *
+ * @param The type of the result of the mapping function
+ * @param mapper a mapping function to apply to the value, if present
+ * @return an {@code Optional} describing the result of applying a mapping
+ * function to the value of this {@code Optional}, if a value is present,
+ * otherwise an empty {@code Optional}
+ * @throws NullPointerException if the mapping function is null
+ */
+ public Optional map(Function1 super T, ? extends U> mapper) {
+ if (mapper == null) {
+ throw new NullPointerException();
+ }
+ if (!isPresent()) {
+ return empty();
+ } else {
+ return Optional.ofNullable(mapper.invoke(value));
+ }
+ }
+
+ /**
+ * If a value is present, apply the provided {@code Optional}-bearing
+ * mapping function to it, return that result, otherwise return an empty
+ * {@code Optional}. This method is similar to {@link #map(Function1)},
+ * but the provided mapper is one whose result is already an {@code Optional},
+ * and if invoked, {@code flatMap} does not wrap it with an additional
+ * {@code Optional}.
+ *
+ * @param The type parameter to the {@code Optional} returned by
+ * @param mapper a mapping function to apply to the value, if present
+ * the mapping function
+ * @return the result of applying an {@code Optional}-bearing mapping
+ * function to the value of this {@code Optional}, if a value is present,
+ * otherwise an empty {@code Optional}
+ * @throws NullPointerException if the mapping function is null or returns
+ * a null result
+ */
+ public Optional flatMap(Function1 super T, Optional> mapper) {
+ if(mapper == null) {
+ throw new NullPointerException();
+ }
+ if (!isPresent()) {
+ return empty();
+ } else {
+ Optional u = mapper.invoke(value);
+ if (u == null) {
+ throw new NullPointerException();
+ }
+ return u;
+ }
+ }
+
+ /**
+ * Return the value if present, otherwise return {@code other}.
+ *
+ * @param other the value to be returned if there is no value present, may
+ * be null
+ * @return the value, if present, otherwise {@code other}
+ */
+ public T orElse(T other) {
+ return value != null ? value : other;
+ }
+
+ /**
+ * Return the value if present, otherwise invoke {@code other} and return
+ * the result of that invocation.
+ *
+ * @param other a {@code Supplier} whose result is returned if no value
+ * is present
+ * @return the value if present otherwise the result of {@code other.get()}
+ * @throws NullPointerException if value is not present and {@code other} is
+ * null
+ */
+ public T orElseGet(Supplier extends T> other) {
+ return value != null ? value : other.get();
+ }
+
+ /**
+ * Return the contained value, if present, otherwise throw an exception
+ * to be created by the provided supplier.
+ *
+ * @apiNote A method reference to the exception constructor with an empty
+ * argument list can be used as the supplier. For example,
+ * {@code IllegalStateException::new}
+ *
+ * @param Type of the exception to be thrown
+ * @param exceptionSupplier The supplier which will return the exception to
+ * be thrown
+ * @return the present value
+ * @throws X if there is no value present
+ * @throws NullPointerException if no value is present and
+ * {@code exceptionSupplier} is null
+ */
+ public T orElseThrow(Supplier extends X> exceptionSupplier) throws X {
+ if (value != null) {
+ return value;
+ } else {
+ throw exceptionSupplier.get();
+ }
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this Optional. The
+ * other object is considered equal if:
+ *
+ *
it is also an {@code Optional} and;
+ *
both instances have no value present or;
+ *
the present values are "equal to" each other via {@code equals()}.
+ *
+ *
+ * @param obj an object to be tested for equality
+ * @return {code true} if the other object is "equal to" this object
+ * otherwise {@code false}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof Optional)) {
+ return false;
+ }
+
+ Optional> other = (Optional>) obj;
+ return ObjectsCompat.equals(value, other.value);
+ }
+
+ /**
+ * Returns the hash code value of the present value, if any, or 0 (zero) if
+ * no value is present.
+ *
+ * @return hash code value of the present value or 0 if no value is present
+ */
+ @Override
+ public int hashCode() {
+ return ObjectsCompat.hashCode(value);
+ }
+
+ /**
+ * Returns a non-empty string representation of this Optional suitable for
+ * debugging. The exact presentation format is unspecified and may vary
+ * between implementations and versions.
+ *
+ * @implSpec If a value is present the result must include its string
+ * representation in the result. Empty and present Optionals must be
+ * unambiguously differentiable.
+ *
+ * @return the string representation of this instance
+ */
+ @Override
+ public String toString() {
+ return value != null
+ ? String.format("Optional[%s]", value)
+ : "Optional.empty";
+ }
+}
diff --git a/src/main/java/com/nextcloud/java/util/function/Predicate.java b/src/main/java/com/nextcloud/java/util/function/Predicate.java
new file mode 100644
index 000000000000..857ba3d3b150
--- /dev/null
+++ b/src/main/java/com/nextcloud/java/util/function/Predicate.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.nextcloud.java.util.function;
+
+/**
+ * This class is backported from Java 8 to be used on older Android API levels.
+ *
+ * Represents a predicate (boolean-valued function) of one argument.
+ *
+ *
This is a functional interface
+ * whose functional method is {@link #test(Object)}.
+ *
+ * @param the type of the input to the predicate
+ */
+@FunctionalInterface
+public interface Predicate {
+
+ /**
+ * Evaluates this predicate on the given argument.
+ *
+ * @param t the input argument
+ * @return {@code true} if the input argument matches the predicate,
+ * otherwise {@code false}
+ */
+ boolean test(T t);
+
+ /**
+ * Returns a composed predicate that represents a short-circuiting logical
+ * AND of this predicate and another. When evaluating the composed
+ * predicate, if this predicate is {@code false}, then the {@code other}
+ * predicate is not evaluated.
+ *
+ *
Any exceptions thrown during evaluation of either predicate are relayed
+ * to the caller; if evaluation of this predicate throws an exception, the
+ * {@code other} predicate will not be evaluated.
+ *
+ * @param other a predicate that will be logically-ANDed with this
+ * predicate
+ * @return a composed predicate that represents the short-circuiting logical
+ * AND of this predicate and the {@code other} predicate
+ * @throws NullPointerException if other is null
+ */
+ default Predicate and(Predicate super T> other) {
+ if (other == null) {
+ throw new NullPointerException();
+ }
+ return (t) -> test(t) && other.test(t);
+ }
+
+ /**
+ * Returns a predicate that represents the logical negation of this
+ * predicate.
+ *
+ * @return a predicate that represents the logical negation of this
+ * predicate
+ */
+ default Predicate negate() {
+ return (t) -> !test(t);
+ }
+
+ /**
+ * Returns a composed predicate that represents a short-circuiting logical
+ * OR of this predicate and another. When evaluating the composed
+ * predicate, if this predicate is {@code true}, then the {@code other}
+ * predicate is not evaluated.
+ *
+ *
Any exceptions thrown during evaluation of either predicate are relayed
+ * to the caller; if evaluation of this predicate throws an exception, the
+ * {@code other} predicate will not be evaluated.
+ *
+ * @param other a predicate that will be logically-ORed with this
+ * predicate
+ * @return a composed predicate that represents the short-circuiting logical
+ * OR of this predicate and the {@code other} predicate
+ * @throws NullPointerException if other is null
+ */
+ default Predicate or(Predicate super T> other) {
+ if (other == null) {
+ throw new NullPointerException();
+ }
+ return (t) -> test(t) || other.test(t);
+ }
+
+ /**
+ * Returns a predicate that tests if two arguments are equal according
+ * to {@link androidx.core.util.ObjectsCompat#equals(Object, Object)}.
+ *
+ * @param the type of arguments to the predicate
+ * @param targetRef the object reference with which to compare for equality,
+ * which may be {@code null}
+ * @return a predicate that tests if two arguments are equal according
+ * to {@link androidx.core.util.ObjectsCompat#equals(Object, Object)}
+ */
+ static Predicate isEqual(Object targetRef) {
+ return (null == targetRef)
+ ? object -> object == null
+ : object -> targetRef.equals(object);
+ }
+}
diff --git a/src/main/java/com/nextcloud/java/util/package-info.java b/src/main/java/com/nextcloud/java/util/package-info.java
new file mode 100644
index 000000000000..fd71fbc2d3b7
--- /dev/null
+++ b/src/main/java/com/nextcloud/java/util/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Chris Narkiewicz
+ * Copyright (C) 2019 Chris Narkiewicz
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * This is a compatibility package providing some backported Java 8 classes
+ * not available in some older Android runtimes.
+ */
+package com.nextcloud.java.util;
diff --git a/src/main/java/com/owncloud/android/MainApp.java b/src/main/java/com/owncloud/android/MainApp.java
index 05c90f3cae86..f4b21718bc95 100644
--- a/src/main/java/com/owncloud/android/MainApp.java
+++ b/src/main/java/com/owncloud/android/MainApp.java
@@ -45,6 +45,7 @@
import com.evernote.android.job.JobRequest;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.appinfo.AppInfo;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.di.ActivityInjector;
import com.nextcloud.client.di.DaggerAppComponent;
@@ -161,6 +162,9 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
@Inject
BackgroundJobManager backgroundJobManager;
+ @Inject
+ Clock clock;
+
private PassCodeManager passCodeManager;
@SuppressWarnings("unused")
@@ -268,7 +272,8 @@ public void onCreate() {
preferences,
uploadsStorageManager,
connectivityService,
- powerManagementService
+ powerManagementService,
+ clock
)
);
@@ -304,7 +309,8 @@ public void onCreate() {
accountManager,
connectivityService,
powerManagementService,
- backgroundJobManager);
+ backgroundJobManager,
+ clock);
initContactsBackup(accountManager);
notificationChannels();
@@ -462,23 +468,24 @@ public static void initSyncOperations(
final UserAccountManager accountManager,
final ConnectivityService connectivityService,
final PowerManagementService powerManagementService,
- final BackgroundJobManager jobManager
+ final BackgroundJobManager jobManager,
+ final Clock clock
) {
updateToAutoUpload();
- cleanOldEntries();
- updateAutoUploadEntries();
+ cleanOldEntries(clock);
+ updateAutoUploadEntries(clock);
if (getAppContext() != null) {
if (PermissionUtil.checkSelfPermission(getAppContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- splitOutAutoUploadEntries();
+ splitOutAutoUploadEntries(clock);
} else {
AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
preferences.setAutoUploadSplitEntriesEnabled(true);
}
}
- initiateExistingAutoUploadEntries();
+ initiateExistingAutoUploadEntries(clock);
FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager);
FilesSyncHelper.restartJobsIfNeeded(
@@ -685,18 +692,18 @@ private static void updateToAutoUpload() {
}
}
- private static void updateAutoUploadEntries() {
+ private static void updateAutoUploadEntries(Clock clock) {
// updates entries to reflect their true paths
Context context = getAppContext();
AppPreferences preferences = AppPreferencesImpl.fromContext(context);
if (!preferences.isAutoUploadPathsUpdateEnabled()) {
SyncedFolderProvider syncedFolderProvider =
- new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences);
+ new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
syncedFolderProvider.updateAutoUploadPaths(mContext);
}
}
- private static void splitOutAutoUploadEntries() {
+ private static void splitOutAutoUploadEntries(Clock clock) {
Context context = getAppContext();
AppPreferences preferences = AppPreferencesImpl.fromContext(context);
if (!preferences.isAutoUploadSplitEntriesEnabled()) {
@@ -705,7 +712,7 @@ private static void splitOutAutoUploadEntries() {
Log_OC.i(TAG, "Migrate synced_folders records for image/video split");
ContentResolver contentResolver = context.getContentResolver();
- SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences);
+ SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
final List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
final List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
@@ -751,16 +758,16 @@ private static void splitOutAutoUploadEntries() {
}
}
- private static void initiateExistingAutoUploadEntries() {
+ private static void initiateExistingAutoUploadEntries(Clock clock) {
new Thread(() -> {
AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
if (!preferences.isAutoUploadInitialized()) {
SyncedFolderProvider syncedFolderProvider =
- new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences);
+ new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled()) {
- FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder);
+ FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder, true);
}
}
@@ -770,7 +777,7 @@ private static void initiateExistingAutoUploadEntries() {
}).start();
}
- private static void cleanOldEntries() {
+ private static void cleanOldEntries(Clock clock) {
// previous versions of application created broken entries in the SyncedFolderProvider
// database, and this cleans all that and leaves 1 (newest) entry per synced folder
@@ -779,7 +786,7 @@ private static void cleanOldEntries() {
if (!preferences.isLegacyClean()) {
SyncedFolderProvider syncedFolderProvider =
- new SyncedFolderProvider(context.getContentResolver(), preferences);
+ new SyncedFolderProvider(context.getContentResolver(), preferences, clock);
List syncedFolderList = syncedFolderProvider.getSyncedFolders();
Map, Long> syncedFolders = new HashMap<>();
diff --git a/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
index ea957a0d4ff3..de9551e416b8 100644
--- a/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
+++ b/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
@@ -92,6 +92,7 @@
import com.blikoon.qrcodescanner.QrCodeActivity;
import com.google.android.material.snackbar.Snackbar;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable;
@@ -402,7 +403,7 @@ private void initWebViewLogin(String baseURL, boolean showLegacyLogin, boolean u
setClient(progressBar);
// show snackbar after 60s to switch back to old login method
- if (showLegacyLogin) {
+ if (showLegacyLogin && getResources().getBoolean(R.bool.show_old_login)) {
final String finalBaseURL = baseURL;
new Handler().postDelayed(() -> DisplayUtils.createSnackbar(mLoginWebView,
R.string.fallback_weblogin_text,
@@ -1735,8 +1736,8 @@ protected boolean createAccount(RemoteOperationResult authResult) {
}
/// add the new account as default in preferences, if there is none already
- Account defaultAccount = accountManager.getCurrentAccount();
- if (defaultAccount == null) {
+ User defaultAccount = accountManager.getUser();
+ if (defaultAccount.isAnonymous()) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
editor.putString("select_oc_account", accountName);
editor.apply();
diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
index aca96b5ff3f4..28bd0e5411ba 100644
--- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
+++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
@@ -1259,8 +1259,8 @@ private OCShare createShareInstance(Cursor c) {
private void resetShareFlagsInAllFiles() {
ContentValues cv = new ContentValues();
- cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, false);
- cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, false);
+ cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
+ cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE);
cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, "");
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
String[] whereArgs = new String[]{account.name};
@@ -1279,8 +1279,8 @@ private void resetShareFlagsInAllFiles() {
private void resetShareFlagsInFolder(OCFile folder) {
ContentValues cv = new ContentValues();
- cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, false);
- cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, false);
+ cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
+ cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE);
cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, "");
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PARENT + "=?";
String[] whereArgs = new String[]{account.name, String.valueOf(folder.getFileId())};
@@ -1299,8 +1299,8 @@ private void resetShareFlagsInFolder(OCFile folder) {
private void resetShareFlagInAFile(String filePath) {
ContentValues cv = new ContentValues();
- cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, false);
- cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, false);
+ cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, Boolean.FALSE);
+ cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, Boolean.FALSE);
cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, "");
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + "=?";
String[] whereArgs = new String[]{account.name, filePath};
diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java
index 24aaa1f3f785..33a42b867dd7 100644
--- a/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java
+++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java
@@ -23,30 +23,30 @@
import java.io.Serializable;
-import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* Synced folder entity containing all information per synced folder.
*/
-@Getter
-@Setter
-@AllArgsConstructor
public class SyncedFolder implements Serializable, Cloneable {
public static final long UNPERSISTED_ID = Long.MIN_VALUE;
+ public static final long EMPTY_ENABLED_TIMESTAMP_MS = -1;
private static final long serialVersionUID = -793476118299906429L;
- private long id = UNPERSISTED_ID;
- private String localPath;
- private String remotePath;
- private Boolean wifiOnly;
- private Boolean chargingOnly;
- private Boolean subfolderByDate;
- private String account;
- private Integer uploadAction;
- private boolean enabled;
- private MediaFolderType type;
+ @Getter @Setter private long id;
+ @Getter @Setter private String localPath;
+ @Getter @Setter private String remotePath;
+ @Getter @Setter private boolean wifiOnly;
+ @Getter @Setter private boolean chargingOnly;
+ @Getter @Setter private boolean existing;
+ @Getter @Setter private boolean subfolderByDate;
+ @Getter @Setter private String account;
+ @Getter @Setter private int uploadAction;
+ @Getter private boolean enabled;
+ @Getter private long enabledTimestampMs;
+ @Getter @Setter private MediaFolderType type;
+ @Getter @Setter private boolean hidden;
/**
* constructor for new, to be persisted entity.
@@ -55,24 +55,69 @@ public class SyncedFolder implements Serializable, Cloneable {
* @param remotePath remote path
* @param wifiOnly upload on wifi only flag
* @param chargingOnly upload on charging only
+ * @param existing upload existing files
* @param subfolderByDate create sub-folders by date (month)
* @param account the account owning the synced folder
* @param uploadAction the action to be done after the upload
* @param enabled flag if synced folder config is active
+ * @param timestampMs the current timestamp in milliseconds
* @param type the type of the folder
+ * @param hidden hide item flag
*/
- public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
- Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
- MediaFolderType type) {
+ public SyncedFolder(String localPath,
+ String remotePath,
+ boolean wifiOnly,
+ boolean chargingOnly,
+ boolean existing,
+ boolean subfolderByDate,
+ String account,
+ int uploadAction,
+ boolean enabled,
+ long timestampMs,
+ MediaFolderType type,
+ boolean hidden) {
+ this(UNPERSISTED_ID, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account,
+ uploadAction, enabled, timestampMs, type, hidden);
+ }
+
+ /**
+ * constructor for wrapping existing folders.
+ *
+ * @param id id
+ */
+ protected SyncedFolder(long id,
+ String localPath,
+ String remotePath,
+ boolean wifiOnly,
+ boolean chargingOnly,
+ boolean existing,
+ boolean subfolderByDate,
+ String account,
+ int uploadAction,
+ boolean enabled,
+ long timestampMs,
+ MediaFolderType type,
+ boolean hidden) {
+ this.id = id;
this.localPath = localPath;
this.remotePath = remotePath;
this.wifiOnly = wifiOnly;
this.chargingOnly = chargingOnly;
+ this.existing = existing;
this.subfolderByDate = subfolderByDate;
this.account = account;
this.uploadAction = uploadAction;
- this.enabled = enabled;
+ this.setEnabled(enabled, timestampMs);
this.type = type;
+ this.hidden = hidden;
+ }
+
+ /**
+ * @param timestampMs the current timestamp in milliseconds
+ */
+ public void setEnabled(boolean enabled, long timestampMs) {
+ this.enabled = enabled;
+ this.enabledTimestampMs = enabled ? timestampMs : EMPTY_ENABLED_TIMESTAMP_MS;
}
public Object clone() {
diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java
index cac3df7a143b..b886954e1c08 100644
--- a/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java
+++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java
@@ -45,6 +45,7 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
* @param remotePath remote path
* @param wifiOnly upload on wifi only flag
* @param chargingOnly upload on charging only
+ * @param existing also upload existing
* @param subfolderByDate create sub-folders by date (month)
* @param account the account owning the synced folder
* @param uploadAction the action to be done after the upload
@@ -53,21 +54,45 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
* @param folderName the UI info for the folder's name
* @param numberOfFiles the UI info for number of files within the folder
* @param type the type of the folder
+ * @param hidden hide item flag
*/
- public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
- Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
- List filePaths, String folderName, long numberOfFiles, MediaFolderType type)
- {
- super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type);
+ public SyncedFolderDisplayItem(long id,
+ String localPath,
+ String remotePath,
+ boolean wifiOnly,
+ boolean chargingOnly,
+ boolean existing,
+ boolean subfolderByDate,
+ String account,
+ int uploadAction,
+ boolean enabled,
+ long timestampMs,
+ List filePaths,
+ String folderName,
+ long numberOfFiles,
+ MediaFolderType type,
+ boolean hidden) {
+ super(id, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account, uploadAction,
+ enabled, timestampMs, type, hidden);
this.filePaths = filePaths;
this.folderName = folderName;
this.numberOfFiles = numberOfFiles;
}
- public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
- Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
- String folderName, MediaFolderType type) {
- super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type);
+ public SyncedFolderDisplayItem(long id,
+ String localPath,
+ String remotePath,
+ boolean wifiOnly,
+ boolean chargingOnly,
+ boolean existing,
+ boolean subfolderByDate,
+ String account,
+ int uploadAction,
+ boolean enabled,
+ long timestampMs,
+ String folderName, MediaFolderType type, boolean hidden) {
+ super(id, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account, uploadAction,
+ enabled, timestampMs, type, hidden);
this.folderName = folderName;
}
}
diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
index 07712a9e3493..118c60e44a31 100644
--- a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
+++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
@@ -27,6 +27,7 @@
import android.database.Cursor;
import android.net.Uri;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.db.ProviderMeta;
@@ -47,20 +48,22 @@
public class SyncedFolderProvider extends Observable {
static private final String TAG = SyncedFolderProvider.class.getSimpleName();
- private ContentResolver mContentResolver;
- private AppPreferences preferences;
+ private final ContentResolver mContentResolver;
+ private final AppPreferences preferences;
+ private final Clock clock;
/**
* constructor.
*
* @param contentResolver the ContentResolver to work with.
*/
- public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences preferences) {
+ public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences preferences, Clock clock) {
if (contentResolver == null) {
throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
}
mContentResolver = contentResolver;
this.preferences = preferences;
+ this.clock = clock;
}
/**
@@ -162,7 +165,7 @@ public int updateSyncedFolderEnabled(long id, Boolean enabled) {
// read sync folder object and update
SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor);
- syncedFolder.setEnabled(enabled);
+ syncedFolder.setEnabled(enabled, clock.getCurrentTime());
// update sync folder object in db
result = updateSyncFolder(syncedFolder);
@@ -335,23 +338,30 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) {
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
String remotePath = cursor.getString(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
- Boolean wifiOnly = cursor.getInt(cursor.getColumnIndex(
+ boolean wifiOnly = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
- Boolean chargingOnly = cursor.getInt(cursor.getColumnIndex(
+ boolean chargingOnly = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
- Boolean subfolderByDate = cursor.getInt(cursor.getColumnIndex(
+ boolean existing = cursor.getInt(cursor.getColumnIndex(
+ ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
+ boolean subfolderByDate = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
String accountName = cursor.getString(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
- Integer uploadAction = cursor.getInt(cursor.getColumnIndex(
+ int uploadAction = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
- Boolean enabled = cursor.getInt(cursor.getColumnIndex(
+ boolean enabled = cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
+ long enabledTimestampMs = cursor.getLong(cursor.getColumnIndex(
+ ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndex(
ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
+ boolean hidden = cursor.getInt(cursor.getColumnIndex(
+ ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
- syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate,
- accountName, uploadAction, enabled, type);
+ syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, existing,
+ subfolderByDate, accountName, uploadAction, enabled, enabledTimestampMs,
+ type, hidden);
}
return syncedFolder;
}
@@ -367,13 +377,16 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol
ContentValues cv = new ContentValues();
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH, syncedFolder.getLocalPath());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.getRemotePath());
- cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly());
- cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.isWifiOnly());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.isChargingOnly());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING, syncedFolder.isExisting());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled());
- cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS, syncedFolder.getEnabledTimestampMs());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.isSubfolderByDate());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
+ cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden());
return cv;
}
diff --git a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
index d31313f378ff..94cd542cfb4e 100644
--- a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
+++ b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
@@ -30,6 +30,7 @@
import android.net.Uri;
import com.nextcloud.client.account.CurrentAccountProvider;
+import com.nextcloud.client.account.User;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.db.UploadResult;
@@ -83,7 +84,7 @@ public long storeUpload(OCUpload ocUpload) {
cv.put(ProviderTableMeta.UPLOADS_FILE_SIZE, ocUpload.getFileSize());
cv.put(ProviderTableMeta.UPLOADS_STATUS, ocUpload.getUploadStatus().value);
cv.put(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR, ocUpload.getLocalAction());
- cv.put(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE, ocUpload.isForceOverwrite() ? 1 : 0);
+ cv.put(ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY, ocUpload.getNameCollisionPolicy().serialize());
cv.put(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER, ocUpload.isCreateRemoteFolder() ? 1 : 0);
cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue());
cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreatedBy());
@@ -328,8 +329,8 @@ private OCUpload createOCUploadFromCursor(Cursor c) {
UploadStatus.fromValue(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_STATUS)))
);
upload.setLocalAction(c.getInt(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR)));
- upload.setForceOverwrite(c.getInt(
- c.getColumnIndex(ProviderTableMeta.UPLOADS_FORCE_OVERWRITE)) == 1);
+ upload.setNameCollisionPolicy(FileUploader.NameCollisionPolicy.deserialize(c.getInt(
+ c.getColumnIndex(ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY))));
upload.setCreateRemoteFolder(c.getInt(
c.getColumnIndex(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER)) == 1);
upload.setUploadEndTimestamp(c.getLong(c.getColumnIndex(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP)));
@@ -345,24 +346,19 @@ private OCUpload createOCUploadFromCursor(Cursor c) {
}
public OCUpload[] getCurrentAndPendingUploadsForCurrentAccount() {
- Account account = currentAccountProvider.getCurrentAccount();
-
- if (account != null) {
- return getUploads(
- ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value +
- " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "==" + UploadResult.DELAYED_FOR_WIFI.getValue() +
- " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "==" + UploadResult.LOCK_FAILED.getValue() +
- " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
- " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
- " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
- account.name);
- } else {
- return new OCUpload[0];
- }
+ User user = currentAccountProvider.getUser();
+
+ return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value +
+ " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "==" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+ " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "==" + UploadResult.LOCK_FAILED.getValue() +
+ " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+ " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
+ " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+ user.getAccountName());
}
/**
@@ -384,14 +380,10 @@ public OCUpload[] getFailedUploads() {
}
public OCUpload[] getFinishedUploadsForCurrentAccount() {
- Account account = currentAccountProvider.getCurrentAccount();
+ User user = currentAccountProvider.getUser();
- if (account != null) {
- return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
- ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", account.name);
- } else {
- return new OCUpload[0];
- }
+ return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
+ ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", user.getAccountName());
}
/**
@@ -403,23 +395,19 @@ public OCUpload[] getFinishedUploads() {
}
public OCUpload[] getFailedButNotDelayedUploadsForCurrentAccount() {
- Account account = currentAccountProvider.getCurrentAccount();
-
- if (account != null) {
- return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.LOCK_FAILED.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
- AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
- account.name);
- } else {
- return new OCUpload[0];
- }
+ User user = currentAccountProvider.getUser();
+
+ return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.LOCK_FAILED.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
+ AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+ user.getAccountName());
}
/**
@@ -446,50 +434,41 @@ private ContentResolver getDB() {
}
public long clearFailedButNotDelayedUploads() {
- Account account = currentAccountProvider.getCurrentAccount();
-
- long result = 0;
- if (account != null) {
- result = getDB().delete(
- ProviderTableMeta.CONTENT_URI_UPLOADS,
- ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.LOCK_FAILED.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
- AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
- new String[]{account.name}
- );
- }
-
+ User user = currentAccountProvider.getUser();
+ final long deleted = getDB().delete(
+ ProviderTableMeta.CONTENT_URI_UPLOADS,
+ ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.LOCK_FAILED.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+ AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+ "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
+ AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+ new String[]{user.getAccountName()}
+ );
Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi");
- if (result > 0) {
+ if (deleted > 0) {
notifyObserversNow();
}
- return result;
+ return deleted;
}
public long clearSuccessfulUploads() {
- Account account = currentAccountProvider.getCurrentAccount();
-
- long result = 0;
- if (account != null) {
- result = getDB().delete(
- ProviderTableMeta.CONTENT_URI_UPLOADS,
- ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
- ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name}
- );
- }
+ User user = currentAccountProvider.getUser();
+ final long deleted = getDB().delete(
+ ProviderTableMeta.CONTENT_URI_UPLOADS,
+ ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
+ ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{user.getAccountName()}
+ );
Log_OC.d(TAG, "delete all successful uploads");
- if (result > 0) {
+ if (deleted > 0) {
notifyObserversNow();
}
- return result;
+ return deleted;
}
/**
diff --git a/src/main/java/com/owncloud/android/db/OCUpload.java b/src/main/java/com/owncloud/android/db/OCUpload.java
index c5259307c9f9..3d9b5b628903 100644
--- a/src/main/java/com/owncloud/android/db/OCUpload.java
+++ b/src/main/java/com/owncloud/android/db/OCUpload.java
@@ -77,9 +77,9 @@ public class OCUpload implements Parcelable {
@Getter @Setter private int localAction;
/**
- * Overwrite destination file?
+ * What to do in case of name collision.
*/
- @Getter @Setter private boolean forceOverwrite;
+ @Getter @Setter private FileUploader.NameCollisionPolicy nameCollisionPolicy;
/**
* Create destination folder?
@@ -172,7 +172,7 @@ private void resetData() {
fileSize = -1;
uploadId = -1;
localAction = FileUploader.LOCAL_BEHAVIOUR_COPY;
- forceOverwrite = false;
+ nameCollisionPolicy = FileUploader.NameCollisionPolicy.DEFAULT;
createRemoteFolder = false;
uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
lastResult = UploadResult.UNKNOWN;
@@ -281,7 +281,7 @@ private void readFromParcel(Parcel source) {
remotePath = source.readString();
accountName = source.readString();
localAction = source.readInt();
- forceOverwrite = source.readInt() == 1;
+ nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(source.readInt());
createRemoteFolder = source.readInt() == 1;
try {
uploadStatus = UploadStatus.valueOf(source.readString());
@@ -312,7 +312,7 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeString(remotePath);
dest.writeString(accountName);
dest.writeInt(localAction);
- dest.writeInt(forceOverwrite ? 1 : 0);
+ dest.writeInt(nameCollisionPolicy.serialize());
dest.writeInt(createRemoteFolder ? 1 : 0);
dest.writeString(uploadStatus.name());
dest.writeLong(uploadEndTimestamp);
diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java
index 093d36b9f2d8..dec8ee6889a1 100644
--- a/src/main/java/com/owncloud/android/db/ProviderMeta.java
+++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java
@@ -31,7 +31,7 @@
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
- public static final int DB_VERSION = 49;
+ public static final int DB_VERSION = 52;
private ProviderMeta() {
// No instance
@@ -204,7 +204,7 @@ static public class ProviderTableMeta implements BaseColumns {
public static final String UPLOADS_STATUS = "status";
public static final String UPLOADS_LOCAL_BEHAVIOUR = "local_behaviour";
public static final String UPLOADS_UPLOAD_TIME = "upload_time";
- public static final String UPLOADS_FORCE_OVERWRITE = "force_overwrite";
+ public static final String UPLOADS_NAME_COLLISION_POLICY = "name_collision_policy";
public static final String UPLOADS_IS_CREATE_REMOTE_FOLDER = "is_create_remote_folder";
public static final String UPLOADS_UPLOAD_END_TIMESTAMP = "upload_end_timestamp";
public static final String UPLOADS_LAST_RESULT = "last_result";
@@ -219,11 +219,14 @@ static public class ProviderTableMeta implements BaseColumns {
public static final String SYNCED_FOLDER_REMOTE_PATH = "remote_path";
public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only";
public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only";
+ public static final String SYNCED_FOLDER_EXISTING = "existing";
public static final String SYNCED_FOLDER_ENABLED = "enabled";
+ public static final String SYNCED_FOLDER_ENABLED_TIMESTAMP_MS = "enabled_timestamp_ms";
public static final String SYNCED_FOLDER_TYPE = "type";
public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date";
public static final String SYNCED_FOLDER_ACCOUNT = "account";
public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option";
+ public static final String SYNCED_FOLDER_HIDDEN = "hidden";
// Columns of external links table
public static final String EXTERNAL_LINKS_ICON_URL = "icon_url";
diff --git a/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java b/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
index 77f1535f0725..311d45240a72 100644
--- a/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
+++ b/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
@@ -28,6 +28,7 @@
import android.content.Intent;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
@@ -53,6 +54,7 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
@Inject ConnectivityService connectivityService;
@Inject PowerManagementService powerManagementService;
@Inject BackgroundJobManager backgroundJobManager;
+ @Inject Clock clock;
/**
* Receives broadcast intent reporting that the system was just boot up.
@@ -69,7 +71,8 @@ public void onReceive(Context context, Intent intent) {
accountManager,
connectivityService,
powerManagementService,
- backgroundJobManager);
+ backgroundJobManager,
+ clock);
MainApp.initContactsBackup(accountManager);
} else {
Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction());
diff --git a/src/main/java/com/owncloud/android/files/services/FileDownloader.java b/src/main/java/com/owncloud/android/files/services/FileDownloader.java
index 0b8e66767c31..0ac96dbe403b 100644
--- a/src/main/java/com/owncloud/android/files/services/FileDownloader.java
+++ b/src/main/java/com/owncloud/android/files/services/FileDownloader.java
@@ -43,6 +43,8 @@
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -86,6 +88,7 @@ public class FileDownloader extends Service
public static final String EXTRA_FILE_PATH = "FILE_PATH";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
+ public static final String EXTRA_CONFLICT_UPLOAD = "CONFLICT_UPLOAD";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
private static final int FOREGROUND_SERVICE_ID = 412;
@@ -109,7 +112,10 @@ public class FileDownloader extends Service
private Notification mNotification;
+ private OCUpload conflictUpload;
+
@Inject UserAccountManager accountManager;
+ @Inject UploadsStorageManager uploadsStorageManager;
public static String getDownloadAddedMessage() {
return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
@@ -194,6 +200,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
final String behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
String activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME);
String packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME);
+ this.conflictUpload = intent.getParcelableExtra(FileDownloader.EXTRA_CONFLICT_UPLOAD);
AbstractList requestedDownloads = new Vector();
try {
DownloadFileOperation newDownload = new DownloadFileOperation(account, file, behaviour, activityName,
@@ -631,6 +638,10 @@ private void notifyDownloadResult(DownloadFileOperation download,
// Remove success notification
if (downloadResult.isSuccess()) {
+ if (this.conflictUpload != null) {
+ uploadsStorageManager.removeUpload(this.conflictUpload);
+ }
+
// Sleep 2 seconds, so show the notification before remove it
NotificationUtils.cancelWithDelay(mNotificationManager,
R.string.downloader_download_succeeded_ticker, 2000);
diff --git a/src/main/java/com/owncloud/android/files/services/FileUploader.java b/src/main/java/com/owncloud/android/files/services/FileUploader.java
index 9b987d34a46f..10dcc8f0e7b3 100644
--- a/src/main/java/com/owncloud/android/files/services/FileUploader.java
+++ b/src/main/java/com/owncloud/android/files/services/FileUploader.java
@@ -37,6 +37,7 @@
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -126,10 +127,6 @@ public class FileUploader extends Service
public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
public static final String KEY_MIME_TYPE = "MIME_TYPE";
- private Notification mNotification;
-
- @Inject UserAccountManager accountManager;
-
/**
* Call this Service with only this Intent key if all pending uploads are to be retried.
*/
@@ -150,9 +147,10 @@ public class FileUploader extends Service
public static final String KEY_ACCOUNT = "ACCOUNT";
/**
- * Set to true if remote file is to be overwritten. Default action is to upload with different name.
+ * What {@link NameCollisionPolicy} to do when the file already exists on the remote.
*/
- public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
+ public static final String KEY_NAME_COLLISION_POLICY = "KEY_NAME_COLLISION_POLICY";
+
/**
* Set to true if remote folder is to be created if it does not exist.
*/
@@ -176,287 +174,227 @@ public class FileUploader extends Service
public static final int LOCAL_BEHAVIOUR_FORGET = 2;
public static final int LOCAL_BEHAVIOUR_DELETE = 3;
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- private IBinder mBinder;
- private OwnCloudClient mUploadClient;
- private Account mCurrentAccount;
- private FileDataStorageManager mStorageManager;
- //since there can be only one instance of an Android service, there also just one db connection.
- @Inject UploadsStorageManager mUploadsStorageManager;
- @Inject ConnectivityService connectivityService;
- @Inject PowerManagementService powerManagementService;
-
- private IndexedForest mPendingUploads = new IndexedForest<>();
-
/**
- * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload!
+ * Upload a new file
*/
- private UploadFileOperation mCurrentUpload;
-
- private NotificationManager mNotificationManager;
- private NotificationCompat.Builder mNotificationBuilder;
- private int mLastPercent;
+ public static void uploadNewFile(
+ Context context,
+ Account account,
+ String localPath,
+ String remotePath,
+ int behaviour,
+ String mimeType,
+ boolean createRemoteFile,
+ int createdBy,
+ boolean requiresWifi,
+ boolean requiresCharging,
+ NameCollisionPolicy nameCollisionPolicy
+ ) {
+ uploadNewFile(
+ context,
+ account,
+ new String[]{localPath},
+ new String[]{remotePath},
+ new String[]{mimeType},
+ behaviour,
+ createRemoteFile,
+ createdBy,
+ requiresWifi,
+ requiresCharging,
+ nameCollisionPolicy
+ );
+ }
- public static String getUploadsAddedMessage() {
- return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE;
+ /**
+ * Upload multiple new files
+ */
+ public static void uploadNewFile(
+ Context context,
+ Account account,
+ String[] localPaths,
+ String[] remotePaths,
+ String[] mimeTypes,
+ Integer behaviour,
+ Boolean createRemoteFolder,
+ int createdBy,
+ boolean requiresWifi,
+ boolean requiresCharging,
+ NameCollisionPolicy nameCollisionPolicy
+ ) {
+ Intent intent = new Intent(context, FileUploader.class);
+
+ intent.putExtra(FileUploader.KEY_ACCOUNT, account);
+ intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
+ intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
+ intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
+ intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
+ intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
+ intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
+ intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
+ intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
+ intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent);
+ } else {
+ context.startService(intent);
+ }
}
- public static String getUploadStartMessage() {
- return FileUploader.class.getName() + UPLOAD_START_MESSAGE;
+ /**
+ * Upload and overwrite an already uploaded file
+ */
+ public static void uploadUpdateFile(
+ Context context,
+ Account account,
+ OCFile existingFile,
+ Integer behaviour,
+ NameCollisionPolicy nameCollisionPolicy
+ ) {
+ uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy);
}
- public static String getUploadFinishMessage() {
- return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
+ /**
+ * Upload and overwrite already uploaded files
+ */
+ public static void uploadUpdateFile(
+ Context context,
+ Account account,
+ OCFile[] existingFiles,
+ Integer behaviour,
+ NameCollisionPolicy nameCollisionPolicy
+ ) {
+ Intent intent = new Intent(context, FileUploader.class);
+
+ intent.putExtra(FileUploader.KEY_ACCOUNT, account);
+ intent.putExtra(FileUploader.KEY_FILE, existingFiles);
+ intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
+ intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent);
+ } else {
+ context.startService(intent);
+ }
}
- @Override
- public void onRenameUpload() {
- mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
- sendBroadcastUploadStarted(mCurrentUpload);
+ /**
+ * Retry a failed {@link OCUpload} identified by {@link OCUpload#getRemotePath()}
+ */
+ public static void retryUpload(@NonNull Context context, @NonNull Account account, @NonNull OCUpload upload) {
+ Intent i = new Intent(context, FileUploader.class);
+ i.putExtra(FileUploader.KEY_RETRY, true);
+ i.putExtra(FileUploader.KEY_ACCOUNT, account);
+ i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(i);
+ } else {
+ context.startService(i);
+ }
}
/**
- * Helper class providing methods to ease requesting commands to {@link FileUploader} .
+ * Retry a subset of all the stored failed uploads.
*
- * Avoids the need of checking once and again what extras are needed or optional
- * in the {@link Intent} to pass to {@link Context#startService(Intent)}.
+ * @param context Caller {@link Context}
+ * @param account If not null, only failed uploads to this OC account will be retried; otherwise,
+ * uploads of all accounts will be retried.
+ * @param uploadResult If not null, only failed uploads with the result specified will be retried;
+ * otherwise, failed uploads due to any result will be retried.
*/
- public static class UploadRequester {
-
- /**
- * Call to upload several new files
- */
- public void uploadNewFile(
- Context context,
- Account account,
- String[] localPaths,
- String[] remotePaths,
- String[] mimeTypes,
- Integer behaviour,
- Boolean createRemoteFolder,
- int createdBy,
- boolean requiresWifi,
- boolean requiresCharging
- ) {
- Intent intent = new Intent(context, FileUploader.class);
-
- intent.putExtra(FileUploader.KEY_ACCOUNT, account);
- intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
- intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
- intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
- intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
- intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
- intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
- intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
- intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
-
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- context.startForegroundService(intent);
- } else {
- context.startService(intent);
- }
- }
+ public static void retryFailedUploads(
+ @NonNull final Context context,
+ @Nullable final Account account,
+ @NotNull final UploadsStorageManager uploadsStorageManager,
+ @NotNull final ConnectivityService connectivityService,
+ @NotNull final UserAccountManager accountManager,
+ @NotNull final PowerManagementService powerManagementService,
+ @Nullable final UploadResult uploadResult
+ ) {
+ OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
+ Account currentAccount = null;
+ boolean resultMatch;
+ boolean accountMatch;
+
+ boolean gotNetwork = connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY &&
+ !connectivityService.isInternetWalled();
+ boolean gotWifi = gotNetwork && Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED);
+ boolean charging = Device.getBatteryStatus(context).isCharging();
+ boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
+
+ for (OCUpload failedUpload : failedUploads) {
+ accountMatch = account == null || account.name.equals(failedUpload.getAccountName());
+ resultMatch = uploadResult == null || uploadResult.equals(failedUpload.getLastResult());
+ if (accountMatch && resultMatch) {
+ if (currentAccount == null || !currentAccount.name.equals(failedUpload.getAccountName())) {
+ currentAccount = failedUpload.getAccount(accountManager);
+ }
- public void uploadFileWithOverwrite(
- Context context,
- Account account,
- String[] localPaths,
- String[] remotePaths,
- String[] mimeTypes,
- Integer behaviour,
- Boolean createRemoteFolder,
- int createdBy,
- boolean requiresWifi,
- boolean requiresCharging,
- boolean overwrite
- ) {
- Intent intent = new Intent(context, FileUploader.class);
-
- intent.putExtra(FileUploader.KEY_ACCOUNT, account);
- intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
- intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
- intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
- intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
- intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
- intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
- intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
- intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
- intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, overwrite);
-
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- context.startForegroundService(intent);
- } else {
- context.startService(intent);
+ if (!new File(failedUpload.getLocalPath()).exists()) {
+ if (!failedUpload.getLastResult().equals(UploadResult.FILE_NOT_FOUND)) {
+ failedUpload.setLastResult(UploadResult.FILE_NOT_FOUND);
+ uploadsStorageManager.updateUpload(failedUpload);
+ }
+ } else {
+ charging = charging || Device.getBatteryStatus(context).getBatteryPercent() == 1;
+ if (!isPowerSaving && gotNetwork && canUploadBeRetried(failedUpload, gotWifi, charging)) {
+ retryUpload(context, currentAccount, failedUpload);
+ }
+ }
}
}
+ }
- /**
- * Call to upload a file
- */
- public void uploadFileWithOverwrite(Context context, Account account, String localPath, String remotePath, int
- behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi,
- boolean requiresCharging, boolean overwrite) {
-
- uploadFileWithOverwrite(
- context,
- account,
- new String[]{localPath},
- new String[]{remotePath},
- new String[]{mimeType},
- behaviour,
- createRemoteFile,
- createdBy,
- requiresWifi,
- requiresCharging,
- overwrite
- );
- }
-
- /**
- * Call to upload a new single file
- */
- public void uploadNewFile(Context context, Account account, String localPath, String remotePath, int
- behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi,
- boolean requiresCharging) {
-
- uploadNewFile(
- context,
- account,
- new String[]{localPath},
- new String[]{remotePath},
- new String[]{mimeType},
- behaviour,
- createRemoteFile,
- createdBy,
- requiresWifi,
- requiresCharging
- );
- }
-
- /**
- * Call to update multiple files already uploaded
- */
- public void uploadUpdate(Context context, Account account, OCFile[] existingFiles, Integer behaviour,
- Boolean forceOverwrite) {
- Intent intent = new Intent(context, FileUploader.class);
-
- intent.putExtra(FileUploader.KEY_ACCOUNT, account);
- intent.putExtra(FileUploader.KEY_FILE, existingFiles);
- intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
- intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, forceOverwrite);
-
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- context.startForegroundService(intent);
- } else {
- context.startService(intent);
- }
- }
+ private static boolean canUploadBeRetried(OCUpload upload, boolean gotWifi, boolean isCharging) {
+ File file = new File(upload.getLocalPath());
+ boolean needsWifi = upload.isUseWifiOnly();
+ boolean needsCharging = upload.isWhileChargingOnly();
- /**
- * Call to update a dingle file already uploaded
- */
- public void uploadUpdate(Context context, Account account, OCFile existingFile, Integer behaviour,
- Boolean forceOverwrite) {
+ return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging);
+ }
- uploadUpdate(context, account, new OCFile[]{existingFile}, behaviour, forceOverwrite);
- }
+ public static String getUploadsAddedMessage() {
+ return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE;
+ }
+ public static String getUploadStartMessage() {
+ return FileUploader.class.getName() + UPLOAD_START_MESSAGE;
+ }
- /**
- * Call to retry upload identified by remotePath
- */
- public void retry (Context context, UserAccountManager accountManager, OCUpload upload) {
- if (upload != null && context != null) {
- Account account = accountManager.getAccountByName(upload.getAccountName());
- retry(context, account, upload);
- } else {
- throw new IllegalArgumentException("Null parameter!");
- }
- }
+ public static String getUploadFinishMessage() {
+ return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
+ }
- private boolean checkIfUploadCanBeRetried(OCUpload ocUpload, boolean gotWifi, boolean isCharging) {
- boolean needsWifi = ocUpload.isUseWifiOnly();
- boolean needsCharging = ocUpload.isWhileChargingOnly();
- return new File(ocUpload.getLocalPath()).exists() && !(needsCharging && !isCharging) &&
- !(needsWifi && !gotWifi);
+ private Notification mNotification;
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+ private IBinder mBinder;
+ private OwnCloudClient mUploadClient;
+ private Account mCurrentAccount;
+ private FileDataStorageManager mStorageManager;
- }
+ @Inject UserAccountManager accountManager;
+ @Inject UploadsStorageManager mUploadsStorageManager;
+ @Inject ConnectivityService connectivityService;
+ @Inject PowerManagementService powerManagementService;
- /**
- * Retry a subset of all the stored failed uploads.
- *
- * @param context Caller {@link Context}
- * @param account If not null, only failed uploads to this OC account will be retried; otherwise,
- * uploads of all accounts will be retried.
- * @param uploadResult If not null, only failed uploads with the result specified will be retried;
- * otherwise, failed uploads due to any result will be retried.
- */
- public void retryFailedUploads(
- @NonNull final Context context,
- @Nullable final Account account,
- @NotNull final UploadsStorageManager uploadsStorageManager,
- @NotNull final ConnectivityService connectivityService,
- @NotNull final UserAccountManager accountManager,
- @NotNull final PowerManagementService powerManagementService,
- @Nullable final UploadResult uploadResult
- ) {
- OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
- Account currentAccount = null;
- boolean resultMatch;
- boolean accountMatch;
-
- boolean gotNetwork = connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY &&
- !connectivityService.isInternetWalled();
- boolean gotWifi = gotNetwork && Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED);
- boolean charging = Device.getBatteryStatus(context).isCharging();
- boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
-
- for ( OCUpload failedUpload: failedUploads) {
- accountMatch = account == null || account.name.equals(failedUpload.getAccountName());
- resultMatch = uploadResult == null || uploadResult.equals(failedUpload.getLastResult());
- if (accountMatch && resultMatch) {
- if (currentAccount == null || !currentAccount.name.equals(failedUpload.getAccountName())) {
- currentAccount = failedUpload.getAccount(accountManager);
- }
+ private IndexedForest mPendingUploads = new IndexedForest<>();
- if (!new File(failedUpload.getLocalPath()).exists()) {
- if (!failedUpload.getLastResult().equals(UploadResult.FILE_NOT_FOUND)) {
- failedUpload.setLastResult(UploadResult.FILE_NOT_FOUND);
- uploadsStorageManager.updateUpload(failedUpload);
- }
- } else {
- charging = charging || Device.getBatteryStatus(context).getBatteryPercent() == 1;
- if (!isPowerSaving && gotNetwork && checkIfUploadCanBeRetried(failedUpload, gotWifi, charging)) {
- retry(context, currentAccount, failedUpload);
- }
- }
- }
- }
- }
+ /**
+ * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload!
+ */
+ private UploadFileOperation mCurrentUpload;
- /**
- * Private implementation of retry.
- *
- * @param context
- * @param account
- * @param upload
- */
- private void retry(Context context, Account account, OCUpload upload) {
- if (upload != null) {
- Intent i = new Intent(context, FileUploader.class);
- i.putExtra(FileUploader.KEY_RETRY, true);
- i.putExtra(FileUploader.KEY_ACCOUNT, account);
- i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
+ private NotificationManager mNotificationManager;
+ private NotificationCompat.Builder mNotificationBuilder;
+ private int mLastPercent;
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- context.startForegroundService(i);
- } else {
- context.startService(i);
- }
- }
- }
+ @Override
+ public void onRenameUpload() {
+ mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
+ sendBroadcastUploadStarted(mCurrentUpload);
}
/**
@@ -482,7 +420,7 @@ public void onCreate() {
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon))
.setColor(ThemeUtils.primaryColor(getApplicationContext(), true));
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
}
@@ -619,7 +557,10 @@ public int onStartCommand(Intent intent, int flags, int startId) {
}
// at this point variable "OCFile[] files" is loaded correctly.
- boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
+ NameCollisionPolicy nameCollisionPolicy = (NameCollisionPolicy) intent.getSerializableExtra(KEY_NAME_COLLISION_POLICY);
+ if(nameCollisionPolicy == null) {
+ nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
+ }
int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
@@ -630,7 +571,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
OCUpload ocUpload = new OCUpload(file, account);
ocUpload.setFileSize(file.getFileLength());
- ocUpload.setForceOverwrite(forceOverwrite);
+ ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
ocUpload.setCreatedBy(createdBy);
ocUpload.setLocalAction(localAction);
@@ -646,7 +587,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
account,
file,
ocUpload,
- forceOverwrite,
+ nameCollisionPolicy,
localAction,
this,
onWifiOnly,
@@ -707,7 +648,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
account,
null,
upload,
- upload.isForceOverwrite(), // TODO should be read from DB?
+ upload.getNameCollisionPolicy(), // TODO should be read from DB?
upload.getLocalAction(), // TODO should be read from DB?
this,
onWifiOnly,
@@ -870,9 +811,8 @@ public void clearListeners() {
* If 'file' is a directory, returns 'true' if some of its descendant files
* is uploading or waiting to upload.
*
- * Warning: If remote file exists and !forceOverwrite the original file
- * is being returned here. That is, it seems as if the original file is
- * being updated when actually a new file is being uploaded.
+ * Warning: If remote file exists and target was renamed the original file is being returned here.
+ * That is, it seems as if the original file is being updated when actually a new file is being uploaded.
*
* @param account Owncloud account where the remote file will be stored.
* @param file A file that could be in the queue of pending uploads
@@ -1170,7 +1110,7 @@ private void notifyUploadStart(UploadFileOperation upload) {
String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())
);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
}
@@ -1364,4 +1304,22 @@ private void cancelUploadsForAccount(Account account) {
mPendingUploads.remove(account.name);
mUploadsStorageManager.removeUploads(account.name);
}
+
+ public enum NameCollisionPolicy {
+ CANCEL,
+ RENAME,
+ OVERWRITE,
+ ASK_USER;
+
+ public static final NameCollisionPolicy DEFAULT = RENAME;
+
+ public static NameCollisionPolicy deserialize(int ordinal) {
+ NameCollisionPolicy[] values = NameCollisionPolicy.values();
+ return ordinal >= 0 && ordinal < values.length ? values[ordinal] : DEFAULT;
+ }
+
+ public int serialize() {
+ return this.ordinal();
+ }
+ }
}
diff --git a/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java b/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java
index b87d3e99de5b..a63793d12908 100644
--- a/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java
+++ b/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java
@@ -38,6 +38,7 @@
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
@@ -57,7 +58,6 @@
import com.owncloud.android.ui.events.AccountRemovedEvent;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.PushUtils;
import org.greenrobot.eventbus.EventBus;
@@ -81,12 +81,14 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback syncedFolders = syncedFolderProvider.getSyncedFolders();
List syncedFolderIds = new ArrayList<>();
for (SyncedFolder syncedFolder : syncedFolders) {
if (syncedFolder.getAccount().equals(account.name)) {
- arbitraryDataProvider.deleteKeyForAccount(FilesSyncHelper.GLOBAL,
- FilesSyncHelper.SYNCEDFOLDERINITIATED + syncedFolder.getId());
syncedFolderIds.add(syncedFolder.getId());
}
}
diff --git a/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java b/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java
index 36a5a1fd89cc..66a45ca4cbc7 100644
--- a/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java
+++ b/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java
@@ -157,18 +157,18 @@ private void backupContact(Account account, String backupFolder) {
}
}
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- requester.uploadNewFile(
- getContext(),
- account,
- file.getAbsolutePath(),
- backupFolder + filename,
- FileUploader.LOCAL_BEHAVIOUR_MOVE,
- null,
- true,
- UploadFileOperation.CREATED_BY_USER,
- false,
- false
+ FileUploader.uploadNewFile(
+ getContext(),
+ account,
+ file.getAbsolutePath(),
+ backupFolder + filename,
+ FileUploader.LOCAL_BEHAVIOUR_MOVE,
+ null,
+ true,
+ UploadFileOperation.CREATED_BY_USER,
+ false,
+ false,
+ FileUploader.NameCollisionPolicy.ASK_USER
);
}
diff --git a/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java b/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
index 0a254580a00d..c234d5aee4c2 100644
--- a/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
+++ b/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
@@ -34,6 +34,7 @@
import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
@@ -76,22 +77,25 @@ public class FilesSyncJob extends Job {
public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
private static final String WAKELOCK_TAG_SEPARATION = ":";
- private UserAccountManager userAccountManager;
- private AppPreferences preferences;
- private UploadsStorageManager uploadsStorageManager;
- private ConnectivityService connectivityService;
- private PowerManagementService powerManagementService;
+ private final UserAccountManager userAccountManager;
+ private final AppPreferences preferences;
+ private final UploadsStorageManager uploadsStorageManager;
+ private final ConnectivityService connectivityService;
+ private final PowerManagementService powerManagementService;
+ private final Clock clock;
FilesSyncJob(final UserAccountManager userAccountManager,
final AppPreferences preferences,
final UploadsStorageManager uploadsStorageManager,
final ConnectivityService connectivityService,
- final PowerManagementService powerManagementService) {
+ final PowerManagementService powerManagementService,
+ final Clock clock) {
this.userAccountManager = userAccountManager;
this.preferences = preferences;
this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityService;
this.powerManagementService = powerManagementService;
+ this.clock = clock;
}
@NonNull
@@ -126,23 +130,21 @@ protected Result onRunJob(@NonNull Params params) {
userAccountManager,
connectivityService,
powerManagementService);
- FilesSyncHelper.insertAllDBEntries(preferences, skipCustom);
+ FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom, false);
// Create all the providers we'll need
final ContentResolver contentResolver = context.getContentResolver();
final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
- SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
- preferences);
+ SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
Locale currentLocale = context.getResources().getConfiguration().locale;
SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, sFormatter,
- requester, syncedFolder);
+ syncedFolder);
}
}
@@ -153,10 +155,15 @@ protected Result onRunJob(@NonNull Params params) {
return Result.SUCCESS;
}
- private void syncFolder(Context context, Resources resources, boolean lightVersion,
- FilesystemDataProvider filesystemDataProvider, Locale currentLocale,
- SimpleDateFormat sFormatter, FileUploader.UploadRequester requester,
- SyncedFolder syncedFolder) {
+ private void syncFolder(
+ Context context,
+ Resources resources,
+ boolean lightVersion,
+ FilesystemDataProvider filesystemDataProvider,
+ Locale currentLocale,
+ SimpleDateFormat sFormatter,
+ SyncedFolder syncedFolder
+ ) {
String remotePath;
boolean subfolderByDate;
Integer uploadAction;
@@ -189,31 +196,31 @@ private void syncFolder(Context context, Resources resources, boolean lightVersi
remotePath = resources.getString(R.string.syncedFolder_remote_folder);
} else {
- needsCharging = syncedFolder.getChargingOnly();
- needsWifi = syncedFolder.getWifiOnly();
+ needsCharging = syncedFolder.isChargingOnly();
+ needsWifi = syncedFolder.isWifiOnly();
uploadAction = syncedFolder.getUploadAction();
- subfolderByDate = syncedFolder.getSubfolderByDate();
+ subfolderByDate = syncedFolder.isSubfolderByDate();
remotePath = syncedFolder.getRemotePath();
}
- requester.uploadFileWithOverwrite(
- context,
- account,
- file.getAbsolutePath(),
- FileStorageUtils.getInstantUploadFilePath(
+ FileUploader.uploadNewFile(
+ context,
+ account,
+ file.getAbsolutePath(),
+ FileStorageUtils.getInstantUploadFilePath(
file,
currentLocale,
remotePath,
syncedFolder.getLocalPath(),
lastModificationTime,
subfolderByDate),
- uploadAction,
- mimeType,
- true, // create parent folder if not existent
- UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
- needsWifi,
- needsCharging,
- true
+ uploadAction,
+ mimeType,
+ true, // create parent folder if not existent
+ UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
+ needsWifi,
+ needsCharging,
+ FileUploader.NameCollisionPolicy.ASK_USER
);
filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
diff --git a/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java b/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java
index 43e073608898..e421b9365a7d 100644
--- a/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java
+++ b/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java
@@ -39,6 +39,7 @@
import com.evernote.android.job.Job;
import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.R;
@@ -74,11 +75,13 @@ public class MediaFoldersDetectionJob extends Job {
private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK";
- private UserAccountManager userAccountManager;
- private Random randomId = new Random();
+ private final UserAccountManager userAccountManager;
+ private final Clock clock;
+ private final Random randomId = new Random();
- MediaFoldersDetectionJob(UserAccountManager accountManager) {
+ MediaFoldersDetectionJob(UserAccountManager accountManager, Clock clock) {
this.userAccountManager = accountManager;
+ this.clock = clock;
}
@NonNull
@@ -88,7 +91,8 @@ protected Result onRunJob(@NonNull Params params) {
ContentResolver contentResolver = context.getContentResolver();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
- AppPreferencesImpl.fromContext(context));
+ AppPreferencesImpl.fromContext(context),
+ clock);
Gson gson = new Gson();
String arbitraryDataString;
MediaFoldersModel mediaFoldersModel;
diff --git a/src/main/java/com/owncloud/android/jobs/NCJobCreator.java b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java
index c0894a59e14b..010ae266897a 100644
--- a/src/main/java/com/owncloud/android/jobs/NCJobCreator.java
+++ b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java
@@ -29,6 +29,7 @@
import com.evernote.android.job.Job;
import com.evernote.android.job.JobCreator;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
@@ -48,6 +49,7 @@ public class NCJobCreator implements JobCreator {
private final UploadsStorageManager uploadsStorageManager;
private final ConnectivityService connectivityService;
private final PowerManagementService powerManagementService;
+ private final Clock clock;
public NCJobCreator(
Context context,
@@ -55,7 +57,8 @@ public NCJobCreator(
AppPreferences preferences,
UploadsStorageManager uploadsStorageManager,
ConnectivityService connectivityServices,
- PowerManagementService powerManagementService
+ PowerManagementService powerManagementService,
+ Clock clock
) {
this.context = context;
this.accountManager = accountManager;
@@ -63,6 +66,7 @@ public NCJobCreator(
this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityServices;
this.powerManagementService = powerManagementService;
+ this.clock = clock;
}
@Override
@@ -73,19 +77,20 @@ public Job create(@NonNull String tag) {
case ContactsImportJob.TAG:
return new ContactsImportJob();
case AccountRemovalJob.TAG:
- return new AccountRemovalJob(uploadsStorageManager, accountManager);
+ return new AccountRemovalJob(uploadsStorageManager, accountManager, clock);
case FilesSyncJob.TAG:
return new FilesSyncJob(accountManager,
preferences,
uploadsStorageManager,
connectivityService,
- powerManagementService);
+ powerManagementService,
+ clock);
case OfflineSyncJob.TAG:
return new OfflineSyncJob(accountManager, connectivityService, powerManagementService);
case NotificationJob.TAG:
return new NotificationJob(context, accountManager);
case MediaFoldersDetectionJob.TAG:
- return new MediaFoldersDetectionJob(accountManager);
+ return new MediaFoldersDetectionJob(accountManager, clock);
default:
return null;
}
diff --git a/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
index 72e4a1ecbe61..d2ed15ccdd45 100644
--- a/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
+++ b/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
@@ -291,8 +291,13 @@ protected RemoteOperationResult run(OwnCloudClient client) {
* @param file OCFile object representing the file to upload
*/
private void requestForUpload(OCFile file) {
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- requester.uploadUpdate(mContext, mAccount, file, FileUploader.LOCAL_BEHAVIOUR_MOVE, true);
+ FileUploader.uploadUpdateFile(
+ mContext,
+ mAccount,
+ file,
+ FileUploader.LOCAL_BEHAVIOUR_MOVE,
+ FileUploader.NameCollisionPolicy.ASK_USER
+ );
mTransferWasRequested = true;
}
diff --git a/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
index 1ffe1dcecced..823760945b81 100644
--- a/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
+++ b/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
@@ -90,6 +90,7 @@
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
+import androidx.annotation.CheckResult;
import androidx.annotation.RequiresApi;
@@ -111,14 +112,14 @@ public class UploadFileOperation extends SyncOperation {
private OCFile mFile;
/**
- * Original OCFile which is to be uploaded in case file had to be renamed
- * (if forceOverwrite==false and remote file already exists).
+ * Original OCFile which is to be uploaded in case file had to be renamed (if nameCollisionPolicy==RENAME and remote
+ * file already exists).
*/
private OCFile mOldFile;
private String mRemotePath;
private String mFolderUnlockToken;
private boolean mRemoteFolderToBeCreated;
- private boolean mForceOverwrite;
+ private FileUploader.NameCollisionPolicy mNameCollisionPolicy;
private int mLocalBehaviour;
private int mCreatedBy;
private boolean mOnWifiOnly;
@@ -183,7 +184,7 @@ public UploadFileOperation(UploadsStorageManager uploadsStorageManager,
Account account,
OCFile file,
OCUpload upload,
- boolean forceOverwrite,
+ FileUploader.NameCollisionPolicy nameCollisionPolicy,
int localBehaviour,
Context context,
boolean onWifiOnly,
@@ -218,7 +219,7 @@ public UploadFileOperation(UploadsStorageManager uploadsStorageManager,
mOnWifiOnly = onWifiOnly;
mWhileChargingOnly = whileChargingOnly;
mRemotePath = upload.getRemotePath();
- mForceOverwrite = forceOverwrite;
+ mNameCollisionPolicy = nameCollisionPolicy;
mLocalBehaviour = localBehaviour;
mOriginalStoragePath = mFile.getStoragePath();
mContext = context;
@@ -504,7 +505,11 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
/**** E2E *****/
// check name collision
- checkNameCollision(client, metadata, parentFile.isEncrypted());
+ RemoteOperationResult collisionResult = checkNameCollision(client, metadata, parentFile.isEncrypted());
+ if (collisionResult != null) {
+ result = collisionResult;
+ return collisionResult;
+ }
String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
expectedFile = new File(expectedPath);
@@ -759,7 +764,11 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) {
}
// check name collision
- checkNameCollision(client, null, false);
+ RemoteOperationResult collisionResult = checkNameCollision(client, null, false);
+ if (collisionResult != null) {
+ result = collisionResult;
+ return collisionResult;
+ }
String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
expectedFile = new File(expectedPath);
@@ -922,24 +931,37 @@ private RemoteOperationResult copyFile(File originalFile, String expectedPath) t
return new RemoteOperationResult(ResultCode.OK);
}
- private void checkNameCollision(OwnCloudClient client, DecryptedFolderMetadata metadata, boolean encrypted)
- throws OperationCancelledException {
- /// automatic rename of file to upload in case of name collision in server
+ @CheckResult
+ private RemoteOperationResult checkNameCollision(OwnCloudClient client, DecryptedFolderMetadata metadata, boolean encrypted)
+ throws OperationCancelledException {
Log_OC.d(TAG, "Checking name collision in server");
- if (!mForceOverwrite) {
- String remotePath = getAvailableRemotePath(client, mRemotePath, metadata, encrypted);
- mWasRenamed = !remotePath.equals(mRemotePath);
- if (mWasRenamed) {
- createNewOCFile(remotePath);
- Log_OC.d(TAG, "File renamed as " + remotePath);
+
+ if (existsFile(client, mRemotePath, metadata, encrypted)) {
+ switch (mNameCollisionPolicy) {
+ case CANCEL:
+ Log_OC.d(TAG, "File exists; canceling");
+ throw new OperationCancelledException();
+ case RENAME:
+ mRemotePath = getNewAvailableRemotePath(client, mRemotePath, metadata, encrypted);
+ mWasRenamed = true;
+ createNewOCFile(mRemotePath);
+ Log_OC.d(TAG, "File renamed as " + mRemotePath);
+ mRenameUploadListener.onRenameUpload();
+ break;
+ case OVERWRITE:
+ Log_OC.d(TAG, "Overwriting file");
+ break;
+ case ASK_USER:
+ Log_OC.d(TAG, "Name collision; asking the user what to do");
+ return new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
}
- mRemotePath = remotePath;
- mRenameUploadListener.onRenameUpload();
}
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
+
+ return null;
}
private void handleSuccessfulUpload(File temporalFile, File expectedFile, File originalFile,
@@ -1039,8 +1061,8 @@ private OCFile createLocalFolder(String remotePath) {
/**
- * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
- * New file is stored as mFile, original as mOldFile.
+ * Create a new OCFile mFile with new remote path. This is required if nameCollisionPolicy==RENAME. New file is
+ * stored as mFile, original as mOldFile.
*
* @param newRemotePath new remote path
*/
@@ -1064,45 +1086,36 @@ private void createNewOCFile(String newRemotePath) {
}
/**
- * Checks if remotePath does not exist in the server and returns it, or adds
- * a suffix to it in order to avoid the server file is overwritten.
+ * Returns a new and available (does not exists on the server) remotePath.
+ * This adds an incremental suffix.
*
* @param client OwnCloud client
* @param remotePath remote path of the file
* @param metadata metadata of encrypted folder
* @return new remote path
*/
- private String getAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
- boolean encrypted) {
- boolean check = existsFile(client, remotePath, metadata, encrypted);
- if (!check) {
- return remotePath;
- }
-
- int pos = remotePath.lastIndexOf('.');
+ private String getNewAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
+ boolean encrypted) {
+ int extPos = remotePath.lastIndexOf('.');
String suffix;
String extension = "";
String remotePathWithoutExtension = "";
- if (pos >= 0) {
- extension = remotePath.substring(pos + 1);
- remotePathWithoutExtension = remotePath.substring(0, pos);
+ if (extPos >= 0) {
+ extension = remotePath.substring(extPos + 1);
+ remotePathWithoutExtension = remotePath.substring(0, extPos);
}
+
int count = 2;
+ boolean exists;
+ String newPath;
do {
suffix = " (" + count + ")";
- if (pos >= 0) {
- check = existsFile(client, remotePathWithoutExtension + suffix + "." + extension, metadata, encrypted);
- } else {
- check = existsFile(client, remotePath + suffix, metadata, encrypted);
- }
+ newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix;
+ exists = existsFile(client, newPath, metadata, encrypted);
count++;
- } while (check);
+ } while (exists);
- if (pos >= 0) {
- return remotePathWithoutExtension + suffix + "." + extension;
- } else {
- return remotePath + suffix;
- }
+ return newPath;
}
private boolean existsFile(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
diff --git a/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java b/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
index 0c17aeb4df87..cf398a425cbf 100644
--- a/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
+++ b/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
@@ -31,6 +31,7 @@
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -61,8 +62,8 @@ public boolean onCreate() {
}
private OCFile getFile(Uri uri) {
- Account account = accountManager.getCurrentAccount();
- FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(account,
+ User user = accountManager.getUser();
+ FileDataStorageManager fileDataStorageManager = new FileDataStorageManager(user.toPlatformAccount(),
MainApp.getAppContext().getContentResolver());
return fileDataStorageManager.getFileByPath(uri.getPath());
diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java
index bcfe381e53ec..94e115c228e7 100644
--- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java
+++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java
@@ -42,9 +42,11 @@
import android.os.Binder;
import android.text.TextUtils;
+import com.nextcloud.client.core.Clock;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.db.ProviderMeta;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.accounts.AccountUtils;
@@ -58,7 +60,10 @@
import java.util.HashMap;
import java.util.Locale;
+import javax.inject.Inject;
+
import androidx.annotation.NonNull;
+import dagger.android.AndroidInjection;
/**
* The ContentProvider for the ownCloud App.
@@ -91,6 +96,7 @@ public class FileContentProvider extends ContentProvider {
public static final int ARBITRARY_DATA_TABLE_INTRODUCTION_VERSION = 20;
public static final int MINIMUM_PATH_SEGMENTS_SIZE = 1;
+ @Inject protected Clock clock;
private DataBaseHelper mDbHelper;
private Context mContext;
private UriMatcher mUriMatcher;
@@ -414,6 +420,7 @@ private void updateFilesTableAccordingToShareInsertion(SQLiteDatabase db, Conten
@Override
public boolean onCreate() {
+ AndroidInjection.inject(this);
mDbHelper = new DataBaseHelper(getContext());
mContext = getContext();
@@ -795,7 +802,7 @@ private void createUploadsTable(SQLiteDatabase db) {
+ ProviderTableMeta.UPLOADS_STATUS + INTEGER // UploadStatus
+ ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + INTEGER // Upload LocalBehaviour
+ ProviderTableMeta.UPLOADS_UPLOAD_TIME + INTEGER
- + ProviderTableMeta.UPLOADS_FORCE_OVERWRITE + INTEGER // boolean
+ + ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY + INTEGER // boolean
+ ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + INTEGER // boolean
+ ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + INTEGER
+ ProviderTableMeta.UPLOADS_LAST_RESULT + INTEGER // Upload LastResult
@@ -821,11 +828,14 @@ private void createSyncedFoldersTable(SQLiteDatabase db) {
+ ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " TEXT, " // remote path
+ ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + " INTEGER, " // wifi_only
+ ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only
+ + ProviderTableMeta.SYNCED_FOLDER_EXISTING + " INTEGER, " // existing
+ ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled
+ + ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER, " // enable date
+ ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date
+ ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account
+ ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, " // upload action
- + ProviderTableMeta.SYNCED_FOLDER_TYPE + " INTEGER );" // type
+ + ProviderTableMeta.SYNCED_FOLDER_TYPE + " INTEGER, " // type
+ + ProviderTableMeta.SYNCED_FOLDER_HIDDEN + " INTEGER );" // hidden
);
}
@@ -2013,6 +2023,110 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
+
+ if (oldVersion < 50 && newVersion >= 50) {
+ Log_OC.i(SQL, "Entering in the #50 add persistent enable date to synced_folders table");
+ db.beginTransaction();
+ try {
+ db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
+ ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER ");
+
+ db.execSQL("UPDATE " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + " SET " +
+ ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " = CASE " +
+ " WHEN enabled = 0 THEN " + SyncedFolder.EMPTY_ENABLED_TIMESTAMP_MS + " " +
+ " ELSE " + clock.getCurrentTime() +
+ " END ");
+
+ upgraded = true;
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ if (!upgraded) {
+ Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+ }
+
+ if (oldVersion < 51 && newVersion >= 51) {
+ Log_OC.i(SQL, "Entering in the #51 add show/hide to folderSync table");
+ db.beginTransaction();
+ try {
+ db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
+ ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_HIDDEN + " INTEGER ");
+
+ upgraded = true;
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ if (!upgraded) {
+ Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+ }
+
+ if(oldVersion < 52 && newVersion >= 52) {
+ Log_OC.i(SQL, "Entering in the #52 add synced.existing," +
+ " rename uploads.force_overwrite to uploads.name_collision_policy");
+ db.beginTransaction();
+ try {
+ // Add synced.existing
+ db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
+ ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_EXISTING + " INTEGER "); // boolean
+
+ // Rename uploads.force_overwrite to uploads.name_collision_policy
+ String tmpTableName = ProviderTableMeta.UPLOADS_TABLE_NAME + "_old";
+ db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME + " RENAME TO " + tmpTableName);
+ createUploadsTable(db);
+ db.execSQL("INSERT INTO " + ProviderTableMeta.UPLOADS_TABLE_NAME + " (" +
+ ProviderTableMeta._ID + ", " +
+ ProviderTableMeta.UPLOADS_LOCAL_PATH + ", " +
+ ProviderTableMeta.UPLOADS_REMOTE_PATH + ", " +
+ ProviderTableMeta.UPLOADS_ACCOUNT_NAME + ", " +
+ ProviderTableMeta.UPLOADS_FILE_SIZE + ", " +
+ ProviderTableMeta.UPLOADS_STATUS + ", " +
+ ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + ", " +
+ ProviderTableMeta.UPLOADS_UPLOAD_TIME + ", " +
+ ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY + ", " +
+ ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + ", " +
+ ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + ", " +
+ ProviderTableMeta.UPLOADS_LAST_RESULT + ", " +
+ ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + ", " +
+ ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + ", " +
+ ProviderTableMeta.UPLOADS_CREATED_BY + ", " +
+ ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN +
+ ") " +
+ " SELECT " +
+ ProviderTableMeta._ID + ", " +
+ ProviderTableMeta.UPLOADS_LOCAL_PATH + ", " +
+ ProviderTableMeta.UPLOADS_REMOTE_PATH + ", " +
+ ProviderTableMeta.UPLOADS_ACCOUNT_NAME + ", " +
+ ProviderTableMeta.UPLOADS_FILE_SIZE + ", " +
+ ProviderTableMeta.UPLOADS_STATUS + ", " +
+ ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR + ", " +
+ ProviderTableMeta.UPLOADS_UPLOAD_TIME + ", " +
+ "force_overwrite" + ", " +
+ ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + ", " +
+ ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + ", " +
+ ProviderTableMeta.UPLOADS_LAST_RESULT + ", " +
+ ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + ", " +
+ ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + ", " +
+ ProviderTableMeta.UPLOADS_CREATED_BY + ", " +
+ ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN +
+ " FROM " + tmpTableName);
+ db.execSQL("DROP TABLE " + tmpTableName);
+
+ upgraded = true;
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ if (!upgraded) {
+ Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+ }
}
@Override
diff --git a/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java b/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
index acc79b695ade..0daa551ab90a 100644
--- a/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
+++ b/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
@@ -34,6 +34,7 @@
import android.provider.BaseColumns;
import android.widget.Toast;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -180,18 +181,14 @@ private Cursor searchForUsersOrGroups(Uri uri) {
// need to trust on the AccountUtils to get the current account since the query in the client side is not
// directly started by our code, but from SearchView implementation
- Account account = accountManager.getCurrentAccount();
-
- if (account == null) {
- throw new IllegalArgumentException("Account may not be null!");
- }
+ User user = accountManager.getUser();
String userQuery = lastPathSegment.toLowerCase(Locale.ROOT);
// request to the OC server about users and groups matching userQuery
GetShareesRemoteOperation searchRequest = new GetShareesRemoteOperation(userQuery, REQUESTED_PAGE,
RESULTS_PER_PAGE);
- RemoteOperationResult result = searchRequest.execute(account, getContext());
+ RemoteOperationResult result = searchRequest.execute(user.toPlatformAccount(), getContext());
List names = new ArrayList<>();
if (result.isSuccess()) {
@@ -217,8 +214,10 @@ private Cursor searchForUsersOrGroups(Uri uri) {
Uri remoteBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_REMOTE).build();
Uri emailBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_EMAIL).build();
- FileDataStorageManager manager = new FileDataStorageManager(account, getContext().getContentResolver());
- boolean federatedShareAllowed = manager.getCapability(account.name).getFilesSharingFederationOutgoing()
+ FileDataStorageManager manager = new FileDataStorageManager(user.toPlatformAccount(),
+ getContext().getContentResolver());
+ boolean federatedShareAllowed = manager.getCapability(user.getAccountName())
+ .getFilesSharingFederationOutgoing()
.isTrue();
try {
diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
index 7d06866c6ef9..4c36bce115b8 100644
--- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
@@ -169,7 +169,8 @@ private void setupContent() {
PorterDuff.Mode.SRC_IN);
FileDataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver());
- adapter = new ActivityListAdapter(this, getUserAccountManager(), this, storageManager, getCapabilities(), false);
+ adapter = new ActivityListAdapter(this, getUserAccountManager(), this, storageManager,
+ getCapabilities(), false);
recyclerView.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
diff --git a/src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java b/src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java
index 450fbcc9cc40..360c2458881e 100644
--- a/src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java
+++ b/src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.os.AsyncTask;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
@@ -58,8 +59,11 @@ public ActivitiesServiceApiImpl(UserAccountManager accountManager) {
@Override
public void getAllActivities(int lastGiven, ActivitiesServiceCallback> callback) {
- Account account = accountManager.getCurrentAccount();
- GetActivityListTask getActivityListTask = new GetActivityListTask(account, accountManager, lastGiven, callback);
+ User user = accountManager.getUser();
+ GetActivityListTask getActivityListTask = new GetActivityListTask(user.toPlatformAccount(),
+ accountManager,
+ lastGiven,
+ callback);
getActivityListTask.execute();
}
diff --git a/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java b/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
index 892d05a4cf25..8492a0751a4f 100644
--- a/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
+++ b/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
@@ -22,19 +22,16 @@
*/
package com.owncloud.android.ui.activities.data.files;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import android.content.Context;
import android.os.AsyncTask;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -44,8 +41,6 @@
import com.owncloud.android.ui.activity.BaseActivity;
import com.owncloud.android.utils.FileStorageUtils;
-import java.io.IOException;
-
/**
* Implementation of the Files service API that communicates with the NextCloud remote server.
*/
@@ -54,15 +49,18 @@ public class FilesServiceApiImpl implements FilesServiceApi {
private static final String TAG = FilesServiceApiImpl.class.getSimpleName();
private UserAccountManager accountManager;
+ private ClientFactory clientFactory;
- public FilesServiceApiImpl(UserAccountManager accountManager) {
+ public FilesServiceApiImpl(UserAccountManager accountManager, ClientFactory clientFactory) {
this.accountManager = accountManager;
+ this.clientFactory = clientFactory;
}
@Override
public void readRemoteFile(String fileUrl, BaseActivity activity, FilesServiceCallback callback) {
ReadRemoteFileTask readRemoteFileTask = new ReadRemoteFileTask(
accountManager,
+ clientFactory,
fileUrl,
activity,
callback
@@ -77,30 +75,29 @@ private static class ReadRemoteFileTask extends AsyncTask
// TODO: Figure out a better way to do this than passing a BaseActivity reference.
private final BaseActivity baseActivity;
private final String fileUrl;
- private final Account account;
+ private final User user;
private final UserAccountManager accountManager;
+ private final ClientFactory clientFactory;
private ReadRemoteFileTask(UserAccountManager accountManager,
+ ClientFactory clientFactory,
String fileUrl,
BaseActivity baseActivity,
FilesServiceCallback callback) {
this.callback = callback;
this.baseActivity = baseActivity;
this.fileUrl = fileUrl;
- this.account = accountManager.getCurrentAccount();
+ this.user = accountManager.getUser();
this.accountManager = accountManager;
+ this.clientFactory = clientFactory;
}
@Override
protected Boolean doInBackground(Void... voids) {
final Context context = MainApp.getAppContext();
- OwnCloudAccount ocAccount;
- OwnCloudClient ownCloudClient;
try {
- ocAccount = new OwnCloudAccount(account, context);
- ownCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, MainApp.getAppContext());
- ownCloudClient.setOwnCloudVersion(accountManager.getServerVersion(account));
+ OwnCloudClient ownCloudClient = clientFactory.create(user);
+ ownCloudClient.setOwnCloudVersion(user.getServer().getVersion());
// always update file as it could be an old state saved in database
RemoteOperationResult resultRemoteFileOp = new ReadFileRemoteOperation(fileUrl).execute(ownCloudClient);
@@ -111,28 +108,19 @@ protected Boolean doInBackground(Void... voids) {
if (remoteOcFile.isFolder()) {
// perform folder synchronization
RemoteOperation synchFolderOp = new RefreshFolderOperation(remoteOcFile,
- System.currentTimeMillis(),
- false,
- true,
- baseActivity.getStorageManager(),
- baseActivity.getAccount(),
- context);
+ System.currentTimeMillis(),
+ false,
+ true,
+ baseActivity.getStorageManager(),
+ baseActivity.getAccount(),
+ context);
synchFolderOp.execute(ownCloudClient);
}
}
return true;
- } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
+ } catch (ClientFactory.CreationException e) {
Log_OC.e(TAG, "Account not found", e);
errorMessage = baseActivity.getString(R.string.account_not_found);
- } catch (IOException e) {
- Log_OC.e(TAG, "IO error", e);
- errorMessage = baseActivity.getString(R.string.io_error);
- } catch (OperationCanceledException e) {
- Log_OC.e(TAG, "Operation has been canceled", e);
- errorMessage = baseActivity.getString(R.string.operation_canceled);
- } catch (AuthenticatorException e) {
- Log_OC.e(TAG, "Authentication Exception", e);
- errorMessage = baseActivity.getString(R.string.authentication_exception);
}
return false;
diff --git a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
index f10bc8a29051..327bb103873d 100644
--- a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
@@ -6,15 +6,16 @@
import android.accounts.AccountManagerFuture;
import android.accounts.OperationCanceledException;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
+import android.os.PersistableBundle;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.preferences.AppPreferencesImpl;
+import com.nextcloud.client.preferences.AppPreferences;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -28,9 +29,7 @@
/**
* Base activity with common behaviour for activities dealing with ownCloud {@link Account}s .
*/
-public abstract class BaseActivity
- extends AppCompatActivity
- implements Injectable, SharedPreferences.OnSharedPreferenceChangeListener {
+public abstract class BaseActivity extends AppCompatActivity implements Injectable {
private static final String TAG = BaseActivity.class.getSimpleName();
@@ -56,7 +55,14 @@ public abstract class BaseActivity
private boolean paused;
@Inject UserAccountManager accountManager;
- @Inject SharedPreferences sharedPreferences;
+ @Inject AppPreferences preferences;
+
+ private AppPreferences.Listener onPreferencesChanged = new AppPreferences.Listener() {
+ @Override
+ public void onDarkThemeEnabledChanged(boolean enabled) {
+ BaseActivity.this.onThemeSettingsChanged();
+ }
+ };
public UserAccountManager getUserAccountManager() {
return accountManager;
@@ -65,13 +71,13 @@ public UserAccountManager getUserAccountManager() {
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
- sharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ preferences.addListener(onPreferencesChanged);
}
@Override
protected void onDestroy() {
super.onDestroy();
- sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ preferences.removeListener(onPreferencesChanged);
}
@Override
@@ -90,9 +96,10 @@ protected void onResume() {
}
}
- @Override
- protected void onPostResume() {
- super.onPostResume();
+ public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
+ super.onCreate(savedInstanceState, persistentState);
+ Account account = accountManager.getCurrentAccount();
+ setAccount(account, false);
}
@Override
@@ -122,17 +129,12 @@ protected void onRestart() {
Log_OC.v(TAG, "onRestart() end");
}
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
- if (!AppPreferencesImpl.PREF__THEME.equals(key)) {
- return;
- }
-
+ private void onThemeSettingsChanged() {
if(paused) {
themeChangePending = true;
- return;
+ } else {
+ recreate();
}
- recreate();
}
/**
@@ -151,6 +153,15 @@ protected void setAccount(Account account, boolean savedAccount) {
} else {
swapToDefaultAccount();
}
+
+ if(currentAccount != null) {
+ storageManager = new FileDataStorageManager(currentAccount, getContentResolver());
+ capabilities = storageManager.getCapability(currentAccount.name);
+ }
+ }
+
+ protected void setUser(User user) {
+ setAccount(user.toPlatformAccount(), false);
}
/**
@@ -187,26 +198,6 @@ protected void createAccount(boolean mandatoryCreation) {
new Handler());
}
- /**
- * Called when the ownCloud {@link Account} associated to the Activity was just updated.
- *
- * Child classes must grant that state depending on the {@link Account} is updated.
- */
- @Deprecated
- protected void onAccountSet() {
- if (getAccount() != null) {
- storageManager = new FileDataStorageManager(getAccount(), getContentResolver());
- capabilities = storageManager.getCapability(currentAccount.name);
- } else {
- Log_OC.e(TAG, "onAccountChanged was called with NULL account associated!");
- }
- }
-
- @Deprecated
- protected void setAccount(Account account) {
- currentAccount = account;
- }
-
/**
* Getter for the capabilities of the server where the current OC account lives.
*
@@ -228,15 +219,14 @@ public Account getAccount() {
return currentAccount;
}
- @Override
- protected void onStart() {
- super.onStart();
-
- if(currentAccount != null) {
- onAccountSet();
+ public Optional getUser() {
+ if (currentAccount != null) {
+ return accountManager.getUser(currentAccount.name);
+ } else {
+ return Optional.empty();
}
}
-
+
public FileDataStorageManager getStorageManager() {
return storageManager;
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
index 7733900af6c3..0be1bb2c4eb3 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
@@ -25,6 +25,8 @@
import android.os.Bundle;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -32,52 +34,81 @@
import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener;
+import javax.inject.Inject;
+
/**
* Wrapper activity which will be launched if keep-in-sync file will be modified by external
* application.
*/
public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener {
+ /**
+ * A nullable upload entry that must be removed when and if the conflict is resolved.
+ */
+ public static final String EXTRA_CONFLICT_UPLOAD = "CONFLICT_UPLOAD";
+ /**
+ * Specify the upload local behaviour when there is no CONFLICT_UPLOAD.
+ */
+ public static final String EXTRA_LOCAL_BEHAVIOUR = "LOCAL_BEHAVIOUR";
private static final String TAG = ConflictsResolveActivity.class.getSimpleName();
+ @Inject UploadsStorageManager uploadsStorageManager;
+
+ private OCUpload conflictUpload;
+ private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ this.conflictUpload = savedInstanceState.getParcelable(EXTRA_CONFLICT_UPLOAD);
+ this.localBehaviour = savedInstanceState.getInt(EXTRA_LOCAL_BEHAVIOUR);
+ } else {
+ this.conflictUpload = getIntent().getParcelableExtra(EXTRA_CONFLICT_UPLOAD);
+ this.localBehaviour = getIntent().getIntExtra(EXTRA_LOCAL_BEHAVIOUR, this.localBehaviour);
+ }
+
+ if (this.conflictUpload != null) {
+ this.localBehaviour = this.conflictUpload.getLocalAction();
+ }
}
@Override
public void conflictDecisionMade(Decision decision) {
+ if (decision == Decision.CANCEL) {
+ return;
+ }
- Integer behaviour = null;
- Boolean forceOverwrite = null;
+ OCFile file = getFile();
- switch (decision) {
- case CANCEL:
- finish();
- return;
- case OVERWRITE:
- // use local version -> overwrite on server
- forceOverwrite = true;
- break;
- case KEEP_BOTH:
- behaviour = FileUploader.LOCAL_BEHAVIOUR_MOVE;
- break;
- case SERVER:
- // use server version -> delete local, request download
- Intent intent = new Intent(this, FileDownloader.class);
- intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
- intent.putExtra(FileDownloader.EXTRA_FILE, getFile());
- startService(intent);
- finish();
- return;
- default:
- Log_OC.e(TAG, "Unhandled conflict decision " + decision);
- return;
+ // Upload
+ if (decision == Decision.KEEP_LOCAL || decision == Decision.KEEP_BOTH) {
+ FileUploader.NameCollisionPolicy collisionPolicy = FileUploader.NameCollisionPolicy.OVERWRITE;
+ if (decision == Decision.KEEP_BOTH) {
+ collisionPolicy = FileUploader.NameCollisionPolicy.RENAME;
+ }
+
+ FileUploader.uploadUpdateFile(this, getAccount(), file, localBehaviour, collisionPolicy);
+
+ if (this.conflictUpload != null) {
+ uploadsStorageManager.removeUpload(this.conflictUpload);
+ }
+ }
+
+ // Download
+ if (decision == Decision.KEEP_SERVER && !this.shouldDeleteLocal()) {
+ // Overwrite local file
+ Intent intent = new Intent(this, FileDownloader.class);
+ intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
+ intent.putExtra(FileDownloader.EXTRA_FILE, file);
+ if (this.conflictUpload != null) {
+ intent.putExtra(FileDownloader.EXTRA_CONFLICT_UPLOAD, this.conflictUpload);
+ }
+ startService(intent);
}
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- requester.uploadUpdate(this, getAccount(), getFile(), behaviour, forceOverwrite);
finish();
}
@@ -87,26 +118,27 @@ protected void onStart() {
if (getAccount() != null) {
OCFile file = getFile();
if (getFile() == null) {
- Log_OC.e(TAG, "No conflictive file received");
+ Log_OC.e(TAG, "No file received");
finish();
} else {
- /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
- file = getStorageManager().getFileByPath(file.getRemotePath()); // file = null if not in the
- // current Account
- if (file != null) {
- setFile(file);
- ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(this);
- d.showDialog(this);
-
+ // Check whether the file is contained in the current Account
+ if (getStorageManager().fileExists(file.getRemotePath())) {
+ ConflictsResolveDialog dialog = new ConflictsResolveDialog(this, !this.shouldDeleteLocal());
+ dialog.showDialog(this);
} else {
- // account was changed to a different one - just finish
+ // Account was changed to a different one - just finish
finish();
}
}
-
} else {
finish();
}
+ }
+ /**
+ * @return whether the local version of the files is to be deleted.
+ */
+ private boolean shouldDeleteLocal() {
+ return localBehaviour == FileUploader.LOCAL_BEHAVIOUR_DELETE;
}
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 9670feb8c55a..5984380b1f6a 100644
--- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -56,8 +56,10 @@
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.google.android.material.navigation.NavigationView;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
@@ -217,6 +219,9 @@ public abstract class DrawerActivity extends ToolbarActivity
@Inject
AppPreferences preferences;
+ @Inject
+ ClientFactory clientFactory;
+
/**
* Initializes the drawer, its content and highlights the menu item with the given id.
* This method needs to be called after the content view has been set.
@@ -357,21 +362,19 @@ public void run() {
navigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false);
}
- Account account = accountManager.getCurrentAccount();
+ User account = accountManager.getUser();
filterDrawerMenu(navigationView.getMenu(), account);
}
- private void filterDrawerMenu(Menu menu, Account account) {
- OCCapability capability = null;
- if (account != null) {
- FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
- capability = storageManager.getCapability(account.name);
- }
+ private void filterDrawerMenu(final Menu menu, @NonNull final User user) {
+ FileDataStorageManager storageManager = new FileDataStorageManager(user.toPlatformAccount(),
+ getContentResolver());
+ OCCapability capability = storageManager.getCapability(user.getAccountName());
- boolean hasSearchSupport = accountManager.getServerVersion(account).isSearchSupported();
+ boolean hasSearchSupport = user.getServer().getVersion().isSearchSupported();
- DrawerMenuUtil.filterSearchMenuItems(menu, account, getResources(), hasSearchSupport);
- DrawerMenuUtil.filterTrashbinMenuItem(menu, account, capability, accountManager);
+ DrawerMenuUtil.filterSearchMenuItems(menu, user.toPlatformAccount(), getResources(), hasSearchSupport);
+ DrawerMenuUtil.filterTrashbinMenuItem(menu, user.toPlatformAccount(), capability, accountManager);
DrawerMenuUtil.filterActivityMenuItem(menu, capability);
DrawerMenuUtil.setupHomeMenuItem(menu, getResources());
@@ -685,7 +688,7 @@ public void updateAccountList() {
if (mNavigationView != null && mDrawerLayout != null) {
if (persistingAccounts.size() > 0) {
repopulateAccountList(persistingAccounts);
- setAccountInDrawer(accountManager.getCurrentAccount());
+ setAccountInDrawer(accountManager.getUser());
populateDrawerOwnCloudAccounts();
// activate second/end account avatar
@@ -791,30 +794,25 @@ protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
* sets the given account name in the drawer in case the drawer is available. The account name is shortened
* beginning from the @-sign in the username.
*
- * @param account the account to be set in the drawer
+ * @param user the account to be set in the drawer
*/
- protected void setAccountInDrawer(Account account) {
- if (mDrawerLayout != null && account != null) {
+ protected void setAccountInDrawer(User user) {
+ if (mDrawerLayout != null && user != null) {
TextView username = (TextView) findNavigationViewChildById(R.id.drawer_username);
TextView usernameFull = (TextView) findNavigationViewChildById(R.id.drawer_username_full);
- usernameFull.setText(DisplayUtils.convertIdn(account.name.substring(account.name.lastIndexOf('@') + 1),
+ String name = user.getAccountName();
+ usernameFull.setText(DisplayUtils.convertIdn(name.substring(name.lastIndexOf('@') + 1),
false));
usernameFull.setTextColor(ThemeUtils.fontColor(this));
- try {
- OwnCloudAccount oca = new OwnCloudAccount(account, this);
- username.setText(oca.getDisplayName());
- username.setTextColor(ThemeUtils.fontColor(this));
- } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
- Log_OC.w(TAG, "Couldn't read display name of account fallback to account name");
- username.setText(UserAccountManager.getUsername(account));
- }
+ username.setText(user.toOwnCloudAccount().getDisplayName());
+ username.setTextColor(ThemeUtils.fontColor(this));
View currentAccountView = findNavigationViewChildById(R.id.drawer_current_account);
- currentAccountView.setTag(account.name);
+ currentAccountView.setTag(name);
- DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(),
+ DisplayUtils.setAvatar(user.toPlatformAccount(), this, mCurrentAccountAvatarRadiusDimension, getResources(),
currentAccountView, this);
// check and show quota info if available
@@ -958,6 +956,7 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) {
};
DisplayUtils.downloadIcon(getUserAccountManager(),
+ clientFactory,
this,
firstQuota.iconUrl,
target,
@@ -1024,14 +1023,14 @@ private void getAndDisplayUserQuota() {
// set user space information
Thread t = new Thread(new Runnable() {
public void run() {
- final Account currentAccount = accountManager.getCurrentAccount();
+ final User user = accountManager.getUser();
- if (currentAccount == null) {
+ if (user.isAnonymous()) {
return;
}
final Context context = MainApp.getAppContext();
- RemoteOperationResult result = new GetUserInfoRemoteOperation().execute(currentAccount, context);
+ RemoteOperationResult result = new GetUserInfoRemoteOperation().execute(user.toPlatformAccount(), context);
if (result.isSuccess() && result.getData() != null) {
final UserInfo userInfo = (UserInfo) result.getData().get(0);
@@ -1101,6 +1100,7 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) {
};
DisplayUtils.downloadIcon(getUserAccountManager(),
+ clientFactory,
this,
link.iconUrl,
target,
@@ -1293,7 +1293,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// current account has changed
if (data.getBooleanExtra(ManageAccountsActivity.KEY_CURRENT_ACCOUNT_CHANGED, false)) {
- setAccount(accountManager.getCurrentAccount());
+ setAccount(accountManager.getCurrentAccount(), false);
updateAccountList();
restart();
} else {
@@ -1375,12 +1375,11 @@ private void populateDrawerOwnCloudAccounts() {
}
}
- Account currentAccount = accountManager.getCurrentAccount();
-
- mAvatars[0] = currentAccount;
+ User user = accountManager.getUser();
+ mAvatars[0] = user.toPlatformAccount();
int j = 0;
for (int i = 1; i <= 2 && i < persistingAccounts.size() && j < persistingAccounts.size(); j++) {
- if (!currentAccount.equals(persistingAccounts.get(j))) {
+ if (!user.equals(persistingAccounts.get(j))) {
mAvatars[i] = persistingAccounts.get(j);
i++;
}
@@ -1462,11 +1461,10 @@ && getStorageManager() != null) {
getCapabilities.execute(getStorageManager(), getBaseContext());
}
- Account account = accountManager.getCurrentAccount();
-
- if (account != null && getStorageManager() != null &&
- getStorageManager().getCapability(account.name) != null &&
- getStorageManager().getCapability(account.name).getExternalLinks().isTrue()) {
+ User user = accountManager.getUser();
+ String name = user.getAccountName();
+ if (getStorageManager() != null && getStorageManager().getCapability(name) != null &&
+ getStorageManager().getCapability(name).getExternalLinks().isTrue()) {
int count = arbitraryDataProvider.getIntegerValue(FilesSyncHelper.GLOBAL,
FileActivity.APP_OPENED_COUNT);
@@ -1481,7 +1479,7 @@ && getStorageManager() != null) {
Log_OC.d("ExternalLinks", "update via api");
RemoteOperation getExternalLinksOperation = new ExternalLinksOperation();
- RemoteOperationResult result = getExternalLinksOperation.execute(account, this);
+ RemoteOperationResult result = getExternalLinksOperation.execute(user.toPlatformAccount(), this);
if (result.isSuccess() && result.getData() != null) {
externalLinksProvider.deleteAllExternalLinks();
diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
index fe872a7b84d0..2dc513c3ed0e 100644
--- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -54,11 +54,13 @@
import android.view.ViewTreeObserver;
import com.google.android.material.snackbar.Snackbar;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.appinfo.AppInfo;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.media.PlayerServiceConnection;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -765,7 +767,7 @@ public boolean onPrepareOptionsMenu(Menu menu) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main_menu, menu);
+ inflater.inflate(R.menu.activity_file_display, menu);
menu.findItem(R.id.action_create_dir).setVisible(false);
menu.findItem(R.id.action_select_all).setVisible(false);
@@ -1051,8 +1053,7 @@ private void requestUploadOfFilesFromFileSystem(String[] filePaths, int resultCo
break;
}
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- requester.uploadNewFile(
+ FileUploader.uploadNewFile(
this,
getAccount(),
filePaths,
@@ -1062,7 +1063,8 @@ private void requestUploadOfFilesFromFileSystem(String[] filePaths, int resultCo
false, // do not create parent folder if not existent
UploadFileOperation.CREATED_BY_USER,
false,
- false
+ false,
+ FileUploader.NameCollisionPolicy.ASK_USER
);
} else {
@@ -2545,8 +2547,8 @@ public void onMessageEvent(TokenPushEvent event) {
@Override
public void onStart() {
super.onStart();
- final Account account = getAccount();
- if (account != null) {
+ Optional optionalUser = getUser();
+ if (optionalUser.isPresent()) {
/// Check whether the 'main' OCFile handled by the Activity is contained in the
// current Account
OCFile file = getFile();
@@ -2572,10 +2574,12 @@ public void onStart() {
}
setFile(file);
- setAccountInDrawer(account);
+ User user = optionalUser.get();
+ setAccountInDrawer(user);
setupDrawer();
- final boolean accountChanged = !account.equals(mLastDisplayedAccount);
+ final String lastDisplayedAccountName = mLastDisplayedAccount != null ? mLastDisplayedAccount.name : null;
+ final boolean accountChanged = !user.getAccountName().equals(lastDisplayedAccountName);
if (accountChanged) {
Log_OC.d(TAG, "Initializing Fragments in onAccountChanged..");
initFragmentsWithFile();
@@ -2587,7 +2591,11 @@ public void onStart() {
updateActionBarTitleAndHomeButton(file.isFolder() ? null : file);
}
}
- mLastDisplayedAccount = account;
+ if (optionalUser.isPresent()) {
+ mLastDisplayedAccount = optionalUser.get().toPlatformAccount();
+ } else {
+ mLastDisplayedAccount = null;
+ }
EventBus.getDefault().post(new TokenPushEvent());
checkForNewDevVersionNecessary(findViewById(R.id.root_layout), getApplicationContext());
@@ -2621,7 +2629,8 @@ private void handleOpenFileViaIntent(Intent intent) {
return;
}
- setAccount(newAccount);
+ setAccount(newAccount, false);
+ updateAccountList();
}
String fileId = String.valueOf(intent.getStringExtra(KEY_FILE_ID));
diff --git a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
index 211fddb9b817..72953d250ae2 100644
--- a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
@@ -293,11 +293,7 @@ protected void onPause() {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main_menu, menu);
- menu.findItem(R.id.action_switch_view).setVisible(false);
- menu.findItem(R.id.action_sync_account).setVisible(false);
- menu.findItem(R.id.action_select_all).setVisible(false);
- // menu.findItem(R.id.action_sort).setVisible(false);
+ inflater.inflate(R.menu.activity_folder_picker, menu);
return true;
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
index 7656482a636b..531f448900e7 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
@@ -38,6 +38,7 @@
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.owncloud.android.MainApp;
@@ -126,15 +127,12 @@ protected void onCreate(Bundle savedInstanceState) {
Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));
originalAccounts = DisplayUtils.toAccountNameSet(Arrays.asList(accountList));
- Account currentAccount = getUserAccountManager().getCurrentAccount();
+ Account currentAccount = getAccount();
if (currentAccount != null) {
originalCurrentAccount = currentAccount.name;
}
- setAccount(currentAccount);
- onAccountSet();
-
arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
multipleAccountsSupported = getResources().getBoolean(R.bool.multiaccount_support);
@@ -201,11 +199,11 @@ private boolean hasAccountListChanged() {
* @return true if account list has changed, false if not
*/
private boolean hasCurrentAccountChanged() {
- Account account = getUserAccountManager().getCurrentAccount();
- if (account == null) {
+ User account = getUserAccountManager().getUser();
+ if (account.isAnonymous()) {
return true;
} else {
- return !account.name.equals(originalCurrentAccount);
+ return !account.getAccountName().equals(originalCurrentAccount);
}
}
@@ -323,7 +321,8 @@ public void run(AccountManagerFuture future) {
}
}
- if (getUserAccountManager().getCurrentAccount() == null) {
+ User user = getUserAccountManager().getUser();
+ if (user.isAnonymous()) {
String accountName = "";
Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));
if (accounts.length != 0) {
diff --git a/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java b/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java
index 1fbe351e3e62..261d12497aee 100644
--- a/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java
@@ -24,9 +24,6 @@
package com.owncloud.android.ui.activity;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
@@ -39,13 +36,14 @@
import android.widget.TextView;
import com.google.android.material.snackbar.Snackbar;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.network.ClientFactory;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.jobs.NotificationJob;
-import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -58,9 +56,10 @@
import com.owncloud.android.utils.PushUtils;
import com.owncloud.android.utils.ThemeUtils;
-import java.io.IOException;
import java.util.List;
+import javax.inject.Inject;
+
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@@ -109,7 +108,9 @@ public class NotificationsActivity extends FileActivity implements Notifications
private NotificationListAdapter adapter;
private Snackbar snackbar;
private OwnCloudClient client;
- private Account currentAccount;
+ private Optional optionalUser;
+
+ @Inject ClientFactory clientFactory;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -119,16 +120,18 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.notifications_layout);
unbinder = ButterKnife.bind(this);
- currentAccount = getAccount();
+ optionalUser = getUser();
// use account from intent (opened via android notification can have a different account than current one)
if (getIntent() != null && getIntent().getExtras() != null) {
- String account = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT);
-
- if (account != null && (currentAccount == null || !account.equalsIgnoreCase(currentAccount.name))) {
- accountManager.setCurrentOwnCloudAccount(account);
- setAccount(getUserAccountManager().getCurrentAccount());
- currentAccount = getAccount();
+ String accountName = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT);
+ if(accountName != null && optionalUser.isPresent()) {
+ User user = optionalUser.get();
+ if (user.getAccountName().equalsIgnoreCase(accountName)) {
+ accountManager.setCurrentOwnCloudAccount(accountName);
+ setUser(getUserAccountManager().getUser());
+ optionalUser = getUser();
+ }
}
}
@@ -142,7 +145,7 @@ protected void onCreate(Bundle savedInstanceState) {
setupDrawer(R.id.nav_notifications);
ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_item_notifications), this);
- if (currentAccount == null) {
+ if (!optionalUser.isPresent()) {
// show error
runOnUiThread(() -> setEmptyContent(noResultsHeadline, getString(R.string.account_not_found)));
return;
@@ -156,7 +159,6 @@ protected void onCreate(Bundle savedInstanceState) {
swipeEmptyListRefreshLayout.setOnRefreshListener(() -> {
setLoadingMessage();
fetchAndSetData();
-
});
setupPushWarning();
@@ -175,16 +177,16 @@ private void setupPushWarning() {
snackbar = Snackbar.make(emptyContentContainer, R.string.push_notifications_not_implemented,
Snackbar.LENGTH_INDEFINITE);
} else {
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
-
- boolean usesOldLogin = arbitraryDataProvider.getBooleanValue(currentAccount.name,
+ final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
+ final String accountName = optionalUser.isPresent() ? optionalUser.get().getAccountName() : "";
+ final boolean usesOldLogin = arbitraryDataProvider.getBooleanValue(accountName,
UserAccountManager.ACCOUNT_USES_STANDARD_PASSWORD);
if (usesOldLogin) {
snackbar = Snackbar.make(emptyContentContainer, R.string.push_notifications_old_login,
Snackbar.LENGTH_INDEFINITE);
} else {
- String pushValue = arbitraryDataProvider.getValue(currentAccount.name, PushUtils.KEY_PUSH);
+ String pushValue = arbitraryDataProvider.getValue(accountName, PushUtils.KEY_PUSH);
if (pushValue == null || pushValue.isEmpty()) {
snackbar = Snackbar.make(emptyContentContainer, R.string.push_notifications_temp_error,
@@ -250,13 +252,12 @@ private void populateList(List notifications) {
private void fetchAndSetData() {
Thread t = new Thread(() -> {
- if (client == null) {
+ if (client == null && optionalUser.isPresent()) {
try {
- OwnCloudAccount ocAccount = new OwnCloudAccount(currentAccount, this);
- client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this);
- client.setOwnCloudVersion(accountManager.getServerVersion(currentAccount));
- } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException |
- IOException | OperationCanceledException | AuthenticatorException e) {
+ User user = optionalUser.get();
+ client = clientFactory.create(user);
+ client.setOwnCloudVersion(user.getServer().getVersion());
+ } catch (ClientFactory.CreationException e) {
Log_OC.e(TAG, "Error initializing client", e);
}
}
@@ -303,7 +304,7 @@ private void hideRefreshLayoutLoader() {
}
public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.notifications_actions_menu, menu);
+ getMenuInflater().inflate(R.menu.activity_notifications, menu);
return true;
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
index fc89c91582e3..5a746157ee65 100755
--- a/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
@@ -208,8 +208,6 @@ protected void setAccount(Account account, boolean savedAccount) {
Log_OC.i(TAG, "No ownCloud account is available");
DialogNoAccount dialog = new DialogNoAccount();
dialog.show(getSupportFragmentManager(), null);
- } else if (!savedAccount) {
- setAccount(accounts[0]);
}
if (!somethingToUpload()) {
@@ -680,7 +678,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// there is no need for checking for is there more then one
// account at this point
// since account setup can set only one account at time
- setAccount(accounts[0]);
+ setAccount(accounts[0], false);
populateDirectoryList();
}
}
@@ -889,19 +887,19 @@ private boolean somethingToUpload() {
}
public void uploadFile(String tmpName, String filename) {
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- requester.uploadNewFile(
+ FileUploader.uploadNewFile(
getBaseContext(),
getAccount(),
- tmpName,
+ tmpName,
mFile.getRemotePath() + filename,
FileUploader.LOCAL_BEHAVIOUR_COPY,
null,
true,
UploadFileOperation.CREATED_BY_USER,
false,
- false
- );
+ false,
+ FileUploader.NameCollisionPolicy.ASK_USER
+ );
finish();
}
@@ -1018,7 +1016,7 @@ private boolean isHaveMultipleAccount() {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.receive_file_menu, menu);
+ inflater.inflate(R.menu.activity_receive_external_files, menu);
if (!isHaveMultipleAccount()) {
MenuItem switchAccountMenu = menu.findItem(R.id.action_switch_account);
diff --git a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
index 9f0188569b3c..e72182bcd93b 100644
--- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
+++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
@@ -49,6 +49,7 @@
import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.CurrentAccountProvider;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
@@ -155,7 +156,8 @@ protected void onCreate(Bundle savedInstanceState) {
break;
}
- Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider)).load(template.getThumbnailLink())
+ Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
+ .load(template.getThumbnailLink())
.placeholder(placeholder)
.error(placeholder)
.into(thumbnail);
@@ -314,9 +316,9 @@ private void handleRemoteFile(Intent data) {
OCFile file = data.getParcelableExtra(FolderPickerActivity.EXTRA_FILES);
new Thread(() -> {
- Account account = currentAccountProvider.getCurrentAccount();
+ User user = currentAccountProvider.getUser();
RichDocumentsCreateAssetOperation operation = new RichDocumentsCreateAssetOperation(file.getRemotePath());
- RemoteOperationResult result = operation.execute(account, this);
+ RemoteOperationResult result = operation.execute(user.toPlatformAccount(), this);
if (result.isSuccess()) {
String asset = (String) result.getSingleData();
diff --git a/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
index ae8474d5eb39..03563a730460 100644
--- a/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
@@ -25,7 +25,6 @@
*/
package com.owncloud.android.ui.activity;
-import android.accounts.Account;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -52,10 +51,12 @@
import android.view.Window;
import android.webkit.URLUtil;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.etm.EtmActivity;
import com.nextcloud.client.logger.ui.LogsActivity;
+import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.BuildConfig;
@@ -69,8 +70,6 @@
import com.owncloud.android.datastorage.StoragePoint;
import com.owncloud.android.lib.common.ExternalLink;
import com.owncloud.android.lib.common.ExternalLinkType;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.utils.DeviceCredentialUtils;
@@ -130,10 +129,11 @@ public class SettingsActivity extends ThemedPreferenceActivity
private String storagePath;
private String pendingLock;
- private Account account;
+ private User user;
@Inject ArbitraryDataProvider arbitraryDataProvider;
@Inject AppPreferences preferences;
@Inject UserAccountManager accountManager;
+ @Inject ClientFactory clientFactory;
@SuppressWarnings("deprecation")
@Override
@@ -156,7 +156,7 @@ public void onCreate(Bundle savedInstanceState) {
String appVersion = getAppVersion();
PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen");
- account = accountManager.getCurrentAccount();
+ user = accountManager.getUser();
// retrieve user's base uri
setupBaseUri();
@@ -408,7 +408,7 @@ private void setupRecommendPreference(PreferenceCategory preferenceCategoryMore)
}
private void setupE2EMnemonicPreference(PreferenceCategory preferenceCategoryMore) {
- String mnemonic = arbitraryDataProvider.getValue(account.name, EncryptionUtils.MNEMONIC);
+ String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
Preference pMnemonic = findPreference("mnemonic");
if (pMnemonic != null) {
@@ -603,11 +603,11 @@ private void setupAutoUploadCategory(int accentColor, PreferenceScreen preferenc
final SwitchPreference pUploadOnWifiCheckbox = (SwitchPreference) findPreference("synced_folder_on_wifi");
pUploadOnWifiCheckbox.setChecked(
- arbitraryDataProvider.getBooleanValue(account, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI));
+ arbitraryDataProvider.getBooleanValue(user.toPlatformAccount(), SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI));
pUploadOnWifiCheckbox.setOnPreferenceClickListener(preference -> {
- arbitraryDataProvider.storeOrUpdateKeyValue(account.name, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI,
- String.valueOf(pUploadOnWifiCheckbox.isChecked()));
+ arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI,
+ String.valueOf(pUploadOnWifiCheckbox.isChecked()));
return true;
});
@@ -692,13 +692,13 @@ private void setupGeneralCategory(int accentColor) {
loadStoragePath();
- SwitchPreference themePref = (SwitchPreference) findPreference(AppPreferencesImpl.PREF__THEME);
-
- themePref.setSummary(preferences.isDarkThemeEnabled() ?
- getString(R.string.prefs_value_theme_dark) : getString(R.string.prefs_value_theme_light));
+ SwitchPreference themePref = (SwitchPreference) findPreference("dark_theme_enabled");
+ boolean darkThemeEnabled = preferences.isDarkThemeEnabled();
+ int summaryResId = darkThemeEnabled ? R.string.prefs_value_theme_dark : R.string.prefs_value_theme_light;
+ themePref.setSummary(summaryResId);
themePref.setOnPreferenceChangeListener((preference, newValue) -> {
- MainApp.setAppTheme((Boolean) newValue);
-
+ boolean enabled = (Boolean)newValue;
+ MainApp.setAppTheme(enabled);
return true;
});
}
@@ -764,7 +764,7 @@ private void launchDavDroidLogin() {
davDroidLoginIntent.setData(Uri.parse(serverBaseUri.toString() + AuthenticatorActivity.WEB_LOGIN));
davDroidLoginIntent.putExtra("davPath", DAV_PATH);
}
- davDroidLoginIntent.putExtra("username", UserAccountManager.getUsername(account));
+ davDroidLoginIntent.putExtra("username", UserAccountManager.getUsername(user.toPlatformAccount()));
startActivityForResult(davDroidLoginIntent, ACTION_REQUEST_CODE_DAVDROID_SETUP);
} else {
@@ -789,9 +789,7 @@ private void setupBaseUri() {
// retrieve and set user's base URI
Thread t = new Thread(() -> {
try {
- OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext());
- serverBaseUri = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount,
- getApplicationContext()).getBaseUri();
+ serverBaseUri = clientFactory.create(user).getBaseUri();
} catch (Exception e) {
Log_OC.e(TAG, "Error retrieving user's base URI", e);
}
@@ -857,7 +855,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
- String mnemonic = arbitraryDataProvider.getValue(account.name, EncryptionUtils.MNEMONIC);
+ String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
int accentColor = ThemeUtils.primaryAccentColor(this);
diff --git a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
index 78ae76286888..62e3936fe7d6 100644
--- a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
@@ -21,7 +21,6 @@
package com.owncloud.android.ui.activity;
-import android.accounts.Account;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationManager;
@@ -38,12 +37,18 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
import android.widget.TextView;
+import com.google.android.material.button.MaterialButton;
+import com.nextcloud.client.account.User;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.BuildConfig;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
@@ -86,6 +91,9 @@
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
@@ -101,18 +109,37 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
private static final String TAG = SyncedFoldersActivity.class.getSimpleName();
- private RecyclerView mRecyclerView;
- private SyncedFolderAdapter mAdapter;
- private LinearLayout mProgress;
- private TextView mEmpty;
- private SyncedFolderProvider mSyncedFolderProvider;
- private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
+ @BindView(R.id.empty_list_view)
+ public LinearLayout emptyContentContainer;
+
+ @BindView(R.id.empty_list_icon)
+ public ImageView emptyContentIcon;
+
+ @BindView(R.id.empty_list_progress)
+ public ProgressBar emptyContentProgressBar;
+
+ @BindView(R.id.empty_list_view_headline)
+ public TextView emptyContentHeadline;
+
+ @BindView(R.id.empty_list_view_text)
+ public TextView emptyContentMessage;
+
+ @BindView(R.id.empty_list_view_action)
+ public MaterialButton emptyContentActionButton;
+
+ @BindView(android.R.id.list)
+ public RecyclerView mRecyclerView;
+
+ private SyncedFolderAdapter adapter;
+ private SyncedFolderProvider syncedFolderProvider;
+ private SyncedFolderPreferencesDialogFragment syncedFolderPreferencesDialogFragment;
private boolean showSidebar = true;
private String path;
private int type;
@Inject AppPreferences preferences;
@Inject PowerManagementService powerManagementService;
+ @Inject Clock clock;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -123,16 +150,17 @@ protected void onCreate(Bundle savedInstanceState) {
}
setContentView(R.layout.synced_folders_layout);
+ ButterKnife.bind(this);
- String account;
- Account currentAccount;
if (getIntent() != null && getIntent().getExtras() != null) {
- account = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT);
- currentAccount = getAccount();
-
- if (account != null && currentAccount != null && !account.equalsIgnoreCase(currentAccount.name)) {
- accountManager.setCurrentOwnCloudAccount(account);
- setAccount(getUserAccountManager().getCurrentAccount());
+ final String accountName = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT);
+ Optional optionalUser = getUser();
+ if (optionalUser.isPresent() && accountName != null) {
+ User user = optionalUser.get();
+ if (!accountName.equalsIgnoreCase(user.getAccountName())) {
+ accountManager.setCurrentOwnCloudAccount(accountName);
+ setUser(getUserAccountManager().getUser());
+ }
}
path = getIntent().getStringExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_PATH);
@@ -175,7 +203,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.synced_folders_menu, menu);
+ inflater.inflate(R.menu.activity_synced_folders, menu);
if (powerManagementService.isPowerSavingExclusionAvailable()) {
MenuItem item = menu.findItem(R.id.action_disable_power_save_check);
@@ -216,50 +244,56 @@ private void showPowerCheckDialog() {
* sets up the UI elements and loads all media/synced folders.
*/
private void setupContent() {
- mRecyclerView = findViewById(android.R.id.list);
-
- mProgress = findViewById(android.R.id.progress);
- mEmpty = findViewById(android.R.id.empty);
-
final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
- mAdapter = new SyncedFolderAdapter(this, gridWidth, this, lightVersion);
- mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences);
+ adapter = new SyncedFolderAdapter(this, clock, gridWidth, this, lightVersion);
+ syncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences, clock);
+ emptyContentIcon.setImageResource(R.drawable.nav_synced_folders);
+ emptyContentActionButton.setBackgroundColor(ThemeUtils.primaryColor(this));
+ emptyContentActionButton.setTextColor(ThemeUtils.fontColor(this));
final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
- mAdapter.setLayoutManager(lm);
+ adapter.setLayoutManager(lm);
int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing));
mRecyclerView.setLayoutManager(lm);
- mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setAdapter(adapter);
load(gridWidth * 2, false);
}
+ @OnClick(R.id.empty_list_view_action)
+ public void showHiddenItems() {
+ if (adapter.getSectionCount() == 0 && adapter.getUnfilteredSectionCount() > adapter.getSectionCount()) {
+ adapter.toggleHiddenItemsVisibility();
+ emptyContentContainer.setVisibility(View.GONE);
+ mRecyclerView.setVisibility(View.VISIBLE);
+ }
+ }
+
/**
* loads all media/synced folders, adds them to the recycler view adapter and shows the list.
*
* @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder
*/
private void load(final int perFolderMediaItemLimit, boolean force) {
- if (mAdapter.getItemCount() > 0 && !force) {
+ if (adapter.getItemCount() > 0 && !force) {
return;
}
- setListShown(false);
+ showLoadingContent();
final List mediaFolders = MediaProvider.getImageFolders(getContentResolver(),
perFolderMediaItemLimit, this, false);
mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit,
this, false));
- List syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
+ List syncedFolderArrayList = syncedFolderProvider.getSyncedFolders();
List currentAccountSyncedFoldersList = new ArrayList<>();
- Account currentAccount = getUserAccountManager().getCurrentAccount();
+ User user = getUserAccountManager().getUser();
for (SyncedFolder syncedFolder : syncedFolderArrayList) {
- if (currentAccount != null && syncedFolder.getAccount().equals(currentAccount.name)) {
-
+ if (syncedFolder.getAccount().equals(user.getAccountName())) {
// delete non-existing & disabled synced folders
if (!new File(syncedFolder.getLocalPath()).exists() && !syncedFolder.isEnabled()) {
- mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
+ syncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
} else {
currentAccountSyncedFoldersList.add(syncedFolder);
}
@@ -269,14 +303,14 @@ private void load(final int perFolderMediaItemLimit, boolean force) {
List syncFolderItems = sortSyncedFolderItems(
mergeFolderData(currentAccountSyncedFoldersList, mediaFolders));
- mAdapter.setSyncFolderItems(syncFolderItems);
- mAdapter.notifyDataSetChanged();
- setListShown(true);
+ adapter.setSyncFolderItems(syncFolderItems);
+ adapter.notifyDataSetChanged();
+ showList();
if (!TextUtils.isEmpty(path)) {
- int section = mAdapter.getSectionByLocalPathAndType(path, type);
+ int section = adapter.getSectionByLocalPathAndType(path, type);
if (section >= 0) {
- onSyncFolderSettingsClick(section, mAdapter.get(section));
+ onSyncFolderSettingsClick(section, adapter.get(section));
}
}
}
@@ -380,16 +414,19 @@ private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull Sy
syncedFolder.getId(),
syncedFolder.getLocalPath(),
syncedFolder.getRemotePath(),
- syncedFolder.getWifiOnly(),
- syncedFolder.getChargingOnly(),
- syncedFolder.getSubfolderByDate(),
+ syncedFolder.isWifiOnly(),
+ syncedFolder.isChargingOnly(),
+ syncedFolder.isExisting(),
+ syncedFolder.isSubfolderByDate(),
syncedFolder.getAccount(),
syncedFolder.getUploadAction(),
syncedFolder.isEnabled(),
+ clock.getCurrentTime(),
filePaths,
localFolder.getName(),
files.length,
- syncedFolder.getType());
+ syncedFolder.getType(),
+ syncedFolder.isHidden());
}
/**
@@ -405,16 +442,19 @@ private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedF
syncedFolder.getId(),
syncedFolder.getLocalPath(),
syncedFolder.getRemotePath(),
- syncedFolder.getWifiOnly(),
- syncedFolder.getChargingOnly(),
- syncedFolder.getSubfolderByDate(),
+ syncedFolder.isWifiOnly(),
+ syncedFolder.isChargingOnly(),
+ syncedFolder.isExisting(),
+ syncedFolder.isSubfolderByDate(),
syncedFolder.getAccount(),
syncedFolder.getUploadAction(),
syncedFolder.isEnabled(),
+ clock.getCurrentTime(),
mediaFolder.filePaths,
mediaFolder.folderName,
mediaFolder.numberOfFiles,
- mediaFolder.type);
+ mediaFolder.type,
+ syncedFolder.isHidden());
}
/**
@@ -431,14 +471,17 @@ private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull Media
getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName,
true,
false,
+ true,
false,
- getAccount().name,
+ getAccount().name,
FileUploader.LOCAL_BEHAVIOUR_FORGET,
false,
+ clock.getCurrentTime(),
mediaFolder.filePaths,
mediaFolder.folderName,
mediaFolder.numberOfFiles,
- mediaFolder.type);
+ mediaFolder.type,
+ false);
}
private File[] getFileList(File localFolder) {
@@ -484,15 +527,35 @@ private Map createSyncedFoldersMap(List sync
}
/**
- * show/hide recycler view list or the empty message / progress info.
- *
- * @param shown flag if list should be shown
+ * show recycler view list or the empty message info (in case list is empty).
*/
- private void setListShown(boolean shown) {
+ private void showList() {
if (mRecyclerView != null) {
- mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE);
- mProgress.setVisibility(shown ? View.GONE : View.VISIBLE);
- mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
+ mRecyclerView.setVisibility(View.VISIBLE);
+ emptyContentProgressBar.setVisibility(View.GONE);
+
+ checkAndShowEmptyListContent();
+ }
+ }
+
+ private void checkAndShowEmptyListContent() {
+ if (adapter.getSectionCount() == 0 && adapter.getUnfilteredSectionCount() > adapter.getSectionCount()) {
+ emptyContentContainer.setVisibility(View.VISIBLE);
+ int hiddenFoldersCount = adapter.getHiddenFolderCount();
+
+ showEmptyContent(getString(R.string.drawer_synced_folders),
+ getResources().getQuantityString(R.plurals.synced_folders_show_hidden_folders,
+ hiddenFoldersCount,
+ hiddenFoldersCount),
+ getResources().getQuantityString(R.plurals.synced_folders_show_hidden_folders,
+ hiddenFoldersCount,
+ hiddenFoldersCount));
+ } else if (adapter.getSectionCount() == 0 && adapter.getUnfilteredSectionCount() == 0) {
+ emptyContentContainer.setVisibility(View.VISIBLE);
+ showEmptyContent(getString(R.string.drawer_synced_folders),
+ getString(R.string.synced_folders_no_results));
+ } else {
+ emptyContentContainer.setVisibility(View.GONE);
}
}
@@ -517,9 +580,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.action_create_custom_folder: {
Log.d(TAG, "Show custom folder dialog");
SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem(
- SyncedFolder.UNPERSISTED_ID, null, null, true, false,
- false, getAccount().name,
- FileUploader.LOCAL_BEHAVIOUR_FORGET, false, null, MediaFolderType.CUSTOM);
+ SyncedFolder.UNPERSISTED_ID, null, null, true, false, true,
+ false, getAccount().name, FileUploader.LOCAL_BEHAVIOUR_FORGET, false,
+ clock.getCurrentTime(), null, MediaFolderType.CUSTOM, false);
onSyncFolderSettingsClick(0, emptyCustomFolder);
}
@@ -548,26 +611,20 @@ public void showFiles(boolean onDeviceOnly) {
@Override
public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
- getContentResolver());
-
if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
- mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(),
- syncedFolderDisplayItem.isEnabled());
+ syncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(),
+ syncedFolderDisplayItem.isEnabled());
} else {
- long storedId = mSyncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem);
+ long storedId = syncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem);
if (storedId != -1) {
syncedFolderDisplayItem.setId(storedId);
}
}
if (syncedFolderDisplayItem.isEnabled()) {
- FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem);
+ FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem, true);
showBatteryOptimizationInfo();
- } else {
- String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId();
- arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
}
}
@@ -577,22 +634,72 @@ public void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem synce
FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
- mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(
+ syncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(
syncedFolderDisplayItem, section);
- mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG);
+ syncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG);
+ }
+
+ @Override
+ public void onVisibilityToggleClick(int section, SyncedFolderDisplayItem syncedFolder) {
+ syncedFolder.setHidden(!syncedFolder.isHidden());
+
+ saveOrUpdateSyncedFolder(syncedFolder);
+ adapter.setSyncFolderItem(section, syncedFolder);
+
+ checkAndShowEmptyListContent();
+ }
+
+ private void showEmptyContent(String headline, String message) {
+ showEmptyContent(headline, message, false);
+ emptyContentActionButton.setVisibility(View.GONE);
+ }
+
+ private void showEmptyContent(String headline, String message, String action) {
+ showEmptyContent(headline, message, false);
+ emptyContentActionButton.setText(action);
+ emptyContentActionButton.setVisibility(View.VISIBLE);
+ emptyContentMessage.setVisibility(View.GONE);
+ }
+
+ private void showLoadingContent() {
+ showEmptyContent(
+ getString(R.string.drawer_synced_folders),
+ getString(R.string.synced_folders_loading_folders),
+ true
+ );
+ emptyContentActionButton.setVisibility(View.GONE);
+ }
+
+ private void showEmptyContent(String headline, String message, boolean loading) {
+ if (emptyContentContainer != null) {
+ emptyContentContainer.setVisibility(View.VISIBLE);
+ mRecyclerView.setVisibility(View.GONE);
+
+ emptyContentHeadline.setText(headline);
+ emptyContentMessage.setText(message);
+ emptyContentMessage.setVisibility(View.VISIBLE);
+
+ if (loading) {
+ emptyContentProgressBar.setVisibility(View.VISIBLE);
+ emptyContentIcon.setVisibility(View.GONE);
+ } else {
+ emptyContentProgressBar.setVisibility(View.GONE);
+ emptyContentIcon.setVisibility(View.VISIBLE);
+ }
+ }
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER
- && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
+ && resultCode == RESULT_OK && syncedFolderPreferencesDialogFragment != null) {
OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
- mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
+ syncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
}
if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_LOCAL_FOLDER
- && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
+ && resultCode == RESULT_OK && syncedFolderPreferencesDialogFragment != null) {
String localPath = data.getStringExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
- mSyncedFolderPreferencesDialogFragment.setLocalFolderSummary(localPath);
+ syncedFolderPreferencesDialogFragment.setLocalFolderSummary(localPath);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
@@ -600,76 +707,82 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
@Override
public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
- getContentResolver());
-
// custom folders newly created aren't in the list already,
// so triggering a refresh
if (MediaFolderType.CUSTOM == syncedFolder.getType() && syncedFolder.getId() == UNPERSISTED_ID) {
SyncedFolderDisplayItem newCustomFolder = new SyncedFolderDisplayItem(
SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(),
- syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(),
- syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(),
- new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType());
- long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder);
- if (storedId != -1) {
- newCustomFolder.setId(storedId);
- if (newCustomFolder.isEnabled()) {
- FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder);
- } else {
- String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId();
- arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
- }
- }
- mAdapter.addSyncFolderItem(newCustomFolder);
+ syncedFolder.isWifiOnly(), syncedFolder.isChargingOnly(),
+ syncedFolder.isExisting(), syncedFolder.isSubfolderByDate(), syncedFolder.getAccount(),
+ syncedFolder.getUploadAction(), syncedFolder.isEnabled(), clock.getCurrentTime(),
+ new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType(), syncedFolder.isHidden());
+
+ saveOrUpdateSyncedFolder(newCustomFolder);
+ adapter.addSyncFolderItem(newCustomFolder);
} else {
- SyncedFolderDisplayItem item = mAdapter.get(syncedFolder.getSection());
- item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder
- .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder
- .getUploadAction(), syncedFolder.getEnabled());
-
- if (syncedFolder.getId() == UNPERSISTED_ID) {
- // newly set up folder sync config
- long storedId = mSyncedFolderProvider.storeSyncedFolder(item);
- if (storedId != -1) {
- item.setId(storedId);
- if (item.isEnabled()) {
- FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
- } else {
- String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
- arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
- }
- }
- } else {
- // existing synced folder setup to be updated
- mSyncedFolderProvider.updateSyncFolder(item);
- if (item.isEnabled()) {
- FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
- } else {
- String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
- arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
- }
- }
+ SyncedFolderDisplayItem item = adapter.get(syncedFolder.getSection());
+ updateSyncedFolderItem(item, syncedFolder.getId(), syncedFolder.getLocalPath(),
+ syncedFolder.getRemotePath(), syncedFolder.isWifiOnly(),
+ syncedFolder.isChargingOnly(), syncedFolder.isExisting(),
+ syncedFolder.isSubfolderByDate(), syncedFolder.getUploadAction(),
+ syncedFolder.isEnabled());
- mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
+ saveOrUpdateSyncedFolder(item);
+
+ // TODO test if notifiyItemChanged is suffiecient (should improve performance)
+ adapter.notifyDataSetChanged();
}
- mSyncedFolderPreferencesDialogFragment = null;
+ syncedFolderPreferencesDialogFragment = null;
- if (syncedFolder.getEnabled()) {
+ if (syncedFolder.isEnabled()) {
showBatteryOptimizationInfo();
}
}
+ private void saveOrUpdateSyncedFolder(SyncedFolderDisplayItem item) {
+ if (item.getId() == UNPERSISTED_ID) {
+ // newly set up folder sync config
+ storeSyncedFolder(item);
+ } else {
+ // existing synced folder setup to be updated
+ syncedFolderProvider.updateSyncFolder(item);
+ if (item.isEnabled()) {
+ FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+ } else {
+ String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
+
+ ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
+ getContentResolver());
+ arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+ }
+ }
+ }
+
+ private void storeSyncedFolder(SyncedFolderDisplayItem item) {
+ ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
+ getContentResolver());
+ long storedId = syncedFolderProvider.storeSyncedFolder(item);
+ if (storedId != -1) {
+ item.setId(storedId);
+ if (item.isEnabled()) {
+ FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+ } else {
+ String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
+ arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+ }
+ }
+ }
+
@Override
public void onCancelSyncedFolderPreference() {
- mSyncedFolderPreferencesDialogFragment = null;
+ syncedFolderPreferencesDialogFragment = null;
}
@Override
public void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
- mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
- mAdapter.removeItem(syncedFolder.getSection());
+ syncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
+ adapter.removeItem(syncedFolder.getSection());
}
/**
@@ -680,27 +793,30 @@ public void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder)
* @param remotePath the remote path
* @param wifiOnly upload on wifi only
* @param chargingOnly upload on charging only
+ * @param existing also upload existing
* @param subfolderByDate created sub folders
* @param uploadAction upload action
* @param enabled is sync enabled
- * @return the updated item
*/
- private SyncedFolderDisplayItem updateSyncedFolderItem(SyncedFolderDisplayItem item,
+ private void updateSyncedFolderItem(SyncedFolderDisplayItem item,
+ long id,
String localPath,
String remotePath,
- Boolean wifiOnly,
- Boolean chargingOnly,
- Boolean subfolderByDate,
+ boolean wifiOnly,
+ boolean chargingOnly,
+ boolean existing,
+ boolean subfolderByDate,
Integer uploadAction,
- Boolean enabled) {
+ boolean enabled) {
+ item.setId(id);
item.setLocalPath(localPath);
item.setRemotePath(remotePath);
item.setWifiOnly(wifiOnly);
item.setChargingOnly(chargingOnly);
+ item.setExisting(existing);
item.setSubfolderByDate(subfolderByDate);
item.setUploadAction(uploadAction);
- item.setEnabled(enabled);
- return item;
+ item.setEnabled(enabled, clock.getCurrentTime());
}
@Override
@@ -727,7 +843,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String permissi
@Override
protected void onResume() {
super.onResume();
-
setDrawerMenuItemChecked(R.id.nav_synced_folders);
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java b/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java
index 1acc839a4f25..058a1bed8a4d 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java
@@ -20,17 +20,16 @@
package com.owncloud.android.ui.activity;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
+import com.nextcloud.client.preferences.AppPreferences;
+
import javax.inject.Inject;
import androidx.annotation.Nullable;
-public class ThemedPreferenceActivity
- extends PreferenceActivity
- implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class ThemedPreferenceActivity extends PreferenceActivity {
/**
* Tracks whether the activity should be recreate()'d after a theme change
@@ -38,18 +37,29 @@ public class ThemedPreferenceActivity
private boolean themeChangePending;
private boolean paused;
- @Inject SharedPreferences sharedPreferences;
+ @Inject AppPreferences preferences;
+
+ private AppPreferences.Listener onThemeChangedListener = new AppPreferences.Listener() {
+ @Override
+ public void onDarkThemeEnabledChanged(boolean enabled) {
+ if(paused) {
+ themeChangePending = true;
+ return;
+ }
+ recreate();
+ }
+ };
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
- sharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ preferences.addListener(onThemeChangedListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
- sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ preferences.removeListener(onThemeChangedListener);
}
@Override
@@ -67,14 +77,4 @@ protected void onResume() {
recreate();
}
}
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
- if(paused) {
- themeChangePending = true;
- return;
- }
-
- recreate();
- }
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
index c65f12731c74..4a46e4e5c7ca 100644
--- a/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
@@ -254,7 +254,7 @@ public static void startUploadActivityForResult(Activity activity, Account accou
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mOptionsMenu = menu;
- getMenuInflater().inflate(R.menu.upload_files_picker, menu);
+ getMenuInflater().inflate(R.menu.activity_upload_files, menu);
if(!mLocalFolderPickerMode) {
MenuItem selectAll = menu.findItem(R.id.action_select_all);
diff --git a/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
index 5bb867f9b867..492b89c9f9b2 100755
--- a/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
@@ -43,9 +43,12 @@
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.core.Clock;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.java.util.Optional;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.files.services.FileUploader;
@@ -119,6 +122,9 @@ public class UploadListActivity extends FileActivity {
@Inject
PowerManagementService powerManagementService;
+ @Inject
+ Clock clock;
+
@Override
public void showFiles(boolean onDeviceOnly) {
super.showFiles(onDeviceOnly);
@@ -167,9 +173,11 @@ private void setupContent() {
uploadListAdapter = new UploadListAdapter(this,
uploadsStorageManager,
+ getStorageManager(),
userAccountManager,
connectivityService,
- powerManagementService);
+ powerManagementService,
+ clock);
final GridLayoutManager lm = new GridLayoutManager(this, 1);
uploadListAdapter.setLayoutManager(lm);
@@ -212,14 +220,15 @@ private void refresh() {
}
// retry failed uploads
- FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
- new Thread(() -> requester.retryFailedUploads(this,
- null,
- uploadsStorageManager,
- connectivityService,
- userAccountManager,
- powerManagementService,
- null)).start();
+ new Thread(() -> FileUploader.retryFailedUploads(
+ this,
+ null,
+ uploadsStorageManager,
+ connectivityService,
+ userAccountManager,
+ powerManagementService,
+ null
+ )).start();
// update UI
uploadListAdapter.loadUploadItemsFromDb();
@@ -230,9 +239,9 @@ private void refresh() {
protected void onStart() {
super.onStart();
ThemeUtils.setColoredTitle(getSupportActionBar(), R.string.uploads_view_title, this);
- final Account account = getAccount();
- if (account != null) {
- setAccountInDrawer(account);
+ final Optional optionalUser = getUser();
+ if (optionalUser.isPresent()) {
+ setAccountInDrawer(optionalUser.get());
}
}
@@ -269,7 +278,7 @@ protected void onPause() {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.upload_list_menu, menu);
+ inflater.inflate(R.menu.activity_upload_list, menu);
return true;
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
index 61523ddb9378..db5e18e2e782 100644
--- a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
@@ -135,9 +135,6 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.user_info_layout);
unbinder = ButterKnife.bind(this);
- setAccount(getUserAccountManager().getCurrentAccount());
- onAccountSet();
-
boolean useBackgroundImage = URLUtil.isValidUrl(
getStorageManager().getCapability(account.name).getServerBackground());
@@ -159,7 +156,7 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.user_info_menu, menu);
+ inflater.inflate(R.menu.activity_user_info, menu);
return true;
}
diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
index 5a5275ced0ed..018dd33fb24d 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
@@ -51,6 +51,7 @@
import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
import com.caverock.androidsvg.SVG;
import com.nextcloud.client.account.CurrentAccountProvider;
+import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -94,6 +95,7 @@ public class ActivityListAdapter extends RecyclerView.Adapter values;
@@ -246,8 +248,11 @@ private ImageView createThumbnailNew(PreviewObject previewObject) {
if (MimeTypeUtil.isImageOrVideo(previewObject.getMimeType())) {
int placeholder = R.drawable.file;
- Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider)).load(previewObject.getSource()).
- placeholder(placeholder).error(placeholder).into(imageView);
+ Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
+ .load(previewObject.getSource())
+ .placeholder(placeholder)
+ .error(placeholder)
+ .into(imageView);
} else {
if (MimeTypeUtil.isFolder(previewObject.getMimeType())) {
imageView.setImageDrawable(
@@ -297,8 +302,10 @@ private void setBitmap(OCFile file, ImageView fileIcon, boolean isDetailView) {
String uri = client.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" + px + "/" + px +
Uri.encode(file.getRemotePath(), "/");
- Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider)).load(uri).placeholder(placeholder)
- .error(placeholder).into(fileIcon); // using custom fetcher
+ Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
+ .load(uri).placeholder(placeholder)
+ .error(placeholder)
+ .into(fileIcon); // using custom fetcher
} else {
if (isDetailView) {
diff --git a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
index 597c1e66016a..ec947a8fb25b 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
@@ -47,6 +47,7 @@
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
+import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
@@ -117,7 +118,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter checkedFiles;
private FileDataStorageManager mStorageManager;
- private Account account;
+ private User user;
private OCFileListFragmentInterface ocFileListFragmentInterface;
private FilesFilter mFilesFilter;
@@ -135,7 +136,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter();
this.transferServiceGetter = transferServiceGetter;
- if (account != null) {
+ if (this.user != null) {
AccountManager platformAccountManager = AccountManager.get(mContext);
- userId = platformAccountManager.getUserData(account,
+ userId = platformAccountManager.getUserData(this.user.toPlatformAccount(),
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
} else {
userId = "";
@@ -399,7 +400,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
showFederatedShareAvatar(sharee.getUserId(), avatarRadius, resources, avatar);
} else {
avatar.setTag(sharee);
- DisplayUtils.setAvatar(account,
+ DisplayUtils.setAvatar(user.toPlatformAccount(),
sharee.getUserId(),
sharee.getDisplayName(),
this,
@@ -460,17 +461,17 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
OperationsService.OperationsServiceBinder operationsServiceBinder = transferServiceGetter.getOperationsServiceBinder();
FileDownloader.FileDownloaderBinder fileDownloaderBinder = transferServiceGetter.getFileDownloaderBinder();
FileUploader.FileUploaderBinder fileUploaderBinder = transferServiceGetter.getFileUploaderBinder();
- if (operationsServiceBinder != null && operationsServiceBinder.isSynchronizing(account, file)) {
+ if (operationsServiceBinder != null && operationsServiceBinder.isSynchronizing(user.toPlatformAccount(), file)) {
//synchronizing
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synchronizing);
gridViewHolder.localFileIndicator.setVisibility(View.VISIBLE);
- } else if (fileDownloaderBinder != null && fileDownloaderBinder.isDownloading(account, file)) {
+ } else if (fileDownloaderBinder != null && fileDownloaderBinder.isDownloading(user.toPlatformAccount(), file)) {
// downloading
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synchronizing);
gridViewHolder.localFileIndicator.setVisibility(View.VISIBLE);
- } else if (fileUploaderBinder != null && fileUploaderBinder.isUploading(account, file)) {
+ } else if (fileUploaderBinder != null && fileUploaderBinder.isUploading(user.toPlatformAccount(), file)) {
//uploading
gridViewHolder.localFileIndicator.setImageResource(R.drawable.ic_synchronizing);
gridViewHolder.localFileIndicator.setVisibility(View.VISIBLE);
@@ -575,13 +576,17 @@ private void setThumbnail(OCFile file, ImageView thumbnailView) {
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
try {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
- new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView, mStorageManager,
- account, asyncTasks);
+ new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView,
+ mStorageManager,
+ user.toPlatformAccount(),
+ asyncTasks);
if (thumbnail == null) {
thumbnail = BitmapUtils.drawableToBitmap(
- MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(),
- account, mContext));
+ MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
+ file.getFileName(),
+ user.toPlatformAccount(),
+ mContext));
}
final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
new ThumbnailsCacheManager.AsyncThumbnailDrawable(mContext.getResources(),
@@ -602,7 +607,7 @@ private void setThumbnail(OCFile file, ImageView thumbnailView) {
} else {
thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
file.getFileName(),
- account,
+ user.toPlatformAccount(),
mContext));
}
}
@@ -685,7 +690,7 @@ private void showShareIcon(OCFileListGridImageViewHolder gridViewHolder, OCFile
sharedIconView.setImageResource(R.drawable.ic_unshared);
sharedIconView.setContentDescription(mContext.getString(R.string.shared_icon_share));
}
- if (accountManager.accountOwnsFile(file, account)) {
+ if (accountManager.accountOwnsFile(file, user.toPlatformAccount())) {
sharedIconView.setOnClickListener(view -> ocFileListFragmentInterface.onShareIconClick(file));
} else {
sharedIconView.setOnClickListener(view -> ocFileListFragmentInterface.showShareDetailView(file));
@@ -703,7 +708,7 @@ private void showShareIcon(OCFileListGridImageViewHolder gridViewHolder, OCFile
* @param limitToMimeType show only files of this mimeType
*/
public void swapDirectory(
- Account account,
+ User account,
OCFile directory,
FileDataStorageManager updatedStorageManager,
boolean onlyOnDevice, String limitToMimeType
@@ -712,8 +717,8 @@ public void swapDirectory(
if (updatedStorageManager != null && !updatedStorageManager.equals(mStorageManager)) {
mStorageManager = updatedStorageManager;
- showShareAvatar = mStorageManager.getCapability(account.name).getVersion().isShareesOnDavSupported();
- this.account = account;
+ showShareAvatar = mStorageManager.getCapability(account.getAccountName()).getVersion().isShareesOnDavSupported();
+ this.user = account;
}
if (mStorageManager != null) {
mFiles = mStorageManager.getFolderContent(directory, onlyOnDevice);
@@ -744,11 +749,11 @@ public void setData(ListLe fichier média ne peut pas être diffuséImpossible de lire le fichier médiaLe fichier média n\'est pas correctement encodé
- Le fichier n\'est pas dans un compte valide
- Aucun fichier média trouvé
- Erreur inattendue lors de la lecture de %1$s Délai dépassé pour la lecture du morceauLe fichier média ne peut être lu avec le lecteur intégréLe codec de ce média n\'est pas pris en charge
- Lecture de %1$s terminéeBouton d\'avance rapideLecteur de musique %1$sBouton de lecture ou de pauseBouton de retour arrière
- %1$s (chargement)%1$s (lecture)Date : Récent en premierDate : Ancien en premier
@@ -451,25 +444,6 @@ Attention la suppression est irréversible.L\'opération a été annuléeLe serveur est obsolète, veuillez mettre à jour!Plus de menu
- Testez la version Bêta
- La version Beta inclut les dernières fonctionnalités qui sont encore toutes fraîches. Des erreurs peuvent se produire et si c\'est le cas, merci de nous les signaler.
- forum
- Aidez les
- Vérifier, corriger et écrire du code, voir %1$s pour plus de détails
- Participez activement
- Rejoignez la discussion sur IRC :
- l\'application
- Traduire
- Obtenir la version de développement par téléchargement direct
- Obtenez la version de développement depuis le F-Droid
- Obtenez la version Release Candidate depuis le F-Droid
- Obtenez la version Release Candidate depuis le Play store Google
- Testez la version Release Candidate
- La version « release candidate » (RC) est un instantanné de la prochaine version et est supposée stable. Le test de votre configuration pourrait nous aider à nous assurer que cette version est entièrement stable. Inscrivez vous pour être testeur sur le Play store ou allez jeter un coup d\'œil dans la section \"versions\" de F-Droid.
- Vous avez trouvé un bug ? Quelque chose vous semble étrange ?
- Aidez-nous à améliorer Nextcloud
- Signaler un problème sur Github
- Êtes-vous intéressé pour nous aider en testant la prochaine version ?Saisissez votre code de sécuritéLe code de sécurité sera demandé à chaque ouverture de l\'applicationVeuillez saisir votre code de sécurité
@@ -508,7 +482,6 @@ Attention la suppression est irréversible.Afin d\'afficher le mnémonique, merci d\'activer les informations d\'identification de l\'appareil.Afficher les notifications d\'analyse multimédiaNotifier les nouveaux dossiers multimédias trouvés
- Nous contacter par mailLicence Publique Générale GNU, version 2AideMentions
@@ -565,17 +538,16 @@ Attention la suppression est irréversible.Mot de passe incorrectConnexion par code QRProtection de vos données
- plateforme de productivité auto-hébergéeParcourir et partagertoutes les actions à portée de main
- Activité, partages, fichiers hors-ligne
+ Activité, partages, …tout accessible rapidementTous vos comptesen un seul endroitTéléversement automatiquepour vos photos & vidéos
- Synchronisez votre calendrier & vos contacts
- avec DAVx5 (anciennement DAVdroid)
+ Agenda & Contacts
+ Synchroniser avec DAVx5Rechercher parmi les utilisateurs et groupesTout sélectionnerSélectionnez un modèle
diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml
index eca3d253b64c..e89186ab9147 100644
--- a/src/main/res/values-gl/strings.xml
+++ b/src/main/res/values-gl/strings.xml
@@ -223,7 +223,6 @@
Administrar contasConta mediaAbrir a barra lateral
- ParticiparUsado %1$s de %2$sUsado %1$sEnvío automático
@@ -264,7 +263,6 @@
Volver ao método de acceso antigoEngadir a favoritosFavorito
- Non hai ningunha aplicación para enviar correos!ficheiroEliminarProduciuse un erro ao recuperar actividades para o ficheiro
@@ -387,18 +385,13 @@
O ficheiro multimedia non pode ser transformado nun fluxoNon foi posíbel ler o ficheiro multimediaO ficheiro multimedia ten unha codificación incorrecta
- O ficheiro non está nunha conta válida
- Non se atopan ficheiros multimedia
- Produciuse un erro non agardado ao tentar reproducir %1$sO intento de reproducir o ficheiro esgotou o tempo de espera.O reprodutor incorporado non pode reproducir o ficheiro multimediaCódec multimedia non admitido
- Rematou a reprodución de %1$sBotón de avance rápidoReprodutor de música %1$sBotón de reprodución ou pausaBotón de retroceso
- %1$s (cargando)%1$s (reproducindo)Os máis recentes primeiroOs máis antigos primeiro
@@ -449,25 +442,6 @@
A operación foi canceladaO servidor acadou a fin da súa vida. Actualíceo!Máis menús
- Probar a versión de desenvolvemento
- Isto inclúe todas as últimas funcionalidades por chegar e é bastante inestábel. Poden ocorrer fallos e erros e, cando sexa o caso, infórmenos diso.
- foro
- Axude a outros no
- Revise, corrixa e escriba código, vexa %1$s para obter máis detalles
- Colabore activamente
- Únase á conversa no IRC:
- a aplicación
- Traducir
- Descargue directamente a versión de desenvolvemento
- Obteña a versión de desenvolvemento no F-Droid
- Obteña a versión candidata no F-Droid
- Obteña a versión candidata no Google Play Store
- Candidata de publicación
- A candidata de publicación (release candidate - RC) é unha instantánea da versión máis próxima a publicar e agardase que sexa estábel. Probar a súa configuración individual podería axudarnos a asegurar iso. Rexístrese para facer probas na Play Store ou búsquea manualmente na sección «Versións» no F-Droid.
- Atopaches un fallo? hai algo estraño?
- Axudanos facendo probas
- Informe dun incidente no GitHub
- Interesaríalle axudarnos a probar como será próxima versión?Introduza o seu código de seguridadeSolicitaráselle o código de seguridade cada vez que inicie a aplicaciónIntroduza o seu código de seguridade
@@ -507,7 +481,6 @@
Amosar as notificacións de escaneo de mediosNotificar cando se atopen novos cartafoles de medios
- ComentariosGNU Licenza Pública Xeral, versión 2AxudaEditor
@@ -564,17 +537,17 @@
Contrasinal incorrectoAcceder cun código QRProtexendo os seus datos
- plataforma de produtividade de aloxamento propio
+ produtividade de aloxamento propioNavegar e compartirtodas as accións ao seu alcance
- Actividade, comparticións, ficheiros sen conexión
+ Actividade, comparticións, …todo accesíbel rapidamenteTodas as súas contasnun só lugarEnvío automáticopara as súas fotos e vídeos
- Sincronizar o calendario e os contactos
- con DAVx5 (anteriormente coñecido como DAVdroid)
+ Calendario e contactos
+ Sincronizar con DAVx5Buscar usuarios e gruposSeleccionar todoSeleccionar o modelo
diff --git a/src/main/res/values-hr/strings.xml b/src/main/res/values-hr/strings.xml
index 7451e0d124a8..b228f168d6c5 100644
--- a/src/main/res/values-hr/strings.xml
+++ b/src/main/res/values-hr/strings.xml
@@ -223,7 +223,6 @@
Upravljaj računimaSrednji računOtvori bočnu traku
- SudjelujIskorišteno %1$s od %2$sUpotrijebljeno %1$sAutomatsko otpremanje
@@ -264,7 +263,6 @@
Vrati na stari način prijaveDodaj u favoriteFavorit
- Nema dostupnih aplikacija za slanje poruka e-pošte!IzbrišiUčitavanje pojedinosti nije uspjeloDatoteka
@@ -384,18 +382,13 @@
Medijsku datoteku nije moguće reproducirati strujanjemNije moguće pročitati medijsku datotekuMedijska datoteka je neispravno kodirana
- Datoteka nije u valjanom računu
- Nema medijskih datoteka
- Neočekivana pogreška pri pokušaju reproduciranja %1$sPokušaj reprodukcije datoteke je istekaoUgrađeni alat za reprodukciju ne može reproducirati medijsku datotekuMedijski kodek nije podržan
- Završena reprodukcija %1$sTipka za ubrzavanje reprodukcijeAlat za reprodukciju glazbe %1$sTipka za reprodukciju ili pauzuTipka za premotavanje
- %1$s (učitavanje)%1$s (reprodukcija)Najnoviji prviNajstariji prvi
@@ -445,25 +438,6 @@
Radnja je otkazanaPoslužitelj je na kraju radnog vijeka, nadogradite ga!Više izbornika
- Testiraj razvojnu inačicu
- Uključuje sve nadolazeće značajke i sadrži najnovije tehnologije. Mogu se pojaviti pogreške/nedostaci koje je potrebno prijaviti ako ih primijetite.
- forum
- Pomozite drugima na
- Pregledajte, izmijenite i pišite kôd, pogledajte %1$s za pojedinosti
- Aktivno doprinosite
- Pridružite se razmjeni poruka na IRC-u:
- aplikacija
- Prevedi
- Izravno preuzmite razvojno izdanje
- Preuzmite razvojno izdanje putem aplikacije F-Droid
- Preuzmite kandidata za objavljivanje iz aplikacije F-Droid
- Preuzmite kandidata za objavljivanje iz trgovine Google Play
- Kandidat za objavljivanje
- Kandidat za objavljivanje (RC) je snimak nadolazećeg izdanja i očekuje se da će biti stabilan. Testiranje vašeg individualnog postava može nam pomoći da osiguramo stabilnost. Prijavite se za testiranje u trgovini Play ili pogledajte odjeljak \„Inačica”\ aplikacije F-Droid.
- Pronašli ste nedostatak? Pogrešku u radu?
- Pomozite nam testiranjem
- Prijavite problem na GitHubu
- Jeste li zainteresirani da nam pomognete testiranjem sljedeće inačice?Unesite zaporkuZaporka će se tražiti prilikom svakog pokretanja aplikacijeUnesite zaporku
@@ -502,7 +476,6 @@
Omogućite vjerodajnice uređaja kako biste prikazali mnemoničku oznaku.Prikaži obavijesti o skeniranju medijaObavijesti o novootkrivenim medijskim mapama
- Povratne informacijeOpća javna licenca za GNU, verzija 2.PomoćBilješke o izdanju
@@ -555,17 +528,13 @@
Pogrešna zaporkaPrijavite se putem QR kodaZaštita podataka
- samopostavljena platforma za pospješivanje produktivnostiPretražujte i dijelitejednostavan pristup svim mogućnostima i radnjama
- Aktivnosti, dijeljenje, izvanmrežne datotekebrz pristup svemuSvi vaši računina jednom mjestuAutomatsko otpremanjefotografija i videozapisa
- Sinkroniziraj kalendar i kontakte
- s aplikacijom DAVx5 (ranije poznatom kao DAVdroid)Pretraži korisnike i grupeOdaberi sveOdaberi predložak
diff --git a/src/main/res/values-hu-rHU/strings.xml b/src/main/res/values-hu-rHU/strings.xml
index 1bbf1d7b2168..95d273b956e3 100644
--- a/src/main/res/values-hu-rHU/strings.xml
+++ b/src/main/res/values-hu-rHU/strings.xml
@@ -223,7 +223,6 @@
Fiókok kezeléseKözépső fiókOldalsáv megnyitása
- Részvétel%1$s felhasználva ennyiből: %2$s%1$s használtAutomatikus feltöltés
@@ -264,8 +263,9 @@
Visszatérés a régi bejelentkezési módhozHozzáadás a kedvencekhez Kedvenc
- Nincs elérhető alkalmazás e-mail küldéséhez!
+ fájlhozTörlés
+ Hiba a fájl tevékenységeinek lekérésekorA részletek betöltése sikertelenFájlMegtartás
@@ -333,6 +333,7 @@
Biztonságos csoportmunka és fájlmegosztásKönnyen használható webes levelezés, naptárkezelés és névjegyekKépernyőmegosztás, online megbeszélések és webes konferenciák
+ mappáhozA mappa már létezikLétrehozásMappaikon
@@ -384,18 +385,13 @@
A médiafájl nem közvetíthetőA médiafájl nem olvashatóA médiafájl kódolása hibás
- A fájl nem érvényes fiókban található
- Nem található médiafájl
- Váratlan hiba a következő lejátszásakor: %1$sIdőtúllépés a lejátszás kísérletekorA beépített médialejátszó nem tudja lejátszani a fájltNem támogatott média kodek
- %1$s lejátszása készElőre gomb%1$s zenelejátszóLejátszás vagy szüneteltetés gombVissza gomb
- %1$s (betöltés)%1$s (lejátszás)Újabbak elölRégebbiek elöl
@@ -419,6 +415,7 @@
Új értesítésÚj verzió létrehozvaNincs elérhető alkalmazás hivatkozások kezeléséhez
+ Csak egy fiók engedélyezettNincs elérhető alkalmazás PDF kezeléséhezKüldésA jegyzet nem küldhető el
@@ -445,25 +442,6 @@
Művelet megszakítvaA kiszolgáló elérte az életciklusa végét, kérjük frissítsen!További menü
- Fejlesztési verzió tesztelése
- Ez tartalmazza az összes új funkciót, és nem stabil. Hibák előfordulhatnak, így ha ilyen történik, akkor jelentse azokat.
- fórumban
- Segítsen másoknak a
- Ellenőrizze, módosítsa és írjon kódot, a részletekért lásd: %1$s
- Működjön közre aktívan
- Csatlakozzon a csevegéshez IRC-n:
- az alkalmazást
- Fordítsa le
- Fejlesztési kiadás közvetlen letöltése
- Fejlesztési kiadás beszerzése az F-Droid alkalmazásból
- Kiadásra jelölt változat beszerzése az F-Droid alkalmazásból
- Kiadásra jelölt változat beszerzése a Google Play áruházból
- Kiadásra jelölt változat
- A kiadásra jelölt változat (RC) a következő kiadás egy pillanatképe, ami stabilnak szánt. Az egyéni beállítása tesztelésével segíthet ennek az elérésében. Iratkozzon fel a teszteléshez a Play áruházban, vagy keresse meg kézzel az F-Droid „Verzió” részében.
- Hibát talált? Vagy furcsaságot?
- Segítsen a teszteléssel
- Jelentse az esetet a GitHubon
- Segítene a következő verzió tesztelésében?Írja be a számkódodatA számkódra minden alkalommal szükség lesz az alkalmazás indításakorAddja meg a számkódot
@@ -502,7 +480,6 @@
A mnenomikus kód megjelenítéséhez engedélyezze az eszköz hitelesítését.Média keresési értesítések megjelenítéseÉrtesítés az újonnan talált médiamappákról
- VisszajelzésGNU General Public License, 2-es verzióSúgóImpresszum
@@ -525,6 +502,9 @@
Tároló útvonalHelyi mappaTávoli mappa
+ Téma
+ Sötét
+ VilágosKép előnézeteNincs helyi fájl az előnézethezA kép nem jeleníthető meg
@@ -536,6 +516,7 @@
Próbálja ki a %1$sot az eszközén!Meg akarom hívni a %1$s használatára az eszközén.\nTöltse le itt: %2$s%1$s vagy %2$s
+ A fájl nem található.Törlés sikertelenAz értesítés eltávolítása sikertelen.Eltávolítás
@@ -555,17 +536,13 @@
Hibás jelszóBelépés QR-kóddalAz adatai védelme
- saját üzemeltetésű irodai platformTallózás és megosztásminden művelet karnyújtásnyira
- Tevékenységek, megosztások, offline fájlokminden gyorsan elérhetőAz összes fiókjaegy helyenAutomatikus feltöltésa képeinek és videóinak
- Naptár és névjegyek szinkronizálása
- DAVx5 (régi nevén DAVdroid) segítségévelFelhasználók és csoportok kereséseÖsszes kiválasztásaVálasszon sablont
@@ -583,6 +560,8 @@
%1$s megosztásaHivatkozás beszerzése%1$s (csoport)
+ Belső hivatkozás megosztása
+ Csak azoknál a felhasználóknál működik, akiknek hozzáférése van ehhez a %1$s%1$s ( itt: %2$s )Meg kell adnia a jelszótHiba történt a fájl vagy mappa megosztásakor
diff --git a/src/main/res/values-in/strings.xml b/src/main/res/values-in/strings.xml
index 5309ab61fd6c..0d07ba87dee7 100644
--- a/src/main/res/values-in/strings.xml
+++ b/src/main/res/values-in/strings.xml
@@ -171,7 +171,6 @@
Kelola akunAkun tengahBuka jendela samping
- Berpartisipasi%1$s dari %2$s sudah digunakan%1$s digunakanUnggah otomatis.
@@ -279,17 +278,13 @@
Berkas media tidak bisa di streamingTidak dapat membaca berkas mediaBerkas media tidak di encoding dengan benar
- Berkas tidak didalam akun yang sah
- Tidak ditemukan berkas mediaWaktu habis saat mencoba untuk mainBerkas media tidak dapat diputar dengan pemutar media yang adaKodek media tidak didukung
- %1$s pemutaran selesaiTombol majuPemutar musik %1$sTombol main dan jedaTombol mundur
- %1$s (sedang dimuat)%1$s (dimainkan)TerbaruPaling lama
@@ -318,20 +313,6 @@
Cek ulang nanti.Tidak ada koneksi ke internetServer telah mencapai batas hidupnya, tolong ditingkatkan!
- Uji versi dev
- Ini termasuk semua fitur yang akan datang. Bug/galat bisa terjadi, bila terjadi harap laporkan ke kami.
- forum
- Bantu yang lain di
- Tinjauan, perubahan dan penulis kode lihat %1$suntuk jelasnya.
- Berkontribusi Aktif
- Gabung untuk mengobrol di IRC:
- aplikasi
- Terjemahkan
- Kandidat rilis
- Kandidat rilis (RC) adalah potret rilis berikutnya dan diharapkan menjadi rilis stabil. Uji pengaturan pribadi anda dapat membantu untuk memastikan hal ini. Daftar untuk menguji di Play Store atau secara manual lihat di seksi \"versi\" di F-Droid.
- Menemukan kesalahan?
- Bantu dengan menguji.
- Laporkan di GitHubMasukkan kode sandi AndaKode sandi akan diminta setiap kali apl dijalankan.Masukkan kode kunci Anda
@@ -361,7 +342,6 @@
UmumLainnyaCadangkan kontak harian
- Umpan balikBantuanJejakBerkas asli akan menjadi…
diff --git a/src/main/res/values-is/strings.xml b/src/main/res/values-is/strings.xml
index 460a33ff48cf..f422b2d3b63e 100644
--- a/src/main/res/values-is/strings.xml
+++ b/src/main/res/values-is/strings.xml
@@ -218,7 +218,6 @@
Sýsla með notandaaðgangaMið-notandaaðgangurOpna hliðarspjald
- Taka þátt%1$s af %2$s notað%1$s notaðSjálfvirk innsending
@@ -254,7 +253,6 @@
Fara til baka í gömlu innskráningaraðferðinaBæta í eftirlætiEftirlæti
- Ekkert forrit tiltækt til að senda póst!EyðaMistókst að hlaða inn ítarupplýsingumSkrá
@@ -369,18 +367,13 @@
Ekki tókst að streyma margmiðlunarskráGat ekki lesið margmiðlunarskránaMargmiðlunarskráin er með ranga kóðun
- Skráin er ekki á gildum notandaaðgangi
- Engin margmiðlunarskrá fannst
- Óvænt villa kom upp við að reyna að spila %1$sTilraun til að spila skrá rann út á tímaInnbyggði margmiðlunarspilarinn ræður ekki við að spila þessa margmiðlunarskrá Óstudd margmiðlunarlyklun (codec)
- Afspilun %1$s er lokiðSpóla-hratt-áfram hnappur%1$s tónlistarspilariAfspilun-eða-hlé hnappurSpóla-til-baka hnappur
- %1$s (hleðst)%1$s (í spilun)Nýjast fyrstElsta fyrst
@@ -430,25 +423,6 @@
Hætt hefur verið við aðgerðNetþjónninn er kominn að endimörkum líftíma síns, endilega uppfærðu hann!Valmynd með fleiru
- Prófaðu þróunarútgáfuna
- Þetta inniheldur alla væntanlega eiginleika og er alveg á jaðrinum hvað varðar stöðugleika. Villur geta komið upp, og ef slíkt gerist, endilega tilkynntu um þær.
- vefspjallinu
- Hjálpaðu öðrum á
- Yfirfarðu, bættu og skrifaðu kóða, skoðaðu %1$s fyrir nánari upplýsingar
- Vertu virkur þáttakandi
- Taktu þátt í umræðum á IRC:
- forritið
- Þýddu
- Náðu í þróunarútgáfu sem beint niðurhal
- Náðu í þróunarútgáfu í f-Droid forritasafninu
- Náðu í forútgáfu í f-Droid forritasafninu
- Náðu í forútgáfu í Google Play forritasafninu
- Forútgáfa
- Prófunarútgáfan (RC) er skyndisamsetning verðandi útgáfu en er þó vænst að sé tiltölulega stöðug. Það að þú prófir uppsetninguna þína ætti að hjálpa til við að ná því markmiði. Skráðu þig til prófana í Play hugbúnaðarsafninu eða gerðu það handvirkt með því að fara í \"Útgáfa\"-hlutann í F-Droid.
- Fannstu villu? Skringilegheit?
- Hjálpaðu til við prófanir
- Tilkynntu um vandamál á GitHub
- Hefurðu áhuga á að hjálpa til við að prófa næstu útgáfu?Settu inn lykilkóðaLykilkóðans verður krafist í hvert skipti sem forritið er ræstSettu inn lykilkóðann þinn
@@ -486,7 +460,6 @@
Til að birta minnishjálp skaltu virkja auðkenningu tækisins.Birta tilkynningar vegna skönnunar eftir margmiðlunarefniLáta vita um nýfundnar möppur með margmiðlunarefni
- UmsögnGNU General Public notkunarleyfið, útgáfa 2HjálpPrenta
diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml
index 15792c016540..58ee37361638 100644
--- a/src/main/res/values-it/strings.xml
+++ b/src/main/res/values-it/strings.xml
@@ -223,7 +223,6 @@
Gestisci accountAccount centraleApri barra laterale
- Partecipa%1$s di %2$s utilizzati%1$s utilizzatoCaricamento automatico
@@ -264,7 +263,6 @@
Torna al vecchio metodo di accessoAggiungi ai preferitiPreferito
- Nessun applicazione disponibile per inviare email!fileEliminaErrore durante il recupero delle attività per i file
@@ -387,18 +385,13 @@
Il file multimediale non può essere trasmessoImpossibile leggere il file multimedialeIl file multimediale ha una codifica non corretta
- Il file non è in un account valido
- Nessun file multimediale trovato
- Errore inatteso durante il tentativo di riproduzione di %1$sIl tentativo di riprodurre il file è scadutoIl lettore multimediale integrato non è in grado di riprodurre il fileCodificatore multimediale non supportato
- Riproduzione di %1$s terminataPulsante Avanti veloceLettore musicale %1$sPulsante Riproduci o pausaPulsante Riavvolgi
- %1$s (in caricamento)%1$s (in riproduzione)Prima i più recentiPrima i più datati
@@ -449,25 +442,6 @@
L\'operazione è stata annullataIl server ha raggiunto la fine della sua vita, aggiornalo!Menu Altro
- Prova la versione di sviluppo
- Ciò include tutte le funzionalità di prossima introduzione e potrebbe presentare seri problemi di stabilità. Bug/errori possono verificars. Se e quando li riscontrerai, segnalaci le tue scoperte.
- forum
- Aiuta gli altri sul
- Esamina, correggi e scrivi codice, vedi %1$s per i dettagli
- Contribuisci attivamente
- Entra in chat su IRC:
- l\'applicazione
- Traduci
- Scarica direttamente la versione di sviluppo
- Ottieni la versione di sviluppo dall\'applicazione F-Droid
- Ottieni la candidata al rilascio dall\'applicazione F-Droid
- Ottieni la candidata al rilascio da Google Play
- Candidata al rilascio
- La candidata al rilascio (RC) è un\'istantanea della versione successiva e dovrebbe essere stabile. Effettuando dei test sulla tua configurazione, puoi aiutarci a esserne sicuri. Registrati per la fase di test su Google Play o controlla manualmente nella sezione \"Versione\" di F-Droid.
- Trovato un bug? Stranezze?
- Aiutaci nella fase di test
- Segnala un problema su GitHub
- Sei interessato ad aiutarci a provare la nostra prossima versione?Digita il tuo codice segretoIl codice segreto sarà richiesto ogni volta che l\'applicazione è avviataDigita il tuo codice segreto
@@ -506,7 +480,6 @@
Per mostrare il codice mnemonico, abilita le credenziali del dispositivo.Mostra notifiche di scansione dei supportiNotifica per le nuove cartelle multimediali trovate
- SegnalazioniGNU General Public License, versione 2AiutoImprint
@@ -563,17 +536,17 @@
Password errataAccedi tramite codice QRProteggere i tuoi dati
- piattaforma di produttività auto-gestita
+ produttività auto-gestitaSfoglia e condividitutte le azioni a portata di mano
- attività, condivisioni, file non in linea
+ Attività, condivisioni, ...tutto rapidamente accessibileTutti i tuoi accountin un postoCaricamento automaticoper e tue foto e i video
- Sincronizza calendario e contatti
- con DAVx5 (originariamente DAVdroid)
+ Calendario e contatti
+ Sincronizza con DAVx5Cerca utenti e gruppiSeleziona tuttoSeleziona modello
diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml
index db3e08297ca3..bd6420aa2804 100644
--- a/src/main/res/values-iw/strings.xml
+++ b/src/main/res/values-iw/strings.xml
@@ -221,7 +221,6 @@
ניהול חשבונותחשבון אמצעיפתיחת סרגל הצד
- השתתפותנעשה שימוש ב־%1$s מתוך %2$s%1$s בשימושהעלאה אוטומטית
@@ -261,7 +260,6 @@
החזרה לשיטת כניסה ישנההוספה למועדפיםסימון כמועדף
- אין יישומון זמין לשליחת הודעות בדוא״ל!מחיקהטעינת הפרטים נכשלהקובץ
@@ -381,18 +379,13 @@
לא ניתן להזרים את קובץ המדיהלא ניתן לקרוא את קובץ המדיהלקובץ המידע יש קידוד שגוי
- הקובץ אינו בחשבון תקני
- לא נמצא קובץ מדיה
- שגיאה בלתי צפויה בעת הניסיון לנגן את %1$sהניסיון לנגן את הקובץ ארך זמן רב מדינגן המדיה המובנה לא יכול לנגן את קובץ המדיהמקודד המדיה אינו נתמך
- הנגינה של %1$s הסתיימהלחצן הרצה קדימהנגן מוזיקה %1$sלחצן ניגון או השהייהלחצן החזרה אחורה
- %1$s (בטעינה)%1$s (מתגנן)החדשים ביותר ראשוניםהישנים ביותר ראשונים
@@ -442,25 +435,6 @@
הפעולה בוטלה.השרת הגיע לתום חייו, נא לשדרג!תפריט עוד
- בדיקת גרסת הפיתוח
- לרבות כל התכונות הקרבות וזה כבר בהליכי פיתוח. תקלות/שגיאות עשויות להתרחש, אפילו כשהן מתרחשות, נא לדווח לנו על הממצאים שלך.
- בפורום
- ניתן לסייע לאחרים
- ניתן לסקור, להוסיף ולכתוב קוד, %1$s לפרטים
- תרומה פעילה
- הצטרפות לשיח ב־IRC:
- היישומון
- תרגום
- הורדת מהדורת פיתוח ישירות
- קבלת מהדורת פיתוח מהיישומון F-Droid
- קבלת מועמד להפצה מהיישומון F-Droid
- קבלת מועמד להפצה מהחנות Google Play
- מועמדת להפצה
- המהדורה שמועמדת להפצה (RC) היא חתך זמני והיא מיועדת להיות המהדורה היציבה. בדיקת התצורה שלך עשויה לסייע לוודא שכך זה יהיה. ניתן להירשם לניסוי בחנות Play או לחפש ידנית את בסעיף „גרסה” בתוך F-Droid.
- מצאת תקלה? התנהגות חריגה?
- עזרה בבדיקות
- דיווח על תקלה ב־GitHub
- מעניין אותך לסייע לנו על ידי בדיקת איכות לגרסה הבאה?יש להכניס את הקוד שלךבכל פעם שיישום זה נפתח יהיה צורך להכניס את הקודנא להקליד את מילת הצופן שלך
@@ -499,7 +473,6 @@
על מנת להציג שינון, הפעל את אישורי המכשיר.הצגת התראות על סריקת מדיההצגת התראות על תיקיות מדיה חדשות שנמצאו
- משובהרישיון הציבורי הכללי של GNU, גרסה 2עזרהחותמת
@@ -552,17 +525,13 @@
סיסמא שגוייהכניסה עם קוד QRהגנה על הנתונים שלך
- פלטפורמת כלי משרד באירוח עצמיעיון ושיתוףכל הפעולות בהינף יד
- פעילות, שיתופים, קבצים בלתי מקווניםהכול נגיש במהירותכל החשבונות שלךבמקום אחדהעלאה אוטומטיתלתמונות ולסרטונים שלך
- סנכרון לוח שנה ואנשי קשר
- עם DAVx5 (לשעבר DAVdroid)חיפוש משתמשים וקבוצותבחר הכלבחירת תבנית
diff --git a/src/main/res/values-ja-rJP/strings.xml b/src/main/res/values-ja-rJP/strings.xml
index 73cb18fb4eaa..654f3e16b341 100644
--- a/src/main/res/values-ja-rJP/strings.xml
+++ b/src/main/res/values-ja-rJP/strings.xml
@@ -223,7 +223,6 @@
アカウント管理ミドルアカウントサイドバーを開く
- 参加する%2$s 中%1$s が使われています。%1$s使用中自動アップロード
@@ -263,7 +262,6 @@
古いログイン方法へ戻すお気に入りに追加お気に入り
- メールを送信するアプリはありません!削除詳細のロードに失敗しましたファイル
@@ -381,18 +379,13 @@
このメディアファイルはストリーミングできませんメディアファイルを読み込めません不正なエンコードのメディアファイルです
- ファイルが有効なアカウントにありません
- メディアファイルが見つかりませんでした
- %1$s の再生時に予期しないエラーが発生しました再生試行がタイムアウトしましたビルトインのメディアプレーヤーはこのメディアファイルを再生できませんサポートされていないメディアコーデックです
- %1$s 再生終了早送りボタン%1$s ミュージックプレイヤー再生/一時停止ボタン巻き戻しボタン
- %1$s (読み込み中)%1$s (再生中)日付(降順)日付(昇順)
@@ -442,25 +435,6 @@
処理がキャンセルされましたサーバーのEOLが終わったので、アップグレードしてください!その他のメニュー
- 開発バージョンをテスト
- これは、すべての最新の機能が含まれており、非常に最先端です。 バグ/エラーが発生する可能性があります。もしその場合は、私たちに報告してください。
- フォーラム
- 他の人に協力する
- 詳細を確認するには、%1$sを参照してください。
- 積極的な貢献
- IRCのチャットに参加する:
- アプリ
- 翻訳
- 開発リリースを直接ダウンロードする
- F-Droidアプリから開発リリースを入手
- F-Droidアプリからリリース候補を取得する
- Google Playストアからリリース候補を取得する
- リリース候補
- リリース候補(RC)は、今後のリリースのスナップショット であり、安定性が期待されます。 個々の設定をテストすることで、これを確実にすることができます。 Playストアでのテストに登録するか、F-Droidの「バージョン」セクションを手動で参照してください。
- バグがありましたか? 問題がありますか?
- テストによるヘルプ
- Githubでエラーを報告する
- 次のバージョンのテストを手伝ってください。パスコードを入力アプリ開始時には毎回パスコードが要求されますパスコードを入力してください
@@ -499,7 +473,6 @@
ニーモニックを表示するには、デバイスクレデンシャルを有効にしてください。メディアのスキャン結果通知を表示する新たに見つかったメディアフォルダを通知する
- フィードバックGNU General Public License, version 2ヘルプインプリント
@@ -553,12 +526,9 @@
QRコードを用いてログインあなたのデータを保護閲覧と共有
- アクティビティ、共有、オフラインファイルあなたのすべてのアカウントひとつの場所に自動アップロード
- コンタクト&カレンダーを同期
- DAVx5(旧称:DAVdroid)でユーザーとグループを検索すべて選択テンプレートを選択する
diff --git a/src/main/res/values-ka-rGE/strings.xml b/src/main/res/values-ka-rGE/strings.xml
index 35bfbd179f5e..cbe67522fc08 100644
--- a/src/main/res/values-ka-rGE/strings.xml
+++ b/src/main/res/values-ka-rGE/strings.xml
@@ -146,7 +146,6 @@
გასვლაანგარიშების მენეჯმენტიშუა ანგარიში
- მონაწილეობის მიღებაგამოყენებულია %1$s სულ %2$s-იდან ავტო-ატვირთვადააყენეთ როგორც დაშიფრული
@@ -243,17 +242,13 @@
მედია ფაილის სტრიმი ვერ ხორციელდებამედია ფაილის წაკითხვა ვერ მოხერხდამედია ფაილს გააჩნია არასწორი კოდირება
- ეს ფაილი არ ეკუთვნის დაშვებულ ანგარიშს
- მედია ფაილი ვერ იქნა ნაპოვნიფაილის დაკვრის მცდელობის დრო ამოიწურაჩაშენებული მედია დამკვრელი ვერ უკრავს მედია ფაილსმედია კოდეკი მხარდაუჭერელია
- %1$s დაკვრა დასრულდასწრაფი გადახვევის ღილაკი%1$s მუსიკის დამკვრელიდაკვრის ან პაუზის ღილაკიგადახვევის ღილაკი
- %1$s (იტვირთება)%1$s (იკვრება)ჯერ ახალიჯერ ძველი
@@ -281,25 +276,6 @@
შეტყობინებების არხის ატვირთვაშეტყობინებები არააგთხოვთ დაბრუნდეთ მოგვინაებით.
- დევ. ვერსიის შემოწმება
- იმყოფება სისხლდენის ზღვარზე და მოიცავს ყველა დამდეგ ფუნქციას. შეიძლება გამოჩნდეს შეცდომები, ასეთი შემთხვევისას გთხოვთ გვამცნობოთ რეპორტით.
- ფურუმი
- დაეხმარეთ სხვებს
- განიხილეთ, შეცვალეთ და დაწერეთ კოდი, დეტალებისთვის იხილეთ %1$s
- აქტიურად შეიტანეთ წვლილი
- შემოგვიერთდით IRC ჩატში:
- აპლიკაცია
- გადათარგმნეთ
- დეველოპმენტ რელიზის პირდაპირი ჩამოტვირთვა
- მიიღეთ დეველოპმენტ რელიზი F-Droid აპლიკაციიდან
- მიიღეთ რელიზ კანდიდატი F-Droid აპლიკაციიდან
- მიიღეთ რელიზ კანდიდატი Google Play-დან
- რელიზის კანდიდატი
- ეს რელიზის კანდიდატი (რკ) დამდეგი რელიზის კადრია და მოსალოდნელია მისი სტაბილურობა. ამის დამოწმებაში დაგვეხმარება ინდივიდუალური მოწყობილობის შემოწმება. შემოწმებისთვის დარეგისტრირდით Play-ზე ან გადახედეთ \"ვერსიის\" სექციას F-Droid-ში.
- იპოვეთ შეცდომა? უცნაურობა?
- დაგვეხმარეთ შემოწმებით
- დაამატეთ მოხსენიება GitHub-ზე
- დაინტერესებული ხართ დაგვეხმაროთ შემდეგი ვერსიის შემოწმებაში?შეიყვანეთ თქვენი პასკოდიპასკოდი მოთხოვნილ იქნება აპლიკაციის ყოველი გაშვებისასგთხოვთ შეიყვანოთ თქვენი პასკოდი
@@ -329,7 +305,6 @@
ზოგადიმეტიკონტაქტების ყოველდღიური ბექაფი
- უკუკავშირიGNU ზოგადი ღია ლიცენზია, ვერსია 2დახმარებაბეჭედი
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index 8d17c526f477..81955c34f08f 100644
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -165,7 +165,7 @@
파일이 대상 폴더에 이미 존재함복사할 수 없습니다. 파일이 존재하는지 확인하십시오내부 링크 복사
- 이 폴더에 액세스 할 수있는 사용자에게만 해당됩니다.
+ 이 폴더에 액세스하는 사용자에게만 해당됩니다.링크 주소 복사암호화된 폴더로 복사/이동은 아직 지원하지 않습니다.다음으로 복사
@@ -223,7 +223,6 @@
계정 관리중간 계정사이드바 열기
- 참여%2$s 중 %1$s 사용됨%1$s 사용됨자동 업로드
@@ -264,7 +263,7 @@
이전 로그인 방식으로 변경즐겨찾기에 추가즐겨찾기
- 메일을 보내기 위한 앱이 없습니다!
+ 파일삭제파일에 대한 작업 검색 중 오류 발생자세한 정보를 불러오는데 실패했습니다.
@@ -334,6 +333,7 @@
안전한 협력 &파일 교환사용하기 쉬운 웹 메일, 달력 & 연락처화면 공유, 온라인 회의 & 웹 회의
+ 폴더폴더가 이미 존재함생성폴더 아이콘
@@ -383,18 +383,13 @@
미디어 파일을 스트리밍할 수 없음미디어 파일을 읽을 수 없음미디어 파일의 인코딩이 잘못됨
- 파일이 올바른 계정에 없음
- 미디어 파일을 찾을 수 없음
- %1$s을(를) 재생 중 알 수 없는 오류 발생재생 시도 중 시간이 초과됨내장 미디어 재생기로 파일을 재생할 수 없음지원하지 않는 미디어 코덱
- %1$s 재생 완료됨빨리감기 단추%1$s 음악 재생기재생 혹은 일시 정지 단추되감기 단추
- %1$s(불러오는 중)%1$s(재생 중)최신 항목 먼저오래된 항목 먼저
@@ -445,25 +440,6 @@
작업이 취소되었습니다.서버의 수명이 다된것 같네요, 업그레이드 해주세요!메뉴 더 보기
- 개발 중인 버전 테스트하기
- 포함될 예정인 기능이 들어 있으며 완벽한 상태가 아닙니다. 버그나 오류가 발생할 수 있으며, 언제 어떻게 발생하는지 정보를 알려 주십시오.
- 포럼
- 다른 사람 돕기:
- 코드를 리뷰, 수정, 작성할 수 있습니다. 자세한 사항은 %1$s 사이트를 참조하십시오
- 적극적으로 기여
- IRC 채팅 참여하기:
- 앱
- 번역:
- 직접 개발 릴리스 다운로드
- F-Droid 앱에서 개발 릴리스 받기
- F-Droid 앱에서 릴리스 후보 버전 받기
- Google Play 스토어에서 릴리스 후보 버전 받기
- 릴리스 후보
- 릴리스 후보(RC) 버전은 다가올 릴리스의 미리 보기 버전이며 안정화를 목표로 합니다. 개별 환경에서의 테스트를 통해서 안정성을 더 높일 수 있습니다. Play Store에서 테스트 프로그램에 등록하거나 F-Droid의 \"버전\"을 직접 확인하십시오.
- 버그를 찾으셨나요? 무언가 이상하게 작동하나요?
- 테스트로 돕기
- GitHub에 문제점 보고하기
- 다음 버전에 포함될 예정인 기능을 테스트해 보고 싶으신가요?암호를 입력하십시오앱을 시작할 때마다 암호를 물어봅니다암호를 입력하십시오
@@ -502,7 +478,6 @@
연상 기호를 표시하려면 장치 자격 증명을 활성화하십시오.미디어 스캔 알림 표시새로 발견된 미디어 폴더 알림
- 피드백GNU General Public License, version 2도움말법적 고지
@@ -558,16 +533,13 @@
잘못된 암호QR 코드로 로그인데이터 보호
- 자체 호스팅 생산성 플랫폼탐색하고 공유모든 터치 액션
- 활동, 공유, 오프라인 파일빠르게 접근 가능한 모든 것.모든 계정한곳에서자동 업로드사진 & 동영상
- 달력 및 &연락처 동기화사용자와 그룹 검색모두 선택템플릿 선택
@@ -585,6 +557,8 @@
%1$s 공유링크 얻기%1$s(그룹)
+ 내부 링크 공유
+ 이 %1$s에 액세스하는 사용자에게만 해당됩니다.%1$s(%2$s에서)암호를 입력해야 합니다이 파일이나 폴더를 공유하는 중 오류 발생
diff --git a/src/main/res/values-lt-rLT/strings.xml b/src/main/res/values-lt-rLT/strings.xml
index ebdc3a773b58..d16c9176d9c9 100644
--- a/src/main/res/values-lt-rLT/strings.xml
+++ b/src/main/res/values-lt-rLT/strings.xml
@@ -38,7 +38,7 @@
Šiame įrenginyje jau yra paskyra šiam naudotojui ir serveriuiĮvestas naudotojas neatitinka šios paskyros naudotojoNežinoma serverio versija
- Nepavyko autentifikuotis šiame serveryje
+ Nepavyko nustatyti tapatybės šiame serveryjePatikrinti serverįRyšys užmegztasPrašome įvesti dabartinį slaptažodį
@@ -162,11 +162,11 @@
Atsisiunčiama…%1$s atsisiųstaAtsisiųsti
- Dar neatsiųsta
+ Kol kas neatsisiųstaUžverti šoninę juostąVeiklosVisi failai
- Mėgstamiausi
+ MėgstamiNamaiPranešimaiĮrenginyje
@@ -180,7 +180,6 @@
AtsijungtiTvarkyti paskyrasAtverti šoninę juostą
- Dalyvautipanaudota %1$s iš %2$spanaudota %1$sAutomatinis įkėlimas
@@ -209,13 +208,13 @@
NuostatosNepavyko perduoti failo į atsiuntimų tvarkytuvęAtgal
- Pridėti prie svarbiausių
- Svarbiausias
+ Pridėti į mėgstamus
+ MėgstamiIštrintiFailasĮkelkite tam tikrą turinį (medžiagą) arba sinchronizuokite su savo įrenginiais.
- Nieko dar nepavyko
- Pagal Jūsų pateiktą užklausą neradome šių failų.
+ Kol kas nėra mėgstamų
+ Jūsų paieška negrąžino jokių mėgstamų failų.Čia bus rodomi failai ir aplankai, kuriuos pažymėsite kaip mėgstamusČia nėra failųŠiame aplanke nėra jokių rezultatų
@@ -226,7 +225,7 @@
Neseniai pridėtų failų nerastaNėra jokių paskiausiai pridėtų failų.Gal tai yra kitame aplanke?
- Visi aplankai (failai), kuriais norite pasidalinti bus šioje vietoje
+ Čia atsiras visi jūsų bendrinami failai ar aplankaiNiekuo nesidalinateĮkelkite nuotraukas arba įjunkite automatinį įkėlimą.Nuotraukų nėra.
@@ -267,6 +266,10 @@
Failo pavadinime yra bent vienas neteisingas simbolisFailo pavadinimasTai yra Nextcloud ypatybė, prašome atsinaujinti.
+ Saugiai laikykite ir valdykite savo duomenis
+ Saugus bendradarbiavimas ir apsikeitimas failais
+ Lengvai naudojamas el. paštas, kalendorius bei adresatai
+ Ekrano bendrinimas, internetiniai susitikimai bei konferencijosAplankas jau yraSukurtiAplanko piktograma
@@ -307,18 +310,13 @@
Nepavyko perskaityti medijos failo.
Media failas yra nepalaikomos koduotės.
- Failas nėra galiojančioje paskyroje (profilyje)
- Nerasta medija failų
- Netikėta klaida bandant atkurti %1$sBaigėsi laukimo laikas bandant paleistiĮtaisytąjai medijos leistuvei nepavyko atkurti medijos failoNepalaikomas kodekas
- %1$s atkūrimas užbaigtasPrasukimo mygtukas%1$s muzikos grotuvasGrojimo arba pauzės mygtukasAtsukimo mygtukas
- %1$s (įkeliama)%1$s (atkuriama)Naujausi pirmaSeniausi pirma
@@ -354,20 +352,6 @@
Nėra interneto ryšioOperacijos atsisakytaDaugiau meniu
- Testuoti dev versiją
- Tai yra ateities funkcijos ir jos yra nepatikrintos. Klaidos ar netikslumai gali pasitaikyti, tuo atveju, informuokite apie savo pastebėjimus.
- Diskusijos
- Padėti kitiems
- Peržiūrėti, pataisyti ir įrašyti kodą, žr. %1$s
- Aktyviai prisidėti
- Prisijungti prie diskusijų IRC
- Programėlė
- Išversti
- Kandidatas kitam leidimui
- Kandidatas kitam leidimui (Releace Candidate - RC) yra fragmentas kito stabilaus leidimo. Individualus testavimas gali tam pagelbėti. Prisijunkite testavimui per Play Store arba ieškokite \"Version\" sekcijos F-Droid.
- Radote klaidą? Netikslumą?
- Pagalba testuojant
- Pranešti problemą GitHub?Įveskite užraktąUžrakto kodas bus reikalaujamas kiekvieną kartą paleidžiant programėlęPrašome įvesti slaptažodį
@@ -395,7 +379,6 @@
InformacijaBendrasDaugiau
- AtsiliepimaiGNU Bendroji Viešoji Licencija, versija 2PagalbaPradinis failas bus…
@@ -455,7 +438,7 @@
%1$s (šiame %2$s)Privalote įvesti slaptažodįĮvyko klaida bandant dalinti šį failą ar aplanką
- Nepavyko pasidalinti. Patikrinkite ar failas egzistuoja
+ Nepavyko pradėti bendrinti. Patikrinkite ar failas yradalintis failuĮveskite slaptažodįNustatyti slaptažodį
diff --git a/src/main/res/values-lv/strings.xml b/src/main/res/values-lv/strings.xml
index 649305b71da1..e42c538e8b12 100644
--- a/src/main/res/values-lv/strings.xml
+++ b/src/main/res/values-lv/strings.xml
@@ -164,7 +164,6 @@
IzrakstītiesPārvaldīt kontusAtvērt malu
- Piedalīties%1$s no %2$s lietoti%1$s izmantotiAutomātiska augšupielāde
@@ -268,18 +267,13 @@
Nevar straumēt multivides datniNevarēja nolasīt medija datniMultivides datnei nav pareizs kodējums
- Šī datne nav pieejamā kontā
- Nav atrasta neviens multivides datne
- Neparedzēta kļūda atskaņojot %1$sMēģinājums atskaņot datni noilgaIegultais atskaņotājs nevarēja atvērt datni.Neatbalstīts mediju kodekss
- %1$s atskaņošana pabeigtaPatīšanas poga%1$s mūzikas atskaņotājsAtskaņot vai pauzētAttīšanas poga
- %1$s (ielādēt)%1$s (atskaņot)Jaunākie pirmsVecākie pirms
@@ -299,20 +293,6 @@
Ielādē paziņojumus…Nav paziņojumuNav interneta savienojuma
- Testēt dev versiju
- Tas ieskaita visas gaidāmās iespējas un ir pati jaunākā versija. Kļūdas/nepilnības var gadīties, ja un kad tās notiek lūdzu ziņojiet par tām.
- forums
- Palīdzi citiem
- Aktīvi dod iegūldījumu
- Pievienojies čatam IRC:
- lietotni
- Tulkot
- Laišanas kandidāts
- Šis laidiena kandidāts (RC) ir pirmslaišanas versija un tai vajadzētu būt stabilai. Testējot savus individuālos uzstādijumus jūs varat palīdzēt to pārbaudīt. Pierakstieties testēšanai Play veikalā vai manuāli skatieties \"Version\" nodalijumā F-Droid.
- Atradi kļūdu vai nepilnību?
- Palīdzi testējot
- Ziņojiet par problēmu Github
- Interesēts mums palīdzēt izmēģinot jauno testēšanas versiju?Ievadiet piekļuves koduKods tiks pieprasīts katru reizi kad atvērsiet lietotniLūdzu, ievadiet piekļuves kodu
@@ -343,7 +323,6 @@
Ikdieniška kontaktu dublēšanaRādīt mediju skenēšanas paziņojumusPaziņt par jaunas mediju mapes atrašanu
- AtsauksmesPalīdzībaImportētOrģinālā datne būs…
@@ -379,7 +358,6 @@
Atjaunot dzēstu datniNepieciešama paroleNepareiza parole
- Darbības, kopīgošana, bezsaistes failiMeklēt lietotājus un grupasAtzīmēt visuSūtīt
diff --git a/src/main/res/values-mk/strings.xml b/src/main/res/values-mk/strings.xml
index 2450eb76efd0..64085e64f204 100644
--- a/src/main/res/values-mk/strings.xml
+++ b/src/main/res/values-mk/strings.xml
@@ -206,7 +206,6 @@
Управување со сметкиMiddle accountОтвори странична лента
- Учествувајискористено %1$s од %2$s искористено %1$sАвтоматско прикачување
@@ -247,7 +246,6 @@
Врти се на стариот начин на за најавувањеДодади во фаворитиОмилен
- Нема достапна апликација за испраќање пораки преку е-пошта!ИзбришиГрешка при превземањето на активностите за датотекатаНеуспешно вчитување на деталите
@@ -368,18 +366,13 @@
Датотеката неможе да се емитуваНеможе да се прочита датотекатаМедиа датотеката не е точно енкодирана
- Датотеката не е во валидна сметка
- Не се најдени медиум датотеки
- Неочекувана грешка при пуштање на %1$sТајм аут при обидот за репродукцијаВградениот музучки плеер неможе да ја уклучи датотекатаНеподржан кодек на медиумот
- репродуцирањето на %1$s завршиКопче за брзо премотување%1$s пуштач на музикаКопче за пуштање или паузаКопче за премотување
- %1$s (се вчитува)%1$s (свири)Новите првоСтарите прво
@@ -430,25 +423,6 @@
Операцијата е откажанаСерверот го достигна крајот на животот, ве молиме надградете го!Мени повеќе
- Test the dev version
- This includes all upcoming features and it is on the very bleeding edge. Bugs/errors can occur, if and when they do, please report of your findings.
- форум
- Help others on the
- Review, amend and write code, see %1$s for details
- Actively Contribute
- Join the chat on IRC:
- ја апликацијата
- Преведи
- Download development release directly
- Get development release from F-Droid app
- Get release candidate from F-Droid app
- Get release candidate from Google Play store
- Release candidate
- The release candidate (RC) is a snapshot of the upcoming release and is expected to be stable. Testing your individual setup could help ensure this. Sign up for testing on the Play store or manually look in the \"Version\" section of F-Droid.
- Најдовте грешка?
- Помогни со тестирање
- Пријави проблем на GitHub
- Дали сте заинтересирани за тестирање на тоа што ќе биде во новата верзија?Венсете го вашиот кодКодот ќе биде баран секогаш кога ќе биде покрената апликацијатаВнесете код
@@ -487,7 +461,6 @@
To show mnemonic please enable device credentials.Прикажи известување за медиумиИзвестува за нови пронајдени папки што содржат податоци
- Повратен одговорGNU Општа јавна лиценца, верзија 2ПомошПечат
@@ -544,17 +517,13 @@
Погрешна лозинкаНајавете се преку QR кодЗаштитете ги вашите податоци
- продуктивна платформа која можете сами да ја хостиратеПрелистај и споделисите активности се во вашите раце
- Активности, акции, офлајн датотекисè брзо достапноСите ваши сметкина едно местоАвтоматско прикачувањеза вашите слики & видеа
- Синхронизирај календар & контакти
- со DAVx5 (поранешен DAVdroid)Пребарувај корисници и групиИзбери сеИзбери шаблон
diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml
index 2de2ac63686d..ae2aa018a060 100644
--- a/src/main/res/values-nb-rNO/strings.xml
+++ b/src/main/res/values-nb-rNO/strings.xml
@@ -222,7 +222,6 @@
Håndter kontoerMellomkontoÅpne sidepanel
- Delta%1$s av %2$s brukt%1$s bruktAuto-opplasting
@@ -261,7 +260,6 @@
Gå tilbake til gammel innloggingsmetodeLegg til i favoritterFavoritt
- Ingen app tilgjengelig for å sende e-post!SlettKlarte ikke å laste inn detaljerFil
@@ -379,18 +377,13 @@
Mediafilen kan ikke strømmesMediafilen kunne ikke lesesMediafilen er ikke riktig kodet
- Filen er ikke i en gyldig konto
- Ingen mediafil funnet
- Uventet feil ved spilling av %1$sTidsavbrudd under avspillingsforsøkMediafilen kan ikke spilles med innebygd mediaspillerUstøttet mediakodek
- %1$s avspilling fullførtSpol fremover%1$s musikkspillerSpill eller pauseSpol tilbake
- %1$s (laster)%1$s (spiller)Nyeste førstEldste først
@@ -440,25 +433,6 @@
Operasjonen har blitt avbruttServer er ikke lengre supportert, ver vennlig å oppgradere!Mer meny
- Test utviklerversjonen
- Denne inneholder alle kommende funksjoner og er på knivseggen hva angår utvikling. Feil/feilmeldinger kan oppstå, og i sådant fall, meld fra om dem til oss.
- forumet
- Hjelp andre på
- Gjennomse, endre og skriv kode, se %1$s for detaljer
- Bidra aktivt
- Sludre på IRC:
- Appen
- Oversett
- Last ned utviklingsversjonen direkte
- Hent utviklingsversjonen via F-Droid-appen
- Hent lanseringskandidaten via F-Droid-appen
- Hent lanseringskandidaten via Google Play butikken
- Slippkandidat
- En Slippkandidat (RC) er en øyeblikkspakke av den kommende utgaven og er forventet å være stabil. Ved å teste denne med ditt oppsett hjelper du til å med dette. Meld deg på testing i Play-butikken, eller se i seksjonen \"Versjon\" for programmet i F-Droid.
- Funnet en feil? Føles noe rart?
- Hjelp oss å teste
- Rapporter en feil på GitHub
- Interessert i å prøve ut det som kommer til å bli neste versjon?Skriv inn passordet dittPassordet vil bli krevd hver gang appen startesSett inn passordet ditt
@@ -497,7 +471,6 @@
For å vise mnemonic, ver vennlig å aktiver enhet legitimasjon.Vis mediaskannvarslerVarsle om nye mediamapper
- TilbakemeldingGNU General Public Lisens, versjon 2HjelpAvtrykk
@@ -552,7 +525,6 @@
på et plassAutomatisk opplastningfor dine bilder & videoer
- Synkronisere & kontakterSøk etter brukere og grupperVelg alleVelg mal
diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml
index b73ba5d8884c..959db3f3c953 100644
--- a/src/main/res/values-nl/strings.xml
+++ b/src/main/res/values-nl/strings.xml
@@ -223,7 +223,6 @@
Accounts beheren Gemiddelde accountOpenen zijbalk
- Meedoen%1$s van %2$s gebruikt%1$s gebruiktAutomatisch uploaden
@@ -264,7 +263,7 @@
Terug naar vorige login-methodeToevoegen aan favorietenFavoriet
- Geen app beschikbaar voor versturen mail!
+ bestandVerwijderenFout bij ophalen activiteiten van bestandKon details niet laden
@@ -334,6 +333,7 @@
Beveiligde samenwerking & bestand uitwisselingEenvoudige webmail, agenda & contactenSchermdelen, online afspraken & web conferenties
+ mapMap bestaat alAanmakenMap pictogram
@@ -385,18 +385,13 @@
Mediabestand kan niet worden gestreamdKon het mediabestand niet lezenMediabestand niet goed gecodeerd
- Het bestand hoort niet bij een geldig account
- Geen mediabestand gevonden
- Onverwachte fout tijdens het afspelen %1$sTime-out tijdens het afspelenMediabestand kan niet worden afgespeeld met de standaard mediaplayerNiet-ondersteunde mediacodec
- %1$s afspelen beëindigdDoorspoelknop%1$s muziekspelerSpeel- of pauzeknopTerugspoelknop
- %1$s (laden)%1$s (speelt)Nieuwste eerstOudste eerst
@@ -447,25 +442,6 @@
De bewerking werd geannuleerdDeze server is verouderd, graag upgraden!Meer menu
- Test de dev versie
- Dit omvat alle komende opties en is zeer \'bleeding edge\'. Bugs/fouten kunnen voorkomen en in dat geval vragen we je die aan ons te melden.
- forum
- Help anderen met het
- Bekijk, wijzig en schrijf code, zie %1$s voor details
- Actief meedoen
- Join de chat op IRC:
- de app
- Vertalen
- Haal de development release binnen via directe download
- Download de development release in F-Droid
- Download de release candidate in F-Droid
- Download de release candidate in Google Play
- Release candidate
- De release candidate (RC) is een snapshot van de komende versie en is naar verwachting stabiel. Door een test met je eigen installatie verzeker je hier van. Meld je aan voor het testen in de Play store of kijk handmatig bij sectie \"Versie\" van F-Droid.
- Bug gevonden? Rare dingen?
- Help bij testen
- Meld een probleem op GitHub
- Geïnteresseerd om ons te helpen de volgende versie te testen?Toegangscode invoerenDe toegangscode wordt elke keer gevraagd bij opstarten van de appVoer je toegangscode in
@@ -504,7 +480,6 @@
Om de mnemonic te tonen moet je apparaatinloggegevens inschakelen.Tonen mediascan meldingenMelden van nieuw gevonden mediamappen
- FeedbackGNU General Public Licence, versie 2HelpAfdruk
@@ -561,17 +536,13 @@
Onjuist wachtwoordInloggen via QR codeBeschermt jouw gegevens
- eigen-hosting productiviteitsplatformBlader en deelalle taken onder je vindertoppen
- Activiteiten, delen, off-line bestandenalles snel toegankelijkAl je accountsop één plekAutomatisch uploadenvoor je foto\'s & video\'s
- Synchroniseren agenda & contactpersonen
- met DAVx5 (voorheen DAVdroid)Zoeken naar gebruikers en groepenAlles selecterenKies sjabloon
@@ -589,6 +560,8 @@
Deel %1$sLink ophalen %1$s (groep)
+ Deel interne link
+ Dit werkt enkel voor gebruikers met toegang tot %1$s%1$s ( op %2$s )Je moet een wachtwoord opgevenEr trad een fout op bij je poging dit bestand of deze map te delen
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 031a663454bc..524b4955a7b6 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -223,7 +223,6 @@
Zarządzaj kontamiKonto poprzednieOtwórz pasek boczny
- WspierajWykorzystane: %1$s z %2$sWykorzystane: %1$sAutomatyczne wysyłanie
@@ -264,7 +263,7 @@
Przywróć starą metodę logowaniaDodaj do ulubionychUlubiony
- Brak aplikacji do wysyłania wiadomości!
+ plikUsuńBłąd podczas pobierania aktywności dla plikuNie udało się załadować szczegółów
@@ -334,6 +333,7 @@
Bezpieczna współpraca i wymiana plikówŁatwy w użyciu klient poczty, kalendarz i kontaktyUdostępnianie ekranu, spotkania i konferencje online
+ katalogKatalog już istniejeUtwórzIkona katalogu
@@ -385,18 +385,13 @@
Plik multimedialny nie może być przesyłany strumieniowoNie można odczytać plikuNieprawidłowe kodowanie pliku multimedialnego
- Plik na nieprawidłowym koncie
- Nie znaleziono plików multimedialnych
- Nieoczekiwany błąd podczas próby odtworzenia %1$sUpłynął limit czasu podczas próby odtwarzania plikuWbudowany odtwarzacz multimedialny nie może odtworzyć plikuNieobsługiwany kodek multimediów
- %1$s odtwarzanie zakończonePrzycisk przewijania do przodu%1$s odtwarzacz muzykiPrzycisk odtwarzania/pauzowaniaPrzycisk przewijania do tyłu
- %1$s (ładowanie)%1$s (odtwarzanie)Od najnowszychOd najstarszych
@@ -447,25 +442,6 @@
Operacja została anulowanaSerwer korzysta ze starej wersji. Dokonaj aktualizacji!Więcej
- Sprawdź wersję deweloperską
- Zawiera w sobie wszystkie nadchodzące funkcje i może być mało stabilna. Przy występowaniu błędów, proszę zgłosić je do nas.
- forum
- Pomóż innym na
- Przejrzyj, zmodyfikuj i napisz kod, zajrzyj do %1$s, aby poznać szczegóły
- Aktywne uczestniczenie
- Dołącz do kanału na IRC:
- aplikację
- Tłumacz
- Pobierz wersję deweloperską bezpośrednio
- Pobierz wersję deweloperską z F-Droid
- Pobierz wersję w wydaniu RC z F-Droid
- Pobierz wersję w wydaniu RC ze sklepu Google Play
- Release Candidate
- Release Candidate (RC) jest zwiastunem nadchodzącego wydania i oczekuje się od niej, że będzie stabilna. Testowanie indywidualnej konfiguracji przez użytkownika może w tym pomóc. Aby przetestować, zarejestruj się w sklepie Play lub zajrzyj do sekcji \"Wersja\" na F-Droid.
- Znalazłeś błąd? Jest coś, co chciałbyś poprawić?
- Pomóż nam testować
- Zgłoś problem na GitHub
- Chcesz pomóc nam w testowaniu następnej wersji?Wprowadź kod PINKod PIN będzie wymagany przy każdym uruchomieniu aplikacjiPodaj kod PIN
@@ -504,7 +480,6 @@
Aby pokazać mnemonik włącz poświadczenia urządzenia.Pokaż powiadomienia o skanowaniu multimediówPowiadamiaj o nowo znalezionych katalogach multimedialnych
- OpinieGNU General Public License, version 2PomocStopka
@@ -561,17 +536,17 @@
Złe hasłoZaloguj się za pomocą kodu QROchrona Twoich danych
- samo-hostująca platforma produkcyjna
+ produkcyjny samo-hostującyPrzeglądaj i udostępniajwszystkie działania na wyciągnięcie ręki
- Aktywność, udostępnienia, pliki offline
+ Aktywność, udostępnienia...wszystko szybko dostępneWszystkie Twoje kontaw jednym miejscuAutomatyczne przesyłaniedla Twoich zdjęć & wideo
- Synchronizuj kontakty kalendarza &
- z DAVx5 (dawniej DAVdroid)
+ Kontakty w kalendarzu: &
+ Synchronizuj z DAVx5Szukaj użytkowników i grupWybierz wszystkoWybierz szablon
@@ -589,6 +564,8 @@
Udostępnij %1$sPobierz link%1$s (grupa)
+ Udostępnij link wewnętrzny
+ Działa tylko dla użytkowników mających dostęp do %1$s%1$s ( w %2$s )Musisz wprowadzić hasłoWystąpił błąd podczas udostępniania tego pliku lub katalogu
@@ -608,9 +585,9 @@
Zatrzymaj udostępnianie%1$s (zdalny)%1$s (konwersacja)
- Nazwa, ID chmury federalnej lub adres e-mail…
+ Nazwa, ID chmury federacyjnej lub adres e-mail…Notatka dla odbiorcy
- Pozwól na edycję
+ Zezwalaj na edycjęUstaw datę wygaśnięciaUkryj pobieranieSchowaj listę plików
@@ -721,7 +698,7 @@
Wystąpił błąd podczas anulowania udostępniania tego pliku lub katalogu.Nie można wyłączyć udostępniania. Sprawdź, czy plik istniejeaby anulować udostępnianie tego pliku
- Usuwanie udostępnienia nie powiodło się
+ Zatrzymywanie udostępnienia nie powiodło sięDostęp przez niezaufaną domenę. Więcej informacji można znaleźć w dokumentacji.Wystąpił błąd podczas próby aktualizacji udostępnianiaNie można zaktualizować. Sprawdź, czy plik istnieje.
diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml
index f4707ab90446..dd545cf7f9f3 100644
--- a/src/main/res/values-pt-rBR/strings.xml
+++ b/src/main/res/values-pt-rBR/strings.xml
@@ -223,7 +223,6 @@
Gerenciar contasConta médiaAbrir barra lateral
- Participar%1$s de %2$s usados%1$s usadosAuto envio
@@ -264,7 +263,6 @@
Voltar ao antigo método de loginAdicionar aos favoritosFavorito
- Sem aplicativo para enviar e-mails!arquivoExcluirErro ao recuperar atividades do arquivo
@@ -387,18 +385,13 @@
O arquivo de mídia não pode ser enviado via streamingNão foi possível ler o arquivo de mídiaO arquivo de mídia tem uma codificação incorreta
- O arquivo não é uma conta válida
- Nenhum arquivo de mídia encontrado
- Erro inesperado ao tocar %1$sTempo esgotado tentando reproduzir o arquivoO media player embutido não consegue tocar esta mídiaO codec de mídia não é suportado
- %1$s playback terminadoBotão de avanço rápido%1$s playerBotão reproduzir ou pausarBotão rebobinar
- %1$s (carregando)%1$s (tocando)Mais novos primeiroMais antigos primeiro
@@ -449,25 +442,6 @@
Operação canceladaO servidor chegou ao fim da vida. Por favor, atualize!Mais menus
- Teste a versão dev
- Isso inclui todos os recursos futuros e está num limite perigoso. Erros podem ocorrer e acontecendo, por favor reporte-os à nós.
- forum
- Ajude outros no
- Revise, modifique e escreva código. Veja detalhes em %1$s
- Contribuir ativamente
- Junte-se ao chat no IRC:
- o aplicativo
- Traduza
- Baixe a versão de desenvolvimento diretamente
- Obtenha a versão de desenvolvimento no F-Droid
- Obtenha a versão de teste no F-Droid
- Obtenha a versão de teste no Google Play
- Candidato a versão
- O release candidate (RC) é uma cópia de uma nova versão e espera-se que seja estável. Testar sua própria configuração pode garantir isso. Assine para testes na Play Store ou manualmente procure na seção \"Version\" do F-Droid,
- Encontrou um erro? Comentários?
- Ajuda para teste
- Relate um problema no GitHub
- Tem interesse em ajudar nos testes da próxima versão?Digite o código de acessoO código de acesso será solicitado toda vez que o aplicativo for iniciadoDigite sua senha
@@ -506,7 +480,6 @@
Para exibir o mnemônico, ative as credenciais do dispositivo.Exibir notificações de varredura de mídiaNotificar sobre pastas de mídia recém-encontradas
- FeedbackGNU Licença Pública Geral, version 2AjudaImprimir
@@ -563,17 +536,17 @@
Senha incorretaEntrar via código QRProtegendo seus dados
- plataforma de produtividade auto-hospedada
+ produtividade auto hospedadaNavegar e compartilhartodas as ações na ponta de seus dedos
- Atividade, compartilhamentos e arquivos offline
+ Atividades, compartilhamentos, ...tudo rapidamente acessívelTodas as suas contasem um lugarEnvio automáticopara suas fotos & vídeos
- Sincronizar calendário & contatos
- com DAVx5 (também chamado DAVdroid)
+ Calendário & contatos
+ Sincronizar com DAVx5Pesquisar usuários e gruposSelecionar tudoSelecionar modelo
diff --git a/src/main/res/values-pt-rPT/strings.xml b/src/main/res/values-pt-rPT/strings.xml
index 13bab411b0e1..72183a330c8b 100644
--- a/src/main/res/values-pt-rPT/strings.xml
+++ b/src/main/res/values-pt-rPT/strings.xml
@@ -198,7 +198,6 @@
Gerir contasConta intermédiaAbrir barra lateral
- ParticipeUsado %1$s de %2$s%1$s utilizadoCarregamento automático
@@ -233,7 +232,6 @@
Reverter para método de início de sessão anteriorAdicionar aos favoritosFavorito
- Nenhuma aplicação disponível para enviar e-mails!ApagarFalha ao carregar detalhesFicheiro
@@ -345,18 +343,13 @@
O ficheiro de multimédia não pode ser transmitidoNão foi possível ler o ficheiro de multimédiaO ficheiro de multimédia tem codificação incorreta
- O ficheiro não está numa conta válida
- Não foi encontrado nenhum ficheiro de multimédia
- Erro inesperado ao tentar reproduzir %1$sTentativa de reproduzir o ficheiro expirouO leitor multimédia pré-configurado é incapaz de reproduzir o ficheiro de multimédiaCodec de multimédia não suportado
- %1$s reprodução terminouBotão de Avançar Rápido%1$s reprodutor de músicaBotão de Reproduzir/PausarBotão de Retroceder
- %1$s (a carregar)%1$s (a reproduzir)Recentes primeiroAntigos primeiro
@@ -406,25 +399,6 @@
A operação foi canceladaO servidor chegou ao fim de vida, por favor, actualize-o!Menu mais
- Teste a versão de desenvolvimento
- Inclui todas as futuras e mais recentes funcionalidades. Falhas/erros podem ocorrer, se e quando tal acontecer, por favor, reporte as suas descobertas.
- fórum
- Ajude outros em
- Reveja, emende e escreva código, veja %1$s para detalhes
- Contribua activamente
- Junte-se à conversação em IRC:
- a aplicação
- Traduzir
- Transfira diretamente a versão de desenvolvimento
- Obter versão de desenvolvimento a partir da aplicação F-Droid
- Obter versão candidata a lançamento a partir da aplicação F-Droid
- Obter versão candidata a lançamento a partir da loja Google Play
- Versão candidata a lançamento
- A versão candidata a lançamento (RC) é um snapshot da próxima versão e é espectável que seja estável. Através do teste da sua configuração individual pode ajudar-nos a assegurá-lo. Para testar, inscreva-se na loja Play ou verifique a secção \"Version\" do F-Droid.
- Encontrou um erro? Ocorrências estranhas?
- Ajude-nos testando
- Reportar um problema no Github
- Interessado em ajudar testando a próxima versão?Insira o seu códigoO código será solicitado sempre que a aplicação é iniciadaPor favor, insira o seu código
@@ -463,7 +437,6 @@
Para mostrar a mnemónica, por favor, ative as credenciais do dispositivo.Mostrar notificações de análise de multimédiaNotificar sobre novas pastas multimédia encontradas
- OpiniãoGNU - Licença Pública Geral, Versão 2AjudaInformação
diff --git a/src/main/res/values-ro/strings.xml b/src/main/res/values-ro/strings.xml
index 4eb68946e546..0bd9dd4c70cb 100644
--- a/src/main/res/values-ro/strings.xml
+++ b/src/main/res/values-ro/strings.xml
@@ -207,7 +207,6 @@
IeșireAdministrare conturiDeschide bara laterală
- Participă%1$s din %2$s folosiți%1$s utilizatÎncărcare automată
@@ -219,9 +218,17 @@
Introduceti parola pentru decriptare cheie personală.Directorul nu este gol.Se generează chei noi…
+ Criptarea end-to-end este dezactivată pe server.
+ Criptarea funcționează începând cu versiunea KitKat(4.4) sau mai mare.
+ Te rog salvează parola de criptare formată din cele 12 cuvinteParolă…
+ Setează criptarea
+ Nu s-au putut salva cheile de criptare, te rog încearcă din nou.
+ Eroare la decriptare. Parola greșită?
+ Te rog sa introduci un nume de fișier%1$s nu a putut fi copiat in dosarul local %2$s Eroare critică: nu se pot executa operațiunile
+ RaportÎnapoiAdăugați la favoriteFavorite
@@ -325,18 +332,13 @@
Fișierul media nu poate fi transmisNu s-a putut citi fișierul mediaFișierul media are codare incorectă
- Fișierul nu este într-un cont valid
- Nu s-a găsit nici un fișier media
- Eroare neașteptată în timpul redării %1$sÎncercarea de a reda fișierul a expiratMedia player-ul încorporat nu poate reda fișierul mediaCodec media fără suport
- Redare terminată %1$sButonul pentru repede înainte%1$sButon de play sau pauză Butonul rewind
- %1$s (încărcare)%1$s (redare)Cel mai nou mai întâiCel mai vechi mai întâi
@@ -363,16 +365,6 @@
Te rugăm să încerci mai târziu.Nu există conexiune la internetOperația a fost anulată
- Testează versiunea în dezvoltare
- forum
- Contribuie în mod activ
- aplicația
- Tradu
- Candidatul pentru lansare
- Ai găsit un bug? Se intamplă ceva ciudat?
- Ajută-ne să testăm
- Raportează o problemă pe Github
- Vrei să ne ajuți în testarea următoarei versiuni?Introdu parolaParola va fi solicitată de fiecare dată când deschideți aplicațiaVă rugăm introduceți parola
@@ -407,7 +399,6 @@
Salvarea zilnică a contactelor dvs.Afișează notificări despre scanarea mediaNotifică despre dosare media noi găsite
- FeedbackAjutorImprintFișierul original va fi…
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 696e0c981b56..d7b8f2f3aa63 100644
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -223,7 +223,6 @@
Управление аккаунтамиСредняя учётная записьОткрыть боковую панель
- Участиеиспользовано %1$s из %2$s %1$s использованоАвтоматическая передача
@@ -264,7 +263,6 @@
Использовать прежний метод авторизацииДобавить в избранноеИзбранное
- Отсутствует приложение для работы с эл. почтой.УдалитьОшибка получения истории событий, связанных с файломНе удалось получить подробные сведения
@@ -385,18 +383,13 @@
Невозможно организовать потоковую передачу медиафайлаМедиафайл не может быть прочитанМедиафайл некорректно закодирован
- Файл в неверном аккаунте
- Медиафайлы не найдены
- Неожиданная ошибка воспроизведения «%1$s»Истекло время попытки воспроизведенияФайл не может быть воспроизведён встроенным мультимедийным проигрывателемНеподдерживаемый кодек
- %1$s воспроизведение завершеноПеремотка вперед%1$s аудиоплеерВоспроизведение или паузаПеремотка назад
- %1$s (загружается)%1$s (проигрывается)Сначала новыеСначала старые
@@ -446,25 +439,6 @@
Операция отмененаВерсия сервера Nextcloud более не поддерживается, требуется её обновление.Дополнительное меню
- Протестируйте разрабатываемую версию
- Она включает в себя все готовящиеся возможности с самой передовой. Могут появляться баги, в таком случае, напишите о них нам.
- форуме
- Помочь другим пользователям на
- Просмотрите, измените и напишите код, подробности смотрите в %1$s
- Активное участие
- Присоединиться к обсуждению на канале IRC:
- приложение
- Помочь с переводом
- Скачать предвыпускную версию
- Скачать разрабатываемую версию из магазина приложений F-Droid
- Скачать предвыпускную версию из магазина приложений F-Droid
- Скачать предвыпускную версию из магазина приложений Google Play
- Предвыпускная версия
- Предвыпускная версия — это текущее состояние готовящегося выпуска, и, ожидается, что он будет стабильным. Проверка с вашими индивидуальными настройками может помочь убедится в этом. Зарегистрируйтесь для тестирования в Google Play или загляните в раздел «версии» каталога приложений F-Droid.
- Нашли ошибку? Заметили необычное поведение программы?
- Помогите нам в тестировании
- Сообщить о проблеме на GitHub
- Хотите помочь проверкой новых функций, разрабатываемых для следующей версии?Введите кодКод будет запрашиваться каждый раз при запуске приложенияВведите ваш код
@@ -476,7 +450,7 @@
Некорректный кодРазрешитьЗапретить
- Для загрузки и скачивания файлов требуются дополнительные права доступа.
+ Для отправки и скачивания файлов требуются дополнительные права доступа.Не найдено приложений для обработки изображений389 Кбайтplaceholder.txt
@@ -503,7 +477,6 @@
Для просмотра мнемофразы включите системную защитную блокировку.Показывать уведомления о сканировании медиафайловУведомлять о найденных новых каталогах с медиафайлами
- Обратная связьОткрытое лицензионное соглашение GNU, версия 2ПомощьОтпечаток
@@ -560,8 +533,6 @@
в одном местеАвтоматическая загрузкаваших фото & видео
- Синхронизуйте календарь & контакты
- с DAVx5 (бывший DAVdroid)Поиск пользователей и группВыбрать всеВыбрать шаблон
@@ -677,7 +648,7 @@
Ошибка синхронизации, необходимо авторизоватьсяСодержимое файла уже синхронизированоСинхронизация %1$s папки(-ок) не может быть завершена.
- Начиная с версии 1.3.16, файлы, загружаемые с этого устройства, копируются в локальный каталог %1$s, чтобы предотвратить потерю данных при синхронизации файла с несколькими учётными записями.\n\nПоэтому все файлы, загруженные предыдущими версиями данного приложения, были скопированы в каталог %2$s. Однако, во время синхронизации что-то помешало завершить эту операцию. Можете оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, c которыми они связаны.
+ Начиная с версии 1.3.16, файлы, отправляемые с этого устройства, копируются в локальный каталог %1$s, чтобы предотвратить потерю данных при синхронизации файла с несколькими учётными записями.\n\nПоэтому все файлы, отправленные предыдущими версиями данного приложения, были скопированы в каталог %2$s. Однако, во время синхронизации что-то помешало завершить эту операцию. Можете оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, c которыми они связаны.Некоторые загруженные файлы не были перенесены в локальную папку Дождитесь получения новой версии файла.Кнопка статуса синхронизации
@@ -753,7 +724,7 @@
исходный каталог доступен только для чтения, файл будет только передан на серверОставить файл в исходном каталогеУдалить файл из исходного каталога
- для загрузки в этот каталог
+ для отправки в этот каталог%1$d%% «%2$s»Передача…Передача файла «%1$s» завершена
diff --git a/src/main/res/values-sk-rSK/strings.xml b/src/main/res/values-sk-rSK/strings.xml
index 587290d1164a..730b22f1387d 100644
--- a/src/main/res/values-sk-rSK/strings.xml
+++ b/src/main/res/values-sk-rSK/strings.xml
@@ -223,7 +223,6 @@
Správa účtovStredný účetOtvoriť bočný panel
- Zúčastniť saVyužité: %1$s z %2$s%1$s použitýchAutomatické nahratie
@@ -264,7 +263,6 @@
Vrátiť sa k pôvodnému spôsobu prihláseniaPridať do obľúbenýchObľúbené
- Aplikácia na odoslanie e-mailu nenájdená!ZmazaťChyba pri načítavaní aktivít pre súborNepodarilo sa načítať podrobnosti
@@ -385,18 +383,13 @@
Multimediálny súbor sa nedá streamovaťNepodarilo sa prečítať súbor médiíSúbor médií má neplatné kódovanie
- Súbor nie je v platnom účte
- Nenájdený žiaden multimediálny súbor
- Neočakávaná chyba pri prehrávaní %1$sVypršal čas pri pokuse o prehratieVstavaný prehrávač médií nedokáže prehrať mediálny súborNepodporovaný kodek
- %1$s prehrávanie dokončenéTlačidlo \"rýchlo vpred\"Hudobný prehrávač %1$sTlačidlo prehrávania / pauzyTlačidlo pretáčania
- %1$s (načítava sa)%1$s (prehráva sa)Najnovšie prvéNajstaršie prvé
@@ -447,25 +440,6 @@
Operácia bola zrušenáVerzia servra už nie je podporovaná, prosím aktualizujte!Ďalšie menu
- Testovať vývojovú verziu
- Táto verzia obsahuje všetký pripravované funkcie. Je veľmi nestabilná a môže obsahovať chyby. Ak na ne natrafíte, prosím nahláste nám ich.
- fórum
- Pomôžte ostatným s
- Prispejte ako vývojár, pre viac informácií pozrite %1$s
- Aktívne prispievať
- Pripojte sa k rozhovoru na IRC:
- aplikácia
- Preložiť
- Získajte vývojovú verziu
- Získajte vývojovú verziu z F-Droid
- Získajte kandidáta na vydanie z F-Droid
- Získajte kandidáta na vydanie z obchodu Google Play
- Release candidate
- Táto aplikácia je kandidátom na vydanie a mala by byť stabilná. Testovanie s Vašim nastavením nám môže pomôcť. Prihláste sa na testovanie na Play store alebo manuálne skontrolujte \"Verziu\" na F-Droid.
- Našli ste chybu? Niečo nefunguje?
- Pomôžte s testovaním
- Nahlásiť chybu na Githube
- Máte záujem pomôcť nám s testovaním ďalšej Verzie?Vložte svoje hesloHeslo bude nutné zadať vždy po štarte aplikácieVložte prosím svoje heslo
@@ -504,7 +478,6 @@
Pre zobrazenie mnemotechnické, zapnite prihlasovacie údaje zariadenia.Zobrazovať notifikácie vyhľadávania médiíUpozorniť na nové mediálne priečinky
- Spätná väzbaGNU Všeobecná Verejná Licencia, Verzia 2PomocPodmienky používania
@@ -558,17 +531,13 @@
Nesprávne hesloPrihlásiť sa pomocou QR kóduChránte vaše údaje
- Platforma pre produktivitu s vlastným hosťovanímPrehliadajte a zdieľajtevšetky akcie na dosah ruky
- Aktivity, zdieľania, offline súboryvšetko rýchlo prístupnéVšetky vaše účtyna jednom miesteAutomatické nahrávanievašich fotiek a videí
- Synchronizácia kalendára a kontaktov
- pomocou DAVx5 (pôvodne DAVdroid)Vyhľadať používateľov alebo skupinyVybrať všetkoVybrať šablónu
diff --git a/src/main/res/values-sl/strings.xml b/src/main/res/values-sl/strings.xml
index 18526e933564..19c913963809 100644
--- a/src/main/res/values-sl/strings.xml
+++ b/src/main/res/values-sl/strings.xml
@@ -204,6 +204,7 @@
SkupnostTrenutni računZadnji račun
+ Slika ozadja glave bočnega oknaDejavnostiVse datotekePriljubljene
@@ -221,7 +222,6 @@
Uredi računeSrednji računOdpri bočno okno
- Možnosti sodelovanja%1$s od %2$s uporabljenoSkupaj %1$sSamodejno pošiljanje
@@ -233,6 +233,9 @@
Vpišite geslo za odšifriranje zasebnega ključa.Mapa ni prazna.Poteka ustvarjanje novih ključev ...
+ Vseh 12 besed skupaj predstavlja izredno močno geslo, s katerim imate le vi možnost ogleda in uporabe šifriranih datotek. Geslo si zapišite in shranite nekje na varnem.
+ Šifriranje deluje le na različici KitKat (4.4) in višjih.
+ Zabeležite si vaših 12 besed gesla za šifriranjeGeslo …Poteka pridobivanje ključev ...Shranjevanje ključev
@@ -245,18 +248,22 @@
Napaka dodajanja opombe k datotekiProgram %1$s se je sesul.Poročilo
+ Ali želite poslati poročilo na sledilnik (zahteva račun Github)?Pridobivanje datoteke je spodleteloNapaka pridobivanja predlogNapaka začenjanja kamereNastavitve
+ Inženirski preizkusni načinDodaj ali pošlji
+ Podajanje datoteke na upravljalnik prenosov je spodleteloTiskanje datoteke je spodleteloNazajPovrni na star način prijaveDodaj med priljubljenePriljubljeno
- Program za pošiljanje elektronskih sporočil ni določen!
+ datotekaIzbriši
+ Napaka pridobivanja dejavnosti za datotekoNalaganje podrobnosti je spodletelo.DatotekaOhrani
@@ -307,6 +314,7 @@
UporabiV čakanju na polno uskladitev …Datoteke ni mogoče najti
+ Datoteke ni mogoče uskladiti. Prikazana je zadnja razpoložljiva različica.PreimenujPrišlo je do napake med obnavljanjem različice datoteke!Različica datoteke je uspešno obnovljena.
@@ -318,6 +326,12 @@
Nedovoljeni znaki: / \\ < > : \" | ? *Ime datoteke vsebuje vsaj en neveljaven znak.Ime datoteke
+ To je zmožnost oblaka Nextcloud. Okolje je treba nadgraditi.
+ Hranite podatke varno in pod vašim nadzorom
+ Varno sodelovanje in izmenjava datotek
+ Enostavna uporaba spletne pošte, koledarja in stikov
+ Souporaba zaslona, spletni sestanki in konference
+ mapaMapa že obstajaUstvariIkona mape
@@ -358,22 +372,24 @@
Preišči dnevnikePošlji dnevnike po elektronski poštiDnevniki: %1$d kB, poizvedba kaže na %2$d / %3$d zadetkov v %4$d ms
- Poteka nalaganje…
+ Poteka nalaganje …Dnevniki: %1$d kB, brez filtraDnevnikiStrežnik je v načinu vzdrževanjaPočisti podatke
+ Nastavitve, podatkovna zbirka, in potrdila strežnika na računu %1$s bodo trajno izbrisana. \n\nPrejete datoteke bodo ostale nedotaknjene.\n\nOpravilo je lahko dolgotrajno.Upravljanje s prostoromPredstavne datoteke ni mogoče prikazati v pretokuPredstavne datoteke ni mogoče prebrati.Predstavna datoteka ni pravilno kodirana.
- Dokument ni shranjen v veljavnem računu.
- Predstavnih datotek ni mogoče najtiPoskus predvajanja je časovno potekel
+ Vgrajen predvajalnik predstavnih vsebin ne podpira podane vrsta datotekeNepodprt predstavni kodekGumb za hitro predvajanje naprej
+ Predvajalnik glasbe %1$sGumb za predvajanje in premorVrni nazaj
+ %1$s (predvajanje)najprej najnovejšenajprej najstarejšepo abecedi naraščajoče A–Ž
@@ -387,8 +403,10 @@
Premakni v …Prišlo je do napake med čakanjem odziva strežnika. Opravila ni mogoče končati.Med povezovanjem s strežnikom je prišlo do napake.
+ Prišlo je do napake med čakanjem na strežnik. Opravila ni mogoče končati.Zahtevanega dejanja ni bilo mogoče končati. Strežnik ni dosegljivNova opomba …
+ Zaznana je nova mapa s predstavno vsebino: %1$s .slikavideoNovo obvestilo
@@ -397,13 +415,17 @@
Dovoljen je le en računNi nameščenega programa za odpiranje datotek PDFPošlji
+ Sporočilca ni mogoče poslatiIzvedba dejanja je spodletelaPokaži napredek prejemanja
+ Kanal obvestil prejemanjaPreverjaj datoteke za spremembeOpazovalnik datotekPokaži napredek usklajevanja datotek in končana opravilaUsklajevanje datotek
+ Pokaži obvestila za zaznane nove mape predstavnih vsebin in podobnoSplošna obvestila
+ Napredek glasbenega predvajalnikaPredvajalnik predstavnih datotekPotisna obvestilaPokaži napredek pošiljanja
@@ -414,24 +436,6 @@
Prosim, preveri kasnejeNi vzpostavljene internetne povezaveOpravilo je preklicano
- Preizkušanje razvojne različice
- To vključuje vse prihajajoče zmožnosti programa. Hrošči in napake se pojavljajo in ko se, je priporočljivo poslati poročilo za razhroščevanje.
- forumu
- Pomoč uporabnikom na
- Preglejte, razvijajte in dopolnite kodo ... več o tem na %1$s
- Dejaven doprinos
- Pridružite se klepetu na kanalu IRC:
- programa
- Pomoč pri prevajanju
- Pridobite razvojno različico s programom F-Droid
- Pridobite različico predogleda s programom F-Droid
- Pridobite različico predogleda v trgovini Google Play
- Različica za objavo
- Različica preizkusne objave (RC) predstvlja zadnji predogled naslednje stabilne različice. Preizkušanje pomaga zagotoviti stabilnost. Sodelovanje je mogoče s prijavo v PlayStore oziroma ročno nastavitvijo »različice« med nastavitvami programa F-Droid.
- Ali ste našli hrošča, neustrezno delovanje?
- Pomoč s preizkušanjem
- Prijava težave na GitHub
- Ali bi morda želeli sodelovati pri preizkušanju razvojne različice?Vnesite kodo PIN programaKoda bo zahtevana vsakič pred zagonom programa.Vnesite kodo PIN
@@ -444,6 +448,7 @@
DovoliZavrniZa prejemanje oziroma pošiljanje datotek v oblak so zahtevana dodatna dovoljenja.
+ Ni najdenega programa za nastavitev slike389 KBvsebnik.txt12:23:45
@@ -461,11 +466,13 @@
SplošnoVečDnevno usklajevanje stikov
+ Pokaži obvestila iskanja predstavnih vsebinObvesti o na novo zaznanih mapah s predstavno vsebino
- Pošlji sporočilo razvijalcem programaSplošno Javno dovoljenje GNU GPL, različice 2PomočNatis
+ Izvorna datoteka bo ...
+ Izvorna datoteka bo ...Shrani v podmape kot leto in mesecUporabi podmapeDovoljenje
@@ -473,6 +480,7 @@
Poverila naprave so omogočenaNi nastavljenih poveril napraveBrez
+ Zaščiti program z uporaboPoverila napraveKoda PINUpravljanje z računi
@@ -480,8 +488,11 @@
Pokaži skrite datotekePridobi izvorno kodoMesto shrambe
- Lokalna mapa
+ Krajevna mapaMapa na strežniku
+ Tema
+ Temna
+ SvetlaPredogled slikeNi krajevne datoteke za predogledTe slike ni mogoče prikazati
@@ -489,6 +500,7 @@
Poskusite %1$s na vaši napravi!Želim priporočiti program %1$s!\nPrejeti ga je mogoče prek: %2$s%1$s ali %2$s
+ Iskanje datoteke je spodletelo!Brisanje je spodleteloOdstranjevanje obvestil je spodletelo.Odstrani
@@ -496,6 +508,9 @@
Vnesite novo imeNi mogoče preimenovati krajevne kopije. Poskusite vpisati drugačno ime.Preimenovanje ni mogoče. Ime je že uporabljeno.
+ Osveževanje ni dovoljena
+ Osveževanje ni dovoljeno
+ Slika prilagojene velikosti ni na voljo.Obnovi datotekoObnovi izbrisano datotekoPoteka pridobivanje datoteke ...
@@ -503,12 +518,13 @@
Zahtevano je gesloNapačno gesloPrijava s kodo QR
+ Zaščita podatkov
+ Brskanje in souporaba
+ Dejavnosti, souporaba ...Vsi vaši računina enem mestuSamodejno pošiljanjeza vaše slike in posnetke
- Usklajevalnik koledarja in stikov
- z DAVx5 (prej poimenovan DAVdroid)Iskanje uporabnikov in skupinIzberi vseIZbor predloge
@@ -524,31 +540,40 @@
Omogoči souporabo %1$sPridobi povezavo%1$s (skupina)
+ Notranja povezava mesta souporabe%1$s ( pri %2$s )Vpisati je treba geslo.Prišlo je do napake med poskusom omogočanja souporabe te datoteke ali mapeSouporaba ni mogoča. Preverite, ali datoteka obstaja.za omogočanje souporabe datoteke.
+ Vpis gesla na željoVpis geslaNastavi datum pretekaNastavi gesloNi datotek, ki bi jih omogočili za souporabo z drugimi
+ Zaščiteno z geslomlahko urejalahko spremenilahko ustvarilahko izbrišelahko omogoči souporabo
+ Prekini souporabo%1$s (oddaljeno)
+ Ime, ID zveznega oblaka ali elektronski naslov ...
+ Sporočilo za prejemnikaDovoli urejanjeNastavi datum pretekaSkrij prejemSkrij spisek datotek
+ Zaščiti z geslom (%1$s)Zaščiti z geslom
+ VarovanoOmogoči souporabo prek povezavePošlji povezavoOmogoči souporabo z %1$sOmogoči souporabo …Omogoči souporabo s skupino ali z uporabnikom
+ Prijava prek ponudnikaRazvrstinajprej najnovejšenajprej najstarejše
@@ -590,6 +615,7 @@
VideoGlasbaSlike
+ Pretakaj s programom ...Notranji pretok ni mogoč\"%1$s\" vam je oddan v souporaboUporabnik %1$s je omogočil souporabo \"%2$s\"
@@ -602,6 +628,7 @@
Usklajevanje je spodletelo; zahtevana je ponovna prijava.Vsebina datoteke je že usklajenaNekatere krajevne datoteke so spregledane
+ Gumb stanja usklajevanjaDatotekeGumb nastavitevNastavi mape
@@ -618,8 +645,12 @@
Nalaganje poteka nepričakovano dolgo ...Izbrisane datotekeNi izbrisanih datotek
+ Datoteke %1$s ni mogoče izbrisati!Datoteke %1$s ni mogoče obnoviti!
+ Nalaganje smeti je spodletelo!
+ Datotek ni mogoče trajno izbrisati!Odstrani šifriranje
+ Odstrani iz priljubljenihPrišlo je do napake med poskusom odstranjevanja souporabe te datoteke ali mapeNi mogoče prekiniti souporabe. Preverite, ali datoteka obstaja.za preklic souporabe datoteke.
diff --git a/src/main/res/values-sq/strings.xml b/src/main/res/values-sq/strings.xml
index 0a4956341cd7..fb26f8d71b48 100644
--- a/src/main/res/values-sq/strings.xml
+++ b/src/main/res/values-sq/strings.xml
@@ -169,6 +169,7 @@
Kopjoje lidhjenKopjimi/lëvizja në direktorinë e enkriptuar për momentin nuk suportohet.Kopjojeni tek…
+ Nuk mund të shkarkohet imazhi i plotëUrl nuk mund të merrejS’u krijua dot dosjaDuke krijuar skedar nga shablloni...
@@ -201,6 +202,7 @@
U shkarkuaEnde e pashkarkuarMbyllni sidebar-in
+ KomunitetiLlogaria e tanishmeLlogaria e funditImazhi në background i drawer header
@@ -221,7 +223,6 @@
Menaxhoni llogaritëLlogari e mesitHapni sidebar-in
- Merni pjesë%1$s e %2$s përdorur%1$s të përdoruraNgarkim automatik
@@ -257,12 +258,14 @@
Provë e mënyrës inxhinierikeShtoni ose ngarkoniKalimi i skedarit për të shkarkuar menaxherin dështoi
+ Printimi i skedarit dështoiPrapaRikthehu në metodën e vjetër te identifikimitShtoje tek të parapëlqyeratE parapëlqyer
- Nuk ka asnjë aplikacion në dispozicion për të dërguar mesazhe!
+ skedariFshini
+ Gabim gjatë marrjes së aktiviteteve të skedaritDështoi ngarkimi i detajeveSkedar Mbaje
@@ -330,6 +333,7 @@
Bashkëpunim i sigurtë &skedar i ndryshuarE-mail i thjeshtë për t\'u përdorur në web, kalendari &i kontakteveNdarja e ekranit, takime në internet & konferenca në web
+ dosjaDosja ekziston tashmëKrijoIkona e dosjes
@@ -381,19 +385,13 @@
Skedari media s’mund të transmetohetS\'u lexua dot skedari mediaSkedar media i koduar jo si duhet
- Skedar jo në llogari të vlefshme
- S’u gjetën skedar media
- Gabim i papritur gjatë përpjekjes për të luajtur %1$s
-Mbaroi koha, teksa përpiqej të luhejNdërtuesi i integruar i medias nuk mund të luajë skedarin medialKodek mediash i pambuluar
- Luajtja e %1$s përfundoiButoni PërparaLojtësi %1$s i muzikësButoni Luaje ose PushojeButoni Mbrapsht
- %1$s (po ngarkohet)%1$s (po luhet)Më i riu në fillimMë i vjetri në fillim
@@ -417,6 +415,7 @@
Njoftim i riU krijua versioni i riNuk ka asnjë aplikacion në dispozicion për të trajtuar lidhjet
+ Vetëm një llogari e lejuarNuk ka asnjë aplikacion në dispozicion për të trajtuar PDFDërgoShënimi nuk mund të dërgohej
@@ -443,25 +442,6 @@
Operacioni është anuluarServeri ka arritur fundin e jetës, ju lutemi përmirësojeni!Më shumë menu
- Testoni versionin Beta
- Kjo përfshin të gjitha karakteristikat që do të vijnë dhe ky është vetëm fillimi. Buget/gabimet mund të ndodhin, nëse dhe kur të ndodhin, ju lutemi të raportoni për gjetjet tuaja.
- forum
- Ndihmo të tjerët në
- Rishiko, ndrysho dhe shkruaj kod, shi %1$s për detaje
- Kontribim aktiv
- Bashkohu bisedës në IRC:
- aplikacioni
- Përkthe
- Shkarkoni direkt lirimin e zhvillimit
- Merni lirimin e zhvillimit nga dyqani i F-Droid
- Merni lirimin kandidat nga dyqani F-Droid app
- Merni lirimin kandidat nga dyqani Google Play
- Lëshimi i kandidatit
- Kandidati për lirim (RC) është një fotografi e lirimit të ardhshëm dhe pritet të jetë i qëndrueshëm. Testimi i konfigurimit tuaj individual mund të ndihmojë në sigurimin e kësaj. Regjistrohuni për testim në Play Store ose shikoni manualisht në seksionin \"Version\" të F-Droid.
- Gjetet një defekt? Diçka është e çuditshme?
- Ndihmo duke testuar
- Raporto një problem në GitHub
- Jeni të interesuar për të na ndihmuar në testimin e versionit të ardhshëm?Jepni kodkalimin tuajKodkalimi do të kërkohet sa herë që niset aplikacioniJu lutemi, futni kodkalimin tuaj
@@ -500,7 +480,6 @@
Për të shfaqur kujtesën, ju lutemi aktivizoni kredencialet e pajisje.Shfaqni njoftimet e skanimit të mediasNjoftoni për dosjet e mediave të sapo gjetura
- PërshtypjeGNU General Public License, versioni 2NdihmëImprint
@@ -523,6 +502,9 @@
Rruga e hapsirës ruajtëseDosje lokaleDosje remote
+ Shabllon
+ E errët
+ E çelëtParapamje figureNuk ka skedar lokal për të shfaqurKjo figurë nuk mund të shfaqet
@@ -534,6 +516,7 @@
Provojeni %1$s në pajisjen tuaj!Dua t’ju ftoj të përdorni %1$s në telefonin tuaj!\nShkarkojeni këtu: %2$s%1$s ose %2$s
+ Nuk u gjet asnjë skedar!Heqja dështoiDështoi heqja e njoftimit.Hiq
@@ -553,17 +536,13 @@
Fjalëkalim i gabuarIdentifikohu nëpërmjet QR codeMbrojtja e të dhënave tuaja
- platformë e vetë-pritur e produktivitetitShfletoni dhe shpërndanitë gjitha veprimet në majë të gishtave tuaj
- Aktiviteti, aksionet, skedarët jashtë linjegjithçka shpejt e arritshmeTë gjitha llogaritë tuajanë një vendNgarkimi automatikpër fotot tuaja & videot
- Kalendar i sinkronizuar & kontaktet
- me DAVx5 (më parë i njohur si DAVdroid)Kërkoni për grupe dhe përdoruesZgjidhi të gjithaZgjidh shabllonin
@@ -581,6 +560,8 @@
Ndajeni %1$sMerreni lidhjen%1$s (grup)
+ Shpërnda linkun e brendshëm
+ Punon vetëm për përdoruesit që kanë akses në %1$s%1$s ( te %2$s )Duhet të jepni një fjalëkalimNdodhi një gabim teksa përpiqej të ndahej me të tjerët ky skedar apo dosje
diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml
index 2e7b06a97631..1effe01065e7 100644
--- a/src/main/res/values-sr/strings.xml
+++ b/src/main/res/values-sr/strings.xml
@@ -170,7 +170,7 @@
Копирај везуКопирање/премештање у шифровану фасциклу није подржано.Копирај у…
- Не могу да преузмем целу слику
+ Није могуће преузети целу сликуНе могу да добавим адресуНе могу да направим фасциклуНаправите фајл из шаблона…
@@ -224,7 +224,6 @@
Управљање налозимаСредишњи налогОтвори бочну траку
- Придружи се%1$s од %2$s заузето%1$s искоришћеноАутоматска отпремања
@@ -265,7 +264,7 @@
Врати се на стари начин пријавеДодај у омиљенеОмиљени
- Нема апликација за слање е-поште!
+ ФајлОбришиГрешка при дохватању активности фајлаГрешка при учитавању детаља
@@ -335,6 +334,7 @@
Безбедна сарадња & и размена фајловаВеб пошта лака за коришћење, календар & контактиДељење екрана, састанци на интернету & веб конференције
+ ФолдерФасцикла већ постојиНаправиИкона фасцикле
@@ -386,18 +386,13 @@
Мултимедијални фајл не може да се пустиНе могу да прочитам мултимедијални фајлМултимедијални фајл има неисправно кодирање знакова
- Фајл није исправан налог
- Нема мултимедијалних фајлова
- Неочекивана грешка при покушају да пустим %1$sИстекло време покушавајући да пустим фајлУграђени мултимедијални плејер не може да пусти овај фајлНеподржан кодек
- %1$s пуштање завршеноУнапред%1$s музички плејерПуштање-паузаУназад
- %1$s (учитавање)%1$s (свира)прво новијепрво старије
@@ -448,25 +443,6 @@
Операција је отказанаСервер је изашао из гаранције, молимо ажурирајте га!још менија
- Тестирајте развојну верзију
- Ово укључује све функционалности које тек долазе и најновији и најсвежији кôд. То значи и да могу лако да се појаве грешке. Ако их уочите, молимо да их пријавите.
- форуму
- Помозите другима на
- Прегледај, дорађуј и мењај кôд, погледај %1$s за детаље
- Активно се придружите
- Придружите се ћаскању на ИРЦ-у:
- апликације
- Превођење
- Преузмите развојну верзију директним скидањем
- Преузмите развојну верзију преко Ф-Дроид продавнице
- Преузмите кандидата за нову верзију преко Ф-Дроид продавнице
- Преузмите кандидата за нову верзију преко Google Play продавнице
- Кандидат за нову верзију
- Кандидат за нову верзију (RC) је тренутно најстабилнији кандидат апликације за издање нове верзије. Ако тестирате овај кôд и код Вас, помажете да кандидат буде још стабилнији и бољи. Пријавите се за тестирање на Play Store продавници или погледајте верзије на Ф-Дроиду.
- Нашли сте грешку? Нешто чудно?
- Помози тестирајући
- Пријавите проблем на GitHub-у
- Заинтересовани да помогнете тако што ћете тестирати шта ће бити у наредној верзији?Унесите код за закључавањеКод ће бити затражен сваки пут кад се апликација покренеМолимо унесите кôд за закључавање
@@ -505,7 +481,6 @@
За приказивање мнемоника, омогућите акредитиве на уређају.Прикажи обавештења о скенирању мултимедијеОбавештавај о новопронађеним мултимедијалним фасциклама
- Ваше мишљењеГНУ Општа Јавна Лиценца, верзија 2ПомоћЖиг
@@ -528,6 +503,9 @@
Путања до складиштаЛокална фасциклаУдаљена фасцикла
+ Тема
+ Тамно
+ СветлоПреглед сликеНема локалног фајла за прегледНе могу да прикажем слику
@@ -539,6 +517,7 @@
Испробај %1$s на свом уређају!Желим да те позовем да испробаш %1$s на свом уређају.\nПреузми га овде: %2$s%1$s или %2$s
+ Није могуће пронаћи фајл!Брисање није успелоГрешка при уклањању обавештења.Уклони
@@ -558,17 +537,17 @@
Погрешна лозинкаПријава преко QR кодаШтити Ваше податке
- платформа за продуктивност коју и Ви можете да хостујете
+ продуктивност коју Ви хостујетеПретражи и делисве радње под Вашим прстима
- Активности, дељења, фајлови доступни без интернета
+ Активности, дељења, …све брзо доступноСви Ваши налозина једном местуАутоматска отпремањаВаших слика & видео записа
- Синхронизуј календар & контакте
- преко DAVx5-а (бивши DAVdroid)
+ Календар & контакти
+ Синхронизација са DAVx5Претражи кориснике и групеОзначи свеОдаберите шаблон
@@ -586,6 +565,8 @@
Подели %1$sДобави везу%1$s (група)
+ Подели интерну везу
+ Ради само за корисике са приступом овоме %1$s%1$s (на %2$s)Морате унети лозинкуДошло је до грешке приликом покушаја дељења овог фајла или фасцикле
diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml
index f2043310aad7..103827882ec6 100644
--- a/src/main/res/values-sv/strings.xml
+++ b/src/main/res/values-sv/strings.xml
@@ -222,7 +222,6 @@
Hantera kontonMittenkontoÖppna sidofältet
- Hjälp oss att bli bättre%1$s av %2$s använt%1$s använtAutomatisk uppladdning
@@ -263,7 +262,6 @@
Gå tillbaka till gammal inloggningsmetodLägg till favoriterFavoritisera
- Ingen app finns att skicka e-post!filRaderaFel vid hämtning av aktiviteter för fil
@@ -386,18 +384,13 @@
Media-filen kan inte strömmasKunde inte läsa mediafilenMediafilen har felaktig kodning
- Filen är inte i ett giltigt konto
- Hittar ingen mediefil
- Oväntat fel uppstod vid uppspelning av %1$sFörsök att spela upp filen tog för lång tidDen inbyggda mediaspelaren kan inte spela upp mediefilenSaknar stöd för denna mediatyp
- %1$s uppspelning slutfördesSnappspolningsknapp%1$s musikspelareSpela- / PausknappBakåtspolningsknapp
- %1$s (laddar)%1$s (spelar)Nyaste förstÄldst först
@@ -408,7 +401,7 @@
Ett fel uppstod vid försök att flytta denna fil eller mappDet är inte möjligt att flytta en mapp till en av dess undermapparFilen finns redan i målmappen
- Kunde inte flytta fil. Vänligen kolla om den finns
+ Kan inte flytta fil. Vänligen kontrollera om det finnsFlytta till…Ett fel uppstod i väntan på servern. Kunde inte slutföra operationenEtt fel uppstod under anslutningen till servern
@@ -443,30 +436,11 @@
MeddelandeikonLäser in aviseringar…Inga aviseringar
- Vänligen kolla igen senare.
+ Vänligen kontrollera igen senare.Ingen internetanslutningOperationen har avbrutitsServern har nått slutet av livet, vänligen uppgradera!Mer Meny
- Testa utvecklingsversionen
- Detta inkluderar alla kommande funktioner och mycket \"bleeding edge\" (den allra senaste koden). Fel/buggar kan uppstå, om och när de gör det, vänligen meddela oss vad du hittar för fel så att vi kan åtgärda dem.
- forum
- Hjälp andra på
- Granska, ändra och skriv kod, se %1$s för detaljer
- Bidra aktivt
- Gå med vår i IRC-chat:
- appen
- Översätt
- Hämta utvecklarversion via direkt nedladdning
- Hämta utvecklarversion från F-droid-appbutiken
- Hämta versionskandidat från F-droid-appbutiken
- Hämta versionskandidat från Google Play-butiken
- Release candidate
- Release Candidate (RC) är en image av den kommande versionen och förväntas vara stabil. Att testa din individuella konfiguration kan hjälpa till att säkerställa detta. Anmäl dig för testning i Play-butiken eller se manuellt i avsnittet \"Version\" på F-Droid.
- Hittat en bugg? Något som är konstigt?
- Hjälp till att testa
- Rapportera ditt problem på GitHub
- Intresserad av att hjälpa till att testa nästa version?Skriv in ditt lösenordKoden kommer efterfrågas varje gång du startar appenVänligen ange ditt lösenord
@@ -505,7 +479,6 @@
För att visa mnemonic vänligen aktivera enhetsuppgifter.Visa aviseringar om mediescanningMeddela om nya media-mappar
- FeedbackGNU General Public License, version 2HjälpImprint
@@ -563,17 +536,16 @@
Fel lösenordLogga in via QR-kodSkydda din data
- själv-hostad produktivitetsplattformBläddra och delaalla åtgärder till hands
- Aktivitet, delningar, offline-filer
+ Aktivitet, delningar, …allt snabbt tillgängligtAlla dina kontonpå ett ställeAutomatisk uppladdningför dina bilder & videor
- Synkronisera kalender & kontakter
- med DAVx5 (tidigare DAVdroid)
+ Kalender & kontakter
+ Synka med DAVx5Sök användare och grupperVälj allaVälj mall
@@ -728,7 +700,7 @@
Sluta dela misslyckadesÅtkomst via opålitlig domän. Se dokumentation för ytterligare information.Ett fel inträffade när delningen skulle uppdateras
- Kunde inte uppdatera. Vänligen kolla om filen existerar.
+ Det går inte att uppdatera. Vänligen kontrollera om filen finnsatt uppdatera denna delningUppdatera delning misslyckadesKan inte skapa lokal fil
@@ -757,7 +729,7 @@
Mottagen data innehåller ingen giltig fil.%1$s har inte tillåtelse att läsa en mottagen filKunde inte kopiera filen till en temporär mapp. Prova att skicka den igen.
- Filen som valdes för uppladdning kan inte hittas. Vänligen dubbelkolla att den existerar.
+ Filen vald för uppladdning hittades inte. Vänligen kontrollera om filen finns.Filen kan inte laddas uppIngen fil att ladda uppMappnamn
diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml
index 463edd5502b1..81540685f11b 100644
--- a/src/main/res/values-tr/strings.xml
+++ b/src/main/res/values-tr/strings.xml
@@ -30,7 +30,7 @@
Henüz bir işlem yapılmamışHenüz ekleme, değiştirme, paylaşım gibi bir işlem yapılmamışGönder
- Bağlantıyı gönder…
+ Bağlantıyı şuraya gönder…İşlem%1$s üzerine ekleİlişkili hesap bulunamadı!
@@ -51,7 +51,7 @@
Sunucu bulunamadıAğ bağlantısı yokGüvenli bağlantı kullanılamıyor.
- Sunucu ayarları doğru değil
+ Sunucu yapılandırması doğru değilKimlik doğrulanamadıKimlik doğrulama sunucusu erişime izin vermediParola
@@ -189,7 +189,7 @@
Yeni bir sürüm yok.Telefonunuz için gerekli algoritma bulunamadı.Devre Dışı Bırak
- Yoksay
+ Yok sayBildirimi yok saySon geliştirici sürümünü indirin%1$s indirmesi tamamlanamadı
@@ -223,7 +223,6 @@
Hesap yönetimiOrta hesapYan çubuğu aç
- Katkıda bulunun%1$s / %2$s kullanıldı%1$s kullanılıyorOtomatik yükleme
@@ -236,7 +235,7 @@
Bu klasör boş değil.Yeni anahtarlar üretiliyor…12 sözcüklü parola çok güçlüdür ve şifrelenmiş dosyalarınızı yalnız sizin görüntüleyip kullanabilmenizi sağlar. Lütfen bir yere yazarak güvenli bir yerde saklayın.
- Sunucu üzerinde uçtan uca şifreleme devre dışı bırakıldı.
+ Sunucu üzerinde uçtan uca şifreleme devre dışı bırakılmış.Şifreleme yalnız KitKat(4.4) ve üzerindeki sürümlerde kullanılabilir12 sözcüklü şifreleme parolanızı not edinParola…
@@ -264,7 +263,6 @@
Eski oturum açma yöntemine dönSık Kullanılanlara EkleSık Kullanılanlara Ekle
- E-posta göndermek için kullanılabilecek bir uygulama yok!dosyaSilDosya işlemleri alınırken sorun çıktı
@@ -310,8 +308,8 @@
TamamlandıDeğiştirilsinAktarıma hazırlanılıyor…
- Hesap ayarları geri yükleniyor…
- Hesap ayarları kaydediliyor…
+ Hesap yapılandırması geri yükleniyor…
+ Hesap yapılandırması kaydediliyor…Depolama yolunu gene de %1$s olarak değiştirmek istediğinizden emin misiniz?\n\nNot: Tüm verilerin yeniden indirilmesi gerekecek.Kaynak klasör okunabilir değil!Dizin güncelleniyor…
@@ -387,18 +385,13 @@
Ortam dosyası akışı sağlanamadıOrtam dosyası okunamadıOrtam dosyası doğru şekilde kodlanmamış
- Dosya geçerli bir hesapta değil
- Herhangi bir ortam dosyası bulunamadı
- %1$s oynatılmaya çalışılırken beklenmeyen bir sorun çıktıOynatmaya çalışılırken zaman aşımına uğradıOrtam dosyası iç ortam oynatıcı ile oynatılamıyorOrtam kod çözücüsü desteklenmiyor
- %1$s oynatıldıİleri Alma Düğmesi%1$s müzik oynatıcıOynatma ya da Duraklatma DüğmesiGeri Alma Düğmesi
- %1$s (yükleniyor)%1$s (oynatılıyor)Yeniden - EskiyeEskiden Yeniye
@@ -449,25 +442,6 @@
İşlem iptal edildiSunucu ömrünün sonuna geldi, lütfen sürümünü yükseltin!Diğerleri menüsü
- Geliştirici sürümünü deneyin
- Bu sürümde tüm yeni özellikler bulunur ve çok taze olduğundan eksikleri olabilir. Bir hata ya da soruna rastlarsanız bize iletin.
- forum
- Diğer kullanıcılara şurada yardımcı olun:
- Kodun gözden geçirilmesine ve geliştirilmesine katkıda bulunun. Ayrıntılı bilgi almak için %1$s dosyasına bakabilirsiniz.
- Etkin Katkıda Bulunun
- IRC sohbetine katılın:
- uygulamayı
- Çevirin
- Geliştirme sürümünü doğrudan indirin
- F-Droid uygulamasından geliştirici sürümünü alın
- F-Droid uygulamasından yayın adayı sürümünü alın
- Google Play Mağazasından yayın adayı sürümünü alın
- Yayın adayı
- Yayın adayı (Release Candidate, RC) yayınlanacak sürümün bir kopyasıdır ve kararlı olması beklenir. Kendi kurulumunuzda kullanmanız bundan emin olmamızı sağlayabilir. Denemek için Play Store üzerinden kayıt olun ya da el ile F-Droid üzerindeki \"Sürüm\" bölümüne bakın.
- Bir hata mı buldunuz? Bir gariplik mi var?
- Denememize yardımcı olun
- GitHub üzerinde sorun bildirin
- Sonraki sürümü deneyerek bize yardımcı olmak ister misiniz?Parolanızı yazınParola uygulama her başlatıldığında sorulacakLütfen parolanızı yazın
@@ -506,12 +480,11 @@
Belleğin görüntülenmesi için aygıt kimlik doğrulama bilgilerini etkinleştirin.Ortam tarama bildirimleri görüntülensinYeni bulunan ortam klasörleri bildirilsin
- Geri bildirimGNU Genel Kamu Lisansı 2. SürümYardımİzlenim
- Özgün dosya için
- Özgün dosya için…
+ Özgün dosya şu olacak…
+ Özgün dosya şu olacak…Yıla ve aya göre alt klasörlere kaydedilsinAlt klasörler kullanılsınLisans
@@ -563,17 +536,17 @@
Parola yanlışQR kodu ile oturum açVerilerinizi korumak
- sizin barındırdığınız üretkenlik platformu
+ sizin barındırdığınız üretkenlikGözat ve paylaştüm işlemler parmaklarınızın ucunda
- İşlemler, paylaşımlar, çevrimdışı dosyalar
+ İşlemler, paylaşımlar, …her şey kolayca erişilebilirTüm hesaplarınıztek bir yerdeOtomatik yüklemefotoğraf ve görüntüleriniz için
- Takvim ve kişileri eşitleme
- DAVx5 (eski adı DAVdroid) ile
+ Takvim ve kişiler
+ DAVx5 ile eşitlemeKullanıcı ve Grup AramaTümünü seçKalıp seçin
diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml
index 650f4221abf7..a862bb170625 100644
--- a/src/main/res/values-vi/strings.xml
+++ b/src/main/res/values-vi/strings.xml
@@ -206,15 +206,11 @@
Xóa dữ liệuQuản lý dung lượngKhông thể đọc tập tin media
- Tập tin không hợp lệ trong tài khoản
- Không tìm thấy tập tin media nàoCodec của media không được hỗ trợ
- %1$s phát lại khi kết thúcNút đẩy nhanh tốc độ phát%1$s trình nghe nhạcNút phát hoặc tạm dừngNút chuyển lại
- %1$s (đang chạy)%1$s (đang phát)Mới trướcCũ trước
@@ -262,7 +258,6 @@
Tổng hợphơnSao lưu hàng ngày danh bạn của bạn
- Phản hồiGiúp đỡĐánh dấuBật chứng nhận thiết bị
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index af76761b07c0..f6804c1cfe70 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -223,7 +223,6 @@
管理账号移动端账户打开侧边栏
- 参加%1$s 中 %2$s 已使用%1$s 已使用自动上传
@@ -264,7 +263,7 @@
采用旧的登录方式添加到收藏列表收藏
- 没有可用的用于发送邮件的应用!
+ 文件删除获取文件动态时出错加载详情失败
@@ -334,6 +333,7 @@
安全的交换 & 个协作文件易于使用的网络邮件,日历和&联系屏幕分享,在线会议,&网络会议
+ 文件夹目录已经存在创建目录图标
@@ -385,18 +385,13 @@
该媒体文件无法被转换成流无法读取媒体文件媒体文件的编码不正确
- 该文件不属于一个有效的账户
- 没有发现媒体文件
- 尝试播放 %1$s 时发生意外错误尝试播放文件超时默认的媒体播放器无法播放该媒体文件不支持的媒体编码格式
- %1$s 回放完毕快进按钮%1$s 音乐播放器播放暂停按钮倒带按钮
- %1$s (加载)%1$s (播放)按时间顺序按时间倒序
@@ -447,25 +442,6 @@
操作已取消服务器即将寿命终止,请升级!更多菜单
- 检查这个设备的版本
- 这包括所有即将到来的功能,它处于非常流行的边缘。 可能会发生错误/错误,如果及时,请报告您的发现。
- 论坛
- 帮助他人在
- 查看,修改和编写代码,有关详细信息,请参阅%1$s
- 活跃的候选版
- 在 IRC 上加入此聊天
- 此应用
- 翻译
- 直接下载开发版本
- 从F-Droid应用商店获取开发版本
- 从F-Droid应用商店获取候选版本
- 从谷歌应用商店获取候选版本
- 发行候选版本
- 候选发行版(RC)是指即将正式发布的版本,理论上说是稳定的。该测试有您的参加将确保正式版本的稳定。您可在Google Play应用商店参与测试或在F-Droid(F-Droid是一个Android应用程序的软件资源库或应用商店,类似于Google Play商店,但只包含自由及开放源代码软件)手动查看应用资源库的“版本”部分
- 发现错误?细节?
- 通过测试帮助
- 在Github报告问题 OR 通过Github提交问题(issue)
- 有兴趣来帮我们测试下一个版本吗?输入安全码每次启动应用都会要求输入安全码请输入安全码
@@ -504,7 +480,6 @@
启用端到端加密请打开设备认证显示媒体扫描通知有关于新发现的媒体文件夹的通知
- 反馈GNU 通用公共许可证,版本2帮助版本说明
@@ -527,6 +502,9 @@
存储路径本地文件夹远端文件夹
+ 主题
+ 深色
+ 浅色图片预览没有可预览的本地文件无法显示图像
@@ -538,6 +516,7 @@
在你的设备上尝试%1$s!我想邀请你在你的设备上使用%1$s。\n这里下载:%2$s%1$s或%2$s
+ 查找文件失败!删除失败移除通知失败移除
@@ -557,17 +536,17 @@
错误密码通过扫描二维码登录保护您的数据
- 自托管生产力平台
+ 自托管生产力浏览与分享所有操控尽在您的指尖
- 活动,共享,离线文件
+ 活动,共享,...快速访问所有数据您所有的账号都在一处自动上传您的照片&视频
- 同步日历&联系人
- 通过 DAVx5(原名DAVdroid)
+ 日历 & 联系人
+ 与DAVx5同步搜索用户和组选择全部选择模板
@@ -585,6 +564,8 @@
分享 %1$s获取链接%1$s (组)
+ 共享内部链接
+ 仅适用于有权限访问此%1$s的用户%1$s ( 在 %2$s )您必须输入密码共享文件或目录出错
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 1eb3416fefce..8eddf4483369 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -222,7 +222,6 @@
管理帳戶中間帳號開啟側邊攔
- 參與在 %2$s中使用了%1$s %1$s已使用自動上傳
@@ -263,7 +262,6 @@
還原舊的登入方式加到我的最愛我的最愛
- 沒有應用程式可以傳送信件!刪除檢所檔案活動時發生錯誤載入詳細資訊失敗
@@ -384,18 +382,13 @@
此媒體檔案無法被串流播放無法讀取媒體檔案媒體檔案未被正確的編碼
- 檔案不存在於有效的帳戶中
- 沒有發現媒體檔案
- 播放 %1$s 時碰到不可遇期的錯誤欲撥放的檔案已逾時內建播放器無法播放此媒體檔案未支援的媒體編碼
- %1$s播放結束快轉按鈕%1$s 音樂播放器播放或暫停按鈕倒帶按鈕
- %1$s(載入中)%1$s(播放中)最新先最舊先
@@ -446,25 +439,6 @@
操作取消伺服器已過期,請升級!更多
- 測試開發版本
- 這包含所有最新功能,預期執行上仍可能會有錯誤,若發現錯誤請回報給我們。
- 論壇
- 幫助其他人
- 檢查、修改和撰寫程式碼,請參見 %1$s
- 積極貢獻
- 加入 IRC:
- 應用程式
- 翻譯
- 直接下載開發版
- 從 F-Droid 應用程式取得開發版
- 從 F-Driod 應用程式取得發行版本
- 從Google Play商店取得正式發行版
- 最終測試版
- 最終測試版是新版本發行前的快照,應該相當穩定。您的測試將協助我們確保穩定性,在 Play 商店中註冊成為測試者,或是在 F-Droid 中尋找「版本」區塊
- 發現問題或瑕疵?
- 協助測試
- 在 GitHub 上回報問題
- 有興趣幫我們測試下一個版本嗎?輸入通行碼每次應用程式開啟時,都需要輸入通行碼請輸入通行碼
@@ -503,7 +477,6 @@
裝置若要顯示註記請啟用裝置憑證顯示媒體掃描通知發現新的多媒體資料夾時通知
- 回饋GNU產生公開授權,第2版說明法律聲明
@@ -557,17 +530,13 @@
密碼錯誤使用 QR Code 登入保護您的資料
- 自建具有生產力的平台瀏覽與分享全在您一指之間
- 活動,分享,離線檔案可快速存取所有資源您所有的帳戶在一個地方自動上傳您的照片&影片
- 同步行事曆&聯絡人
- 透過DAVx5(前身為DAVdroid)搜尋使用者或群組全選選擇範本
diff --git a/src/main/res/values/dims.xml b/src/main/res/values/dims.xml
index 1db7bb7af6b7..42a3bd131bd6 100644
--- a/src/main/res/values/dims.xml
+++ b/src/main/res/values/dims.xml
@@ -133,8 +133,6 @@
32dp32dp24dp
- 72dp
- 72dp-3dp12dp16sp
diff --git a/src/main/res/values/setup.xml b/src/main/res/values/setup.xml
index 1c0d9baafbd9..30a6dcbeabb0 100644
--- a/src/main/res/values/setup.xml
+++ b/src/main/res/values/setup.xml
@@ -111,6 +111,7 @@
+ truefalse
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 3c1fbb8a5aa7..576a5cda8b9c 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -170,6 +170,7 @@
Unknown errorWaiting for Wi-FiWaiting to exit power save mode
+ Fetching server version…Waiting to upload%1$s (%2$d)Downloading…
@@ -322,6 +323,7 @@
Only upload on unmetered Wi-FiOnly upload when charging
+ Also upload existing files/InstantUpload/AutoUploadFile conflict
@@ -554,7 +556,11 @@
Choose remote folder…Choose local folder…Loading folders…
- No media folders found.
+ No media folders found
+
+ Show %1$d hidden folder
+ Show %1$d hidden folders
+ Preferences for auto uploadingInstant uploading has been revamped completely. Re-configure your auto upload from within the main menu.\n\nEnjoy the new and extended auto uploading.For %1$s
@@ -627,6 +633,8 @@
Set up a custom folderCreate new custom folder setup
+ Hide folder
+ ConfigureConfigure folders
@@ -710,12 +718,12 @@
Account not found!Protecting your data
- self-hosted productivity platform
+ self-hosted productivityBrowse and shareall actions at your fingertips
- Activity, shares, offline files
+ Activity, shares, …everything quickly accessibleAll your accounts
@@ -724,8 +732,8 @@
Automatic uploadfor your photos & videos
- Sync calendar & contacts
- with DAVx5 (formerly DAVdroid)
+ Calendar & contacts
+ Sync with DAVx5No personal info setAdd name, picture and contact details on your profile page.
@@ -868,9 +876,6 @@
MusicDocumentsDownloads
- IO error
- Operation has been canceled
- Authentication ExceptionAvatar from shared userShared with you by %1$sResharing is not allowed
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index 4dae1b4f4be8..67e479c50c71 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -27,8 +27,9 @@
android:title="@string/prefs_storage_path"
android:key="storage_path"/>
diff --git a/src/qa/AndroidManifest.xml b/src/qa/AndroidManifest.xml
new file mode 100644
index 000000000000..33ba737d83d5
--- /dev/null
+++ b/src/qa/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt b/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt
index 826d5a8621f9..23a5f88eed97 100644
--- a/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt
+++ b/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt
@@ -24,6 +24,7 @@ import android.content.ContentResolver
import android.content.Context
import android.os.Build
import androidx.work.WorkerParameters
+import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.preferences.AppPreferences
@@ -59,6 +60,9 @@ class BackgroundJobFactoryTest {
@Mock
private lateinit var deviceInfo: DeviceInfo
+ @Mock
+ private lateinit var clock: Clock
+
private lateinit var factory: BackgroundJobFactory
@Before
@@ -67,6 +71,7 @@ class BackgroundJobFactoryTest {
factory = BackgroundJobFactory(
preferences,
contentResolver,
+ clock,
powerManagementService,
Provider { backgroundJobManager },
deviceInfo
diff --git a/src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt b/src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt
index 23933fbfad53..e805d3555f62 100644
--- a/src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt
+++ b/src/test/java/com/nextcloud/client/network/ConnectivityServiceTest.kt
@@ -19,16 +19,17 @@
*/
package com.nextcloud.client.network
-import android.accounts.Account
import android.net.ConnectivityManager
import android.net.NetworkInfo
-import android.net.Uri
+import com.nextcloud.client.account.Server
+import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.logger.Logger
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.HttpStatus
@@ -42,6 +43,7 @@ import org.junit.runners.Suite
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import java.net.URI
@RunWith(Suite::class)
@Suite.SuiteClasses(
@@ -86,13 +88,14 @@ class ConnectivityServiceTest {
lateinit var requestBuilder: ConnectivityServiceImpl.GetRequestBuilder
@Mock
- lateinit var platformAccount: Account
+ lateinit var logger: Logger
- @Mock
- lateinit var ownCloudAccount: OwnCloudAccount
+ val baseServerUri = URI.create(SERVER_BASE_URL)
+ val newServer = Server(baseServerUri, OwnCloudVersion.nextcloud_14)
+ val legacyServer = Server(baseServerUri, OwnCloudVersion.nextcloud_13)
@Mock
- lateinit var baseServerUri: Uri
+ lateinit var user: User
lateinit var connectivityService: ConnectivityServiceImpl
@@ -103,15 +106,15 @@ class ConnectivityServiceTest {
platformConnectivityManager,
accountManager,
clientFactory,
- requestBuilder
+ requestBuilder,
+ logger
)
+
whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
whenever(clientFactory.createPlainClient()).thenReturn(client)
- whenever(accountManager.currentOwnCloudAccount).thenReturn(ownCloudAccount)
- whenever(accountManager.currentAccount).thenReturn(platformAccount)
- whenever(baseServerUri.toString()).thenReturn(SERVER_BASE_URL)
- whenever(ownCloudAccount.baseUri).thenReturn(baseServerUri)
+ whenever(user.server).thenReturn(newServer)
+ whenever(accountManager.user).thenReturn(user)
}
}
@@ -158,7 +161,7 @@ class ConnectivityServiceTest {
fun setUp() {
whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
- whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_13)
+ whenever(user.server).thenReturn(legacyServer)
assertTrue("Precondition failed", connectivityService.isOnlineWithWifi)
}
@@ -207,6 +210,28 @@ class ConnectivityServiceTest {
assertTrue("Precondition failed", connectivityService.isOnlineWithWifi)
}
+ @Test
+ fun `check request is not sent when server uri is not set`() {
+ // GIVEN
+ // network connectivity is present
+ // user has no server URI (empty)
+ val serverWithoutUri = Server(URI(""), OwnCloudVersion.nextcloud_14)
+ whenever(user.server).thenReturn(serverWithoutUri)
+
+ // WHEN
+ // connectivity is checked
+ val result = connectivityService.isInternetWalled
+
+ // THEN
+ // connection is walled
+ // request is not sent
+ assertTrue("Server should not be accessible", result)
+ verify(requestBuilder, never()).invoke(any())
+ verify(client, never()).executeMethod(any())
+ verify(client, never()).executeMethod(any(), any())
+ verify(client, never()).executeMethod(any(), any(), any())
+ }
+
fun mockResponse(contentLength: Long = 0, status: Int = HttpStatus.SC_OK) {
whenever(client.executeMethod(any())).thenReturn(status)
whenever(getRequest.statusCode).thenReturn(status)
diff --git a/src/test/java/com/nextcloud/client/preferences/TestAppPreferences.java b/src/test/java/com/nextcloud/client/preferences/TestAppPreferences.java
new file mode 100644
index 000000000000..9e7fef260255
--- /dev/null
+++ b/src/test/java/com/nextcloud/client/preferences/TestAppPreferences.java
@@ -0,0 +1,156 @@
+package com.nextcloud.client.preferences;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.nextcloud.client.account.CurrentAccountProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+
+import org.mockito.MockitoAnnotations;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TestAppPreferences.Preferences.class,
+ TestAppPreferences.ListenerRegistery.class
+})
+public class TestAppPreferences {
+
+ public static class ListenerRegistery {
+ private static final SharedPreferences NOT_USED_NULL = null;
+
+ @Mock
+ private AppPreferences.Listener listener1;
+
+ @Mock
+ private AppPreferences.Listener listener2;
+
+ @Mock
+ private AppPreferences.Listener listener3;
+
+ @Mock
+ private AppPreferences.Listener listener4;
+
+ @Mock
+ AppPreferences appPreferences;
+
+ private AppPreferencesImpl.ListenerRegistry registry;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(appPreferences.isDarkThemeEnabled()).thenReturn(true);
+ registry = new AppPreferencesImpl.ListenerRegistry(appPreferences);
+ }
+
+ @Test
+ public void canRemoveListenersFromCallback() {
+
+ // GIVEN
+ // registery has few listeners
+ // one listener will try to remove itself and other listener
+ registry.add(listener1);
+ registry.add(listener2);
+ registry.add(listener3);
+ registry.add(listener4);
+
+ doAnswer((i) -> {
+ registry.remove(listener2);
+ registry.remove(listener3);
+ return null;
+ }).when(listener2).onDarkThemeEnabledChanged(anyBoolean());
+
+ // WHEN
+ // callback is called twice
+ registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
+ registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
+
+ // THEN
+ // no ConcurrentModificationException
+ // 1st time, all listeners (including removed) are called
+ // 2nd time removed callbacks are not called
+ verify(listener1, times(2)).onDarkThemeEnabledChanged(anyBoolean());
+ verify(listener2).onDarkThemeEnabledChanged(anyBoolean());
+ verify(listener3).onDarkThemeEnabledChanged(anyBoolean());
+ verify(listener4, times(2)).onDarkThemeEnabledChanged(anyBoolean());
+ }
+
+ @Test
+ public void nullsAreNotAddedToRegistry() {
+ // GIVEN
+ // registry has no listeners
+ // attempt to add null listener was made
+ registry.add(null);
+
+ // WHEN
+ // callback is called
+ registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
+
+ // THEN
+ // nothing happens
+ // null was not added to registry
+ }
+
+ @Test
+ public void nullsAreNotRemovedFromRegistry() {
+ // GIVEN
+ // registry has no listeners
+
+ // WHEN
+ // attempt to remove null listener was made
+ registry.remove(null);
+
+ // THEN
+ // null is ignored
+ }
+ }
+
+ public static class Preferences {
+ @Mock
+ private Context testContext;
+
+ @Mock
+ private SharedPreferences sharedPreferences;
+
+ @Mock
+ private SharedPreferences.Editor editor;
+
+ @Mock
+ private CurrentAccountProvider accountProvider;
+
+ private AppPreferencesImpl appPreferences;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(editor.remove(anyString())).thenReturn(editor);
+ when(sharedPreferences.edit()).thenReturn(editor);
+ appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, accountProvider);
+ }
+
+ @Test
+ public void removeLegacyPreferences() {
+ appPreferences.removeLegacyPreferences();
+ InOrder inOrder = inOrder(editor);
+ inOrder.verify(editor).remove("instant_uploading");
+ inOrder.verify(editor).remove("instant_video_uploading");
+ inOrder.verify(editor).remove("instant_upload_path");
+ inOrder.verify(editor).remove("instant_upload_path_use_subfolders");
+ inOrder.verify(editor).remove("instant_upload_on_wifi");
+ inOrder.verify(editor).remove("instant_upload_on_charging");
+ inOrder.verify(editor).remove("instant_video_upload_path");
+ inOrder.verify(editor).remove("instant_video_upload_path_use_subfolders");
+ inOrder.verify(editor).remove("instant_video_upload_on_wifi");
+ inOrder.verify(editor).remove("instant_video_uploading");
+ inOrder.verify(editor).remove("instant_video_upload_on_charging");
+ inOrder.verify(editor).remove("prefs_instant_behaviour");
+ inOrder.verify(editor).apply();
+ }
+ }
+}
diff --git a/src/test/java/com/nextcloud/client/preferences/TestPreferenceManager.java b/src/test/java/com/nextcloud/client/preferences/TestPreferenceManager.java
deleted file mode 100644
index 64bdca3e5e4c..000000000000
--- a/src/test/java/com/nextcloud/client/preferences/TestPreferenceManager.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.nextcloud.client.preferences;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import com.nextcloud.client.account.CurrentAccountProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import static org.mockito.Mockito.*;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TestPreferenceManager {
-
- @Mock
- private Context testContext;
-
- @Mock
- private SharedPreferences sharedPreferences;
-
- @Mock
- private SharedPreferences.Editor editor;
-
- @Mock
- private CurrentAccountProvider accountProvider;
-
- private AppPreferencesImpl appPreferences;
-
- @Before
- public void setUp() {
- when(editor.remove(anyString())).thenReturn(editor);
- when(sharedPreferences.edit()).thenReturn(editor);
- appPreferences = new AppPreferencesImpl(testContext, sharedPreferences, accountProvider);
- }
-
- @Test
- public void removeLegacyPreferences() {
- appPreferences.removeLegacyPreferences();
- InOrder inOrder = inOrder(editor);
- inOrder.verify(editor).remove("instant_uploading");
- inOrder.verify(editor).remove("instant_video_uploading");
- inOrder.verify(editor).remove("instant_upload_path");
- inOrder.verify(editor).remove("instant_upload_path_use_subfolders");
- inOrder.verify(editor).remove("instant_upload_on_wifi");
- inOrder.verify(editor).remove("instant_upload_on_charging");
- inOrder.verify(editor).remove("instant_video_upload_path");
- inOrder.verify(editor).remove("instant_video_upload_path_use_subfolders");
- inOrder.verify(editor).remove("instant_video_upload_on_wifi");
- inOrder.verify(editor).remove("instant_video_uploading");
- inOrder.verify(editor).remove("instant_video_upload_on_charging");
- inOrder.verify(editor).remove("prefs_instant_behaviour");
- inOrder.verify(editor).apply();
- }
-}
diff --git a/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java b/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java
index b29b06c6bc95..29bffa5c3ce5 100644
--- a/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java
+++ b/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java
@@ -32,8 +32,6 @@
import java.util.Collections;
import java.util.List;
-import third_parties.daveKoeller.AlphanumComparator;
-
import static org.junit.Assert.assertTrue;
public class SyncedFoldersActivityTest {
@@ -123,14 +121,6 @@ private boolean sortAndTest(List sortedList) {
return test(sortedList, SyncedFoldersActivity.sortSyncedFolderItems(unsortedList));
}
- private List sort(List sortedList) {
- List unsortedList = shuffle(sortedList);
-
- Collections.sort(unsortedList, new AlphanumComparator<>());
-
- return unsortedList;
- }
-
private boolean test(List target, List actual) {
for (int i = 0; i < target.size(); i++) {
@@ -167,19 +157,22 @@ private boolean test(List target, List(),
folderName,
2,
- MediaFolderType.IMAGE);
+ MediaFolderType.IMAGE,
+ false);
}
}
diff --git a/src/versionDev/fastlane/metadata/android/cs-CZ/full_description.txt b/src/versionDev/fastlane/metadata/android/cs-CZ/full_description.txt
index fa069beeb0be..a249bb46cdba 100644
--- a/src/versionDev/fastlane/metadata/android/cs-CZ/full_description.txt
+++ b/src/versionDev/fastlane/metadata/android/cs-CZ/full_description.txt
@@ -1,4 +1,3 @@
-Svobodná (copyleft) aplikace Nexcloud pro Android, dává vám mobilní svobodu.
-Toto je oficiální vývojová verze, obsahující denní vzorek všech nových nevyzkoušených funkcí, které mohou způsobovat nestabilitu a vést ke ztrátě dat. Aplikace v tomto stádiu vývoje je určena uživatelům, kteří jsou ochotní zkoušet a hlásit chyby, které se vyskytnou. Nepoužívejte pro svou produkční práci!
-
-Obě oficiální verze, jak vývojová tak ostrá jsou k dispozici na F-droid a je možné je mít nainstalované souběžně.
\ No newline at end of file
+Kompletní privátní řešení pro všechy vaše soubory, fotky, kalendáře a kontakty.
+Toto je oficiální vývojová verze aktualizovaná denně. Obsahuje nejnovější fukcionaliti, ale může být nestabilní a ohrozit vaše data. Je pro uživatele, kteří jsou ochotni testovat a hlásit chyby na které narazí. Nepoužívejte ji pro svoje produkční prostředí.
+Jak vývojová tak produkční verze je k dispozici na F-droidu a mohou být nainstalovány souběžně.
\ No newline at end of file
diff --git a/src/versionDev/fastlane/metadata/android/cs-CZ/short_description.txt b/src/versionDev/fastlane/metadata/android/cs-CZ/short_description.txt
index af93a0364e56..01dc4806ace1 100644
--- a/src/versionDev/fastlane/metadata/android/cs-CZ/short_description.txt
+++ b/src/versionDev/fastlane/metadata/android/cs-CZ/short_description.txt
@@ -1 +1 @@
-Produktivní platforma hostovaná u vás, kde vaše data jsou opravdu vaše (vývojová
\ No newline at end of file
+Platforma pro produktivitu hostovaná u vás, kde vaše data jsou opravdu vaše (výv
\ No newline at end of file
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191106.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191106.txt
new file mode 100644
index 000000000000..a5edb780817c
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191106.txt
@@ -0,0 +1,9 @@
+9e15f5ea3 Merge pull request #4799 from nextcloud/dependabot/gradle/com.android.tools.build-gradle-3.5.2
+d208681fe Merge pull request #4796 from nextcloud/datepicker
+7a8aee3e2 [tx-robot] updated from transifex
+a3ae32129 Bump gradle from 3.5.1 to 3.5.2
+b0563b931 Theming: Fix date picker button text colour
+207f8947c [tx-robot] updated from transifex
+1ec09848b [tx-robot] updated from transifex
+513151491 [tx-robot] updated from transifex
+38a83bb36 daily dev 20191102
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191107.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191107.txt
new file mode 100644
index 000000000000..cb4d3dd2244f
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191107.txt
@@ -0,0 +1,4 @@
+890789f0a Merge pull request #4797 from nextcloud/ezaquarii/replace-shared-preferences-with-app-preferences-in-base-activity
+180bdee72 Merge pull request #4801 from Infomaniak/feature/resolve-set-as-wallpaper-issue
+039adc0a1 [tx-robot] updated from transifex
+b59938d96 daily dev 20191106
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191108.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191108.txt
new file mode 100644
index 000000000000..ae871c08acc7
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191108.txt
@@ -0,0 +1,2 @@
+403fc8756 [tx-robot] updated from transifex
+142c79bc0 daily dev 20191107
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191113.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191113.txt
new file mode 100644
index 000000000000..27d66f662495
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191113.txt
@@ -0,0 +1,7 @@
+f245b72de Merge pull request #4823 from nextcloud/changelog-master-stable-3.9.0
+d30b574ff Add changelog for 3.9.0
+df6d847f2 [tx-robot] updated from transifex
+4343c3d67 Merge pull request #4814 from nextcloud/dependabot/gradle/org.jetbrains-annotations-18.0.0
+86027dd09 Bump annotations from 17.0.0 to 18.0.0
+4690042b0 [tx-robot] updated from transifex
+ff646aa3d daily dev 20191108
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191114.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191114.txt
new file mode 100644
index 000000000000..cf3d342ffaec
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191114.txt
@@ -0,0 +1,3 @@
+ed8b9cbb7 Merge pull request #4713 from nextcloud/ezaquarii/remove-set-account-from-first-run-activity
+f38900d98 [tx-robot] updated from transifex
+e43ef8e06 daily dev 20191113
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191116.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191116.txt
new file mode 100644
index 000000000000..ecbdc020b530
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191116.txt
@@ -0,0 +1,5 @@
+5273e1e7f Merge pull request #4832 from nextcloud/dependabot/gradle/kotlin_version-1.3.60
+bd503a86c [tx-robot] updated from transifex
+20c9ce361 Bump kotlin_version from 1.3.50 to 1.3.60
+3943e1c75 [tx-robot] updated from transifex
+75de7b88d daily dev 20191114
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191119.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191119.txt
new file mode 100644
index 000000000000..32577837bd3f
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191119.txt
@@ -0,0 +1,7 @@
+dbe12e6d2 Merge pull request #4789 from nextcloud/name_pattern_menus
+77b6b178a Merge pull request #4841 from nextcloud/disableOldLoginMethod
+b561f6883 allow to switch off old login method
+b59d96ace [tx-robot] updated from transifex
+9ae550731 [tx-robot] updated from transifex
+bfdaba8bd [tx-robot] updated from transifex
+c82c08059 daily dev 20191116
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191120.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191120.txt
new file mode 100644
index 000000000000..0a30249a3a52
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191120.txt
@@ -0,0 +1,7 @@
+21ef00654 Merge pull request #4858 from nextcloud/changeScreenshot
+0223085a8 shorten text a bit
+8879179cf Drone: update FindBugs results to reflect reduced error/warning count [skip ci]
+94502f66b Merge pull request #4787 from ArisuOngaku/auto-upload-start-date-persistence
+21109375c Merge pull request #4839 from nextcloud/dependabot/gradle/markwonVersion-4.2.0
+f1a0ac5c2 [tx-robot] updated from transifex
+a00677cd6 daily dev 20191119
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191121.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191121.txt
new file mode 100644
index 000000000000..858f2fc0dde5
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191121.txt
@@ -0,0 +1,2 @@
+b9ff6cecf Merge pull request #4843 from Unpublished/fix4842
+0977c9d2e daily dev 20191120
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191123.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191123.txt
new file mode 100644
index 000000000000..cd2e13307ba8
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191123.txt
@@ -0,0 +1,4 @@
+c8eba1d5a Merge pull request #4835 from nextcloud/ezaquarii/new-user-model
+609b99666 New user model
+44220d73f Merge pull request #4867 from nextcloud/dependabot/gradle/androidx.exifinterface-exifinterface-1.1.0
+6e5d7a89c daily dev 20191121
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191127.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191127.txt
new file mode 100644
index 000000000000..6f8de459d7c3
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/20191127.txt
@@ -0,0 +1,10 @@
+e92daa2fc Merge pull request #4853 from nextcloud/migrate-simple-cases-of-get-current-account-to-get-user
+99d9a6923 Merge pull request #4885 from nextcloud/disable_android_backup_qa
+10960bf3a [tx-robot] updated from transifex
+9f24b254f [tx-robot] updated from transifex
+b7876c425 disable GoogleAppIndexingWarning lint check
+11687c65c Drone: update FindBugs results to reflect reduced error/warning count [skip ci]
+80fb800ef Merge commit 'a7eb7148fa0ceb42981366eb2ddcf0ff921e6a55'
+b91136c36 Disable android backup on the QA app
+a7eb7148f Migrate simple cases of getCurrentAccount() to getUser()
+6249a06a8 daily dev 20191123