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 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 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}: + * + *

{@code
+     *     Optional fis =
+     *         names.stream().filter(name -> !isProcessedYet(name))
+     *                       .findFirst()
+     *                       .map(name -> new FileInputStream(name));
+     * }
+ * + * 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 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> 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 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 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 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 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(List objects, ExtendedListFragment.SearchType search FileDataStorageManager storageManager, OCFile folder, boolean clear) { if (storageManager != null && mStorageManager == null) { mStorageManager = storageManager; - showShareAvatar = mStorageManager.getCapability(account.name).getVersion().isShareesOnDavSupported(); + showShareAvatar = mStorageManager.getCapability(user.getAccountName()).getVersion().isShareesOnDavSupported(); } if (mStorageManager == null) { - mStorageManager = new FileDataStorageManager(account, mContext.getContentResolver()); + mStorageManager = new FileDataStorageManager(user.toPlatformAccount(), mContext.getContentResolver()); } if (clear) { @@ -808,12 +813,12 @@ private void parseShares(List objects) { shares.add(ocShare); // get ocFile from Server to have an up-to-date copy - RemoteOperationResult result = new ReadFileRemoteOperation(ocShare.getPath()).execute(account, + RemoteOperationResult result = new ReadFileRemoteOperation(ocShare.getPath()).execute(user.toPlatformAccount(), mContext); if (result.isSuccess()) { OCFile file = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); - FileStorageUtils.searchForLocalFileInDefaultPath(file, account); + FileStorageUtils.searchForLocalFileInDefaultPath(file, user.toPlatformAccount()); file = mStorageManager.saveFileWithParent(file, mContext); ShareType newShareType = ocShare.getShareType(); @@ -868,7 +873,7 @@ private void parseVirtuals(List objects, ExtendedListFragment.SearchType for (Object remoteFile : objects) { OCFile ocFile = FileStorageUtils.fillOCFile((RemoteFile) remoteFile); - FileStorageUtils.searchForLocalFileInDefaultPath(ocFile, account); + FileStorageUtils.searchForLocalFileInDefaultPath(ocFile, user.toPlatformAccount()); try { if (ExtendedListFragment.SearchType.PHOTO_SEARCH == searchType) { @@ -885,9 +890,9 @@ private void parseVirtuals(List objects, ExtendedListFragment.SearchType true, false, mStorageManager, - account, + user.toPlatformAccount(), mContext); - refreshFolderOperation.execute(account, mContext); + refreshFolderOperation.execute(user.toPlatformAccount(), mContext); } } diff --git a/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java index 8a8176e54ef4..57c72e554905 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java @@ -23,16 +23,20 @@ import android.content.Context; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.PopupMenu; import android.widget.RelativeLayout; import android.widget.TextView; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.afollestad.sectionedrecyclerview.SectionedViewHolder; +import com.nextcloud.client.core.Clock; import com.owncloud.android.R; import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; @@ -53,62 +57,174 @@ */ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter { - private final Context mContext; - private final int mGridWidth; - private final int mGridTotal; - private final ClickListener mListener; - private final List mSyncFolderItems; - private final boolean mLight; - - public SyncedFolderAdapter(Context context, int gridWidth, ClickListener listener, boolean light) { - mContext = context; - mGridWidth = gridWidth; - mGridTotal = gridWidth * 2; - mListener = listener; - mSyncFolderItems = new ArrayList<>(); - mLight = light; + private final Context context; + private final Clock clock; + private final int gridWidth; + private final int gridTotal; + private final ClickListener clickListener; + private final List syncFolderItems; + private final List filteredSyncFolderItems; + private final boolean light; + private final int VIEW_TYPE_EMPTY = Integer.MAX_VALUE; + private boolean hideItems; + + public SyncedFolderAdapter(Context context, Clock clock, int gridWidth, ClickListener listener, boolean light) { + this.context = context; + this.clock = clock; + this.gridWidth = gridWidth; + gridTotal = gridWidth * 2; + clickListener = listener; + syncFolderItems = new ArrayList<>(); + filteredSyncFolderItems = new ArrayList<>(); + this.light = light; + this.hideItems = true; shouldShowHeadersForEmptySections(true); + shouldShowFooters(true); + } + + public void toggleHiddenItemsVisibility() { + hideItems = !hideItems; + filteredSyncFolderItems.clear(); + filteredSyncFolderItems.addAll(filterHiddenItems(syncFolderItems, hideItems)); + notifyDataSetChanged(); } public void setSyncFolderItems(List syncFolderItems) { - mSyncFolderItems.clear(); - mSyncFolderItems.addAll(syncFolderItems); + this.syncFolderItems.clear(); + this.syncFolderItems.addAll(syncFolderItems); + + this.filteredSyncFolderItems.clear(); + this.filteredSyncFolderItems.addAll(filterHiddenItems(this.syncFolderItems, hideItems)); } public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) { - mSyncFolderItems.set(location, syncFolderItem); + if (hideItems && syncFolderItem.isHidden() && filteredSyncFolderItems.contains(syncFolderItem)) { + filteredSyncFolderItems.remove(location); + } else { + if (filteredSyncFolderItems.contains(syncFolderItem)) { + filteredSyncFolderItems.set(filteredSyncFolderItems.indexOf(syncFolderItem), syncFolderItem); + } else { + filteredSyncFolderItems.add(syncFolderItem); + } + } + + if (syncFolderItems.contains(syncFolderItem)) { + syncFolderItems.set(syncFolderItems.indexOf(syncFolderItem), syncFolderItem); + } else { + syncFolderItems.add(syncFolderItem); + } + notifyDataSetChanged(); } public void addSyncFolderItem(SyncedFolderDisplayItem syncFolderItem) { - mSyncFolderItems.add(syncFolderItem); - notifyDataSetChanged(); + syncFolderItems.add(syncFolderItem); + + // add item for display when either all items should be shown (!hideItems) + // or if item should be shown (!.isHidden()) + if (!hideItems || !syncFolderItem.isHidden()) { + filteredSyncFolderItems.add(syncFolderItem); + notifyDataSetChanged(); + } } public void removeItem(int section) { - mSyncFolderItems.remove(section); - notifyDataSetChanged(); + if (filteredSyncFolderItems.contains(syncFolderItems.get(section))) { + filteredSyncFolderItems.remove(syncFolderItems.get(section)); + notifyDataSetChanged(); + } + syncFolderItems.remove(section); + } + + /** + * Filter for hidden items + * + * @param items Collection of items to filter + * @return Non-hidden items + */ + private List filterHiddenItems(List items, boolean hide) { + if (!hide) { + return items; + } else { + List result = new ArrayList<>(); + + for (SyncedFolderDisplayItem item : items) { + if (!item.isHidden() && !result.contains(item)) { + result.add(item); + } + } + + return result; + } } @Override public int getSectionCount() { - return mSyncFolderItems.size(); + if (filteredSyncFolderItems.size() > 0) { + return filteredSyncFolderItems.size() + 1; + } else { + return 0; + } + } + + public int getUnfilteredSectionCount() { + if (syncFolderItems.size() > 0) { + return syncFolderItems.size() + 1; + } else { + return 0; + } } @Override public int getItemCount(int section) { - List filePaths = mSyncFolderItems.get(section).getFilePaths(); + if (section < filteredSyncFolderItems.size()) { + List filePaths = filteredSyncFolderItems.get(section).getFilePaths(); - if (filePaths != null) { - return mSyncFolderItems.get(section).getFilePaths().size(); + if (filePaths != null) { + return filteredSyncFolderItems.get(section).getFilePaths().size(); + } else { + return 1; + } } else { return 1; } } public SyncedFolderDisplayItem get(int section) { - return mSyncFolderItems.get(section); + return filteredSyncFolderItems.get(section); + } + + @Override + public int getItemViewType(int section, int relativePosition, int absolutePosition) { + if (isLastSection(section)) { + return VIEW_TYPE_EMPTY; + } else { + return VIEW_TYPE_ITEM; + } + } + + @Override + public int getHeaderViewType(int section) { + if (isLastSection(section)) { + return VIEW_TYPE_EMPTY; + } else { + return VIEW_TYPE_HEADER; + } + } + + @Override + public int getFooterViewType(int section) { + if (isLastSection(section) && showFooter()) { + return VIEW_TYPE_FOOTER; + } else { + // only show footer after last item and only if folders have been hidden + return VIEW_TYPE_EMPTY; + } + } + + private boolean showFooter() { + return syncFolderItems.size() > filteredSyncFolderItems.size(); } /** @@ -119,9 +235,9 @@ public SyncedFolderDisplayItem get(int section) { * @return the section index of the looked up synced folder, -1 if not present */ public int getSectionByLocalPathAndType(String localPath, int type) { - for (int i = 0; i < mSyncFolderItems.size(); i++) { - if (mSyncFolderItems.get(i).getLocalPath().equalsIgnoreCase(localPath) && - mSyncFolderItems.get(i).getType().getId().equals(type)) { + for (int i = 0; i < filteredSyncFolderItems.size(); i++) { + if (filteredSyncFolderItems.get(i).getLocalPath().equalsIgnoreCase(localPath) && + filteredSyncFolderItems.get(i).getType().getId().equals(type)) { return i; } } @@ -131,70 +247,96 @@ public int getSectionByLocalPathAndType(String localPath, int type) { @Override public void onBindHeaderViewHolder(SectionedViewHolder commonHolder, final int section, boolean expanded) { - HeaderViewHolder holder = (HeaderViewHolder) commonHolder; + if (section < filteredSyncFolderItems.size()) { + HeaderViewHolder holder = (HeaderViewHolder) commonHolder; + holder.mainHeaderContainer.setVisibility(View.VISIBLE); - holder.mainHeaderContainer.setVisibility(View.VISIBLE); + holder.title.setText(filteredSyncFolderItems.get(section).getFolderName()); - holder.title.setText(mSyncFolderItems.get(section).getFolderName()); + if (MediaFolderType.VIDEO == filteredSyncFolderItems.get(section).getType()) { + holder.type.setImageResource(R.drawable.video_32dp); + } else if (MediaFolderType.IMAGE == filteredSyncFolderItems.get(section).getType()) { + holder.type.setImageResource(R.drawable.image_32dp); + } else { + holder.type.setImageResource(R.drawable.folder_star_32dp); + } - if (MediaFolderType.VIDEO == mSyncFolderItems.get(section).getType()) { - holder.type.setImageResource(R.drawable.video_32dp); - } else if (MediaFolderType.IMAGE == mSyncFolderItems.get(section).getType()) { - holder.type.setImageResource(R.drawable.image_32dp); - } else { - holder.type.setImageResource(R.drawable.folder_star_32dp); + holder.syncStatusButton.setVisibility(View.VISIBLE); + holder.syncStatusButton.setTag(section); + holder.syncStatusButton.setOnClickListener(v -> { + filteredSyncFolderItems.get(section).setEnabled( + !filteredSyncFolderItems.get(section).isEnabled(), + clock.getCurrentTime() + ); + setSyncButtonActiveIcon(holder.syncStatusButton, filteredSyncFolderItems.get(section).isEnabled()); + clickListener.onSyncStatusToggleClick(section, filteredSyncFolderItems.get(section)); + }); + setSyncButtonActiveIcon(holder.syncStatusButton, filteredSyncFolderItems.get(section).isEnabled()); + + if (light) { + holder.menuButton.setVisibility(View.GONE); + } else { + holder.menuButton.setVisibility(View.VISIBLE); + holder.menuButton.setTag(section); + holder.menuButton.setOnClickListener(v -> onOverflowIconClicked(section, + filteredSyncFolderItems.get(section), + v)); + } } + } + + private void onOverflowIconClicked(int section, SyncedFolderDisplayItem item, View view) { + PopupMenu popup = new PopupMenu(context, view); + popup.inflate(R.menu.synced_folders_adapter); + popup.setOnMenuItemClickListener(i -> optionsItemSelected(i, section, item)); + popup.getMenu() + .findItem(R.id.action_auto_upload_folder_toggle_visibility) + .setChecked(item.isHidden()); - holder.syncStatusButton.setVisibility(View.VISIBLE); - holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnClickListener(v -> { - mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); - setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); - mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); - }); - setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); - - holder.syncStatusButton.setVisibility(View.VISIBLE); - holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnClickListener(v -> { - mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); - setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); - mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); - }); - setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); - - if (mLight) { - holder.menuButton.setVisibility(View.GONE); + popup.show(); + } + + private boolean optionsItemSelected(MenuItem menuItem, int section, SyncedFolderDisplayItem item) { + if (menuItem.getItemId() == R.id.action_auto_upload_folder_toggle_visibility) { + clickListener.onVisibilityToggleClick(section, item); } else { - holder.menuButton.setVisibility(View.VISIBLE); - holder.menuButton.setTag(section); - holder.menuButton.setOnClickListener(v -> mListener.onSyncFolderSettingsClick(section, - mSyncFolderItems.get(section))); + // default: R.id.action_create_custom_folder + clickListener.onSyncFolderSettingsClick(section, item); } + return true; } @Override public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { - // not needed + if (isLastSection(section) && showFooter()) { + FooterViewHolder footerHolder = (FooterViewHolder) holder; + footerHolder.title.setOnClickListener(v -> toggleHiddenItemsVisibility()); + footerHolder.title.setText( + context.getResources().getQuantityString( + R.plurals.synced_folders_show_hidden_folders, + getHiddenFolderCount(), + getHiddenFolderCount() + ) + ); + } } - @Override public void onBindViewHolder(SectionedViewHolder commonHolder, int section, int relativePosition, int absolutePosition) { - if (mSyncFolderItems.get(section).getFilePaths() != null) { + if (section < filteredSyncFolderItems.size() && filteredSyncFolderItems.get(section).getFilePaths() != null) { MainViewHolder holder = (MainViewHolder) commonHolder; - File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); + File file = new File(filteredSyncFolderItems.get(section).getFilePaths().get(relativePosition)); ThumbnailsCacheManager.MediaThumbnailGenerationTask task = - new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image, mContext); + new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image, context); ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( - mContext.getResources(), - ThumbnailsCacheManager.mDefaultImg, - task + context.getResources(), + ThumbnailsCacheManager.mDefaultImg, + task ); holder.image.setImageDrawable(asyncDrawable); @@ -203,11 +345,11 @@ public void onBindViewHolder(SectionedViewHolder commonHolder, int section, int // set proper tag holder.image.setTag(file.hashCode()); - holder.itemView.setTag(relativePosition % mGridWidth); + holder.itemView.setTag(relativePosition % gridWidth); - if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { + if (filteredSyncFolderItems.get(section).getNumberOfFiles() > gridTotal && relativePosition >= gridTotal - 1) { holder.counterValue.setText(String.format(Locale.US, "%d", - mSyncFolderItems.get(section).getNumberOfFiles() - mGridTotal)); + filteredSyncFolderItems.get(section).getNumberOfFiles() - gridTotal)); holder.counterBar.setVisibility(View.VISIBLE); holder.thumbnailDarkener.setVisibility(View.VISIBLE); } else { @@ -223,15 +365,34 @@ public SectionedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int vie if (viewType == VIEW_TYPE_HEADER) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.synced_folders_item_header, parent, false); return new HeaderViewHolder(v); + } else if (viewType == VIEW_TYPE_FOOTER) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.synced_folders_footer, parent, false); + return new FooterViewHolder(v); + } else if (viewType == VIEW_TYPE_EMPTY) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.synced_folders_empty, parent, false); + return new EmptyViewHolder(v); } else { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.grid_sync_item, parent, false); return new MainViewHolder(v); } } + private boolean isLastSection(int section) { + return section >= getSectionCount() - 1; + } + + public int getHiddenFolderCount() { + if (syncFolderItems != null && filteredSyncFolderItems != null) { + return syncFolderItems.size() - filteredSyncFolderItems.size(); + } else { + return 0; + } + } + public interface ClickListener { void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); + void onVisibilityToggleClick(int section, SyncedFolderDisplayItem item); } static class HeaderViewHolder extends SectionedViewHolder { @@ -256,7 +417,29 @@ private HeaderViewHolder(View itemView) { } } + static class FooterViewHolder extends SectionedViewHolder { + @BindView(R.id.footer_container) + public LinearLayout mainFooterContainer; + + @BindView(R.id.footer_text) + public TextView title; + + private FooterViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + static class EmptyViewHolder extends SectionedViewHolder { + private EmptyViewHolder(View itemView) { + super(itemView); + } + } + static class MainViewHolder extends SectionedViewHolder { + @BindView(R.id.grid_item_container) + public FrameLayout item_container; + @BindView(R.id.thumbnail) public ImageView image; @@ -278,7 +461,7 @@ private MainViewHolder(View itemView) { private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) { if (enabled) { syncStatusButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_cloud_sync_on, - ThemeUtils.primaryColor(mContext))); + ThemeUtils.primaryColor(context))); } else { syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off); } diff --git a/src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java index 40a42bf5a067..6769d5b53033 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java @@ -33,6 +33,7 @@ import com.bumptech.glide.Glide; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.datamodel.Template; import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment; @@ -56,17 +57,20 @@ public class TemplateAdapter extends RecyclerView.Adapter files; private final Context context; - private final Account account; + private final User user; private final FileDataStorageManager storageManager; private final AppPreferences preferences; @@ -76,11 +76,11 @@ public TrashbinListAdapter( FileDataStorageManager storageManager, AppPreferences preferences, Context context, - Account account + User user ) { this.files = new ArrayList<>(); this.trashbinActivityInterface = trashbinActivityInterface; - this.account = account; + this.user = user; this.storageManager = storageManager; this.preferences = preferences; this.context = context; @@ -237,7 +237,7 @@ private void setThumbnail(TrashbinFile file, ImageView thumbnailView) { try { final ThumbnailsCacheManager.ThumbnailGenerationTask task = new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView, storageManager, - account, asyncTasks); + user.toPlatformAccount(), asyncTasks); final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = new ThumbnailsCacheManager.AsyncThumbnailDrawable(context.getResources(), @@ -257,7 +257,7 @@ private void setThumbnail(TrashbinFile file, ImageView thumbnailView) { } } else { thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), - account, context)); + user.toPlatformAccount(), context)); } } } diff --git a/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java index 7c503029ada9..81cbe9a6025d 100755 --- a/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -25,6 +25,7 @@ import android.accounts.Account; import android.content.ActivityNotFoundException; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; @@ -41,9 +42,12 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.afollestad.sectionedrecyclerview.SectionedViewHolder; 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.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.datamodel.UploadsStorageManager; @@ -52,7 +56,10 @@ import com.owncloud.android.db.OCUploadComparator; import com.owncloud.android.db.UploadResult; import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.RefreshFolderOperation; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -74,9 +81,11 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter new FileUploader.UploadRequester() - .retryFailedUploads( - parentActivity, - null, - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService, - null)) - .start(); + new Thread(() -> FileUploader.retryFailedUploads( + parentActivity, + null, + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService, + null + )).start(); break; default: @@ -157,15 +165,19 @@ public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { public UploadListAdapter(final FileActivity fileActivity, final UploadsStorageManager uploadsStorageManager, + final FileDataStorageManager storageManager, final UserAccountManager accountManager, final ConnectivityService connectivityService, - final PowerManagementService powerManagementService) { + final PowerManagementService powerManagementService, + final Clock clock) { Log_OC.d(TAG, "UploadListAdapter"); this.parentActivity = fileActivity; this.uploadsStorageManager = uploadsStorageManager; + this.storageManager = storageManager; this.accountManager = accountManager; this.connectivityService = connectivityService; this.powerManagementService = powerManagementService; + this.clock = clock; uploadGroups = new UploadGroup[3]; shouldShowHeadersForEmptySections(false); @@ -338,26 +350,54 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati // click on item if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED) { - if (UploadResult.CREDENTIAL_ERROR == item.getLastResult()) { - itemViewHolder.itemLayout.setOnClickListener(v -> - parentActivity.getFileOperationsHelper().checkCurrentCredentials( - item.getAccount(accountManager))); - } else { - // not a credentials error - itemViewHolder.itemLayout.setOnClickListener(v -> { - File file = new File(item.getLocalPath()); - if (file.exists()) { - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retry(parentActivity, accountManager, item); - loadUploadItemsFromDb(); - } else { - DisplayUtils.showSnackMessage( - v.getRootView().findViewById(android.R.id.content), - R.string.local_file_not_found_message - ); + final UploadResult uploadResult = item.getLastResult(); + itemViewHolder.itemLayout.setOnClickListener(v -> { + if (uploadResult == UploadResult.CREDENTIAL_ERROR) { + parentActivity.getFileOperationsHelper().checkCurrentCredentials( + item.getAccount(accountManager)); + return; + } else if (uploadResult == UploadResult.SYNC_CONFLICT) { + String remotePath = item.getRemotePath(); + OCFile ocFile = storageManager.getFileByPath(remotePath); + + if (ocFile == null) { // Remote file doesn't exist, try to refresh folder + OCFile folder = storageManager.getFileByPath(new File(remotePath).getParent() + "/"); + if (folder != null && folder.isFolder()) { + this.refreshFolder(itemViewHolder, account, folder, (caller, result) -> { + itemViewHolder.status.setText(status); + if (result.isSuccess()) { + OCFile file = storageManager.getFileByPath(remotePath); + if (file != null) { + this.openConflictActivity(file, item); + } + } + }); + return; + } + + // Destination folder doesn't exist anymore } - }); - } + + if (ocFile != null) { + this.openConflictActivity(ocFile, item); + return; + } + + // Remote file doesn't exist anymore = there is no more conflict + } + + // not a credentials error + File file = new File(item.getLocalPath()); + if (file.exists()) { + FileUploader.retryUpload(parentActivity, item.getAccount(accountManager), item); + loadUploadItemsFromDb(); + } else { + DisplayUtils.showSnackMessage( + v.getRootView().findViewById(android.R.id.content), + R.string.local_file_not_found_message + ); + } + }); } else { itemViewHolder.itemLayout.setOnClickListener(v -> onUploadItemClick(item)); @@ -459,6 +499,36 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati } } + private void refreshFolder(ItemViewHolder view, Account account, OCFile folder, OnRemoteOperationListener listener) { + view.itemLayout.setClickable(false); + view.status.setText(R.string.uploads_view_upload_status_fetching_server_version); + Context context = MainApp.getAppContext(); + new RefreshFolderOperation(folder, + clock.getCurrentTime(), + false, + false, + true, + storageManager, + account, + context) + .execute(account, context, (caller, result) -> { + view.itemLayout.setClickable(true); + listener.onRemoteOperationFinish(caller, result); + }, parentActivity.getHandler()); + } + + private void openConflictActivity(OCFile file, OCUpload upload) { + file.setStoragePath(upload.getLocalPath()); + + Context context = MainApp.getAppContext(); + Intent i = new Intent(context, ConflictsResolveActivity.class); + i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, upload.getAccount(accountManager)); + i.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD, upload); + context.startActivity(i); + } + /** * Gets the status text to show to the user according to the status and last result of the * the given upload. diff --git a/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java index 2ac47fb9ca8c..9bebf2244313 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java @@ -183,7 +183,7 @@ private void onOverflowIconClicked(View view, AppCompatCheckBox allowEditsCheckB context.getTheme().applyStyle(R.style.FallbackThemingTheme, true); } PopupMenu popup = new PopupMenu(context, view); - popup.inflate(R.menu.file_detail_sharing_menu); + popup.inflate(R.menu.item_user_sharing_settings); prepareOptionsMenu(popup.getMenu(), share); diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java index f60552ebfbbe..9ae8d452fb8a 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java @@ -219,8 +219,7 @@ protected ResultCode doInBackground(Object[] params) { } private void requestUpload(Account account, String localPath, String remotePath, int behaviour, String mimeType) { - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.uploadNewFile( + FileUploader.uploadNewFile( mAppContext, account, localPath, @@ -230,7 +229,8 @@ private void requestUpload(Account account, String localPath, String remotePath, false, // do not create parent folder if not existent UploadFileOperation.CREATED_BY_USER, false, - false + false, + FileUploader.NameCollisionPolicy.ASK_USER ); } diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java index a300ac0ca975..e388d96380d8 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/PhotoSearchTask.java @@ -21,9 +21,9 @@ package com.owncloud.android.ui.asynctasks; -import android.accounts.Account; import android.os.AsyncTask; +import com.nextcloud.client.account.User; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -38,18 +38,18 @@ public class PhotoSearchTask extends AsyncTask { private int columnCount; - private Account account; + private User user; private WeakReference photoFragmentWeakReference; private SearchRemoteOperation searchRemoteOperation; private FileDataStorageManager storageManager; public PhotoSearchTask(int columnsCount, PhotoFragment photoFragment, - Account account, + User user, SearchRemoteOperation searchRemoteOperation, FileDataStorageManager storageManager) { this.columnCount = columnsCount; - this.account = account; + this.user = user; this.photoFragmentWeakReference = new WeakReference<>(photoFragment); this.searchRemoteOperation = searchRemoteOperation; this.storageManager = storageManager; @@ -88,7 +88,7 @@ protected RemoteOperationResult doInBackground(Void... voids) { searchRemoteOperation.setTimestamp(timestamp); if (photoFragment.getContext() != null) { - return searchRemoteOperation.execute(account, photoFragment.getContext()); + return searchRemoteOperation.execute(user.toPlatformAccount(), photoFragment.getContext()); } else { return new RemoteOperationResult(new IllegalStateException("No context available")); } diff --git a/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java index 044932479085..90bffa6dc35f 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java @@ -24,7 +24,6 @@ package com.owncloud.android.ui.dialog; -import android.accounts.Account; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; @@ -40,16 +39,16 @@ import android.widget.EditText; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.account.User; import com.nextcloud.client.di.Injectable; +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.datamodel.Template; import com.owncloud.android.files.CreateFileFromTemplateOperation; import com.owncloud.android.files.FetchTemplateOperation; -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.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.ExternalSiteWebView; @@ -88,7 +87,8 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial private TemplateAdapter adapter; private OCFile parentFolder; private OwnCloudClient client; - @Inject CurrentAccountProvider currentAccount; + @Inject CurrentAccountProvider currentUser; + @Inject ClientFactory clientFactory; public enum Type { DOCUMENT, @@ -151,9 +151,8 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { fileName.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP); try { - Account account = currentAccount.getCurrentAccount(); - OwnCloudAccount ocAccount = new OwnCloudAccount(account, activity); - client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext()); + User user = currentUser.getUser(); + client = clientFactory.create(user); new FetchTemplateTask(this, client).execute(type); } catch (Exception e) { @@ -162,7 +161,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { listView.setHasFixedSize(true); listView.setLayoutManager(new GridLayoutManager(activity, 2)); - adapter = new TemplateAdapter(type, this, getContext(), currentAccount); + adapter = new TemplateAdapter(type, this, getContext(), currentUser, clientFactory); listView.setAdapter(adapter); // Build the dialog diff --git a/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java b/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java index 7c139e307af6..a231792fbada 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java +++ b/src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java @@ -43,44 +43,48 @@ public class ConflictsResolveDialog extends DialogFragment { public enum Decision { CANCEL, KEEP_BOTH, - OVERWRITE, - SERVER + KEEP_LOCAL, + KEEP_SERVER, } - OnConflictDecisionMadeListener mListener; + private final OnConflictDecisionMadeListener listener; + private final boolean canKeepServer; - public static ConflictsResolveDialog newInstance(OnConflictDecisionMadeListener listener) { - ConflictsResolveDialog f = new ConflictsResolveDialog(); - f.mListener = listener; - return f; + public ConflictsResolveDialog(OnConflictDecisionMadeListener listener, boolean canKeepServer) { + this.listener = listener; + this.canKeepServer = canKeepServer; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(requireActivity(), R.style.Theme_ownCloud_Dialog) - .setIcon(R.drawable.ic_warning) - .setTitle(R.string.conflict_title) - .setMessage(getString(R.string.conflict_message)) - .setPositiveButton(R.string.conflict_use_local_version, - (dialog, which) -> { - if (mListener != null) { - mListener.conflictDecisionMade(Decision.OVERWRITE); - } - }) - .setNeutralButton(R.string.conflict_keep_both, - (dialog, which) -> { - if (mListener != null) { - mListener.conflictDecisionMade(Decision.KEEP_BOTH); - } - }) - .setNegativeButton(R.string.conflict_use_server_version, - (dialog, which) -> { - if (mListener != null) { - mListener.conflictDecisionMade(Decision.SERVER); - } - }) - .create(); + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity(), R.style.Theme_ownCloud_Dialog) + .setIcon(R.drawable.ic_warning) + .setTitle(R.string.conflict_title) + .setMessage(getString(R.string.conflict_message)) + .setPositiveButton(R.string.conflict_use_local_version, + (dialog, which) -> { + if (listener != null) { + listener.conflictDecisionMade(Decision.KEEP_LOCAL); + } + }) + .setNeutralButton(R.string.conflict_keep_both, + (dialog, which) -> { + if (listener != null) { + listener.conflictDecisionMade(Decision.KEEP_BOTH); + } + }); + + if (this.canKeepServer) { + builder.setNegativeButton(R.string.conflict_use_server_version, + (dialog, which) -> { + if (listener != null) { + listener.conflictDecisionMade(Decision.KEEP_SERVER); + } + }); + } + + return builder.create(); } public void showDialog(AppCompatActivity activity) { @@ -96,8 +100,8 @@ public void showDialog(AppCompatActivity activity) { @Override public void onCancel(DialogInterface dialog) { - if (mListener != null) { - mListener.conflictDecisionMade(Decision.CANCEL); + if (listener != null) { + listener.conflictDecisionMade(Decision.CANCEL); } } diff --git a/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java index c5800ffd52ea..fb9d941f90a5 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java @@ -142,9 +142,9 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { }); dialog.show(); - dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL).setTextColor(ThemeUtils.primaryColor(getContext())); - dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setTextColor(ThemeUtils.primaryColor(getContext())); - dialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setTextColor(ThemeUtils.primaryColor(getContext())); + dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL).setTextColor(ThemeUtils.primaryColor(getContext(), true)); + dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setTextColor(ThemeUtils.primaryColor(getContext(), true)); + dialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setTextColor(ThemeUtils.primaryColor(getContext(), true)); // Prevent days in the past may be chosen DatePicker picker = dialog.getDatePicker(); diff --git a/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index b590d60e8214..91ae78d593fe 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -77,6 +77,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private SwitchCompat mEnabledSwitch; private AppCompatCheckBox mUploadOnWifiCheckbox; private AppCompatCheckBox mUploadOnChargingCheckbox; + private AppCompatCheckBox mUploadExistingCheckbox; private AppCompatCheckBox mUploadUseSubfoldersCheckbox; private TextView mUploadBehaviorSummary; private TextView mLocalFolderPath; @@ -189,6 +190,9 @@ private void setupDialogElements(View view) { ThemeUtils.tintCheckbox(mUploadOnChargingCheckbox, accentColor); } + mUploadExistingCheckbox = view.findViewById(R.id.setting_instant_upload_existing_checkbox); + ThemeUtils.tintCheckbox(mUploadExistingCheckbox, accentColor); + mUploadUseSubfoldersCheckbox = view.findViewById( R.id.setting_instant_upload_path_use_subfolders_checkbox); ThemeUtils.tintCheckbox(mUploadUseSubfoldersCheckbox, accentColor); @@ -202,7 +206,7 @@ private void setupDialogElements(View view) { ThemeUtils.themeDialogActionButton(mSave); // Set values - setEnabled(mSyncedFolder.getEnabled()); + setEnabled(mSyncedFolder.isEnabled()); if (!TextUtils.isEmpty(mSyncedFolder.getLocalPath())) { mLocalFolderPath.setText( @@ -223,11 +227,12 @@ private void setupDialogElements(View view) { mRemoteFolderSummary.setText(R.string.choose_remote_folder); } - mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly()); + mUploadOnWifiCheckbox.setChecked(mSyncedFolder.isWifiOnly()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly()); + mUploadOnChargingCheckbox.setChecked(mSyncedFolder.isChargingOnly()); } - mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate()); + mUploadExistingCheckbox.setChecked(mSyncedFolder.isExisting()); + mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.isSubfolderByDate()); mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]); } @@ -318,6 +323,9 @@ private void setupViews(View view, boolean enable) { view.findViewById(R.id.setting_instant_upload_on_charging_container).setAlpha(alpha); } + view.findViewById(R.id.setting_instant_upload_existing_container).setEnabled(enable); + view.findViewById(R.id.setting_instant_upload_existing_container).setAlpha(alpha); + view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setEnabled(enable); view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setAlpha(alpha); @@ -344,7 +352,7 @@ private void setupListeners(View view) { new OnClickListener() { @Override public void onClick(View v) { - mSyncedFolder.setWifiOnly(!mSyncedFolder.getWifiOnly()); + mSyncedFolder.setWifiOnly(!mSyncedFolder.isWifiOnly()); mUploadOnWifiCheckbox.toggle(); } }); @@ -355,17 +363,26 @@ public void onClick(View v) { new OnClickListener() { @Override public void onClick(View v) { - mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly()); + mSyncedFolder.setChargingOnly(!mSyncedFolder.isChargingOnly()); mUploadOnChargingCheckbox.toggle(); } }); } + view.findViewById(R.id.setting_instant_upload_existing_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setExisting(!mSyncedFolder.isExisting()); + mUploadExistingCheckbox.toggle(); + } + }); + view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { - mSyncedFolder.setSubfolderByDate(!mSyncedFolder.getSubfolderByDate()); + mSyncedFolder.setSubfolderByDate(!mSyncedFolder.isSubfolderByDate()); mUploadUseSubfoldersCheckbox.toggle(); } }); @@ -401,7 +418,7 @@ public void onClick(View v) { view.findViewById(R.id.sync_enabled).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - setEnabled(!mSyncedFolder.getEnabled()); + setEnabled(!mSyncedFolder.isEnabled()); } }); diff --git a/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index c435857cc961..b59cf77625d3 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -27,70 +27,80 @@ import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.files.services.FileUploader; +import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; /** * Parcelable for {@link SyncedFolderDisplayItem} objects to transport them from/to dialog fragments. */ @NoArgsConstructor public class SyncedFolderParcelable implements Parcelable { - private String mFolderName; - private String mLocalPath; - private String mRemotePath; - private Boolean mWifiOnly = false; - private Boolean mChargingOnly = false; - private Boolean mEnabled = false; - private Boolean mSubfolderByDate = false; - private Integer mUploadAction; - private MediaFolderType mType; - private long mId; - private String mAccount; - private int mSection; + @Getter @Setter private String folderName; + @Getter @Setter private String localPath; + @Getter @Setter private String remotePath; + @Getter @Setter private boolean wifiOnly = false; + @Getter @Setter private boolean chargingOnly = false; + @Getter @Setter private boolean existing = true; + @Getter @Setter private boolean enabled = false; + @Getter @Setter private boolean subfolderByDate = false; + @Getter private Integer uploadAction; + @Getter @Setter private MediaFolderType type; + @Getter @Setter private boolean hidden = false; + @Getter @Setter private long id; + @Getter @Setter private String account; + @Getter @Setter private int section; public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, int section) { - mId = syncedFolderDisplayItem.getId(); - mFolderName = syncedFolderDisplayItem.getFolderName(); - mLocalPath = syncedFolderDisplayItem.getLocalPath(); - mRemotePath = syncedFolderDisplayItem.getRemotePath(); - mWifiOnly = syncedFolderDisplayItem.getWifiOnly(); - mChargingOnly = syncedFolderDisplayItem.getChargingOnly(); - mEnabled = syncedFolderDisplayItem.isEnabled(); - mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate(); - mType = syncedFolderDisplayItem.getType(); - mAccount = syncedFolderDisplayItem.getAccount(); - mUploadAction = syncedFolderDisplayItem.getUploadAction(); - mSection = section; + id = syncedFolderDisplayItem.getId(); + folderName = syncedFolderDisplayItem.getFolderName(); + localPath = syncedFolderDisplayItem.getLocalPath(); + remotePath = syncedFolderDisplayItem.getRemotePath(); + wifiOnly = syncedFolderDisplayItem.isWifiOnly(); + chargingOnly = syncedFolderDisplayItem.isChargingOnly(); + existing = syncedFolderDisplayItem.isExisting(); + enabled = syncedFolderDisplayItem.isEnabled(); + subfolderByDate = syncedFolderDisplayItem.isSubfolderByDate(); + type = syncedFolderDisplayItem.getType(); + account = syncedFolderDisplayItem.getAccount(); + uploadAction = syncedFolderDisplayItem.getUploadAction(); + this.section = section; + hidden = syncedFolderDisplayItem.isHidden(); } private SyncedFolderParcelable(Parcel read) { - mId = read.readLong(); - mFolderName = read.readString(); - mLocalPath = read.readString(); - mRemotePath = read.readString(); - mWifiOnly = read.readInt()!= 0; - mChargingOnly = read.readInt() != 0; - mEnabled = read.readInt() != 0; - mSubfolderByDate = read.readInt() != 0; - mType = MediaFolderType.getById(read.readInt()); - mAccount = read.readString(); - mUploadAction = read.readInt(); - mSection = read.readInt(); + id = read.readLong(); + folderName = read.readString(); + localPath = read.readString(); + remotePath = read.readString(); + wifiOnly = read.readInt()!= 0; + chargingOnly = read.readInt() != 0; + existing = read.readInt() != 0; + enabled = read.readInt() != 0; + subfolderByDate = read.readInt() != 0; + type = MediaFolderType.getById(read.readInt()); + account = read.readString(); + uploadAction = read.readInt(); + section = read.readInt(); + hidden = read.readInt() != 0; } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mId); - dest.writeString(mFolderName); - dest.writeString(mLocalPath); - dest.writeString(mRemotePath); - dest.writeInt(mWifiOnly ? 1 : 0); - dest.writeInt(mChargingOnly ? 1 : 0); - dest.writeInt(mEnabled ? 1 : 0); - dest.writeInt(mSubfolderByDate ? 1 : 0); - dest.writeInt(mType.getId()); - dest.writeString(mAccount); - dest.writeInt(mUploadAction); - dest.writeInt(mSection); + dest.writeLong(id); + dest.writeString(folderName); + dest.writeString(localPath); + dest.writeString(remotePath); + dest.writeInt(wifiOnly ? 1 : 0); + dest.writeInt(chargingOnly ? 1 : 0); + dest.writeInt(existing ? 1 : 0); + dest.writeInt(enabled ? 1 : 0); + dest.writeInt(subfolderByDate ? 1 : 0); + dest.writeInt(type.getId()); + dest.writeString(account); + dest.writeInt(uploadAction); + dest.writeInt(section); + dest.writeInt(hidden ? 1 : 0); } public static final Creator CREATOR = @@ -112,76 +122,8 @@ public int describeContents() { return 0; } - public String getFolderName() { - return mFolderName; - } - - public void setFolderName(String mFolderName) { - this.mFolderName = mFolderName; - } - - public String getLocalPath() { - return mLocalPath; - } - - public void setLocalPath(String mLocalPath) { - this.mLocalPath = mLocalPath; - } - - public String getRemotePath() { - return mRemotePath; - } - - public void setRemotePath(String mRemotePath) { - this.mRemotePath = mRemotePath; - } - - public Boolean getWifiOnly() { - return mWifiOnly; - } - - public void setWifiOnly(Boolean mWifiOnly) { - this.mWifiOnly = mWifiOnly; - } - - public Boolean getChargingOnly() { - return mChargingOnly; - } - - public void setChargingOnly(Boolean mChargingOnly) { - this.mChargingOnly = mChargingOnly; - } - - public Boolean getEnabled() { - return mEnabled; - } - - public void setEnabled(boolean mEnabled) { - this.mEnabled = mEnabled; - } - - public Boolean getSubfolderByDate() { - return mSubfolderByDate; - } - - public void setSubfolderByDate(Boolean mSubfolderByDate) { - this.mSubfolderByDate = mSubfolderByDate; - } - - public MediaFolderType getType() { - return mType; - } - - public void setType(MediaFolderType mType) { - this.mType = mType; - } - - public Integer getUploadAction() { - return mUploadAction; - } - public Integer getUploadActionInteger() { - switch (mUploadAction) { + switch (uploadAction) { case FileUploader.LOCAL_BEHAVIOUR_FORGET: return 0; case FileUploader.LOCAL_BEHAVIOUR_MOVE: @@ -192,41 +134,17 @@ public Integer getUploadActionInteger() { return 0; } - public void setUploadAction(String mUploadAction) { - switch (mUploadAction) { + public void setUploadAction(String uploadAction) { + switch (uploadAction) { case "LOCAL_BEHAVIOUR_FORGET": - this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_FORGET; + this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_FORGET; break; case "LOCAL_BEHAVIOUR_MOVE": - this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_MOVE; + this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_MOVE; break; case "LOCAL_BEHAVIOUR_DELETE": - this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_DELETE; + this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_DELETE; break; } } - - public long getId() { - return mId; - } - - public void setId(long mId) { - this.mId = mId; - } - - public String getAccount() { - return mAccount; - } - - public void setAccount(String mAccount) { - this.mAccount = mAccount; - } - - public int getSection() { - return mSection; - } - - public void setSection(int mSection) { - this.mSection = mSection; - } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java index a1032b363b56..165bc9d0e4db 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -53,6 +53,7 @@ import android.widget.TextView; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.preferences.AppPreferences; @@ -304,7 +305,8 @@ private void performSearch(final String query, boolean isSubmit) { handler.postDelayed(new Runnable() { @Override public void run() { - if (accountManager.isSearchSupported(accountManager.getCurrentAccount())) { + User user = accountManager.getUser(); + if (user.getServer().getVersion().isSearchSupported()) { EventBus.getDefault().post(new SearchEvent(query, SearchRemoteOperation.SearchType.FILE_SEARCH, SearchEvent.UnsetType.NO_UNSET)); } else { diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java index 60886c3f7fd3..ce4d1e37c202 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java @@ -24,9 +24,6 @@ package com.owncloud.android.ui.fragment; import android.accounts.Account; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.content.Context; import android.graphics.PorterDuff; import android.os.AsyncTask; import android.os.Bundle; @@ -41,15 +38,14 @@ import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputEditText; +import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; -import com.owncloud.android.MainApp; +import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; 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.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.activities.GetActivitiesRemoteOperation; @@ -72,7 +68,6 @@ import org.apache.commons.httpclient.HttpStatus; import org.greenrobot.eventbus.EventBus; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -149,6 +144,7 @@ public class FileDetailActivitiesFragment extends Fragment implements private VersionListInterface.CommentCallback callback; @Inject UserAccountManager accountManager; + @Inject ClientFactory clientFactory; public static FileDetailActivitiesFragment newInstance(OCFile file, Account account) { FileDetailActivitiesFragment fragment = new FileDetailActivitiesFragment(); @@ -258,7 +254,9 @@ private void setupView() { PorterDuff.Mode.SRC_IN); emptyContentIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_activity_light_grey)); - adapter = new ActivityAndVersionListAdapter(getContext(), accountManager, this, this, storageManager, capability); + adapter = new ActivityAndVersionListAdapter(getContext(), accountManager, this, this, + storageManager, + capability); recyclerView.setAdapter(adapter); LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); @@ -301,9 +299,9 @@ private void fetchAndSetData(int lastGiven) { final SwipeRefreshLayout empty = swipeEmptyListRefreshLayout; final SwipeRefreshLayout list = swipeListRefreshLayout; - final Account currentAccount = accountManager.getCurrentAccount(); + final User user = accountManager.getUser(); - if (currentAccount == null) { + if (user.isAnonymous()) { activity.runOnUiThread(() -> { setEmptyContent(getString(R.string.common_error), getString(R.string.file_detail_activity_error)); list.setVisibility(View.GONE); @@ -312,15 +310,10 @@ private void fetchAndSetData(int lastGiven) { return; } - final Context context = MainApp.getAppContext(); - Thread t = new Thread(() -> { - OwnCloudAccount ocAccount; try { - ocAccount = new OwnCloudAccount(currentAccount, context); - ownCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, MainApp.getAppContext()); - ownCloudClient.setOwnCloudVersion(accountManager.getServerVersion(currentAccount)); + ownCloudClient = clientFactory.create(user); + ownCloudClient.setOwnCloudVersion(user.getServer().getVersion()); isLoadingActivities = true; GetActivitiesRemoteOperation getRemoteNotificationOperation; @@ -385,8 +378,7 @@ private void fetchAndSetData(int lastGiven) { } hideRefreshLayoutLoader(activity); - } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException | IOException | - OperationCanceledException | AuthenticatorException | NullPointerException e) { + } catch (ClientFactory.CreationException e) { Log_OC.e(TAG, "Error fetching file details activities", e); } }); diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index c9391fb2fed4..6b7943c46963 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -300,7 +300,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat private void onOverflowIconClicked(View view) { PopupMenu popup = new PopupMenu(getActivity(), view); - popup.inflate(R.menu.file_details_actions_menu); + popup.inflate(R.menu.fragment_file_detail); prepareOptionsMenu(popup.getMenu()); popup.setOnMenuItemClickListener(this::optionsItemSelected); @@ -468,6 +468,10 @@ private boolean optionsItemSelected(MenuItem item) { containerActivity.getFileOperationsHelper().syncFile(getFile()); return true; } + case R.id.action_set_as_wallpaper: { + containerActivity.getFileOperationsHelper().setPictureAs(getFile(), getView()); + return true; + } case R.id.action_encrypted: { // TODO implement or remove return true; diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 69139ec13694..35be700d8c05 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -409,7 +409,7 @@ public void showLinkOverflowMenu() { } PopupMenu popup = new PopupMenu(context, overflowMenuShareLink); - popup.inflate(R.menu.file_detail_sharing_link_menu); + popup.inflate(R.menu.fragment_file_detail_sharing_link); prepareOptionsMenu(popup.getMenu()); popup.setOnMenuItemClickListener(this::optionsItemSelected); popup.show(); diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index ab26974836d6..3d3b85aaa591 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -47,9 +47,11 @@ import android.widget.PopupMenu; 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; +import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -169,6 +171,7 @@ public class OCFileListFragment extends ExtendedListFragment implements @Inject AppPreferences preferences; @Inject UserAccountManager accountManager; + @Inject ClientFactory clientFactory; protected FileFragment.ContainerActivity mContainerActivity; protected OCFile mFile; @@ -332,7 +335,7 @@ public void onActivityCreated(Bundle savedInstanceState) { mAdapter = new OCFileListAdapter( getActivity(), - accountManager.getCurrentAccount(), + accountManager.getUser(), preferences, accountManager, mContainerActivity, @@ -467,7 +470,7 @@ public void showActivityDetailView(OCFile file) { @Override public void onOverflowIconClicked(OCFile file, View view) { PopupMenu popup = new PopupMenu(getActivity(), view); - popup.inflate(R.menu.file_actions_menu); + popup.inflate(R.menu.item_file); Account currentAccount = ((FileActivity) getActivity()).getAccount(); FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(), Collections.singleton(file), @@ -587,7 +590,7 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { mActiveActionMode = mode; MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.file_actions_menu, menu); + inflater.inflate(R.menu.item_file, menu); mode.invalidate(); //set gray color @@ -942,10 +945,11 @@ public void onItemClicked(OCFile file) { mContainerActivity.getFileOperationsHelper().openFile(file); } } else { - Account account = accountManager.getCurrentAccount(); - OCCapability capability = mContainerActivity.getStorageManager().getCapability(account.name); + User account = accountManager.getUser(); + OCCapability capability = mContainerActivity.getStorageManager() + .getCapability(account.getAccountName()); - if (PreviewMediaFragment.canBePreviewed(file) && accountManager.getServerVersion(account) + if (PreviewMediaFragment.canBePreviewed(file) && account.getServer().getVersion() .isMediaStreamingSupported()) { // stream media preview on >= NC14 ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true); @@ -1180,7 +1184,7 @@ public void run() { } mAdapter.swapDirectory( - accountManager.getCurrentAccount(), + accountManager.getUser(), directory, storageManager, onlyOnDevice, @@ -1430,26 +1434,19 @@ public void onMessageEvent(CommentsEvent event) { @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessageEvent(FavoriteEvent event) { - Account currentAccount = accountManager.getCurrentAccount(); - - OwnCloudAccount ocAccount; - try { - ocAccount = new OwnCloudAccount(currentAccount, MainApp.getAppContext()); - - OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, MainApp.getAppContext()); + User user = accountManager.getUser(); + OwnCloudClient client = clientFactory.create(user); ToggleFavoriteRemoteOperation toggleFavoriteOperation = new ToggleFavoriteRemoteOperation( event.shouldFavorite, event.remotePath); - RemoteOperationResult remoteOperationResult = toggleFavoriteOperation.execute(mClient); + RemoteOperationResult remoteOperationResult = toggleFavoriteOperation.execute(client); if (remoteOperationResult.isSuccess()) { mAdapter.setFavoriteAttributeForItemID(event.remoteId, event.shouldFavorite); } - } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException | AuthenticatorException - | IOException | OperationCanceledException e) { + } catch (ClientFactory.CreationException e) { Log_OC.e(TAG, "Error processing event", e); } } @@ -1483,7 +1480,7 @@ public void onMessageEvent(final SearchEvent event) { new Handler(Looper.getMainLooper()).post(switchViewsRunnable); - final Account currentAccount = accountManager.getCurrentAccount(); + final User currentAccount = accountManager.getUser(); final RemoteOperation remoteOperation; if (currentSearchType != SearchType.SHARED_FILTER) { @@ -1503,7 +1500,8 @@ public void onMessageEvent(final SearchEvent event) { protected Object doInBackground(Object[] params) { setTitle(); if (getContext() != null && !isCancelled()) { - RemoteOperationResult remoteOperationResult = remoteOperation.execute(currentAccount, getContext()); + RemoteOperationResult remoteOperationResult = remoteOperation.execute( + currentAccount.toPlatformAccount(), getContext()); FileDataStorageManager storageManager = null; if (mContainerActivity != null && mContainerActivity.getStorageManager() != null) { @@ -1555,18 +1553,12 @@ protected void onPostExecute(Object o) { @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessageEvent(EncryptionEvent event) { - Account currentAccount = accountManager.getCurrentAccount(); - - OwnCloudAccount ocAccount; try { - ocAccount = new OwnCloudAccount(currentAccount, MainApp.getAppContext()); - - OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, MainApp.getAppContext()); - - ToggleEncryptionRemoteOperation toggleEncryptionOperation = new ToggleEncryptionRemoteOperation( + final User user = accountManager.getUser(); + final OwnCloudClient client = clientFactory.create(user); + final ToggleEncryptionRemoteOperation toggleEncryptionOperation = new ToggleEncryptionRemoteOperation( event.localId, event.remotePath, event.shouldBeEncrypted); - RemoteOperationResult remoteOperationResult = toggleEncryptionOperation.execute(mClient); + final RemoteOperationResult remoteOperationResult = toggleEncryptionOperation.execute(client); if (remoteOperationResult.isSuccess()) { mAdapter.setEncryptionAttributeForItemID(event.remoteId, event.shouldBeEncrypted); @@ -1576,14 +1568,8 @@ public void onMessageEvent(EncryptionEvent event) { Snackbar.make(getRecyclerView(), R.string.common_error_unknown, Snackbar.LENGTH_LONG).show(); } - } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) { - Log_OC.e(TAG, "Account not found", e); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Authentication failed", e); - } catch (IOException e) { - Log_OC.e(TAG, "IO error", e); - } catch (OperationCanceledException e) { - Log_OC.e(TAG, "Operation has been canceled", e); + } catch (ClientFactory.CreationException e) { + Log_OC.e(TAG, "Cannot create client", e); } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/PhotoFragment.java b/src/main/java/com/owncloud/android/ui/fragment/PhotoFragment.java index e405e2947bc0..a6bc85eff1b3 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/PhotoFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/PhotoFragment.java @@ -161,7 +161,7 @@ private void searchAndDisplay() { if (!photoSearchQueryRunning && !photoSearchNoNew) { photoSearchTask = new PhotoSearchTask(getColumnsCount(), this, - accountManager.getCurrentAccount(), + accountManager.getUser(), searchRemoteOperation, mContainerActivity.getStorageManager()) .execute(); diff --git a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java index 11c089471673..540c9646b647 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java @@ -61,6 +61,7 @@ import com.google.android.material.snackbar.Snackbar; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; +import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -149,6 +150,7 @@ public class ContactListFragment extends FileFragment implements Injectable { private List vCards = new ArrayList<>(); private OCFile ocFile; @Inject UserAccountManager accountManager; + @Inject ClientFactory clientFactory; public static ContactListFragment newInstance(OCFile file, Account account) { ContactListFragment frag = new ContactListFragment(); @@ -166,7 +168,7 @@ public static ContactListFragment newInstance(OCFile file, Account account) { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.contactlist_menu, menu); + inflater.inflate(R.menu.fragment_contact_list, menu); } @Override @@ -191,7 +193,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup conta recyclerView = view.findViewById(R.id.contactlist_recyclerview); if (savedInstanceState == null) { - contactListAdapter = new ContactListAdapter(accountManager, getContext(), vCards); + contactListAdapter = new ContactListAdapter(accountManager, clientFactory, getContext(), vCards); } else { Set checkedItems = new HashSet<>(); int[] itemsArray = savedInstanceState.getIntArray(CHECKED_ITEMS_ARRAY_KEY); @@ -589,12 +591,15 @@ class ContactListAdapter extends RecyclerView.Adapter vCards) { + ContactListAdapter(UserAccountManager accountManager, ClientFactory clientFactory, Context context, + List vCards) { this.vCards = vCards; this.context = context; this.checkedVCards = new HashSet<>(); this.accountManager = accountManager; + this.clientFactory = clientFactory; } ContactListAdapter(UserAccountManager accountManager, @@ -699,6 +704,7 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) { } }; DisplayUtils.downloadIcon(accountManager, + clientFactory, context, url, target, diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 0c6c0d1115c6..387125c67c72 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -46,6 +46,7 @@ import com.evernote.android.job.JobRequest; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.account.User; import com.nextcloud.client.network.ConnectivityService; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -291,14 +292,14 @@ public void openFile(OCFile file) { new Thread(new Runnable() { @Override public void run() { - Account account = currentAccount.getCurrentAccount(); + User user = currentAccount.getUser(); FileDataStorageManager storageManager = - new FileDataStorageManager(account, fileActivity.getContentResolver()); + new FileDataStorageManager(user.toPlatformAccount(), fileActivity.getContentResolver()); // a fresh object is needed; many things could have occurred to the file // since it was registered to observe again, assuming that local files // are linked to a remote file AT MOST, SOMETHING TO BE DONE; SynchronizeFileOperation sfo = - new SynchronizeFileOperation(file, null, account, true, fileActivity); + new SynchronizeFileOperation(file, null, user.toPlatformAccount(), true, fileActivity); RemoteOperationResult result = sfo.execute(storageManager, fileActivity); fileActivity.dismissLoadingDialog(); if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { @@ -307,7 +308,7 @@ public void run() { Intent i = new Intent(fileActivity, ConflictsResolveActivity.class); i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); - i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, account); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, user.toPlatformAccount()); fileActivity.startActivity(i); } else { if (!launchables.isEmpty()) { @@ -397,10 +398,10 @@ private Uri getFileUri(OCFile file, String... officeExtensions) { public void streamMediaFile(OCFile file) { fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)); - final Account account = currentAccount.getCurrentAccount(); + final User user = currentAccount.getUser(); new Thread(() -> { StreamMediaFileOperation sfo = new StreamMediaFileOperation(file.getLocalId()); - RemoteOperationResult result = sfo.execute(account, fileActivity); + RemoteOperationResult result = sfo.execute(user.toPlatformAccount(), fileActivity); fileActivity.dismissLoadingDialog(); diff --git a/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java b/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java index e4c1432d178c..9dc46e3a2235 100644 --- a/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java +++ b/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java @@ -158,18 +158,18 @@ private String generateDiplayName() { * @param remotePath Absolute path in the current OC account to set to the uploaded file. */ private void requestUpload(String localPath, String remotePath) { - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.uploadNewFile( - mActivity, - mAccount, - localPath, - remotePath, - mBehaviour, - null, // MIME type will be detected from file name - false, // do not create parent folder if not existent - UploadFileOperation.CREATED_BY_USER, - false, - false + FileUploader.uploadNewFile( + mActivity, + mAccount, + localPath, + remotePath, + mBehaviour, + null, // MIME type will be detected from file name + false, // do not create parent folder if not existent + UploadFileOperation.CREATED_BY_USER, + false, + false, + FileUploader.NameCollisionPolicy.ASK_USER ); } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index d956b2462c82..81f7d4d3b0bc 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -37,6 +37,7 @@ import android.view.View; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.client.account.User; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.MainApp; @@ -385,11 +386,11 @@ private void backToDisplayActivity() { @SuppressFBWarnings("DLS") @Override public void showDetails(OCFile file) { - final Account currentAccount = getUserAccountManager().getCurrentAccount(); + final User currentUser = getUserAccountManager().getUser(); final Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); showDetailsIntent.setAction(FileDisplayActivity.ACTION_DETAILS); showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, currentAccount); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, currentUser.toPlatformAccount()); showDetailsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(showDetailsIntent); finish(); diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 385850b8591f..ac094285d20c 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -315,7 +315,7 @@ public void onStop() { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.file_actions_menu, menu); + inflater.inflate(R.menu.item_file, menu); } /** diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index ca91c8869eb4..30f6698412e6 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -334,7 +334,7 @@ private void stopAudio() { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.file_actions_menu, menu); + inflater.inflate(R.menu.item_file, menu); } @Override diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java index 4eb15b2e0225..d4345bc9ac8f 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -396,7 +396,7 @@ protected void onPostExecute(final StringWriter stringWriter) { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.file_actions_menu, menu); + inflater.inflate(R.menu.item_file, menu); MenuItem menuItem = menu.findItem(R.id.action_search); menuItem.setVisible(true); diff --git a/src/main/java/com/owncloud/android/ui/trashbin/RemoteTrashbinRepository.java b/src/main/java/com/owncloud/android/ui/trashbin/RemoteTrashbinRepository.java index a10646f6e525..fcf9ad155bc9 100644 --- a/src/main/java/com/owncloud/android/ui/trashbin/RemoteTrashbinRepository.java +++ b/src/main/java/com/owncloud/android/ui/trashbin/RemoteTrashbinRepository.java @@ -27,6 +27,8 @@ import android.content.Context; import android.os.AsyncTask; +import com.nextcloud.client.account.User; +import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; @@ -47,39 +49,46 @@ public class RemoteTrashbinRepository implements TrashbinRepository { private static final String TAG = RemoteTrashbinRepository.class.getSimpleName(); - private OwnCloudClient client; + private final User user; + private final ClientFactory clientFactory; - RemoteTrashbinRepository(final Context context, final Account account) { - try { - OwnCloudAccount nextcloudAccount = new OwnCloudAccount(account, context); - client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(nextcloudAccount, context); - } catch (Exception e) { - Log_OC.e(TAG, e.getMessage()); - } + RemoteTrashbinRepository(User user, ClientFactory clientFactory) { + this.user = user; + this.clientFactory = clientFactory; } public void removeTrashbinFile(TrashbinFile file, OperationCallback callback) { - new RemoveTrashbinFileTask(client, file, callback).execute(); + new RemoveTrashbinFileTask(user, clientFactory, file, callback).execute(); } private static class RemoveTrashbinFileTask extends AsyncTask { - private OwnCloudClient client; + private User user; + private ClientFactory clientFactory; private TrashbinFile file; private OperationCallback callback; - private RemoveTrashbinFileTask(OwnCloudClient client, TrashbinFile file, OperationCallback callback) { - this.client = client; + private RemoveTrashbinFileTask(User user, + ClientFactory clientFactory, + TrashbinFile file, + OperationCallback callback) { + this.user = user; + this.clientFactory = clientFactory; this.file = file; this.callback = callback; } @Override protected Boolean doInBackground(Void... voids) { - RemoteOperationResult result = new RemoveTrashbinFileRemoteOperation(file.getFullRemotePath()) - .execute(client); - - return result.isSuccess(); + try { + OwnCloudClient client = clientFactory.create(user); + RemoteOperationResult result = new RemoveTrashbinFileRemoteOperation(file.getFullRemotePath()) + .execute(client); + return result.isSuccess(); + } catch (ClientFactory.CreationException e) { + Log_OC.e(this, "Cannot create client", e); + return false; + } } @Override @@ -91,25 +100,32 @@ protected void onPostExecute(Boolean success) { } public void emptyTrashbin(OperationCallback callback) { - new EmptyTrashbinTask(client, callback).execute(); + new EmptyTrashbinTask(user, clientFactory, callback).execute(); } private static class EmptyTrashbinTask extends AsyncTask { - private OwnCloudClient client; + private User user; + private ClientFactory clientFactory; private OperationCallback callback; - private EmptyTrashbinTask(OwnCloudClient client, OperationCallback callback) { - this.client = client; + private EmptyTrashbinTask(User user, ClientFactory clientFactory, OperationCallback callback) { + this.user = user; + this.clientFactory = clientFactory; this.callback = callback; } @Override protected Boolean doInBackground(Void... voids) { - EmptyTrashbinRemoteOperation emptyTrashbinFileOperation = new EmptyTrashbinRemoteOperation(); - RemoteOperationResult result = emptyTrashbinFileOperation.execute(client); - - return result.isSuccess(); + try { + OwnCloudClient client = clientFactory.create(user); + EmptyTrashbinRemoteOperation emptyTrashbinFileOperation = new EmptyTrashbinRemoteOperation(); + RemoteOperationResult result = emptyTrashbinFileOperation.execute(client); + return result.isSuccess(); + } catch (ClientFactory.CreationException e) { + Log_OC.e(this, "Cannot create client", e); + return false; + } } @Override @@ -122,28 +138,36 @@ protected void onPostExecute(Boolean success) { @Override public void restoreFile(TrashbinFile file, OperationCallback callback) { - new RestoreTrashbinFileTask(file, client, callback).execute(); + new RestoreTrashbinFileTask(file, user, clientFactory, callback).execute(); } private static class RestoreTrashbinFileTask extends AsyncTask { private TrashbinFile file; - private OwnCloudClient client; + private User user; + private ClientFactory clientFactory; private TrashbinRepository.OperationCallback callback; - private RestoreTrashbinFileTask(TrashbinFile file, OwnCloudClient client, + private RestoreTrashbinFileTask(TrashbinFile file, User user, ClientFactory clientFactory, TrashbinRepository.OperationCallback callback) { this.file = file; - this.client = client; + this.user = user; + this.clientFactory = clientFactory; this.callback = callback; } @Override protected Boolean doInBackground(Void... voids) { - RemoteOperationResult result = new RestoreTrashbinFileRemoteOperation(file.getFullRemotePath(), - file.getFileName()).execute(client); - - return result.isSuccess(); + try { + OwnCloudClient client = clientFactory.create(user); + RemoteOperationResult result = new RestoreTrashbinFileRemoteOperation(file.getFullRemotePath(), + file.getFileName()).execute(client); + + return result.isSuccess(); + } catch (ClientFactory.CreationException e) { + Log_OC.e(this, "Cannot create client", e); + return false; + } } @Override @@ -156,30 +180,37 @@ protected void onPostExecute(Boolean success) { @Override public void getFolder(String remotePath, @NonNull LoadFolderCallback callback) { - new ReadRemoteTrashbinFolderTask(remotePath, client, callback).execute(); + new ReadRemoteTrashbinFolderTask(remotePath, user, clientFactory, callback).execute(); } private static class ReadRemoteTrashbinFolderTask extends AsyncTask { private String remotePath; - private OwnCloudClient client; + private User user; + private ClientFactory clientFactory; private List trashbinFiles; private LoadFolderCallback callback; - private ReadRemoteTrashbinFolderTask(String remotePath, OwnCloudClient client, LoadFolderCallback callback) { + private ReadRemoteTrashbinFolderTask(String remotePath, User user, ClientFactory clientFactory, + LoadFolderCallback callback) { this.remotePath = remotePath; - this.client = client; + this.user = user; + this.clientFactory = clientFactory; this.callback = callback; } @Override protected Boolean doInBackground(Void... voids) { - RemoteOperationResult result = new ReadTrashbinFolderRemoteOperation(remotePath).execute(client); - - if (result.isSuccess()) { - trashbinFiles = result.getData(); - return true; - } else { + try { + OwnCloudClient client = clientFactory.create(user); + RemoteOperationResult result = new ReadTrashbinFolderRemoteOperation(remotePath).execute(client); + if (result.isSuccess()) { + trashbinFiles = result.getData(); + return true; + } else { + return false; + } + } catch (ClientFactory.CreationException e) { return false; } } diff --git a/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java b/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java index 83200f40e2e6..e43a46bcb1bf 100644 --- a/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java +++ b/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java @@ -23,7 +23,6 @@ */ package com.owncloud.android.ui.trashbin; -import android.accounts.Account; import android.content.Intent; import android.os.Bundle; import android.view.Menu; @@ -34,7 +33,10 @@ import android.widget.TextView; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.account.User; import com.nextcloud.client.di.Injectable; +import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.R; import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile; @@ -92,6 +94,8 @@ public class TrashbinActivity extends FileActivity implements public String noResultsMessage; @Inject AppPreferences preferences; + @Inject CurrentAccountProvider accountProvider; + @Inject ClientFactory clientFactory; private Unbinder unbinder; private TrashbinListAdapter trashbinListAdapter; private TrashbinPresenter trashbinPresenter; @@ -101,29 +105,20 @@ public class TrashbinActivity extends FileActivity implements @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - final Account currentAccount = getUserAccountManager().getCurrentAccount(); - final RemoteTrashbinRepository trashRepository = new RemoteTrashbinRepository(this, currentAccount); + final User user = accountProvider.getUser(); + final RemoteTrashbinRepository trashRepository = new RemoteTrashbinRepository(user, clientFactory); trashbinPresenter = new TrashbinPresenter(trashRepository, this); - setContentView(R.layout.trashbin_activity); unbinder = ButterKnife.bind(this); - - // setup toolbar setupToolbar(); - - // setup drawer setupDrawer(R.id.nav_trashbin); - ThemeUtils.setColoredTitle(getSupportActionBar(), R.string.trashbin_activity_title, this); } @Override protected void onStart() { super.onStart(); - active = true; - setupContent(); } @@ -142,7 +137,7 @@ private void setupContent() { getStorageManager(), preferences, this, - getUserAccountManager().getCurrentAccount() + getUserAccountManager().getUser() ); recyclerView.setAdapter(trashbinListAdapter); recyclerView.setHasFixedSize(true); @@ -212,7 +207,7 @@ public void onDestroy() { @Override public void onOverflowIconClicked(TrashbinFile file, View view) { PopupMenu popup = new PopupMenu(this, view); - popup.inflate(R.menu.trashbin_actions_menu); + popup.inflate(R.menu.item_trashbin); popup.setOnMenuItemClickListener(item -> { trashbinPresenter.removeTrashbinFile(file); @@ -243,7 +238,7 @@ public void onRestoreIconClicked(TrashbinFile file, View view) { @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.trashbin_options_menu, menu); + getMenuInflater().inflate(R.menu.activity_trashbin, menu); return true; } diff --git a/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/src/main/java/com/owncloud/android/utils/DisplayUtils.java index 047268759979..9fa2ee6a040f 100644 --- a/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -55,6 +55,7 @@ import com.caverock.androidsvg.SVG; import com.google.android.material.snackbar.Snackbar; 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.ArbitraryDataProvider; @@ -515,6 +516,7 @@ public static void setAvatar(@NonNull Account account, } public static void downloadIcon(CurrentAccountProvider currentAccountProvider, + ClientFactory clientFactory, Context context, String iconUrl, SimpleTarget imageView, @@ -523,7 +525,8 @@ public static void downloadIcon(CurrentAccountProvider currentAccountProvider, int height) { try { if (iconUrl.endsWith(".svg")) { - downloadSVGIcon(currentAccountProvider, context, iconUrl, imageView, placeholder, width, height); + downloadSVGIcon(currentAccountProvider, clientFactory, context, iconUrl, imageView, placeholder, width, + height); } else { downloadPNGIcon(context, iconUrl, imageView, placeholder); } @@ -544,6 +547,7 @@ private static void downloadPNGIcon(Context context, String iconUrl, SimpleTarge } private static void downloadSVGIcon(CurrentAccountProvider currentAccountProvider, + ClientFactory clientFactory, Context context, String iconUrl, SimpleTarget imageView, @@ -551,7 +555,7 @@ private static void downloadSVGIcon(CurrentAccountProvider currentAccountProvide int width, int height) { GenericRequestBuilder requestBuilder = Glide.with(context) - .using(new CustomGlideUriLoader(currentAccountProvider), InputStream.class) + .using(new CustomGlideUriLoader(currentAccountProvider, clientFactory), InputStream.class) .from(Uri.class) .as(SVG.class) .transcode(new SvgDrawableTranscoder(), PictureDrawable.class) diff --git a/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java index 9116a3d57067..93ad2b35159a 100644 --- a/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java +++ b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java @@ -30,18 +30,16 @@ import android.net.Uri; import android.os.Build; import android.provider.MediaStore; -import android.text.TextUtils; -import android.util.Log; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; 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; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.MainApp; -import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.FilesystemDataProvider; import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.SyncedFolder; @@ -51,6 +49,7 @@ import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.jobs.FilesSyncJob; import com.owncloud.android.jobs.OfflineSyncJob; +import com.owncloud.android.lib.common.utils.Log_OC; import org.lukhnos.nnio.file.FileVisitResult; import org.lukhnos.nnio.file.Files; @@ -73,7 +72,6 @@ public final class FilesSyncHelper { public static final String TAG = "FileSyncHelper"; public static final String GLOBAL = "global"; - public static final String SYNCEDFOLDERINITIATED = "syncedFolderIntitiated_"; public static final int ContentSyncJobId = 315; @@ -81,62 +79,37 @@ private FilesSyncHelper() { // utility class -> private constructor } - public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) { + public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder, boolean syncNow) { final Context context = MainApp.getAppContext(); final ContentResolver contentResolver = context.getContentResolver(); - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); - Long currentTime = System.currentTimeMillis(); - double currentTimeInSeconds = currentTime / 1000.0; - String currentTimeString = Long.toString((long) currentTimeInSeconds); + final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs(); - String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); - boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue - (GLOBAL, syncedFolderInitiatedKey)); - - if (MediaFolderType.IMAGE == syncedFolder.getType()) { - if (dryRun) { - arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, - currentTimeString); - } else { - FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI - , syncedFolder); + if (syncedFolder.isEnabled() && (syncedFolder.isExisting() || enabledTimestampMs >= 0)) { + MediaFolderType mediaType = syncedFolder.getType(); + if (mediaType == MediaFolderType.IMAGE) { + FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.INTERNAL_CONTENT_URI + , syncedFolder); FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - syncedFolder); - } - - } else if (MediaFolderType.VIDEO == syncedFolder.getType()) { - - if (dryRun) { - arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, - currentTimeString); - } else { - FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI, - syncedFolder); + syncedFolder); + } else if (mediaType == MediaFolderType.VIDEO) { + FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.INTERNAL_CONTENT_URI, + syncedFolder); FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - syncedFolder); - } - - } else { - try { - if (dryRun) { - arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, - currentTimeString); - } else { + syncedFolder); + } else { + try { FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); Path path = Paths.get(syncedFolder.getLocalPath()); - String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, - syncedFolderInitiatedKey); - Files.walkFileTree(path, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { - File file = path.toFile(); - if (attrs.lastModifiedTime().toMillis() >= Long.parseLong(dateInitiated) * 1000) { + if (syncedFolder.isExisting() || attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) { filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(), - attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder); + attrs.lastModifiedTime().toMillis(), + file.isDirectory(), syncedFolder); } return FileVisitResult.CONTINUE; @@ -147,24 +120,30 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) { return FileVisitResult.CONTINUE; } }); - + } catch (IOException e) { + Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e); } + } - } catch (IOException e) { - Log.e(TAG, "Something went wrong while indexing files for auto upload " + e.getLocalizedMessage()); + if (syncNow) { + new JobRequest.Builder(FilesSyncJob.TAG) + .setExact(1_000L) + .setUpdateCurrent(false) + .build() + .schedule(); } } } - public static void insertAllDBEntries(AppPreferences preferences, boolean skipCustom) { + public static void insertAllDBEntries(AppPreferences preferences, Clock clock, boolean skipCustom, + boolean syncNow) { final Context context = MainApp.getAppContext(); final ContentResolver contentResolver = context.getContentResolver(); - SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, - preferences); + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock); for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { - if (syncedFolder.isEnabled() && (MediaFolderType.CUSTOM != syncedFolder.getType() || !skipCustom)) { - insertAllDBEntriesForSyncedFolder(syncedFolder); + if (syncedFolder.isEnabled() && (!skipCustom || syncedFolder.getType() != MediaFolderType.CUSTOM)) { + insertAllDBEntriesForSyncedFolder(syncedFolder, syncNow); } } } @@ -172,7 +151,6 @@ public static void insertAllDBEntries(AppPreferences preferences, boolean skipCu private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) { final Context context = MainApp.getAppContext(); final ContentResolver contentResolver = context.getContentResolver(); - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); Cursor cursor; int column_index_data; @@ -191,11 +169,10 @@ private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) { } path = path + "%"; - String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); - String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, syncedFolderInitiatedKey); + long enabledTimestampMs = syncedFolder.getEnabledTimestampMs(); cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?", - new String[]{path}, null); + new String[]{path}, null); if (cursor != null) { column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); @@ -203,9 +180,10 @@ private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) { while (cursor.moveToNext()) { contentPath = cursor.getString(column_index_data); isFolder = new File(contentPath).isDirectory(); - if (cursor.getLong(column_index_date_modified) >= Long.parseLong(dateInitiated)) { + if (syncedFolder.isExisting() || cursor.getLong(column_index_date_modified) >= enabledTimestampMs / 1000.0) { filesystemDataProvider.storeOrUpdateFileValue(contentPath, - cursor.getLong(column_index_date_modified), isFolder, syncedFolder); + cursor.getLong(column_index_date_modified), isFolder, + syncedFolder); } } cursor.close(); @@ -218,8 +196,6 @@ public static void restartJobsIfNeeded(final UploadsStorageManager uploadsStorag final PowerManagementService powerManagementService) { final Context context = MainApp.getAppContext(); - FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester(); - boolean accountExists; OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads(); @@ -243,13 +219,15 @@ public static void restartJobsIfNeeded(final UploadsStorageManager uploadsStorag new Thread(() -> { if (connectivityService.getActiveNetworkType() != JobRequest.NetworkType.ANY && !connectivityService.isInternetWalled()) { - uploadRequester.retryFailedUploads(context, - null, - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService, - null); + FileUploader.retryFailedUploads( + context, + null, + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService, + null + ); } }).start(); } diff --git a/src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java b/src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java index 51345864b054..196b882af1bd 100644 --- a/src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java +++ b/src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java @@ -25,6 +25,7 @@ import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.model.stream.StreamModelLoader; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.network.ClientFactory; import java.io.InputStream; @@ -34,13 +35,15 @@ public class CustomGlideStreamLoader implements StreamModelLoader { private final CurrentAccountProvider currentAccount; + private final ClientFactory clientFactory; - public CustomGlideStreamLoader(CurrentAccountProvider currentAccount) { + public CustomGlideStreamLoader(CurrentAccountProvider currentAccount, ClientFactory clientFactory) { this.currentAccount = currentAccount; + this.clientFactory = clientFactory; } @Override public DataFetcher getResourceFetcher(String url, int width, int height) { - return new HttpStreamFetcher(currentAccount, url); + return new HttpStreamFetcher(currentAccount, clientFactory, url); } } diff --git a/src/main/java/com/owncloud/android/utils/glide/CustomGlideUriLoader.java b/src/main/java/com/owncloud/android/utils/glide/CustomGlideUriLoader.java index 48694c0180c4..5216aea35b16 100644 --- a/src/main/java/com/owncloud/android/utils/glide/CustomGlideUriLoader.java +++ b/src/main/java/com/owncloud/android/utils/glide/CustomGlideUriLoader.java @@ -25,6 +25,7 @@ import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.model.stream.StreamModelLoader; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.network.ClientFactory; import java.io.InputStream; @@ -34,13 +35,15 @@ public class CustomGlideUriLoader implements StreamModelLoader { private final CurrentAccountProvider currentAccount; + private final ClientFactory clientFactory; - public CustomGlideUriLoader(CurrentAccountProvider currentAccount) { + public CustomGlideUriLoader(CurrentAccountProvider currentAccount, ClientFactory clientFactory) { this.currentAccount = currentAccount; + this.clientFactory = clientFactory; } @Override public DataFetcher getResourceFetcher(Uri url, int width, int height) { - return new HttpStreamFetcher(currentAccount, url.toString()); + return new HttpStreamFetcher(currentAccount, clientFactory, url.toString()); } } diff --git a/src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java b/src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java index 3af6e41838e2..ae12ac31f3c6 100644 --- a/src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java +++ b/src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java @@ -27,6 +27,8 @@ import com.bumptech.glide.Priority; import com.bumptech.glide.load.data.DataFetcher; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.account.User; +import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.MainApp; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; @@ -47,30 +49,30 @@ public class HttpStreamFetcher implements DataFetcher { private static final String TAG = HttpStreamFetcher.class.getName(); private final String url; private final CurrentAccountProvider currentAccount; + private final ClientFactory clientFactory; - HttpStreamFetcher(final CurrentAccountProvider currentAccount, final String url) { + HttpStreamFetcher(final CurrentAccountProvider currentAccount, ClientFactory clientFactory, final String url) { this.currentAccount = currentAccount; + this.clientFactory = clientFactory; this.url = url; } @Override public InputStream loadData(Priority priority) throws Exception { - Account account = currentAccount.getCurrentAccount(); - OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext()); - OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, MainApp.getAppContext()); + User user = currentAccount.getUser(); + OwnCloudClient client = clientFactory.create(user); - if (mClient != null) { + if (client != null) { GetMethod get; try { get = new GetMethod(url); get.setRequestHeader("Cookie", "nc_sameSiteCookielax=true;nc_sameSiteCookiestrict=true"); get.setRequestHeader(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE); - int status = mClient.executeMethod(get); + int status = client.executeMethod(get); if (status == HttpStatus.SC_OK) { return get.getResponseBodyAsStream(); } else { - mClient.exhaustResponse(get.getResponseBodyAsStream()); + client.exhaustResponse(get.getResponseBodyAsStream()); } } catch (Exception e) { Log_OC.e(TAG, e.getMessage(), e); diff --git a/src/main/res/drawable-hdpi/checker_16_16.png b/src/main/res/drawable-hdpi/checker_16_16.png new file mode 100644 index 000000000000..1448873071b4 Binary files /dev/null and b/src/main/res/drawable-hdpi/checker_16_16.png differ diff --git a/src/main/res/drawable-mdpi/checker_16_16.png b/src/main/res/drawable-mdpi/checker_16_16.png new file mode 100644 index 000000000000..3e9e3d0969fc Binary files /dev/null and b/src/main/res/drawable-mdpi/checker_16_16.png differ diff --git a/src/main/res/drawable-xhdpi/checker_16_16.png b/src/main/res/drawable-xhdpi/checker_16_16.png new file mode 100644 index 000000000000..a700c752975f Binary files /dev/null and b/src/main/res/drawable-xhdpi/checker_16_16.png differ diff --git a/src/main/res/drawable-xxhdpi/checker_16_16.png b/src/main/res/drawable-xxhdpi/checker_16_16.png new file mode 100644 index 000000000000..1cfdf0a9a50a Binary files /dev/null and b/src/main/res/drawable-xxhdpi/checker_16_16.png differ diff --git a/src/main/res/drawable-xxxhdpi/checker_16_16.png b/src/main/res/drawable-xxxhdpi/checker_16_16.png new file mode 100644 index 000000000000..9c964435ce96 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/checker_16_16.png differ diff --git a/src/main/res/drawable/checker_16_16.xml b/src/main/res/drawable/checker_16_16.xml deleted file mode 100644 index 685071dd2044..000000000000 --- a/src/main/res/drawable/checker_16_16.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/src/main/res/layout/drawer.xml b/src/main/res/layout/drawer.xml index 84f3773a362a..240f965d93c0 100644 --- a/src/main/res/layout/drawer.xml +++ b/src/main/res/layout/drawer.xml @@ -31,7 +31,7 @@ android:background="@color/bg_default" android:theme="@style/NavigationView_ItemTextAppearance" app:headerLayout="@layout/drawer_header" - app:menu="@menu/drawer_menu"> + app:menu="@menu/partial_drawer_entries"> . --> @@ -62,7 +63,18 @@ android:layout_gravity="center_horizontal" android:ellipsize="end" android:gravity="center" + android:paddingTop="@dimen/standard_half_padding" + android:paddingBottom="@dimen/standard_half_padding" android:text="@string/file_list_empty" android:visibility="gone"/> - \ No newline at end of file + + + diff --git a/src/main/res/layout/synced_folders_empty.xml b/src/main/res/layout/synced_folders_empty.xml new file mode 100644 index 000000000000..b17c9141d0d0 --- /dev/null +++ b/src/main/res/layout/synced_folders_empty.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/res/layout/synced_folders_footer.xml b/src/main/res/layout/synced_folders_footer.xml new file mode 100644 index 000000000000..46cc018b65c5 --- /dev/null +++ b/src/main/res/layout/synced_folders_footer.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/src/main/res/layout/synced_folders_layout.xml b/src/main/res/layout/synced_folders_layout.xml index 9973ea5bcc3d..2846dec1e0ab 100644 --- a/src/main/res/layout/synced_folders_layout.xml +++ b/src/main/res/layout/synced_folders_layout.xml @@ -50,39 +50,7 @@ android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/res/menu/logs_menu.xml b/src/main/res/menu/activity_logs.xml similarity index 100% rename from src/main/res/menu/logs_menu.xml rename to src/main/res/menu/activity_logs.xml diff --git a/src/main/res/menu/notifications_actions_menu.xml b/src/main/res/menu/activity_notifications.xml similarity index 96% rename from src/main/res/menu/notifications_actions_menu.xml rename to src/main/res/menu/activity_notifications.xml index 97e8b0805b5d..5a4eaab72110 100644 --- a/src/main/res/menu/notifications_actions_menu.xml +++ b/src/main/res/menu/activity_notifications.xml @@ -1,4 +1,5 @@ - + + + + + diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index 6e4ab6268eb1..46406306f6b2 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -210,7 +210,6 @@ الخروج إدارة الحسابات الحساب الوسيط - شارك معنا تم استخدام %1$s من %2$s تحميل تلقائي تعديل @@ -300,12 +299,10 @@ مسح البيانات إدارة المساحة ملف الوسائط ﻻ يمكن عرضه - لا يوجد ملف وسائط ترميز غير مدعّم زر التقدم للأمام زر التشغيل أو الإيقاف زر الترجيع - (جارٍ تحميل) %1$s الأحدث أولا الأقدم أولا أ - ى @@ -327,15 +324,6 @@ أيقونة الإشعار لا توجد هناك إخطارات ليس هناك اتصال بالأنترنت - جرّب تطبيق المطورين - المنتدى - قم بمساعدة الآخرين على - ساهم بفعالية - إنضم إلى الدردشة على : - التطبيق - ترجمة - ساعدنا بالتجريب - إرسل تقرير أخطاء على جيت هب إدخل رمزك السري إدخل رمزك السري من فضلك الجملتين السريتين لا تتطابقان @@ -361,7 +349,6 @@ عام المزيد نسخ أحتياطي يومي لجهات الإتصال - ملاحظات المساعدة الدمغة. إستخدم المجلدات الفرعية diff --git a/src/main/res/values-b+en+001/strings.xml b/src/main/res/values-b+en+001/strings.xml index 91c01a1b2fd4..63444868a0a9 100644 --- a/src/main/res/values-b+en+001/strings.xml +++ b/src/main/res/values-b+en+001/strings.xml @@ -223,7 +223,6 @@ Manage accounts Middle account Open sidebar - Participate %1$s of %2$s used %1$s used Auto upload @@ -264,7 +263,6 @@ Revert to old login method Add to favorites Favorite - No app available to send mails! Delete Failed to load details File @@ -384,18 +382,13 @@ The media file can not be streamed Could not read the media file The media file has incorrect encoding - The file is not in a valid account - No media file found - Unexpected error while trying to play %1$s Attempt to play file timed out The built-in media player is unable to play the media file Unsupported media codec - %1$s playback finished Fast-forward button %1$s music player Play or pause button Rewind button - %1$s (loading) %1$s (playing) Newest first Oldest first @@ -445,25 +438,6 @@ Operation has been canceled The server has reached end of life, please upgrade! More menu - 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. - forum - Help others on the - Review, amend and write code, see %1$s for details - Actively Contribute - Join the chat on IRC: - the app - Translate - 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. - Found a bug? Oddments? - Help by testing - Report an issue on GitHub - Interested in helping out by testing what will be the next version? Enter your passcode The passcode will be requested every time the app is started Please enter your passcode @@ -502,7 +476,6 @@ To show mnemonic please enable device credentials. Show media scan notifications Notify about newly found media folders - Feedback GNU General Public License, version 2 Help Imprint @@ -555,17 +528,13 @@ Incorrect password Login via QR code Protecting your data - self-hosted productivity platform Browse and share all actions at your fingertips - Activity, shares, offline files everything quickly accessible All your accounts in one place Automatic upload for your photos & videos - Sync calendar & contacts - with DAVx5 (formerly DAVdroid) Search users and groups Select all Select template diff --git a/src/main/res/values-b+es+419/strings.xml b/src/main/res/values-b+es+419/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-b+es+419/strings.xml +++ b/src/main/res/values-b+es+419/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-bg-rBG/strings.xml b/src/main/res/values-bg-rBG/strings.xml index 55171d993bb6..e167fdfbb6d8 100644 --- a/src/main/res/values-bg-rBG/strings.xml +++ b/src/main/res/values-bg-rBG/strings.xml @@ -205,7 +205,6 @@ Управление на профилите Среден профил Отвори страничното меню - Участвайте %1$s от %2$s използвани %1$s използвани Автоматично качване @@ -226,7 +225,6 @@ Връщане към стария метод за вписване Добави към любимите Любими - Няма налично приложение за изпращане на имейл. Изтрий Файл Запази @@ -336,18 +334,13 @@ Медийният файл не може да бъде излъчен Медия файлът не може да бъде прочетен Неправилно кодиран медиен файл - Файлът не е във валиден профил - Не са открити медийни файлове - Неизвестна грешка при опит за възпроизвеждане на %1$s Опитът за възпроизвеждане на файла беше неуспешен Вграденият медия плеър не може да възпроизведе файла Неподдържан медиен кодек - %1$s възпроизвеждането завърши Бутон за превъртане напред %1$s музикален плеър Бутон възпроизведи / пауза Бутон за превъртане назад - %1$s (се зарежда) %1$s (възпроизвеждане) Най-нов Най-стар @@ -380,25 +373,6 @@ Моля, опитайте отново по-късно. Няма връзка с Интернет Повече - Тестване на бета версия - Това включва всички най-нови функционалности. Грешки и проблеми могат да възникнат, ако и когато възникнат, моля да докладвате. - Форум - Помогнете на други във форума - Прегледай, промени и пиши код, вижте %1$s за повече информация - Активен принос - Присъединете се към IRC чата: - приложението - Преведи - Изтегляне на развойна версия - Свали развоен релийз от F-Droid приложение - Изтегляне на release candidate от приложението F-Droid - Изтегляне на release candidate от магазина Google play - Release candidate - Release candidate (RC) е отделна версия на предстоящ release и се очаква да е стабилна. Тестването на вашите индивидуални устройства може да гарантира стабилността. Запишете се за тестване от Play магазина или ръчно проверете в секция \"Версия\" на F-Droid. - Открили сте грешка? Нещо странно? - Помогни с тестването - Докладвайте за проблем чрез Github - Желаете ли се да помогнете с тестването на следващата версия? Въведете кода за достъп Кода ще бъде изискван при всяко стартиране на приложението Моля, въведете кода за достъп @@ -429,7 +403,6 @@ Общи Още Дневно копие на вашите контакти - Обратна връзка GNU Генерален Публичен Лиценз, версия 2 Помощ Отпечатък diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 38cb29811f88..a918d2111c47 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -223,7 +223,6 @@ Gestió de comptes Compte mitjà Obre la barra lateral - Participa Usats %1$s de %2$s %1$s utilitzat Càrrega automàtica @@ -264,7 +263,7 @@ Revertiu l\'antic mètode d\'inici de sessió Afegix a preferits Preferits - No hi ha cap aplicació disponible per enviar correus electrònics! + arxiu Suprimeix Hi ha hagut un error mentre es recuperaven les activitats per al fitxer No s\'han pogut carregar els detalls @@ -334,6 +333,7 @@ Intercanvi de fitxers & amb coŀlaboració segura Correu web, agenda i contactes fàcils de fer servir Compartició de pantalles, reunions en línia i conferències web + carpeta La carpeta ja existeix Crea Icona de la carpeta @@ -385,18 +385,13 @@ El fitxer multimèdia no es pot reproduir No s\'ha pogut llegir el fitxer multimèdia El fitxer multimèdia té una codificació incorrecta - El fitxer no està en un compte vàlid - No s\'han trobat fitxers multimèdia - Error inesperat mentre s\'intentava reproduir %1$s Ha exhaurit el temps d\'espera per reproduir el fitxer El reproductor multimèdia integrat no pot reproduir aquest fitxer multimèdia El còdec multimèdia no és compatible - %1$s reproducció acabada Botó d\'avançament ràpid Reproductor de música %1$s Botó de reproducció o pausa Botó de rebobinat - %1$s (s\'està carregant) %1$s (està sonant) Més recent primer Més antic primer @@ -447,25 +442,6 @@ L\'operació s\'ha cancel·lat El servidor ha arribat al final de la seva vida, si us plau actualitzeu! Menú més - Proveu la versió en desenvolupament - Això inclou totes les futures característiques en un estat incipient. Hi poden haver errors, i si els hi trobeu, si us plau feu-nos-ho saber. - fòrum - Ajudeu a d\'altres en - Reviseu, corregiu i escriviu codi, veieu %1$s per més detalls - Contribuïu activament - Uniu-vos al xat a l\'IRC: - l\'aplicació - Traduïu - Descarregueu la versió en desenvolupament directament - Aconseguiu la versió en desenvolupament des de l\'aplicació F-Droid - Aconseguiu la versió candidata des de l\'aplicació F-Droid - Aconseguiu la versió candidata des de la botiga Google Play - Versió candidata - La versió candidata (RC) és una instantània de la propera versió estable que sortirà. Provar-la en el vostre entorn individual podrà ajudar a assegurar-la. Registreu-vos per provar-la a la botiga Google Play o cerqueu-la manualment a la secció \"Versió\" de l\'F-Droid. - Heu trobat cap error? Alguna cosa fora del corrent? - Ajudeu provant - Informeu d\'una incidència a GitHub - Esteu interessats en provar què hi haurà de nou a la propera versió? Escriviu el vostre codi d\'accés Es requerirà el codi d\'accés cada vegada que s\'iniciï l\'aplicació Escriviu el codi d\'accés @@ -504,7 +480,6 @@ Per mostrar els mnemònics, si us plau activeu les credencials del dispositiu. Mostra notificacions de cerca de mitjans Notifica sobre noves carpetes amb multimèdia - Comentaris Llicència Pública General GNU, versió 2 Ajuda Peu d\'impremta @@ -527,6 +502,9 @@ Ruta d\'emmagatzematge Carpeta local Carpeta remota + Tema + Fosc + Clar Visualització prèvia d\'imatge No hi ha cap fitxer local per fer una vista prèvia No es pot mostrar la imatge @@ -538,6 +516,7 @@ Proveu %1$s al vostre dispositiu! Us vull convidar a fer servir %1$s al vostre dispositiu.\nDescarregueu-lo d\'aquí: %2$s %1$s o %2$s + No s\'ha pogut trobar l\'arxiu! No s\'ha pogut completar l\'eliminació Ha fallat la supressió de la notificació. Suprimeix @@ -557,17 +536,16 @@ Contrasenya incorrecta Inicia la sessió via un codi QR Protegint les vostres dades - plataforma de productivitat auto-hostatjada Explorar i compartir totes les accions als vostres dits - Activitat, comparticions, fitxers fóra de línia + Activitat, comparticions, … tot molt ràpidament accesible Tots els vostres comptes En un sol lloc Pujada automàtica pels vostres vídeos i imatges - Sincronitzar el calendari i els contactes - amb DAVx5 (formalment DAVdroid) + Calendari i contactes + Sincronitza amb DAVx5 Cerca usuaris i grups Selecciona-ho tot Selecciona una plantilla @@ -585,6 +563,8 @@ Comparteix %1$s Obtén l\'enllaç %1$s (grup) + Compartir l\'enllaç intern + Només funciona amb els usuaris que poden accedir a %1$s %1$s (a %2$s) Heu d\'escriure una contrasenya S\'ha produït un error mentre s\'intentava compartir aquest fitxer o carpeta diff --git a/src/main/res/values-cs-rCZ/strings.xml b/src/main/res/values-cs-rCZ/strings.xml index fedff6986286..94f835e5376c 100644 --- a/src/main/res/values-cs-rCZ/strings.xml +++ b/src/main/res/values-cs-rCZ/strings.xml @@ -25,7 +25,7 @@ Odeslat Nastavení Seřadit - Obnovit účet + Znovu načíst účet Aktivní uživatel Zatím žádná aktivita Zatím žádné události jako přidání, změny či sdílení @@ -43,17 +43,17 @@ Zkontrolovat server Spojení navázáno Zadejte stávající heslo - Váš server nevrací správný přihlašovací identifikátor, obraťte se na svého správce systému + Vámi používaný server nevrací správný identifikátor uživatele – obraťte se na správce serveru Adresa serveru Adresa serveru https://… Byla použita neplatná přihlašovací URL Nesprávný formát adresy serveru Server nenalezen Žádné síťové spojení - Zabezpečené spojení není k dispozici - Neplatné nastavení serveru - Neúspěšné přihlášení - Přístup zamítnut autorizačním serverem + Zabezpečené spojení není k dispozici. + Nesprávně formulované nastavení serveru + Neúspěšné ověření se + Přístup zamítnut pověřovacím (autorizace) serverem Heslo Zabezpečené spojení je přesměrováváno nezabezpečenou trasou. Obnovit připojení @@ -63,10 +63,10 @@ Testuje se připojení Čekání na odpověď ze serveru bylo příliš dlouhé Pokus o přihlášení… - Nesprávné přihlašovací jméno nebo heslo + Nesprávné uživatelské jméno nebo heslo Neznámá chyba: %1$s Došlo k neznámé HTTP chybě! - Nastala neznámá chyba + Došlo k neznámé chybě Nedaří se najít hostitele %1$s nepodporuje vícero účtů Uživatelské jméno @@ -77,7 +77,8 @@ Nahrávat pouze přes neplacené Wi-Fi připojení /AutoUpload Vytvořte nové nastavení vlastní složky - Vytvořte vlastní složku + Nastavit uživatelsky určenou složku + Zakázat kontrolu úspory energie Avatar Zavřít Vypnout @@ -85,6 +86,7 @@ Nedaří se spustit nastavení akumulátoru přímo. Upravte ručně v nastavení. Optimalizace pro nižší vybíjení akumulátoru Došlo k problému s načtením certifikátu. + Seznam změn ve vývojářské verzi Zatržítko Zvolte místní složku… Zvolte složku na protějšku… @@ -98,7 +100,7 @@ Zrušit synchronizaci Vybrat účet Smazat - Error + Chyba Nedostatek paměti Neznámá chyba Načítání… @@ -113,11 +115,11 @@ Přepnout účet neznámý Ano - Vyzkoušet noční sestavení + Vyzkoušet vývojové sestavení Zahrnuje všechny nadcházející funkce a pohybuje se na hraně stability. Mohou se vyskytnout chyby, a pokud ano, nahlaste nám je prosím. diskuzní fórum Pomozte ostatním na - Kontrolujte, vylepšujte a programujte – více informací naleznete na %1$s + Kontrolujte, vylepšujte a programujte – podrobnosti naleznete na %1$s Aktivně něčím přispět Připojte se k IRC chatu: aplikace @@ -127,10 +129,10 @@ Získat kandidáta na vydání z aplikace F-Droid Získat kandidáta na vydání z Google Play store Kandidát na vydání - Kandidát na vydání (RC) je snímkem nadcházejícího vydání a očekává se, že bude stabilní. Testování vašeho individuálního nastavení by to mohlo pomoci to zajistit. Přihlaste se k testování v Obchodě Play nebo se podívejte do F-Droid v části „Verze“ a přihlaste se ručně. + Kandidát na vydání (RC) je zachyceným stavem nadcházejícího vydání a očekává se, že bude stabilní. Testování vašeho individuálního nastavení by to mohlo pomoci to zajistit. Přihlaste se k testování v katalogu aplikací Play nebo se podívejte do F-Droid v části „Verze“ a přihlaste se ručně. Našli jste chybu? Něco podivného? Pomozte testováním - Nahlásit chybu na Github + Nahlásit problém prostřednictvím portálu Github Chcete nám pomoci zkoušet nadcházející verzi? Nastavit Opravdu chcete odstranit %1$s? @@ -146,14 +148,14 @@ Obnovit vybrané kontakty Vyberte účet, který chcete importovat Ikona uživatele v seznamu kontaktů - Práva nejsou přidělena, nic nebylo naimportováno. + Nejsou udělena oprávnění, proto nebylo nic naimportováno. Automatické zálohování Zálohovat nyní - Poslední záloha - Nikdy + Nejnovější záloha + nikdy Zvolte datum - Zálohování je naplánováno a brzy začne - Import je naplánován a brzy začne + Zálohování naplánováno a brzy začne + Import naplánován a brzy začne Nenalezen žádný soubor Nepodařilo se najít vaši poslední zálohu! Je třeba oprávnění ke čtení seznamu kontaktů @@ -167,20 +169,22 @@ Zkopírovat odkaz Kopírování/přesouvání do šifrované složky není v tuto chvíli podporováno. Zkopírovat do… + Nepodařilo se stáhnout celý obrázek + Nepodařilo se získat URL Nepodařilo se vytvořit složku Vytváření souboru ze šablony… Vytvořit nový dokument Vytvořit novou složku Vytvořit novou prezentaci Vytvořit nový sešit - Přihlašovací údaje zakázány + Přihlašovací údaje znepřístupněny Neznámé - Neplatné pověření + Neplatné přihlašovací údaje Odebrat účet Odebrat účet %s a smazat všechny místní soubory?\n\nTuto akci nelze vzít zpět. Smazat položky Odznačit vše - Je k dispozici novější verze + Je k dispozici nová verze Nejsou k dispozici žádné informace. Není k dispozici žádná novější verze. Tento algoritmus vytváření otisku (digest) není na vašem telefonu dostupný. @@ -190,8 +194,8 @@ Stáhnout si nejnovější vývojářskou verzi Nepodařilo se stáhnout %1$s Stahování se nezdařilo, přihlaste se znovu - Stažení se nezdařilo - Tento soubor už není dostupný na serveru + Stahování se nezdařilo + Tento soubor už není na serveru k dispozici %1$d%% Stahuje se %2$s Stahování… %1$s staženo @@ -206,7 +210,7 @@ Všechny soubory Oblíbené Domů - Upozornění + Oznámení Na přístroji Fotky Nedávno přidané @@ -219,7 +223,6 @@ Spravovat účty Prostřední účet Otevřít postranní panel - Zúčastnit se %1$s z %2$s použito %1$s použito Automatické nahrávání @@ -231,7 +234,7 @@ Zadejte heslo pro rozšifrování soukromého klíče. Tato složka není prázdná. Vytváření nových klíčů… - Všech 12 slov dohromady tvoří velmi silné heslo, které pouze vám umožní prohlížet a používat vaše zašifrované soubory. Poznamenejte si ho někam na bezpečné místo. + Všech 12 slov dohromady tvoří velmi silné heslo, které pouze vám umožní prohlížet a používat vaše zašifrované soubory. Poznamenejte si ho a někam bezpečně uložte. End-to-end šifrování zakázáno na serveru. Šifrování funguje pouze v KitKat (4.4) a výše. Poznamenejte si někam své 12 slovné šifrovací heslo @@ -240,17 +243,19 @@ Ukládání klíčů Nastavit šifrování Nepodařilo se uložit klíče, zkuste to znovu. - Chyba při rozšifrovávání. Chybné heslo? + Chyba při rozšifrovávání. Nesprávné heslo? Zadejte název souboru %1$s nelze zkopírovat do místní složky %2$s Kritická chyba: operace nelze provést Chyba při přidávání komentáře k souboru %1$s zhavarovalo + Report + Nahlásit problém? (vyžaduje Github účet) Chyba při získávání souboru Chyba při získávání šablon Chyba při spouštění kamery Předvolby - Vývojářský testovací reži + Vývojářský testovací režim Přidat nebo nahrát Předání souboru správci stahování se nezdařilo Soubor se nepodařilo vytisknout @@ -258,44 +263,45 @@ Vrátit se ke starému způsobu přihlašování Přidat do oblíbených Oblíbené - Není k dispozici aplikace pro posílání emailů! + soubor Smazat + Při načítání aktivit u souboru došlo k chybě Nepodařilo se načíst podrobnosti. Soubor Ponechat - Nahrajte nějaký obsah, nebo synchronizujte s vašimi zařízeními! - Zatím není nic oblíbeno - Vaše vyhledávání nenalezlo žádné oblíbené soubory - Zde budou zobrazeny soubory a složky označené jako oblíbené + Nahrajte nějaký obsah, nebo synchronizujte s vašimi zařízeními. + Zatím žádné oblíbené položky + Vaše vyhledávání nenalezlo žádné oblíbené soubory. + Zde budou zobrazeny soubory a složky, které označíte jako oblíbené Nejsou zde žádné soubory V této složce nebylo nic nalezeno Žádné výsledky - Žádná fotka - Žádné video + Žádné fotky + Žádná videa Nic tu není. Můžete přidat složku. Nenalezeny žádné nedávno přidané soubory Žádné nedávno přidané soubory. Nenalezeny žádné soubory změněné za uplynulých 7 dnů - Žádné soubory nebyly v posledních sedmi dnech změněny. + V uplynulých sedmi dnech nebyly změněny žádné soubory. Zkusit hledat v jiné složce? Zde budou zobrazeny vámi sdílené soubory a složky - Zatím nebylo nic sdílené - Nahrajte nějaké fotky nebo aktivujte automatické nahrávání. + Zatím nebylo nic sdíleno + Nahrajte nějaké fotky nebo zapněte automatické nahrávání. Žádné fotky. - Nahrajte nějaká videa nebo aktivujte automatické nahrávání. + Nahrajte nějaká videa nebo zapněte automatické nahrávání. Žádná videa. složka Načítání… - Pro tento typ souboru nebyla nalezena aplikace! + Není nastaveno pomocí jaké aplikace otevírat tento typ souboru. před několika sekundami Prověřování cílového umístění… Čištění… Aktualizace popisu umístění úložiště - Datová složka už existuje. Zvolte z následujících možností: + Datová složka už existuje. Vyberte z následujících možností: Nextcloud složka už existuje - Je třeba více místa - Nedaří se číst ze zdrojového souboru - Nedaří se zapsat do cílového souboru + Je zapotřebí více místa + Ze zdrojového souboru se nedaří číst + Do cílového souboru se nedaří zapsat Nezdar během migrace Nepodařilo se aktualizovat rejstřík Přesouvání dat… @@ -304,7 +310,7 @@ Příprava migrace… Obnovování nastavení účtu… Ukládání nastavení účtu… - Opravdu chcete změnit složku úložiště na %1$s?\n\nPoznámka: Všechna data budou stažena znovu. + Pořád ještě chcete chcete změnit popis umístění úložiště na %1$s?\n\nPoznámka: Všechna data bude třeba znovu stáhnout. Zdrojová složka není čitelná! Aktualizace rejstříku… Použít @@ -324,9 +330,10 @@ Název souboru Toto je funkce Nextcloud, přejděte na něj. Mějte svá data zabezpečená a pod svou správou - Zabezpečená spolupráce & výměna souborů - Snadno použitelný webový emailový klient, kalendáře & kontakty - Sdílení obrazovky, online schůzky & webové konference + Zabezpečená spolupráce a výměna souborů + Snadno použitelný webový e-mailový klient, kalendáře a kontakty + Sdílení obrazovky, schůzky na dálku a webové konference + složka Složka už existuje Vytvořit Ikona složky @@ -340,7 +347,7 @@ pro přejmenování tohoto souboru Stahování souborů… Nahrávání souborů… - Některé soubory nebylo možno přesunout + Některé soubory nebylo možné přesunout Místní: %1$s Přesunout vše Vzdálené: %1$s @@ -356,38 +363,35 @@ vst/výstup. chyba Zjistit více Odkaz - Náhled seznamu + Zobrazit jako seznam V této složce nejsou žádné soubory. Soubor nenalezen na souborovém systému - Nejsou zde další složky. + Nejsou zde žádné další složky. Záznamy událostí v aplikaci %1$s pro Android - Nebyla nalezena aplikace k odesílání záznamů událostí. Nainstalujte emailového klienta. + Nebyla nalezena žádná aplikace, přes kterou by bylo možné odeslat záznamy událostí. Nainstalujte si e-mailového klienta. Přihlásit Smazat záznamy událostí - Obnovit + Načíst znovu Prohledat záznamy událostí Posílat záznamy událostí e-mailem + Záznamy událostí: %1$d kB, dotazu odpovídalo %2$d / %3$d v %4$d ms Načítání… + Záznamy událostí: %1$d kB, žádný filtr Záznamy událostí - Server je v režimu údržby + Na serveru probíhá údržba Vyčistit data - Nastavení, databáze a certifikáty serverů z %1$s data budou natrvalo smazány. \n\nStažené soubory tak, jak jsou.\n\nTento proces může chvíli trvat. + Nastavení, databáze a certifikáty serverů z dat %1$s budou natrvalo smazány. \n\nStažené soubory zůstanou beze změny.\n\nTento proces může chvíli trvat. Spravovat úložný prostor - Multimediální soubor nelze proudově odesílat + Soubor s médii se nedaří proudově vysílat (stream) Nepodařilo se přečíst soubor médií Soubor médií nemá platné kódování - Soubor není v platném účtu - Nenalezen žádný multimediální soubor - Neočekávaná chyba při pokusu o přehrání %1$s Překročen časový limit pokusu o přehrání souboru - Multimediální soubor se nedaří přehrát vestavěným přehrávačem + Soubor s médii se nedaří přehrát vestavěným přehrávačem Nepodporovaný kodek médií - %1$s přehrávání dokončeno Tlačítko Rychle vpřed Hudební přehrávač %1$s Tlačítko Přehrát/Pozastavit - Tlačítko Přetočit - %1$s (načítá) + Tlačítko přeskočit na začátek/konec %1$s (přehrává) Nejnovější jako první Nejstarší jako první @@ -395,15 +399,15 @@ Z - A Největší jako první Nejmenší jako první - Při pokusu o přesun tohoto souboru či složky nastala chyba + Při pokusu o přesun tohoto souboru či složky došlo k chybě Není možné přesunout složku do její vlastní podsložky Soubor už v cílové složce existuje - Soubor se nedaří přesunout. Zkontrolujte zda existuje + Soubor se nedaří přesunout – zkontrolujte zda existuje Přesunout do… Při čekání na server došlo k chybě. Operace nemohla být dokončena - Při připojení k serveru došlo k chybě + Při připojování k serveru došlo k chybě Při čekání na server došlo k chybě. Operace nemohla být dokončena - Operace nemohla být dokončena, server není dostupný + Operace nemohla být dokončena, protože server není dostupný Nový komentář… Zjištěna nová složka s médii %1$s. foto @@ -411,6 +415,7 @@ Nové oznámení Byla vytvořena nová verze Není k dispozici aplikace pro práci s odkazy + Povolen je pouze jeden účet Není k dispozice aplikace pro práci s PDF Poslat Poznámku se nepodařilo odeslat @@ -435,53 +440,35 @@ Vraťte se sem později. Bez připojení k Internetu Operace byla zrušena - Verze aplikace na straně serveru dosáhla konce své životnosti – přejděte na novou! + Verze aplikace na straně serveru už je příliš stará na to, aby šla nadále používat – přejděte na novou! Další nabídka - Vyzkoušet noční sestavení - Zahrnuje všechny nadcházející funkce a pohybuje se na hraně stability. Mohou se vyskytnout chyby, a pokud ano, nahlaste nám je prosím. - diskuzní fórum - Pomozte ostatním na - Kontrolujte, vylepšujte a programujte – více informací naleznete na %1$s - Aktivně něčím přispět - Připojte se k IRC chatu: - aplikace - Překládat - Stáhnout vývojové vydání přímo - Získat vývojové vydání z aplikace F-Droid - Získat kandidáta na vydání z aplikace F-Droid - Získat kandidáta na vydání z Google Play store - Kandidát na vydání - Kandidát na vydání (RC) je snímkem nadcházejícího vydání a očekává se, že bude stabilní. Testování vašeho individuálního nastavení by to mohlo pomoci to zajistit. Přihlaste se k testování v Obchodě Play nebo se podívejte do F-Droid v části „Verze“ a přihlaste se ručně. - Našli jste chybu? Něco podivného? - Pomozte testováním - Nahlásit chybu na Github - Chcete nám pomoci zkoušet nadcházející verzi? Zadejte svůj bezpečnostní kód Bezpečnostní kód bude vyžadován při každém spuštění aplikace Zadejte svůj bezpečnostní kód - Bezpečnostní kód se liší - Zopakujte svůj bezpečnostní kód + Zadání bezpečnostního kódu se neshodují + Zopakujte zadání svého bezpečnostního kódu Odstraňte svůj bezpečnostní kód Bezpečnostní kód odstraněn Bezpečnostní kód uložen Nesprávný bezpečnostní kód Umožnit Odepřít - Vyžadována dodatečná oprávnění pro nahrávání & stahování souborů. + Vyžadována dodatečná oprávnění pro nahrávání a stahování souborů. Nenalezena aplikace pro nastavení obrázku 389 KB - placeholder.txt + jen_jako_vypln.txt 12:23:45 Toto je zástupný text 2012/05/18 12:23 odp. + Zákaz kontroly úspory energie může způsobit nahrávání souborů i v případě nízkého stavu nabití akumulátoru! smazáno - ponechán v původní složce - přesunut do složky aplikace + ponecháno v původní složce + přesunuto do složky aplikace Přidat účet - Synchronizovat kalendář & kontakty + Synchronizovat kalendář a kontakty Adresu serveru pro účet se pro DAVx5 (dříve známé pod názvem DAVdroid) nepodařilo přeložit F-Droid ani Google Play není nainstalováno - Nastavit pro stávající účet DAVx5 (dříve známé pod názvem DAVdroid) (v1.3.0+) + Nastavit pro stávající účet DAVx5 (dříve známé pod názvem DAVdroid) (verze 1.3.0 a novější) Synchronizace kalendáře & kontaktů úspěšně dokončena O aplikaci Podrobnosti @@ -493,7 +480,6 @@ Pro zobrazení mnemotechnické, zapněte přihlašovací údaje zařízení. Zobrazit oznámení skenování médií Oznamovat nalezení nových složek s médii - Odezva GNU General Public License, verze 2 Nápověda Imprint @@ -512,30 +498,34 @@ Spravovat účty Doporučit přátelům Zobrazit skryté soubory - Získat zdrojový kód + Získat zdrojové kódy Popis umístění úložiště Místní složka - Vzdálená složka + Složka na protějšku + Motiv + Tmavý + Světlý Náhled obrázku - K náhledu není žádný místní soubor + Není zde žádný místní soubor pro který zobrazit náhled Obrázek se nedaří zobrazit Je nám líto Soukromí Push oznámení zakázáno kvůli závislostem na proprietárních službách Google Play. Žádné push oznámení kvůli zastaralé relaci přihlášení. Zvažte opětovné přidání svého účtu. Push oznámení momentálně nejsou k dispozici. - Vyzkoušejte %1$s ve svém telefonu! - Rád bych vás pozval(a) k používání %1$s ve vašem telefonu.\nKe stažení zde: %2$s + Vyzkoušejte %1$s ve svém zařízení! + Rád(a) bych Vás/Tě pozval(a) k používání %1$s na vašem/tvém zařízení.\nKe stažení zde: %2$s %1$s nebo %2$s + Nepodařilo se najít soubor! Odstranění se nezdařilo Oznámení se nepodařilo odebrat. Odebrat Smazáno Zadejte nový název - Nepodařilo se přejmenovat místní kopii, použijte jiný název + Nepodařilo se přejmenovat místní kopii, zkuste použít jiný název Přejmenování není možné, název už je použit - Sdílet sdílenou složku dál není umožněno - Opětovné sdílení není povoleno + Příjemcům tohoto sdílení není dovoleno ho nasdílet dál dalším + Příjemcům tohoto sdílení není dovoleno ho nasdílet dál dalším Nejsou k dispozici žádné zmenšené obrázky. Zmenšená verze obrázku není k dispozici. Stáhnout obrázek v plné velikosti? Obnovit soubor @@ -546,18 +536,18 @@ Nesprávné heslo Přihlášení prostřednictvím QR kódu Ochrana vašich dat - Platforma pro produktivitu, hostovaná u vás + nástroje pro produktivitu, hostované u vás Procházet a sdílet všechny akce na dosah ruky - Aktivity, sdílení, soubory offline + Aktivita, sdílení, … vše pohotově přístupné Všechny vaše účty na jednom místě Automatické nahrání pro vaše fotky a videa - Synchronizovat kalendář a kontakty - pomocí DAVx5 (dříve DAVdroid) - Prohledat uživatele a skupiny + Kalendář a kontakty + Synchronizace s DAVx5 + Hledat uživatele a skupiny Vybrat vše Vybrat šablonu Odeslat @@ -569,15 +559,17 @@ Sdílet Přidat uživatele nebo skupinu Sdílení - %1$s (email) + %1$s (e-mail) Platnost skončí %1$s Sdílet %1$s - Vytvořit odkaz + Získat odkaz %1$s (skupina) + Sdílet vnitřní odkaz + Bude fungovat pouze pro uživatele s přístupem do tohoto %1$s %1$s (v %2$s) Je třeba zadat heslo - Při pokusu o sdílení tohoto souboru či složky nastala chyba - Nelze sdílet. Zkontrolujte zda soubor existuje + Při pokusu o sdílení tohoto souboru či složky došlo k chybě + Nedaří se sdílet – zkontrolujte zda soubor existuje pro sdílení tohoto souboru Zadejte nepovinné heslo Zadejte heslo @@ -593,16 +585,16 @@ Zrušit sdílení %1$s (vzdálený) %1$s (konverzace) - Jméno, sdružené cloud ID, nebo emailová adresa… + Jméno, identifikátor v rámci sdruženého cloudu, nebo e-mailová adresa… Poznámka pro příjemce - Povolit úpravy + Umožnit úpravy Nastavit datum skončení platnosti Skrýt stahování Skrýt seznam souborů Chránit heslem (%1$s) Chránit heslem Zabezpečeno - Sdílet odkaz + Odkaz na sdílení Poslat odkaz Zrušit nastavení Sdílet s %1$s @@ -627,7 +619,7 @@ Podrobnosti Totožnost serveru se nepodařilo ověřit Země: - Běžný název: + Běžný název (CN): Umístění: Organizace: Organizační jednotka: @@ -642,12 +634,12 @@ Pro: - Žádné informace o této chybě Certifikát se nedaří uložit - Certifikát nemohl být zobrazen. - Přejete si přesto tomuto certifikátu důvěřovat? + Certifikát se nepodařilo zobrazit. + Chcete i tak tomuto certifikátu důvěřovat? - Certifikátu serveru skončila platnost - Certifikát serveru není důvěryhodný - certifikát serveru ještě nezačal platit (datum začátku platnosti je v budoucnosti) - - URL adresa neodpovídá názvu stroje v certifikátu + - URL adresa neodpovídá názvu hostitele v certifikátu Kamera Zvolte umístění úložiště Výchozí @@ -657,8 +649,28 @@ Videa Hudba Obrázky - Produktivní platforma hostovaná u vás, kde vaše data jsou opravdu vaše - Produktivní platforma hostovaná u vás, kde vaše data jsou opravdu vaše (vývojová verze) + 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: +* 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. + +Díky Nextcloudu budete mít kompletní privátní řešení pro všechy vaše soubory, fotky, kalendáře a kontakty. + +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  + 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ě. + Platforma pro produktivitu hostovaná u vás, kde vaše data jsou opravdu vaše + Platforma pro produktivitu hostovaná u vás, kde vaše data jsou opravdu vaše (vývojová verze) Proudově vysílat s… Vnitřní proudové vysílání není možné Médium si namísto toho stáhněte nebo použijte externí aplikaci. @@ -667,13 +679,13 @@ Nalezeny konflikty Složka %1$s už neexistuje Nepodařilo se synchronizovat %1$s - Chybné heslo pro %1$s + Nesprávné heslo pro %1$s Automatická synchronizace souborů se nezdařila Synchronizace se nezdařila Synchronizace se nezdařila, přihlaste se znovu Obsah souboru už je synchronizován Synchronizaci složky %1$s se nedaří dokončit - Počínaje verzí 1.3.16, soubory nahrané z tohoto zařízení jsou zkopírovány do místní složky %1$s, aby se zabránilo ztrátě dat když je jeden soubor synchronizován s vícero účty.\n\nKvůli této změně byly všechny soubory, nahrané starší verzí zkopírovány do složky %2$s. Nicméně, chyba při synchronizaci účtu zabránila dokončení této operace. Buď tyto soubory můžete ponechat, jak jsou a smazat odkaz na %3$s, nebo je přesunout do složky %1$s ponechat odkaz na %4$s.\n\nNíže jsou vypsány místní soubory a vzdálené soubory v %5$s na který byly odkazovány. + Od verze 1.3.16, soubory nahrané z tohoto zařízení jsou zkopírovány do místní složky %1$s, aby se zabránilo ztrátě dat, když je jeden soubor synchronizován s vícero účty.\n\nKvůli této změně byly všechny soubory, nahrané starší verzí zkopírovány do složky %2$s. Nicméně, chyba při synchronizaci účtu zabránila dokončení této operace. Buď tyto soubory můžete ponechat, jak jsou a smazat odkaz na %3$s, nebo je přesunout do složky %1$s ponechat odkaz na %4$s.\n\nNíže jsou vypsány místní soubory a vzdálené soubory v %5$s na který byly odkazovány. Některé místní soubory byly zapomenuty Stahuje se nejnovější verzi souboru. Tlačítko Stav synchronizace @@ -701,8 +713,8 @@ Existuje nepřečtený komentář Zrušit šifrování Odebrat z oblíbených - Při pokusu o zrušení sdílení tohoto souboru či složky nastala chyba - Nedaří se ukončit sdílení. Zkontrolujte že soubor existuje + Při pokusu o zrušení sdílení tohoto souboru či složky došlo k chybě + Nedaří se ukončit sdílení – kontrolujte zda soubor existuje pro zrušení sdílení tohoto souboru Zrušení sdílení se nezdařilo Přístup prostřednictvím nedůvěryhodné domény. Více informací naleznete v dokumentaci. @@ -716,35 +728,35 @@ Nahrát z kamery Název souboru Typ souboru - Soubor zkratky pro Google mapy(%s) - Soubor zástupce na Internet(%s) - Soubor textového úryvku(.txt) + Soubor s odkazem do map Google (%s) + Soubor s odkazem na webovou stránku (%s) + Soubor textového úryvku (.txt) Název a typ vstupního souboru k nahrání Nahrát soubory Tlačítko akce Nahrát položku Nic k nahrání - Nahrajte nějaký obsah nebo aktivujte automatické nahrávání. + Nahrajte nějaký obsah nebo zapněte automatické nahrávání. Místní úložiště je plné Soubor se nepodařilo zkopírovat na místní úložiště Uzamčení složky se nezdařilo - Pro zkopírování vybraných souborů do složky %1$s není dostatek volného místa. Chcete je místo toho přesunout? + Pro zkopírování vybraných souborů do složky %1$s není dostatek volného místa. Chcete je tam namísto toho přesunout? Konflikt synchronizace, vyřešte ručně Neznámá chyba Vybrat Nahrát - Obdržená data neobsahují žádný platný soubor. + Obdržená data neobsahují platný soubor. %1$s nemá oprávnění číst přijatý soubor Došlo k chybě při kopírování souboru do dočasné složky. Zkuste zopakovat odeslání. - Soubor označený k nahrání nebyl v tomto umístění nalezen. Zkontrolujte zda soubor existuje. + Soubor označený k nahrání nebyl nalezen. Zkontrolujte zda soubor existuje. Soubor se nedaří nahrát Žádný soubor k nahrání Název složky - Vybrat složku k nahrávání + Vybrat složku pro nahrávání Nepodařilo se nahrát %1$s Nahrání se nezdařilo, znovu se přihlaste Odesílání se nezdařilo - Nahrát možnost: + Předvolba nahrávání: Přesunout soubor do složky %1$s zdrojová složka je pouze pro čtení, soubor bude pouze nahrán Ponechat soubor ve zdrojové složce @@ -759,8 +771,8 @@ Nenalezen žádný účet Aktuální Neúspěšný/čekající restart - Odesláno - Čekání na nahrávání + Nahráno + Čeká se na nahrání Nahrání Zrušeno Konflikt @@ -780,7 +792,7 @@ Čeká se na Wi-Fi Uživatel Adresa - Email + E-mail Telefonní číslo Twitter Webové stránky @@ -792,7 +804,7 @@ Chvilku strpení… Ověřování uložených přihlašovacích údajů Kopírování souboru ze soukromého úložiště - Obrázek Co je nového + Obrázek „Co je nového“ Přeskočit Co je v %1$s nového diff --git a/src/main/res/values-da/strings.xml b/src/main/res/values-da/strings.xml index 22e357ee0c56..8978bcdfb70f 100644 --- a/src/main/res/values-da/strings.xml +++ b/src/main/res/values-da/strings.xml @@ -223,7 +223,6 @@ Administrer konti Mellemkonto Åbn sidebar - Deltag %1$s af %2$s brugt %1$s brugt Auto upload @@ -264,7 +263,6 @@ Tilbage til foregående login metode Tilføj til favoritter Favorit - Ingen app tilgængelig til afsendelse af mails! Slet Fejl ved indlæsning af aktiviteter for fil Fejl ved indlæsning af detaljer @@ -385,18 +383,13 @@ Mediefilen kan ikke streames Kunne ikke læse mediefil Mediefilen har en ugyldig indkodning. - Filen er ikke i en gyldig konto - Mediefil ikke fundet - Uventet fejl ved forsøg på afspilning af %1$s Ventetid på forsøg på afspilning udløbet Den indbyggede media afspiller kan ikke afspille filen Ikke-understøttet medie codec - %1$s afspilning færdig Hurtigt fremad-knap %1$s musik afspiller Afspil eller pause knap Tilbagespolingsknap - %1$s (loader) %1$s (afspiller) Nyeste først Ældste først @@ -447,25 +440,6 @@ Operation annulleret. Serveren har nået sit livs ende, opgrader venligst! Mere menu - Test versionen under udvikling - Dette indeholder alle nye features og er på alleryderste forkant. Bugs/fejl kan forekomme, og når det sker, venligst rapporter dine observationer. - forum - Hjælp andre på - Anmelde, tilføje og skrive kode, se %1$s for information - Aktivt bidrage - Deltag i chatten på IRC: - Appen - Oversæt - Hent udviklingsversion direkte - Hent udviklingsversion fra F-Droid app - Hent kanditat til frigivelse fra F-Droid app - Hent kanditat til frigivelse fra Google Play store - Release candidate - Denne kandidat til frigivelse (RC) er et snapshot af den kommende version og forventes at være stabil. Testing af dit individuelle miljø kunne hjælpe med at sikre dette. Indkriv dig til testing på Play store eller led manuelt in \"Version\"s afdelingen af F-Droid. - Fundet en bug? Mærkværdighed? - Hjælp med at test - Rapporter et problem på Github - Interesseret i at hjælpe med at teste det der bliver den næste version? Angiv din passcode Denne passcode vil blive forespurgt hver gang app\'en startes Indtast venligst din adgangskode @@ -504,7 +478,6 @@ For at vise mnemonic venligst aktiver enhedens brugeroplysninger Vis media scan notifikationer Notificer om nyfundne media mapper - Feedback GNU General Public License, Version 2 Hjælp Aftryk @@ -559,17 +532,13 @@ Enheds legitimationsoplysninger er sat op Forkert kodeord Login med QR kode Beskytter dine data - Vært i eget hus produktivitets platform Defiler og del Alle funktioner lige ved hånden - Aktiviteter, delinger, offline filer alt hurtigt tilgængeligt Alle dine konti på et sted Automatisk upload Til dine billeder & videoer - Synk kalender & kontakter - med DAVx5 (forhenværende DAVdroid) Søg brugere og grupper Vælg alle Vælg model diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 4f5cc62fca61..ef2e0f5e2482 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -223,7 +223,6 @@ Konten verwalten Mittleres Konto Hauptmenü öffnen - Mitmachen %1$s von %2$s verwendet %1$s verwendet Automatisches Hochladen @@ -264,7 +263,6 @@ Zur alten Anmeldungsmethode zurückkehren Zu den Favoriten hinzufügen Favoriten - Keine App verfügbar für den E-Mailversand! Datei Löschen Fehler beim Abrufen der Aktivitäten für die Datei @@ -387,18 +385,13 @@ Die Mediendatei kann nicht gestreamt werden Konnte die Mediendatei nicht lesen Mediendatei ist nicht korrekt encodiert - Die Datei befindet sich nicht in einem gültigen Konto - Keine Mediendatei gefunden - Unerwarteter Fehler beim Versuch %1$s wiederzugeben Wartezeit beim Abspielversuch abgelaufen Der interne Player kann die Mediendatei nicht abspielen Medien-Codec wird nicht unterstützt - %1$s Wiedergabe beendet Vorspulknopf %1$s Musik Player Wiedergabe-/Pause Knopf Rückspulknopf - %1$s (wird geladen) %1$s (wird abgespielt) Neueste zuerst Älteste zuerst @@ -449,25 +442,6 @@ Vorgang abgebrochen Die Serverversion wird nicht mehr unterstützt, bitte aktualisieren Sie diesen! Weiteres Menü - Teste die Entwicklerversion - Dies beinhaltet neue Funktionalitäten und ist nicht vollumfänglich qualitätsgesichert. Es können daher Fehler/Bugs auftreten, melden Sie uns diese bitte. - im Forum - Helfen Sie anderen - Überprüfen, Ändern und Schreiben von Code, schauen Sie in die %1$s für weitere Details - Aktiv etwas beitragen - Dem Chat im IRC beitreten: - mit der App - beim Übersetzen - Entwicklungs-Version direkt herunterladen - Entwicklungs-Version über die F-Droid-App beziehen - Release-Kandidat über die F-Droid-App beziehen - Release-Kandidat über den Google-Play-Store beziehen - Vorabversion - Der Release-Kandidat (RC) ist eine Vorschau auf die kommende Version und sollte stabil sein. Das Testen des RC unter Ihrer individuellen Umgebung kann helfen, die Stabilität und Qualität zu sichern. Registrieren Sie sich als Tester im Play-Store oder schauen Sie in die \"Version\"-Sektion auf F-Droid. - Fehler gefunden? Komisches Verhalten? - Hilf uns Testen - Fehlerbericht GitHub senden - Interessiert uns beim Test der nächsten Version zu unterstützen? PIN eingeben Die PIN wird jedes mal beim Start der App abgefragt Bitte geben Sie Ihre PIN ein @@ -506,7 +480,6 @@ Um Mnemonic anzuzeigen aktivieren Sie bitte Geräte-Zugangsdaten Benachrichtigungen der Mediensuche anzeigen Über neu gefundene Medienordner informieren - Rückmeldungen GNU General Public Lizenz, version 2 Hilfe Impressum @@ -563,17 +536,17 @@ Falsches Passwort Anmelden mit QR-Code Schützt Ihre Daten - Die Selbst-Gehostete Plattform für Produktivität + Selbst gehostete Produktivität Durchsuchen und teilen Alle Aktionen schnell erreichbar - Aktivitäten, Freigaben, Offline-Dateien + Aktivitäten, Freigaben, … Alles schnell erreichbar All ihre Konten an einem Ort Automatisches Hochladen ihrer Fotos & Videos - Synchronisieren sie Kalender & Kontakte - mit DAVx5 (zuvor DAVdroid) + Kalender & Kontakte + Synchronisiere mit DAVx5 Nutzer und Gruppen suchen Alle auswählen Vorlage auswählen diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 181b037b1f75..f4b927e29700 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -221,7 +221,6 @@ Διαχείριση λογαριασμών Μεσαίος λογαριασμός Άνοιγμα πλευρικής στήλης - Συμμετοχή χρησιμοποιούνται %1$s από %2$s %1$sσε χρήση Αυτόματη μεταφόρτωση @@ -258,7 +257,6 @@ Επαναφορά στην παλιά μέθοδο σύνδεσης Προσθήκη στα αγαπημένα Αγαπημένο - Δεν υπάρχει διαθέσιμη εφαρμογή για αποστολή αλληλογραφίας! Διαγραφή Σφάλμα ανάκτησης δραστηριοτήτων για το αρχείο Αποτυχία φόρτωσης λεπτομερειών @@ -378,18 +376,13 @@ Το αρχείο πολυμέσων δεν μπορεί να μεταδοθεί Αδυναμία ανάγνωσης αρχείου πολυμέσων Εσφαλμένη κωδικοποίηση αρχείου πολυμέσων - Το αρχείο δεν είναι έγκυρος λογαριασμός - Δεν βρέθηκαν αρχεία πολυμέσων - Απροσδόκητο σφάλμα κατά την προσπάθεια αναπαραγωγής του %1$s Λήξη χρόνου κατά την προσπάθεια αναπαραγωγής Η ενσωματωμένη εφαρμογή αναπαραγωγής πολυμέσων δεν μπορεί να αναπαράγει το αρχείο πολυμέσων Δεν υποστηρίζεται η μορφή κωδικοποιήσης πολυμέσων - %1$s αναπαραγωγή τελείωσε Κουμπί γρήγορης προώθησης %1$sαναπαραγωγή μουσικής Κουμπί αναπαραγωγής ή παύσης Κουμπί ταχείας κίνησης πίσω - %1$s(φόρτωση) %1$s(αναπαραγωγή) Νεότερο πρώτα Παλαιότερο πρώτα @@ -439,25 +432,6 @@ Η λειτουργία ακυρώθηκε Ο διακομιστής έφτασε στο τέλος το χρόνου υποστήριξης, παρακαλούμε αναβαθμίστε! Περισσότερο μενού - Δοκιμή της εκδοσης προς ανάπτυξη - Αυτή περιέχει όλες τις επερχόμενες λειτουργίες και δαθέτει ότι τελευταίο έχει ενσωματωθεί. Σφάλματα/λάθη μπορεί να προκύψουν. Σας παρακαλούμε να μας τα αναφέρετε. - forum - Βοηθήστε τους άλλους σε - Επιθεωρήστε, τροποποιήστε και γράψτε κώδικα, δείτε %1$s για λεπτομέρειες - Ενεργή συνεισφορά - Μπείτε για chat στο IRC: - η εφαρμογή - Μετάφραση - Απευθείας λήψη της υπό ανάπτυξη έκδοσης - Λάβετε την υπό ανάπτυξη έκδοση από την εφαρμογή F-Droid - Λάβετε την υπό ανάπτυξη έκδοση από την εφαρμογή F-Droid - Λάβετε την υπό ανάπτυξη έκδοση από την εφαρμογή Google Play store - Release candidate - Η υποψήφια έκδοση (RC) είναι ένα στιγμιότυπο της επερχόμενης έκδοσης και να αναμένεται να είναι σταθερή. Δοκιμάζοντας τις προσωπικές σας ρυθμίσεις μπορεί να βοηθήσει στην διασφάλιση της έκδοσης. Δηλώστε υποψηφιότητα δοκιμής στο Play store ή αναζητήστε χειροκίνητα στον τομέα \"εκδόσεων\" στο F-Droid. - Βρήκατε σφάλμα; Κάτι σας φαίνεται παράξενο; - Βοηθήστε μας στις δοκιμές - Αναφορά προβλήματος στο GitHub - Σας ενδιαφέρει να μας βοηθήσετε να δοκιμάσουμε την επόμενη έκδοση; Εισάγετε τον κωδικό πρόσβασης Ο κωδικός πρόσβασης θα ζητείται κάθε φορά που εκκινεί η εφαρμογή Παρακαλούμε εισάγετε τον κωδικό πρόσβασης @@ -491,7 +465,6 @@ Ημερήσιο αντίγραφο ασφαλείας των επαφών σας Εμφάνιση ειδοποιήσεων σάρωσης πολυμέσων Ειδοποιήστε για νέους φακέλους πολυμέσων - Aνάδραση Γενική Άδεια Δημόσιας Χρήσης GNU, έκδοση 2 Βοήθεια Αποτύπωμα @@ -549,8 +522,6 @@ σε ένα σημείο Αυτόματη μεταφόρτωση για τις φωτογραφίες & τα βίντεό σας - Συγχρονισμός ημερολογίου & επαφών - με DAVx5 (πρώην DAVdroid) Αναζήτηση χρηστών και ομάδων Επιλογή όλων Επιλογή προτύπου diff --git a/src/main/res/values-eo/strings.xml b/src/main/res/values-eo/strings.xml index 373ea5947ee7..8feae538bba0 100644 --- a/src/main/res/values-eo/strings.xml +++ b/src/main/res/values-eo/strings.xml @@ -57,9 +57,9 @@ Sekura konekto stariĝis SSL-ekigo malsukcesis Ne eblis kontroli SSL-servilan identecon - Provante konekton + Provado de la konekto La servilo tro malfruis por respondi - Provante ensaluti... + Provo ensaluti... Malĝusta uzantonomo aŭ pasvorto Nekonata eraro: %1$s Nekonata HTTP-eraro okazis! @@ -98,7 +98,7 @@ Eraro Nesufiĉa memoro Nekonata eraro - Ŝargante... + Ŝargado... Ne Akcepti Pritraktotaj @@ -142,7 +142,7 @@ Kopii aŭ movi en ĉifritan dosierujon ne estas subtenata momente. Kopii al... Ne eblis krei dosierujon - Kreante dosieron el ŝablono... + Kreado de dosiero el ŝablono... Krei novan dokumenton Krei novan dosierujon Krei novan prezentaĵon @@ -165,8 +165,8 @@ Elŝuto malsukcesis, bonvolu re-ensaluti Elŝuto malsukcesis La dosiero ne plu disponeblas en la servilo - %1$d%% Elŝutante %2$s - Elŝutante... + %1$d%% Elŝutado %2$s + Elŝutado... %1$s elŝutiĝis Elŝutita Ankoraŭ ne elŝutita @@ -191,24 +191,23 @@ Administri kontojn Meza konto Malfermi flankopanelon - Partopreni %1$s el %2$s uzataj %1$s uzataj Aŭtomata alŝuto modifi Difini kiel ĉifrita Agordi ĉifradon - Malĉifrante... + Malĉifrado... Fermi Bonvolu entajpi pasvorton por malĉifri privatan ŝlosilon. Tiu dosierujo ne malplenas. - Generante novajn ŝlosilojn... + Genero de novaj ŝlosiloj... Tiu 12-vorta frazo estas tre fortika pasvorto, kiu permesas nur al vi vidi kaj uzi viajn ĉifritajn dosierojn. Bonvolu noti tiun frazon kaj konservi ĝin en fidinda loko. Tutvoja ĉifrado malebligita en la servilo. Ĉifrado funkcias nur ekde KitKat (Android versio 4.4). Notu vian 12-vortan ĉifran pasfrazon Pasvorto... - Ricevante ŝlosilojn... + Ricevo de ŝlosiloj... Konservado de la ŝlosiloj Agordi ĉifradon Ne povis konservi ŝlosilojn, bonvolu reprovi. @@ -224,7 +223,6 @@ Reiri al la malnova konektmetodo Aldoni al pliŝataĵoj Pliŝatataj - Neniu aplikaĵo disponeblas por sendi retmesaĝojn! Forigi Ŝargo de detaloj malsukcesis Dosiero @@ -251,12 +249,12 @@ Alŝutu kelkajn videaĵojn aŭ aktivigu aŭtomatan alŝuton. Neniu videaĵo. dosierujo - Ŝargante... + Ŝargado... Neniu aplikaĵo agordita por malfermi tiun dosiertipon. sekundoj antaŭe - Kontrolante la celon... - Purigante... - Ĝisdatigante la vojon al konservejo + Kontrolado de la celo... + Purigado... + Ĝisdatigo de la vojo al konservejo Datumdosierujo jam ekzistas. Elektu unu opcion: Nextcloud-dosierujo jam ekzistas Pli da diskospaco bezonata @@ -264,17 +262,17 @@ Ne eblis skribi al la celdosiero Malsukceso dum datummigrado Ne eblis ĝisdatigi la indekson - Movante datumojn... + Movo de datumoj... Finita Anstataŭigi - Preparante la datummigradon... - Restaŭrante la kontagordon... - Konservante la kontagordon... + Preparado de la datummigrado... + Restaŭrado de la kontagordo... + Konservado de la kontagordo... Ĉu vi plu volas ŝanĝi la konservejan lokon al %1$s?\n\nNotu: ĉiuj datumoj estos denove elŝutitaj. Fontodosierujo ne legeblas! - Ĝisdatigante la indekson... + Ĝisdatigo de la indekso... Uzi - Atendante la plenan sinkronigon... + Atendo de la plena sinkronigo... Netrovita dosiero La dosiero ne eblis sinkroniĝi. Nun montriĝas lasta disponebla versio. Alinomi @@ -304,8 +302,8 @@ forigi ĉi tiun dosieron movi ĉi tiun dosieron alinomi ĉi tiun dosieron - Elŝutante dosierojn... - Alŝutante dosierojn... + Elŝutado de dosieroj... + Alŝutado de dosieroj... Kelkaj dosieroj ne eblis moviĝi Loka: %1$s Movi ĉion @@ -336,18 +334,13 @@ La aŭdovida dosiero ne eblis esti flue elsendita Ne eblis legi la aŭdovidan dosieron La aŭdovida dosiero havas neĝustan kodadon - La dosiero ne troviĝas en valida konto - Neniu aŭdovida dosiero troveblas - Neatendita eraro dum ludado de %1$s Provado ludi la dosieron eltempiĝis La integrita ludilo ne eblis ludi tiun aŭdovidan dosieron Nesubtenata aŭdovida kodeko - Ludado de %1$s finita Rapidpluiga butono Muzika ludilo %1$s Luda aŭ paŭziga butono Revolva butono - %1$s (ŝargante) %1$s (ludante) Pli novaj unue Malpli novaj unue @@ -390,32 +383,13 @@ Montras alŝutoprogreson Kanalo por sciigi alŝuton Piktogramo pri sciigoj - Ŝargante sciigojn... + Ŝargo de la sciigoj... Neniu sciigo Bonvolu rekontroli poste. Neniu reta konekto La funkcio estis nuligita La servilo nun malaktualas, bonvolu ĝisdatigi ĝin! Pliaj menuoj - Testu la beta-version - Ĝi enhavas ĉiujn venontajn funkciojn, kaj ĝi estas freŝfreŝa. Eraroj povas okazi: se kaj kiam tio okazas, bonvolu raporti ilin. - forumo - Helpu la aliajn uzantojn per - Reviziu, korekti kaj kodu: vidu ĉi tie %1$s por pli da detaloj - Aktive kontribuu - Partoprenu babilante per IRC (interreta relajsa babilo, angle „internet relay chat“): - la aplikaĵon - Traduku - Elŝutu beta-version senpere - Havu beta-version el la aplikaĵo „F-Droid“ - Havu testan antaŭversion el la aplikaĵo „F-Droid“ - Havu testan antaŭversion el „Play Store“ de Google - Testa antaŭversio - La testa antaŭversio (RC, el la angla „release candidate“) estas ekfoto de la venonta versio kaj devus esti stabila. Per testado de via sistemo, vi povas helpi. Uzu tiun RC-version per registriĝo en „Play Store“ aŭ per la sekcio „Versioj“ de „F-Droid“. - Ĉu vi trovis eraron? Ion stranga? - Helpi per testado - Raporti problemon per GitHub - Ĉu vi pretas helpi testante la proksiman version? Entajpu vian pasvorton La pasvorto estos petita ĉiufoje, kiam la aplikaĵo ekkomenciĝas Bonvolu entajpi vian pasvorton @@ -453,7 +427,6 @@ Por montri memorigan helpilon, ŝalti uzon de akreditiloj sur via aparato. Montri sciigoj pri aŭdvidaj dosierujoj Sciigi pri novaj aŭdvidaj dosierujoj trovitaj - Prikomentado Ĝenerala Publika Permesilo, versio 2 Helpo Informoj @@ -624,7 +597,7 @@ Dosieroj Butono pri agordoj Agordi dosierujojn - Ŝargante dosierujojn... + Ŝargado de dosierujoj... Ni tute elrenovigis tuj-alŝutadon. Reagordu vian aŭtomatan alŝuton pere de la ĉefa menuo.\n\nUzu la novan, kaj pli kapablan, aŭtomatan alŝuton. Neniu aŭdovida dosierujo troveblas Agordoj pri aŭtomata alŝuto @@ -695,7 +668,7 @@ Forigi la dosieron el la origina dosierujo alŝuti al ĉi tiu dosierujo %1$d%% Alŝutante %2$s - Alŝutante… + Alŝutado… %1$s alŝutiĝis Forlasi Agordi @@ -704,7 +677,7 @@ Nuna Malsukcesa aŭ atendante reprovadon Alŝutita - Atendante por alŝuti + Atendo por alŝuti Alŝutoj Nuligita Konflikto @@ -719,9 +692,9 @@ Plenumita Nekonata eraro Komputila viruso eltrovita. Alŝuto ne povas plui! - Atendante eliron el energiŝpara reĝimo - Atendante ŝargon de via aparato - Atendante sendratan reton („Wi-Fi“) + Atendo de eliro el energiŝpara reĝimo + Atendo de la ŝargo de via aparato + Atendado de sendrata reto („Wi-Fi“) Uzanto Adreso Retpoŝtadreso @@ -734,8 +707,8 @@ Uzantonomo Elŝuti Atendu momenton... - Kontrolante konservitajn akreditilojn - Kopiante dosieron el malpublika konservejo + Kontrolado de konservitaj akreditiloj + Kopio de dosiero el malpublika konservejo Bildo pri Kio nova Preterpasi Novaĵoj en %1$s diff --git a/src/main/res/values-es-rAR/strings.xml b/src/main/res/values-es-rAR/strings.xml index 53e699d2141c..03c960e43e35 100644 --- a/src/main/res/values-es-rAR/strings.xml +++ b/src/main/res/values-es-rAR/strings.xml @@ -191,7 +191,6 @@ Administrar cuentas Cuenta media Abrir barra lateral - Participe %1$s de %2$s usados %1$s usado Carga automática @@ -224,7 +223,6 @@ Volver al antiguo método de inicio de sesión Agregar a favoritos Favorito - No hay aplicación disponible para enviar correos! Borrar Error al cargar los detalles Archivo @@ -336,18 +334,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificacion incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo - Error inesperado al intentar reproducir %1$s El intento de reproducir el archivo agotó el tiempo de espera. El reproductor multimedia incorporado no puede reproducir el archivo multimedia Codec no soportado - %1$sreproducción terminada Botón de avanzar rápido %1$s reproductor de música Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -397,25 +390,6 @@ Operación ha sido cancelada El servidor ha llegado al final de su vida útil, por favor actualice! Más menú - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, favor de reportarnoslos.  - Foro - Ayudar a otros en el - Revisar, modificar y escribir código, vea %1$s para detalles - Contribuya activamente - Únase al chat en IRC: - La app - Traducir - Descargar la versión de desarrollo directamente - Obtener la versión de desarrollo de la aplicación F-Droid - Obtener el candidato de lanzamiento de la aplicación F-Droid - Obtener el candidato de lanzamiento de Google Play Store - Candidato a lanzamiento - El candidato de lanzamiento (RC) es una instantánea de la próxima versión y se espera que sea estable. Probando su configuración individual podría ayudar a asegurar esto. Regístrese para realizar pruebas en Play Store o busque manualmente en la sección \"Versión\" de F-Droid. - ¿Encontró una falla? ¿Hay algo raro? - Ayúdenos probando - Reportar un error en GitHub - ¿Interesado en ayudar probando cuál será la próxima versión? Ingrese su código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Favor de ingresar su código de seguridad @@ -453,7 +427,6 @@ Para mostrar mnemónico por favor habilite las credenciales del dispositivo. Mostrar notificaciones de escaneo de medios Notificar sobre las nuevas carpetas de medios encontradas - Retroalimentación Licencia pública general de GNU, versión 2 Ayuda Exención de responsabilidad diff --git a/src/main/res/values-es-rCL/strings.xml b/src/main/res/values-es-rCL/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rCL/strings.xml +++ b/src/main/res/values-es-rCL/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rCO/strings.xml b/src/main/res/values-es-rCO/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rCO/strings.xml +++ b/src/main/res/values-es-rCO/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rCR/strings.xml b/src/main/res/values-es-rCR/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rCR/strings.xml +++ b/src/main/res/values-es-rCR/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rDO/strings.xml b/src/main/res/values-es-rDO/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rDO/strings.xml +++ b/src/main/res/values-es-rDO/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rEC/strings.xml b/src/main/res/values-es-rEC/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rEC/strings.xml +++ b/src/main/res/values-es-rEC/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rGT/strings.xml b/src/main/res/values-es-rGT/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rGT/strings.xml +++ b/src/main/res/values-es-rGT/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rHN/strings.xml b/src/main/res/values-es-rHN/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rHN/strings.xml +++ b/src/main/res/values-es-rHN/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rMX/strings.xml b/src/main/res/values-es-rMX/strings.xml index 891b03c6be86..d5f7dc81fd9b 100644 --- a/src/main/res/values-es-rMX/strings.xml +++ b/src/main/res/values-es-rMX/strings.xml @@ -170,7 +170,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática editar @@ -195,7 +194,6 @@ Error crítico: No se pueden realizar operaciones Atrás Regresar al antiguo método de inicio de sesión - ¡No hay aplicación disponible para enviar mails! Borrar Archivo Mantener @@ -296,17 +294,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -344,25 +338,6 @@ No hay notificaciones Por favor verifica más tarde. No hay conexión a internet - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -394,7 +369,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rNI/strings.xml b/src/main/res/values-es-rNI/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rNI/strings.xml +++ b/src/main/res/values-es-rNI/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rPA/strings.xml b/src/main/res/values-es-rPA/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rPA/strings.xml +++ b/src/main/res/values-es-rPA/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rPE/strings.xml b/src/main/res/values-es-rPE/strings.xml index 9b08212a6cff..f83db6fbfeea 100644 --- a/src/main/res/values-es-rPE/strings.xml +++ b/src/main/res/values-es-rPE/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rPR/strings.xml b/src/main/res/values-es-rPR/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rPR/strings.xml +++ b/src/main/res/values-es-rPR/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rPY/strings.xml b/src/main/res/values-es-rPY/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rPY/strings.xml +++ b/src/main/res/values-es-rPY/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rSV/strings.xml b/src/main/res/values-es-rSV/strings.xml index f3b78a9c7a5d..2d807a232cd2 100644 --- a/src/main/res/values-es-rSV/strings.xml +++ b/src/main/res/values-es-rSV/strings.xml @@ -159,7 +159,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática Establecer como encriptado @@ -275,17 +274,13 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera El reproductor de medios integrado es incapaz de reproducir el archivo de medios Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -317,25 +312,6 @@ Cargando notificaciones… No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -365,7 +341,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es-rUY/strings.xml b/src/main/res/values-es-rUY/strings.xml index bfebe6afcd1b..ef7ba1d4e0f9 100644 --- a/src/main/res/values-es-rUY/strings.xml +++ b/src/main/res/values-es-rUY/strings.xml @@ -134,7 +134,6 @@ Cerrar sesión Administrar cuentas Cuenta intermedia - Participa %1$s de %2$s usados Carga automática %1$s no pudo ser copiado a la carpeta local %2$s @@ -198,16 +197,12 @@ Archivo de medio no puede ser transformado a un flujo No fue posible leer el archivo de medios El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se encontró el archivo El intento de reproducir el archivo sobrepasó el tiempo de espera Codec no soportado - %1$s reproducción finalizada Botón de avanzar rápido Reproductor de músca %1$s Botón de reproducir o pausar Botón de rebobinar - %1$s (cargando) %1$s (reproduciendo) Más reciente primero Más antiguo primero @@ -222,25 +217,6 @@ La operación no pudo ser completada. El servidor no está disponible No hay notificaciones Por favor verifica más tarde. - Probar la versión de desarrollo - Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo. - foro - Ayuda a otros en - Revisa, corrige y escribe código, ve %1$s para más detalles - Contribuye activamente - Únete a la conversación en IRC: - la aplicación - Traducir - Obten la liberación de desarrollo directamente - Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de aplicaciones F-Droid - Obten el candidato a liberación de la tienda de Google Play - Candidato a lanzamiento - El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid. - ¿Encontraste una falla? ¿Hay algo raro? - Ayúdanos probando - Reporta un tema en GitHub - ¿Te interesaría ayudarnos a probar lo que viene en la siguiente versión? Ingresa tu código de seguridad El código de seguridad será solicitado cada vez que inicie la aplicación Por favor ingresa tu código de seguridad @@ -269,7 +245,6 @@ General Más Respaldo diario de tus contactos - Retroalimentación GNU Licencia Pública General, versión 2 Ayuda Excención de responsabilidad diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 9cd9138b514f..3953e76ea6ce 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -223,7 +223,6 @@ Administrar cuentas Cuenta media Abrir barra lateral - Participar %1$s de %2$s utilizados usado %1$s Carga automática @@ -264,7 +263,7 @@ Volver al método de registro antiguo Añadir a favoritos Favorito - ¡No hay ninguna app disponible para mandar correos! + archivo Eliminar Error al recuperar las actividades del archivo Fallo al cargar los detalles @@ -334,6 +333,7 @@ Colaboración segura e intercambio de archivos Correo web, calendario y contactos fáciles de usar Compartir pantalla, reuniones online y conferencias web + carpeta La carpeta ya existe Crear Icono de carpeta @@ -385,18 +385,13 @@ El archivo de medios no puede ser transmitido El archivo de medios no se ha podido leer El archivo de medios tiene una codificación incorrecta - El archivo no está en una cuenta válida - No se ha encontrado el archivo multimedia - Error inesperado al intentar reproducir %1$s Tiempo de espera agotado en el intento de reproducción El reproductor de medios incorporado no puede reproducir el archivo Códec de medios no soportado - %1$s reproducción finalizada Botón de avance rápido Reproductor de música %1$s Botón de reproducción o pausa Botón de rebobinado - %1$s (cargando) %1$s (reproduciendo) El más reciente primero El más viejo primero @@ -447,25 +442,6 @@ La operación se ha cancelado El servidor ha alcanzado su final de vida. ¡Por favor, actualiza! Más menú - Probar la versión de desarrollo - Esto incluye todas las características por llegar y es bastante inestable. Pueden ocurrir fallos y errores. Cuando sucedan infórmanos, por favor. - foro - Ayuda a otros en el - Revisa, corrige y escribe código. Ve %1$s para más detalles - Colabora activamente - Únete a la conversación en IRC: - la app - Traduce - Desgargar versión de desarrollo directamente - Obtener versión de desarrollo vía la tienda de apps F-Droid - Obtener candidato a liberar desde la tienda de apps F-Droid - Obtener candidato a liberar desde la tienda de Google Play - Versión candidata - La versión candidata (RC) es una instantánea de la próxima versión y se espera que sea estable. Probar tu configuración individual puede ayudar a asegurarlo. Apúntate para probarla en Google Play o búscala manualmente en la sección \"Versiones\" de F-Droid. - ¿Encontraste un error? ¿Algo va mal? - Ayúdanos a realizar pruebas - Informar de un problema en GitHub - ¿Interesado en ayudar probando cómo será la próxima versión? Introduce tu código de acceso Se solicitará el código de acceso cada vez que se inicie la aplicación Por favor introduzca su código de acceso @@ -504,7 +480,6 @@ Para mostrar el nemotécnico, habilite las credenciales del dispositivo Mostrar notificaciones de escaneo de medios Notificar cuando se encuentren nuevas carpetas de medios - Observaciones Licencia GNU GPL, versión 2 Ayuda Pie de imprenta @@ -561,17 +536,17 @@ Contraseña incorrecta Entrar vía código QR Protegiendo sus datos - plataforma de productividad auto hospedada + plataforma de productividad autoalojada Navegar y compartir todas las acciones en la punta de sus dedos - Actividad, compartir, archivos offline + Actividad, compartir… todo accesible rápidamente Todas sus cuentas en un lugar Subida automática para sus fotos & vídeos - Sincronice calendario & contactos - con DAVx5 (anteriormente DAVdroid) + Calendario y contactos + Sincronización con DAVx5 Buscar usuarios y grupos Seleccionar todo Escoge plantilla @@ -589,6 +564,8 @@ Compartir %1$s Obtener enlace %1$s (grupo) + Compartir enlace interno + Sólo funciona para usuarios con acceso a esto %1$s %1$s ( en %2$s ) Debes introducir una contraseña Ha ocurrido un error al tratar de compartir este archivo o carpeta diff --git a/src/main/res/values-et-rEE/strings.xml b/src/main/res/values-et-rEE/strings.xml deleted file mode 100644 index 465da18f94dc..000000000000 --- a/src/main/res/values-et-rEE/strings.xml +++ /dev/null @@ -1,417 +0,0 @@ - - - %1$s Androidi rakendus - Info - versioon %1$s - Konto loomine ebaõnnestus - Kontot ei leitud! - Puhasta ebaõnnestunud üleslaadimised - Tühjenda prügikast - Saada/Jaga - Ruudustikvaade - Nimekirjavaade - Varunda kontaktid - Taasta kontaktid - Kopeeri - Uus kaust - Tõsta ümber - Ava rakendusega - Otsi - Üksikasjad - Saada - Seaded - Sorteeri - Värskenda kontot - Aktiivne kasutaja - Tegevusi veel pole - Tegevusi, nagu lisamised, muutmised ja jagamised, pole - Saada - Saada link… - Ligipääs ebaõnnestus: %1$s - Seda kontot pole veel sellesse seadmesse lisatud - Sama konto kasutaja ja server on juba selles seadmes olemas - Sisestatud kasutaja ei kattu selle konto kasutajaga - Tundmatu serveri versioon - Selle serveri vastu autentimine ebaõnnestus - Kontrolli serverit - Saadi ühendus - Palun sisesta praegune parool - Serveri aadress - Serveri aadress https://… - Vale serveriaadressi formaat - Serverit ei leitud - Võrguühendust pole - Turvaline ühendus pole saadaval - Vigases vormingus server seadistus - Autoriseerimine ebaõnnestus - Ligipääs keelatud autoriseeriva serveri poolt - Parool - Turvalist ühendust suunatakse läbi turvamata ühenduse. - Värskenda ühendust - Saavutati turvaline ühendus - SSL-i käivitamine ebaõnnestus - SSL serveri identiteeti ei suudetud tuvastada - Ühenduse testimine - Serveri vastus võttis liiga kaua aega - Vale kasutajanimi või parool - Tundmatu tõrge: %1$s - Esines tundmatu HTTP tõrge! - Tekkis tundmatu tõrge! - Ei leidnud hosti - %1$s ei toeta mitme konto kasutamist - Kasutajanimi - Ühenduse loomine ebaõnnestus - Lae üles ainult piiramata Wi-Fi võrgus - Avatar - Tekkis tõrge sertifikaadi laadimisel. - Tagasi - Loobu - Peata sünkroniseerimine - Vali konto - Kustuta - Viga - Mälu pole piisavalt - Tundmatu viga - Laadimine… - Ei - OK - Ootel - Kustuta - Nimeta ümber - Salvesta - Saada - Vaheta kontot - tundmatu - Jah - Ainult kohalik - Säilita mõlemad - Millist faili sa soovid säilitada? Kui valid mõlemad versioonid, siis lisatakse kohaliku faili nimele number. - Failikonflikt - kohalik versioon - serveri versioon - Automaatne varundus - Varunda kohe - Viimane varukoopia - Vali kuupäev - Varundus on planeeritud ning algab koheselt - Viimast varundust ei leitud! - Selle faili või kausta kopeerimisel tekkis tõrge - Kausta ei saa kopeerida tema enda alamkausta - See fail on juba sihtkaustas olemas - Kopeerimine ebaõnnestus. Palun kontrolli, kas fail on olemas - Kopeeri link - Ei suuda kausta luua - Loo uus kaust - Teadmata - Eemalda konto - Eemalda konto %s ja kustuta kõik kohalikud failid?\n\nKustutamist ei saa tagasi võtta. - Uus versioon saadaval - Ei saanud alla laadida %1$s - Allalaadimine ebaõnnestus, logi uuesti sisse - Allalaadimine ebaõnnestus - Fail ei ole serveris enam kättesaadav - %1$d%% allalaadimine %2$s - Allalaadimine… - %1$s alla laetud - Alla laetud - Allalaadimata - Sulge külgriba - Tegevused - Kõik failid - Lemmikud - Kodu - Märguanded - Seadmel - Fotod - Viimati lisatud - Viimati muudetud - Jagatud - Üleslaadimised - Videod - Logi välja - Halda kontosid - Ava külgriba - Osavõtt - Kasutatud %1$s/%2$s - Automaatne üleslaadimine - Määra krüpteerituks - Sulge - %1$s ei suudetud kopeerida kohalikku kataloogi %2$s - Tagasi - Lae midagi üles või sünkroniseeri oma seadmetega. - Midagi pole veel lemmikutese lisatud - Sinu poolt sooritatud otsing ei leidnud ühtegi lemmikuks märgitud faili. - Lemmikuteks märgitud failid ja kaustad kuvatakse siin - Siin ei ole faile - Selles kaustas tulemused puuduvad - Vasteid ei leitud - Fotosid pole - Videosid pole - Siin pole midagi. Sa võid lisada kausta. - Hiljuti lisatud faile ei leitud - Hiljuti lisatud faile pole. - Viimase 7 päeva jooksul muudetud faile ei leitud - Viimase 7 päeva jooksul pole faile muudetud. - Võibolla on see teises kaustas? - Sinu poolt jagatud failid ja kaustad on siin näha - Midagi pole veel jagatud - Lae üles mõned pildid või lülita sisse automaatne üleslaadimine. - Pilte pole. - Lae üles mõned videod või lülita sisse automaatne üleslaadimine. - Videosid pole. - kaust - Laadimine… - Puudub rakendus seda tüüpi failide avamiseks. - sekundit tagasi - Vaja rohkem ruumi - Valmis - Asenda - Faili ei leitud - Lae alla - Sünkrooni - Faile ei ole valitud - Faili nimi ei tohi olla tühi - Keelatud sümbolid: / \\ < > : \" | ? * - Faili nimes on vähemalt üks keelatud märk - Kausta ikoon - Siin ei ole kaustu - Vali - Sul ei ole %s õigusi - et kopeerida seda faili - selle faili loomiseks - selle faili kustutamiseks - selle faili liigutamiseks - selle faili ümbernimetamiseks - Failide allalaadimine… - Failide ülesaadimine… - Mõnda faili ei saanud liigutada - Kohalik: %1$s - Liiguta kõik - Kaughallatav: %1$s - Kõik failid on liigutatud - Edasi - Nimi - Lae ülesse ainult laadimise ajal - /InstantUpload - Link - Kaustas pole faile. - Rohkem kaustu ei ole. - %1$s Androidi rakenduse logid - Server on hooldusrežiimis - Tühjenda andmed - Ruumi haldamine - Meediafaili lugemine ebaõnnestus - Meedia faili ei leitud - Mittetoetatud meedia koodek - Kiire kerimise nupp - %1$s muusika pleier - Mängimise või pausi nupp - Tagasikerimise nupp - %1$s (laeb) - %1$s (mängib) - Uuemad esimesena - Vanemad esimesena - A - Z - Z - A - Suuremad esimesena - Väiksemad esimesenaa - Selle faili või kausta liigutamisel tekkis tõrge - Kausta ei saa liigutada tema enda alamkausta - See fail on sihtkohas juba olemas. - Liigutamine ebaõnnestus. Palun kontrolli, kas fail on olemas - Süsteemiteavitused - Teavituste laadimine… - Märguandeid pole - Internetiühendus puudub - Testi arendusversiooni - See sisaldab kõiki tulevasi funktsioona. Bugisid/vigu võib esineda ja kui nii juhtub, siis palun andke sellest teada. - foorumis - Aita teisi - Vaata, paranda ja kirjuta koodi, täpsem info: %1$s - Löö aktiivselt kaasa - Ühine jutuajamiseg IRC serveris: - rakendust - Tõlgi - Väljalaske kandidaat - Leidsid bugi? Midagi veidrat? - Aita testides - Teavita probleemist GitHubis - Oled huvitatud aitamisest uut versiooni testides? - Sisesta oma parool - Parooli küsitakse iga kord, kui sa selle rakenduse käivitad - Palun sisestage oma parool - Paroolid pole samad - Palun sisesta parool uuesti - Kustuta oma parool - Parool kustutatud - Parool on salvestatud - Vale parool - 389 KB - placeholder.txt - 12:23:45 - See on kohahoidja - 2012/05/18 12:23 PM - kustutatud - hoitakse algses kaustas - liigutatakse rakenduse kausta - Lisa konto - Sünkroniseeri kalender & kontaktid - F-Droid ega Google Play ei ole installeeritud - Üksikasjad - Üldine - Rohkem - Igapäevane varundus sinu kontaktidest - Tagasiside - GNU General Public License, versioon 2 - Abiinfo - Impressum - Litsents - Parooli - Halda kontosid - Soovita sõbrale - Näita peidetud faile - Vaata lähtekoodi - Salvestuskoht - Pildi eelvaade - Vabandame - Privaatsus - Süsteemiteavitused pole hetkel saadaval - %1$s või %2$s - Kustutamine ebaõnnestus - Kustutatud - Sisesta uus nimi - Lokaalse koopia ümbernimetamine ebaõnnestus, proovi teist nime - Ümbernimetamine pole võimalik, nimi on juba kasutuses - Vale parool - Otsi kasutajaid ja gruppe - Vali kõik - Saada - Kasuta pilti kui - Ühenda - Jaga - Lisa kasutaja või grupp - Jagamine - %1$s (e-kiri) - Jaga %1$s - Hangi link - %1$s (grupp) - Sa pead parooli sisestama - Faili või kausta jagamisel esines viga - Jagamine ebaõnnestus. Palun kontrolli, kas fail on olemas - faili jagamiseks - Sisesta parool - Määra aegumise kuupäev - Kasutajatega pole veel midagi jagatud - saab redigeerida - saab muuta - saab luua - saab kustutada - saab jagada - Lõpeta jagamine - Määra aegumise kuupäev - Peida failide nimekiri - Jaga linki - Saada link - Jaga kasutajate ja gruppidega - Sorteeri - Peida - Üksikasjad - Riik: - Üldnimetus: - Asukoht: - Organisatsioon: - Organisatsiooni üksus: - Maakond: - Sõrmejälg: - Väljastaja: - Allkiri: - Algoritm: - Väljastatud: - Kehtivus: - Saatja: - Saaja: - - Vea kohta puudub info - Ei suuda kuvada sertifikaati. - Kas sa soovid siiski seda sertifikaati usaldada? - - Serveri sertifikaat on aegunud - - Serveri sertifikaat pole usaldusväärne - - Serveri sertifikaat kehtivad kuupäevad on alles tulevikus - - URL ei kattu sertifikaadis oleva hostinimega - \"%1$s\" on sinuga jagatud - %1$s jagas sinuga \"%2$s\" - Leite konflikte - %1$s sünkroniseerimine ebaõnnestus - Sünkroniseeritavad failid ebaõnnestusid - Sünkroniseerimine ebaõnnestus - Sünkroniseerimine ebaõnnestus, logi uuesti sisse - Faili sisu on juba sünkroniseeritud - Osad kohalikud faili ununesid - Failid - Seadete nupp - Tüüp - Testi ühendust serveriga - Kustutatud faile pole - Siit saad kustutatud faile taastada - Eemalda krüpteering - Faili või kausta jagamise tühistamisel esines viga - Liigutamise lõpetamine ebaõnnestus. Palun kontrolli, kas fail on olemas - faili jagamise lõpetamiseks - jagamise uuendamiseks - Lae üles sisu teistest rakendustest - Faili nimi - Failide üleslaadimine - Lae üles mõned failid või lülita sisse automaatne üleslaadimine. - Vali - Lae üles - Saadud andmed ei sisaldanud kehtivat faili - %1$s ei ole lubatud saabunud faili lugeda. - Faili kopeerimine ajutisse kausta ebaõnnestus. Palun proovi see uuesti saata. - Üleslaadimiseks valitud faili ei leitud. Palun kontrolli, kas valisid õige faili. - Seda faili ei saa üles laadida - Puuduvad üleslaetavad failid - Kausta nimi - Vali üleslaadimise kaust - %1$s ei õnnestunud üles laadida - Üleslaadimine ebaõnnestus, logi uuesti sisse - Üleslaadimine ebaõnnestus - Üleslaadimise valikud: - Liiguta fail kausta %1$s - Jäta fail originaalkausta - Kustuta fail originaalkaustast - selle kausta üleslaadimiseks - %1$d%% üleslaadimine %2$s - Üleslaadimine… - %1$s üles laetud - Lõpeta - Seadista - Sinu seadmes ei ole ühtegi %1$s kontot. Palun seadista oma konto. - Kontot ei leitud - Praegune - Üles laetud - Üleslaadimise ootamine - Üleslaadimised - Tühistatud - Konflikt - Ühenduse viga - Autentimisandmete tõrge - Faili tõrge - Kausta tõrge - Kohalikku faili ei leitud - Õiguse viga - Rakendus jõuda suletud - Lõpetatud - Tundmatu viga - Voolusäästurežiimist väljumise ootamine - Laadimise ootel - Wi-Fi ootamine - Aadress - E-post - Telefoninumber - Twitter - Veebileht - Kasutajanimi - Lae alla - Oota üks hetk… - Faili kopeerimine privaatsest salvestusalast - Jäta vahele - diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index 5f8efd12b6b0..aba3f5fed3a9 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -221,7 +221,6 @@ Kudeatu kontuak Tarteko kontua Alboko barra ireki - Parte hartu %1$s %2$s-(e)tik erabilita %1$s erabilita Eguneratu automatikoki @@ -262,7 +261,7 @@ Lehengoratu saioa hasteko metodo zaharrera Gogokoenetara gehitu Gogokoa - Ez dago eskuragarri posta elektronikoak bidaltzeko aplikaziorik! + fitxategia Ezabatu Huts egin du zehaztapenak kargatzen Fitxategia @@ -331,6 +330,7 @@ Lankidetza & fitxategi partekatze segurua Web posta, egutegi & kontaktu erabilerraza Pantaila partekatzea, online bilerak &web konferentziak + karpeta Karpeta dagoeneko existitzen da Sortu Karpetaren ikonoa @@ -382,18 +382,13 @@ Media fitxategia ezin da jariotu Ezin da media fitxategia irakurri Media fitxategiak kodeketa ezegokia du - Fitxategia ez dago kontu baliagarri batean - Ez da euskarri fitxategia aurkitu - %1$s erreproduzitzen saiatzean ustekabeko errore bat gertatu da Fitxategia erreproduzitzeko denbora agortu da Multimedia erreproduzitzaile integratuak ezin du multimedia fitxategia erreproduzitu Onartzen ez de euskarri kodeka - %1$s erreprodukzioa amaitua Azkar aurreratu botoia %1$s musika erreproduzigailua Erreproduzitu edo pausatu botoia Atzeratu botoia - %1$s (kargatzen) %1$s (erreproduzitzen) Berriena lehenengo Zaharrena lehenengo @@ -417,6 +412,7 @@ Jakinarazpen berria Bertsio berria sortu da Ez dago eskuragarri estekak kudeatzeko aplikaziorik + Bakarrik kontu bat onartzen da Ez dago eskuragarri PDFak kudeatzeko aplikaziorik Bidali Ezin da oharrik bidali @@ -443,25 +439,6 @@ Eragiketa bertan behera utzi da Zerbitzaria bizitzaren amaierara iritsi da, berritu! Gehiago menua - Dev bertsioa probatu - Ezaugarri berri guztiak datoz eta punta-puntakoa da. Bug/erroreak gerta daitezke, eta gertatzen direnean, mesedez bidaliguzu topatutakoa - foroa - Besteak lagundu - Errebisatu, hobetu eta kodea idatzi, %1$s begira itzazu xehetasunak - Aktiboki lagundu - IRC txatean sar zaitez - app-a - Itzuli - Deskargatu garapenaren bertsioa zuzenean - Garapen hautagaia F-Droidetik jaso ezazu - Askatutako bertsioa F-Droidetik jaso - Askatutako bertsioa Google Play storetik jaso - Release hautagaia - Askatutako bertsioa (Release Candidate) datorren bertsioaren argazkia da eta egonkorra izatea espero da. Zure banakako konfigurazioa probatzeak hori bermatzen lagun dezake. Eman izena Play Storen edo eskuz begiratu F-Droid \'bertsioa\' atalean. - Errore bat topatu duzu? - Testeatzen lagundu - Arazo bezala GitHub-era igo - Lagundu nahi gaituzu hurrengo bertsioa probatzen? Sartu zure pasahitza Pasahitza eskatuko da aplikazioa abiatzen den aldiro Zure pasahitza sar ezazu mesedez @@ -500,7 +477,6 @@ Mnemonic erakusteko, gaitu gailuaren kredentzialak. Erakutsi multimedia azterketen jakinarazpenak Eman aurkitutako multimedia karpeten berri - Oharrak GNU Lizentzia Publiko Orokorra, 2. bertsioa Laguntza Inprimategi-zigilua @@ -523,6 +499,9 @@ Biltegiratze helbidea Karpeta lokala Urruneko karpeta + Gaia + Iluna + Argia Irudi aurreikuspena Ez dago aurreikusteko fitxategi lokalik Ezin izan da irudia erakutsi @@ -553,17 +532,13 @@ Pasahitz okerra Saioa hasi QR kode bidez Zure datuak babesten - norberak ostatatutako produktibitate plataforma Arakatu eta partekatu ekintza guztiak zure atzamarretan - Jarduera, partekatzeak, lineaz kanpoko fitxategiak guztia azkar eskuragarri Zure kontu guztiak leku bakar batean Igoera automatikoa zure argazki eta bideoentzat - Egutegia eta kontaktuak sinkronizatu - DAVx5ekin (lehen DAVdroid) Bilatu erabiltzaile eta taldeak Hautatu dena Aukeratu txantiloia diff --git a/src/main/res/values-fi-rFI/strings.xml b/src/main/res/values-fi-rFI/strings.xml index d4e1f367c2ce..af655a3e6be3 100644 --- a/src/main/res/values-fi-rFI/strings.xml +++ b/src/main/res/values-fi-rFI/strings.xml @@ -223,7 +223,6 @@ Tilien hallinta Keskimmäinen tili Avaa valikko - Ilmoita ongelmasta! %1$s / %2$s käytetty %1$s käytetty Automaattinen lähetys @@ -263,7 +262,6 @@ Palaa vanhaan kirjautumistapaan Lisää suosikkeihin Suosikki - Sovellusta ei löydy sähköpostin lähettämiseen! Poista Lisätietoja ei voitu lataa. Tiedosto @@ -383,18 +381,13 @@ Mediatiedostoa ei voi suoratoistaa Mediatiedostoa ei voitu lukea Mediatiedostoa ei ole koodattu kelvollisesti - Tiedosto ei ole kelvollinen tili - Mediatiedostoa ei löytynyt - Odottamaton virhe yrittäessä toistaa kohdetta %1$s Tiedoston toisto aikakatkaistiin. Sisäänrakennettu mediasoitin ei voi toistaa mediatiedostoa Mediakoodekki ei ole tuettu - %1$s toistaminen on valmis Eteenpäin kelaus -painike %1$s-musiikkisoitin Toisto tai keskeytys -painike Taaksepäin kelaus -painike - %1$s (ladataan) %1$s (toistetaan) Uusin ensin Vanhin ensin @@ -445,25 +438,6 @@ Toiminto on peruttu Palvelimen elinkaari on päättynyt, päivitä! Lisää -valikko - Testaa kehitysversiota - Tämä käsittää kaikki tulevat ominaisuudet ja sisältää täysin uutta, jopa testaamatonta, koodia. Bugeja ja virheitä voi löytyä. Jos ja kun niin käy, muista raportoida niistä, jotta ne korjattaisiin. - keskustelupalsta - Auta muita. Avaa - Tutki, muokkaa ja luo ohjelmia. Katso lisätietoja %1$s - Osallistu kehittämiseen - Liity keskusteluun IRC:ssä: - sovellus - Käännä - Lataa kehitysjulkaisu suoraan - Lataa kehitysjulkaisu F-Droidin äpillä. - Lataa julkaisuehdokas F-Droidin äpillä. - Lataa julkaisuehdokas Googlen Play storesta. - Julkaisuehdokas - Julkaisuehdokas (RC) on lähes valmis versio virallisesti julkaistavasta versiosta ja on yleensä täysin toimiva. Testaa omat asetukset ja varmista niiden toimivuus. Kirjaudu siis testaajaksi Play storessa tai katso \"Version\" valikosta F-Droid äpissä. - Löysitkö bugin tai jotain muuta outoa? - Auta testaamalla - Ilmoita ongelmasta GitHubissa - Oletko kiinnostunut auttamaan testaamalla seuraavaa versiota? Anna suojakoodisi Suojakoodi kysytään joka kerta, kun sovellus käynnistetään Anna suojakoodi @@ -502,7 +476,6 @@ Jos haluat näyttää mnemonisen, ota käyttöön laitteen valtuudet. Näytä mediakartoituksen ilmoitukset Ilmoita uusista löydetyistä mediakansioista - Palaute GNU yleinen lisenssi, versio 2 Ohje @@ -557,17 +530,13 @@ GNU yleinen lisenssi, versio 2 Väärä salasana Kirjaudu QR-koodilla Tietojasi suojaten - Itse ylläpidettävä tuottavuusalusta Selaa ja jaa kaikki toiminnot lähettyvillä - Toiminnot, jaot ja yhteydettömän tilan tiedostot kaikki käytettävissä nopeasti Kaikki tilisi yhdessä paikassa Automaattinen lähetys kuvillesi ja videoillesi - Synkronoi kalenteri ja yhteystiedot - DAVx5:llä (aiemmin DAVdroid) Etsi käyttäjiä ja ryhmiä Valitse kaikki Valitse mallipohja diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 7a502f142782..9ebfabc4af33 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -225,7 +225,6 @@ Attention la suppression est irréversible. Gérer les comptes Compte du milieu Ouvrir la barre latérale - Participer %1$s utilisés sur %2$s %1$s utilisé Envoi automatique @@ -266,7 +265,6 @@ Attention la suppression est irréversible. Revenir à l\'ancienne méthode de connexion Ajouter aux favoris Favoris - Aucune application disponible pour envoyer des mails ! fichier Supprimer Erreur lors de la récupération de l’activité du fichier @@ -389,18 +387,13 @@ Attention la suppression est irréversible. Le fichier média ne peut pas être diffusé Impossible de lire le fichier média Le 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 morceau Le 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ée Bouton d\'avance rapide Lecteur de musique %1$s Bouton de lecture ou de pause Bouton de retour arrière - %1$s (chargement) %1$s (lecture) Date : Récent en premier Date : Ancien en premier @@ -451,25 +444,6 @@ Attention la suppression est irréversible. L\'opération a été annulée Le 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\'application Veuillez 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édia Notifier les nouveaux dossiers multimédias trouvés - Nous contacter par mail Licence Publique Générale GNU, version 2 Aide Mentions @@ -565,17 +538,16 @@ Attention la suppression est irréversible. Mot de passe incorrect Connexion par code QR Protection de vos données - plateforme de productivité auto-hébergée Parcourir et partager toutes les actions à portée de main - Activité, partages, fichiers hors-ligne + Activité, partages, … tout accessible rapidement Tous vos comptes en un seul endroit Téléversement automatique pour vos photos & vidéos - Synchronisez votre calendrier & vos contacts - avec DAVx5 (anciennement DAVdroid) + Agenda & Contacts + Synchroniser avec DAVx5 Rechercher parmi les utilisateurs et groupes Tout sélectionner Sé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 contas Conta media Abrir a barra lateral - Participar Usado %1$s de %2$s Usado %1$s Envío automático @@ -264,7 +263,6 @@ Volver ao método de acceso antigo Engadir a favoritos Favorito - Non hai ningunha aplicación para enviar correos! ficheiro Eliminar Produciuse un erro ao recuperar actividades para o ficheiro @@ -387,18 +385,13 @@ O ficheiro multimedia non pode ser transformado nun fluxo Non foi posíbel ler o ficheiro multimedia O 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$s O intento de reproducir o ficheiro esgotou o tempo de espera. O reprodutor incorporado non pode reproducir o ficheiro multimedia Códec multimedia non admitido - Rematou a reprodución de %1$s Botón de avance rápido Reprodutor de música %1$s Botón de reprodución ou pausa Botón de retroceso - %1$s (cargando) %1$s (reproducindo) Os máis recentes primeiro Os máis antigos primeiro @@ -449,25 +442,6 @@ A operación foi cancelada O 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 seguridade Solicitaráselle o código de seguridade cada vez que inicie a aplicación Introduza o seu código de seguridade @@ -507,7 +481,6 @@ Amosar as notificacións de escaneo de medios Notificar cando se atopen novos cartafoles de medios - Comentarios GNU Licenza Pública Xeral, versión 2 Axuda Editor @@ -564,17 +537,17 @@ Contrasinal incorrecto Acceder cun código QR Protexendo os seus datos - plataforma de produtividade de aloxamento propio + produtividade de aloxamento propio Navegar e compartir todas as accións ao seu alcance - Actividade, comparticións, ficheiros sen conexión + Actividade, comparticións, … todo accesíbel rapidamente Todas as súas contas nun só lugar Envío automático para 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 DAVx5 Buscar usuarios e grupos Seleccionar todo Seleccionar 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čunima Srednji račun Otvori bočnu traku - Sudjeluj Iskorišteno %1$s od %2$s Upotrijebljeno %1$s Automatsko otpremanje @@ -264,7 +263,6 @@ Vrati na stari način prijave Dodaj u favorite Favorit - Nema dostupnih aplikacija za slanje poruka e-pošte! Izbriši Učitavanje pojedinosti nije uspjelo Datoteka @@ -384,18 +382,13 @@ Medijsku datoteku nije moguće reproducirati strujanjem Nije moguće pročitati medijsku datoteku Medijska datoteka je neispravno kodirana - Datoteka nije u valjanom računu - Nema medijskih datoteka - Neočekivana pogreška pri pokušaju reproduciranja %1$s Pokušaj reprodukcije datoteke je istekao Ugrađeni alat za reprodukciju ne može reproducirati medijsku datoteku Medijski kodek nije podržan - Završena reprodukcija %1$s Tipka za ubrzavanje reprodukcije Alat za reprodukciju glazbe %1$s Tipka za reprodukciju ili pauzu Tipka za premotavanje - %1$s (učitavanje) %1$s (reprodukcija) Najnoviji prvi Najstariji prvi @@ -445,25 +438,6 @@ Radnja je otkazana Posluž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 zaporku Zaporka će se tražiti prilikom svakog pokretanja aplikacije Unesite zaporku @@ -502,7 +476,6 @@ Omogućite vjerodajnice uređaja kako biste prikazali mnemoničku oznaku. Prikaži obavijesti o skeniranju medija Obavijesti o novootkrivenim medijskim mapama - Povratne informacije Opća javna licenca za GNU, verzija 2. Pomoć Bilješke o izdanju @@ -555,17 +528,13 @@ Pogrešna zaporka Prijavite se putem QR koda Zaštita podataka - samopostavljena platforma za pospješivanje produktivnosti Pretražujte i dijelite jednostavan pristup svim mogućnostima i radnjama - Aktivnosti, dijeljenje, izvanmrežne datoteke brz pristup svemu Svi vaši računi na jednom mjestu Automatsko otpremanje fotografija i videozapisa - Sinkroniziraj kalendar i kontakte - s aplikacijom DAVx5 (ranije poznatom kao DAVdroid) Pretraži korisnike i grupe Odaberi sve Odaberi 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ése Középső fiók Oldalsáv megnyitása - Részvétel %1$s felhasználva ennyiből: %2$s %1$s használt Automatikus feltöltés @@ -264,8 +263,9 @@ Visszatérés a régi bejelentkezési módhoz Hozzáadás a kedvencekhez Kedvenc - Nincs elérhető alkalmazás e-mail küldéséhez! + fájlhoz Törlés + Hiba a fájl tevékenységeinek lekérésekor A részletek betöltése sikertelen Fájl Megtartás @@ -333,6 +333,7 @@ Biztonságos csoportmunka és fájlmegosztás Könnyen használható webes levelezés, naptárkezelés és névjegyek Képernyőmegosztás, online megbeszélések és webes konferenciák + mappához A mappa már létezik Létrehozás Mappaikon @@ -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$s Időtúllépés a lejátszás kísérletekor A beépített médialejátszó nem tudja lejátszani a fájlt Nem támogatott média kodek - %1$s lejátszása kész Előre gomb %1$s zenelejátszó Lejátszás vagy szüneteltetés gomb Vissza gomb - %1$s (betöltés) %1$s (lejátszás) Újabbak elöl Régebbiek elöl @@ -419,6 +415,7 @@ Új értesítés Új verzió létrehozva Nincs elérhető alkalmazás hivatkozások kezeléséhez + Csak egy fiók engedélyezett Nincs elérhető alkalmazás PDF kezeléséhez Küldés A jegyzet nem küldhető el @@ -445,25 +442,6 @@ Művelet megszakítva A 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ódodat A számkódra minden alkalommal szükség lesz az alkalmazás indításakor Addja 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és GNU General Public License, 2-es verzió Súgó Impresszum @@ -525,6 +502,9 @@ Tároló útvonal Helyi mappa Távoli mappa + Téma + Sötét + Világos Kép előnézete Nincs helyi fájl az előnézethez A 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 sikertelen Az értesítés eltávolítása sikertelen. Eltávolítás @@ -555,17 +536,13 @@ Hibás jelszó Belépés QR-kóddal Az adatai védelme - saját üzemeltetésű irodai platform Tallózás és megosztás minden művelet karnyújtásnyira - Tevékenységek, megosztások, offline fájlok minden gyorsan elérhető Az összes fiókja egy helyen Automatikus feltöltés a képeinek és videóinak - Naptár és névjegyek szinkronizálása - DAVx5 (régi nevén DAVdroid) segítségével Felhasználók és csoportok keresése Összes kiválasztása Válasszon sablont @@ -583,6 +560,8 @@ %1$s megosztása Hivatkozá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ót Hiba 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 akun Akun tengah Buka jendela samping - Berpartisipasi %1$s dari %2$s sudah digunakan %1$s digunakan Unggah otomatis. @@ -279,17 +278,13 @@ Berkas media tidak bisa di streaming Tidak dapat membaca berkas media Berkas media tidak di encoding dengan benar - Berkas tidak didalam akun yang sah - Tidak ditemukan berkas media Waktu habis saat mencoba untuk main Berkas media tidak dapat diputar dengan pemutar media yang ada Kodek media tidak didukung - %1$s pemutaran selesai Tombol maju Pemutar musik %1$s Tombol main dan jeda Tombol mundur - %1$s (sedang dimuat) %1$s (dimainkan) Terbaru Paling lama @@ -318,20 +313,6 @@ Cek ulang nanti. Tidak ada koneksi ke internet Server 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 GitHub Masukkan kode sandi Anda Kode sandi akan diminta setiap kali apl dijalankan. Masukkan kode kunci Anda @@ -361,7 +342,6 @@ Umum Lainnya Cadangkan kontak harian - Umpan balik Bantuan Jejak Berkas 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ðganga Mið-notandaaðgangur Opna 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ðina Bæta í eftirlæti Eftirlæti - Ekkert forrit tiltækt til að senda póst! Eyða Mistókst að hlaða inn ítarupplýsingum Skrá @@ -369,18 +367,13 @@ Ekki tókst að streyma margmiðlunarskrá Gat ekki lesið margmiðlunarskrána Margmið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$s Tilraun til að spila skrá rann út á tíma Innbyggð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ónlistarspilari Afspilun-eða-hlé hnappur Spóla-til-baka hnappur - %1$s (hleðst) %1$s (í spilun) Nýjast fyrst Elsta 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óða Lykilkóðans verður krafist í hvert skipti sem forritið er ræst Settu 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ðlunarefni Láta vita um nýfundnar möppur með margmiðlunarefni - Umsögn GNU General Public notkunarleyfið, útgáfa 2 Hjálp Prenta 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 account Account centrale Apri barra laterale - Partecipa %1$s di %2$s utilizzati %1$s utilizzato Caricamento automatico @@ -264,7 +263,6 @@ Torna al vecchio metodo di accesso Aggiungi ai preferiti Preferito - Nessun applicazione disponibile per inviare email! file Elimina Errore durante il recupero delle attività per i file @@ -387,18 +385,13 @@ Il file multimediale non può essere trasmesso Impossibile leggere il file multimediale Il 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$s Il tentativo di riprodurre il file è scaduto Il lettore multimediale integrato non è in grado di riprodurre il file Codificatore multimediale non supportato - Riproduzione di %1$s terminata Pulsante Avanti veloce Lettore musicale %1$s Pulsante Riproduci o pausa Pulsante Riavvolgi - %1$s (in caricamento) %1$s (in riproduzione) Prima i più recenti Prima i più datati @@ -449,25 +442,6 @@ L\'operazione è stata annullata Il 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 segreto Il codice segreto sarà richiesto ogni volta che l\'applicazione è avviata Digita il tuo codice segreto @@ -506,7 +480,6 @@ Per mostrare il codice mnemonico, abilita le credenziali del dispositivo. Mostra notifiche di scansione dei supporti Notifica per le nuove cartelle multimediali trovate - Segnalazioni GNU General Public License, versione 2 Aiuto Imprint @@ -563,17 +536,17 @@ Password errata Accedi tramite codice QR Proteggere i tuoi dati - piattaforma di produttività auto-gestita + produttività auto-gestita Sfoglia e condividi tutte le azioni a portata di mano - attività, condivisioni, file non in linea + Attività, condivisioni, ... tutto rapidamente accessibile Tutti i tuoi account in un posto Caricamento automatico per e tue foto e i video - Sincronizza calendario e contatti - con DAVx5 (originariamente DAVdroid) + Calendario e contatti + Sincronizza con DAVx5 Cerca utenti e gruppi Seleziona tutto Seleziona 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 naudotojo Nežinoma serverio versija - Nepavyko autentifikuotis šiame serveryje + Nepavyko nustatyti tapatybės šiame serveryje Patikrinti serverį Ryšys užmegztas Prašome įvesti dabartinį slaptažodį @@ -162,11 +162,11 @@ Atsisiunčiama… %1$s atsisiųsta Atsisiųsti - Dar neatsiųsta + Kol kas neatsisiųsta Užverti šoninę juostą Veiklos Visi failai - Mėgstamiausi + Mėgstami Namai Pranešimai Įrenginyje @@ -180,7 +180,6 @@ Atsijungti Tvarkyti paskyras Atverti šoninę juostą - Dalyvauti panaudota %1$s iš %2$s panaudota %1$s Automatinis įkėlimas @@ -209,13 +208,13 @@ Nuostatos Nepavyko perduoti failo į atsiuntimų tvarkytuvę Atgal - Pridėti prie svarbiausių - Svarbiausias + Pridėti į mėgstamus + Mėgstami Ištrinti Failas Į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ų nerasta Nė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 aplankai Niekuo nesidalinate Įkelkite nuotraukas arba įjunkite automatinį įkėlimą. Nuotraukų nėra. @@ -267,6 +266,10 @@ Failo pavadinime yra bent vienas neteisingas simbolis Failo pavadinimas Tai 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 konferencijos Aplankas jau yra Sukurti Aplanko 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$s Baigėsi laukimo laikas bandant paleisti Įtaisytąjai medijos leistuvei nepavyko atkurti medijos failo Nepalaikomas kodekas - %1$s atkūrimas užbaigtas Prasukimo mygtukas %1$s muzikos grotuvas Grojimo arba pauzės mygtukas Atsukimo mygtukas - %1$s (įkeliama) %1$s (atkuriama) Naujausi pirma Seniausi pirma @@ -354,20 +352,6 @@ Nėra interneto ryšio Operacijos atsisakyta Daugiau 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 @@ Informacija Bendras Daugiau - Atsiliepimai GNU Bendroji Viešoji Licencija, versija 2 Pagalba Pradinis 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 yra dalintis 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īties Pārvaldīt kontus Atvērt malu - Piedalīties %1$s no %2$s lietoti %1$s izmantoti Automātiska augšupielāde @@ -268,18 +267,13 @@ Nevar straumēt multivides datni Nevarēja nolasīt medija datni Multivides datnei nav pareizs kodējums - Šī datne nav pieejamā kontā - Nav atrasta neviens multivides datne - Neparedzēta kļūda atskaņojot %1$s Mēģinājums atskaņot datni noilga Iegultais atskaņotājs nevarēja atvērt datni. Neatbalstīts mediju kodekss - %1$s atskaņošana pabeigta Patīšanas poga %1$s mūzikas atskaņotājs Atskaņot vai pauzēt Attīšanas poga - %1$s (ielādēt) %1$s (atskaņot) Jaunākie pirms Vecākie pirms @@ -299,20 +293,6 @@ Ielādē paziņojumus… Nav paziņojumu Nav 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 kodu Kods tiks pieprasīts katru reizi kad atvērsiet lietotni Lūdzu, ievadiet piekļuves kodu @@ -343,7 +323,6 @@ Ikdieniška kontaktu dublēšana Rādīt mediju skenēšanas paziņojumus Paziņt par jaunas mediju mapes atrašanu - Atsauksmes Palīdzība Importēt Orģinālā datne būs… @@ -379,7 +358,6 @@ Atjaunot dzēstu datni Nepieciešama parole Nepareiza parole - Darbības, kopīgošana, bezsaistes faili Meklēt lietotājus un grupas Atzīmēt visu Sū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 kontoer Mellomkonto Åpne sidepanel - Delta %1$s av %2$s brukt %1$s brukt Auto-opplasting @@ -261,7 +260,6 @@ Gå tilbake til gammel innloggingsmetode Legg til i favoritter Favoritt - Ingen app tilgjengelig for å sende e-post! Slett Klarte ikke å laste inn detaljer Fil @@ -379,18 +377,13 @@ Mediafilen kan ikke strømmes Mediafilen kunne ikke leses Mediafilen er ikke riktig kodet - Filen er ikke i en gyldig konto - Ingen mediafil funnet - Uventet feil ved spilling av %1$s Tidsavbrudd under avspillingsforsøk Mediafilen kan ikke spilles med innebygd mediaspiller Ustøttet mediakodek - %1$s avspilling fullført Spol fremover %1$s musikkspiller Spill eller pause Spol tilbake - %1$s (laster) %1$s (spiller) Nyeste først Eldste først @@ -440,25 +433,6 @@ Operasjonen har blitt avbrutt Server 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 ditt Passordet vil bli krevd hver gang appen startes Sett inn passordet ditt @@ -497,7 +471,6 @@ For å vise mnemonic, ver vennlig å aktiver enhet legitimasjon. Vis mediaskannvarsler Varsle om nye mediamapper - Tilbakemelding GNU General Public Lisens, versjon 2 Hjelp Avtrykk @@ -552,7 +525,6 @@ på et plass Automatisk opplastning for dine bilder & videoer - Synkronisere & kontakter Søk etter brukere og grupper Velg alle Velg 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 account Openen zijbalk - Meedoen %1$s van %2$s gebruikt %1$s gebruikt Automatisch uploaden @@ -264,7 +263,7 @@ Terug naar vorige login-methode Toevoegen aan favorieten Favoriet - Geen app beschikbaar voor versturen mail! + bestand Verwijderen Fout bij ophalen activiteiten van bestand Kon details niet laden @@ -334,6 +333,7 @@ Beveiligde samenwerking & bestand uitwisseling Eenvoudige webmail, agenda & contacten Schermdelen, online afspraken & web conferenties + map Map bestaat al Aanmaken Map pictogram @@ -385,18 +385,13 @@ Mediabestand kan niet worden gestreamd Kon het mediabestand niet lezen Mediabestand niet goed gecodeerd - Het bestand hoort niet bij een geldig account - Geen mediabestand gevonden - Onverwachte fout tijdens het afspelen %1$s Time-out tijdens het afspelen Mediabestand kan niet worden afgespeeld met de standaard mediaplayer Niet-ondersteunde mediacodec - %1$s afspelen beëindigd Doorspoelknop %1$s muziekspeler Speel- of pauzeknop Terugspoelknop - %1$s (laden) %1$s (speelt) Nieuwste eerst Oudste eerst @@ -447,25 +442,6 @@ De bewerking werd geannuleerd Deze 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 invoeren De toegangscode wordt elke keer gevraagd bij opstarten van de app Voer je toegangscode in @@ -504,7 +480,6 @@ Om de mnemonic te tonen moet je apparaatinloggegevens inschakelen. Tonen mediascan meldingen Melden van nieuw gevonden mediamappen - Feedback GNU General Public Licence, versie 2 Help Afdruk @@ -561,17 +536,13 @@ Onjuist wachtwoord Inloggen via QR code Beschermt jouw gegevens - eigen-hosting productiviteitsplatform Blader en deel alle taken onder je vindertoppen - Activiteiten, delen, off-line bestanden alles snel toegankelijk Al je accounts op één plek Automatisch uploaden voor je foto\'s & video\'s - Synchroniseren agenda & contactpersonen - met DAVx5 (voorheen DAVdroid) Zoeken naar gebruikers en groepen Alles selecteren Kies sjabloon @@ -589,6 +560,8 @@ Deel %1$s Link 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 opgeven Er 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 kontami Konto poprzednie Otwórz pasek boczny - Wspieraj Wykorzystane: %1$s z %2$s Wykorzystane: %1$s Automatyczne wysyłanie @@ -264,7 +263,7 @@ Przywróć starą metodę logowania Dodaj do ulubionych Ulubiony - Brak aplikacji do wysyłania wiadomości! + plik Usuń Błąd podczas pobierania aktywności dla pliku Nie 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 kontakty Udostępnianie ekranu, spotkania i konferencje online + katalog Katalog już istnieje Utwórz Ikona katalogu @@ -385,18 +385,13 @@ Plik multimedialny nie może być przesyłany strumieniowo Nie można odczytać pliku Nieprawidłowe kodowanie pliku multimedialnego - Plik na nieprawidłowym koncie - Nie znaleziono plików multimedialnych - Nieoczekiwany błąd podczas próby odtworzenia %1$s Upłynął limit czasu podczas próby odtwarzania pliku Wbudowany odtwarzacz multimedialny nie może odtworzyć pliku Nieobsługiwany kodek multimediów - %1$s odtwarzanie zakończone Przycisk przewijania do przodu %1$s odtwarzacz muzyki Przycisk odtwarzania/pauzowania Przycisk przewijania do tyłu - %1$s (ładowanie) %1$s (odtwarzanie) Od najnowszych Od najstarszych @@ -447,25 +442,6 @@ Operacja została anulowana Serwer 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 PIN Kod PIN będzie wymagany przy każdym uruchomieniu aplikacji Podaj kod PIN @@ -504,7 +480,6 @@ Aby pokazać mnemonik włącz poświadczenia urządzenia. Pokaż powiadomienia o skanowaniu multimediów Powiadamiaj o nowo znalezionych katalogach multimedialnych - Opinie GNU General Public License, version 2 Pomoc Stopka @@ -561,17 +536,17 @@ Złe hasło Zaloguj się za pomocą kodu QR Ochrona Twoich danych - samo-hostująca platforma produkcyjna + produkcyjny samo-hostujący Przeglądaj i udostępniaj wszystkie działania na wyciągnięcie ręki - Aktywność, udostępnienia, pliki offline + Aktywność, udostępnienia... wszystko szybko dostępne Wszystkie Twoje konta w jednym miejscu Automatyczne przesyłanie dla Twoich zdjęć & wideo - Synchronizuj kontakty kalendarza & - z DAVx5 (dawniej DAVdroid) + Kontakty w kalendarzu: & + Synchronizuj z DAVx5 Szukaj użytkowników i grup Wybierz wszystko Wybierz szablon @@ -589,6 +564,8 @@ Udostępnij %1$s Pobierz 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ło Wystą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ęcia Ukryj pobieranie Schowaj 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 istnieje aby 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ępniania Nie 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 contas Conta média Abrir barra lateral - Participar %1$s de %2$s usados %1$s usados Auto envio @@ -264,7 +263,6 @@ Voltar ao antigo método de login Adicionar aos favoritos Favorito - Sem aplicativo para enviar e-mails! arquivo Excluir Erro ao recuperar atividades do arquivo @@ -387,18 +385,13 @@ O arquivo de mídia não pode ser enviado via streaming Não foi possível ler o arquivo de mídia O 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$s Tempo esgotado tentando reproduzir o arquivo O media player embutido não consegue tocar esta mídia O codec de mídia não é suportado - %1$s playback terminado Botão de avanço rápido %1$s player Botão reproduzir ou pausar Botão rebobinar - %1$s (carregando) %1$s (tocando) Mais novos primeiro Mais antigos primeiro @@ -449,25 +442,6 @@ Operação cancelada O 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 acesso O código de acesso será solicitado toda vez que o aplicativo for iniciado Digite sua senha @@ -506,7 +480,6 @@ Para exibir o mnemônico, ative as credenciais do dispositivo. Exibir notificações de varredura de mídia Notificar sobre pastas de mídia recém-encontradas - Feedback GNU Licença Pública Geral, version 2 Ajuda Imprimir @@ -563,17 +536,17 @@ Senha incorreta Entrar via código QR Protegendo seus dados - plataforma de produtividade auto-hospedada + produtividade auto hospedada Navegar e compartilhar todas as ações na ponta de seus dedos - Atividade, compartilhamentos e arquivos offline + Atividades, compartilhamentos, ... tudo rapidamente acessível Todas as suas contas em um lugar Envio automático para suas fotos & vídeos - Sincronizar calendário & contatos - com DAVx5 (também chamado DAVdroid) + Calendário & contatos + Sincronizar com DAVx5 Pesquisar usuários e grupos Selecionar tudo Selecionar 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 contas Conta intermédia Abrir barra lateral - Participe Usado %1$s de %2$s %1$s utilizado Carregamento automático @@ -233,7 +232,6 @@ Reverter para método de início de sessão anterior Adicionar aos favoritos Favorito - Nenhuma aplicação disponível para enviar e-mails! Apagar Falha ao carregar detalhes Ficheiro @@ -345,18 +343,13 @@ O ficheiro de multimédia não pode ser transmitido Não foi possível ler o ficheiro de multimédia O 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$s Tentativa de reproduzir o ficheiro expirou O leitor multimédia pré-configurado é incapaz de reproduzir o ficheiro de multimédia Codec de multimédia não suportado - %1$s reprodução terminou Botão de Avançar Rápido %1$s reprodutor de música Botão de Reproduzir/Pausar Botão de Retroceder - %1$s (a carregar) %1$s (a reproduzir) Recentes primeiro Antigos primeiro @@ -406,25 +399,6 @@ A operação foi cancelada O 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ódigo O código será solicitado sempre que a aplicação é iniciada Por 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édia Notificar sobre novas pastas multimédia encontradas - Opinião GNU - Licença Pública Geral, Versão 2 Ajuda Informaçã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șire Administrare conturi Deschide 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 cuvinte Parolă… + 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 Înapoi Adăugați la favorite Favorite @@ -325,18 +332,13 @@ Fișierul media nu poate fi transmis Nu s-a putut citi fișierul media Fiș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 expirat Media player-ul încorporat nu poate reda fișierul media Codec media fără suport - Redare terminată %1$s Butonul pentru repede înainte %1$s Buton de play sau pauză Butonul rewind - %1$s (încărcare) %1$s (redare) Cel mai nou mai întâi Cel mai vechi mai întâi @@ -363,16 +365,6 @@ Te rugăm să încerci mai târziu. Nu există conexiune la internet Operaț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 parola Parola va fi solicitată de fiecare dată când deschideți aplicația Vă rugăm introduceți parola @@ -407,7 +399,6 @@ Salvarea zilnică a contactelor dvs. Afișează notificări despre scanarea media Notifică despre dosare media noi găsite - Feedback Ajutor Imprint Fiș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 účtov Stredný účet Otvoriť bočný panel - Zúčastniť sa Využité: %1$s z %2$s %1$s použitých Automatické nahratie @@ -264,7 +263,6 @@ Vrátiť sa k pôvodnému spôsobu prihlásenia Pridať do obľúbených Obľúbené - Aplikácia na odoslanie e-mailu nenájdená! Zmazať Chyba pri načítavaní aktivít pre súbor Nepodarilo 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$s Vypršal čas pri pokuse o prehratie Vstavaný prehrávač médií nedokáže prehrať mediálny súbor Nepodporovaný kodek - %1$s prehrávanie dokončené Tlačidlo \"rýchlo vpred\" Hudobný prehrávač %1$s Tlačidlo prehrávania / pauzy Tlač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 heslo Heslo bude nutné zadať vždy po štarte aplikácie Vlož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äzba GNU Všeobecná Verejná Licencia, Verzia 2 Pomoc Podmienky používania @@ -558,17 +531,13 @@ Nesprávne heslo Prihlásiť sa pomocou QR kódu Chránte vaše údaje - Platforma pre produktivitu s vlastným hosťovaním Prehliadajte a zdieľajte všetky akcie na dosah ruky - Aktivity, zdieľania, offline súbory všetko rýchlo prístupné Všetky vaše účty na jednom mieste Automatické nahrávanie vašich fotiek a videí - Synchronizácia kalendára a kontaktov - pomocou DAVx5 (pôvodne DAVdroid) Vyhľadať používateľov alebo skupiny Vybrať všetko Vybrať š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 @@ Skupnost Trenutni račun Zadnji račun + Slika ozadja glave bočnega okna Dejavnosti Vse datoteke Priljubljene @@ -221,7 +222,6 @@ Uredi račune Srednji račun Odpri bočno okno - Možnosti sodelovanja %1$s od %2$s uporabljeno Skupaj %1$s Samodejno 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 šifriranje Geslo … Poteka pridobivanje ključev ... Shranjevanje ključev @@ -245,18 +248,22 @@ Napaka dodajanja opombe k datoteki Program %1$s se je sesul. Poročilo + Ali želite poslati poročilo na sledilnik (zahteva račun Github)? Pridobivanje datoteke je spodletelo Napaka pridobivanja predlog Napaka začenjanja kamere Nastavitve + Inženirski preizkusni način Dodaj ali pošlji + Podajanje datoteke na upravljalnik prenosov je spodletelo Tiskanje datoteke je spodletelo Nazaj Povrni na star način prijave Dodaj med priljubljene Priljubljeno - Program za pošiljanje elektronskih sporočil ni določen! + datoteka Izbriši + Napaka pridobivanja dejavnosti za datoteko Nalaganje podrobnosti je spodletelo. Datoteka Ohrani @@ -307,6 +314,7 @@ Uporabi V čakanju na polno uskladitev … Datoteke ni mogoče najti + Datoteke ni mogoče uskladiti. Prikazana je zadnja razpoložljiva različica. Preimenuj Priš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 + mapa Mapa že obstaja Ustvari Ikona mape @@ -358,22 +372,24 @@ Preišči dnevnike Pošlji dnevnike po elektronski pošti Dnevniki: %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 filtra Dnevniki Strežnik je v načinu vzdrževanja Poč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 prostorom Predstavne datoteke ni mogoče prikazati v pretoku Predstavne datoteke ni mogoče prebrati. Predstavna datoteka ni pravilno kodirana. - Dokument ni shranjen v veljavnem računu. - Predstavnih datotek ni mogoče najti Poskus predvajanja je časovno potekel + Vgrajen predvajalnik predstavnih vsebin ne podpira podane vrsta datoteke Nepodprt predstavni kodek Gumb za hitro predvajanje naprej + Predvajalnik glasbe %1$s Gumb za predvajanje in premor Vrni nazaj + %1$s (predvajanje) najprej najnovejše najprej najstarejše po 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 dosegljiv Nova opomba … + Zaznana je nova mapa s predstavno vsebino: %1$s . slika video Novo obvestilo @@ -397,13 +415,17 @@ Dovoljen je le en račun Ni nameščenega programa za odpiranje datotek PDF Pošlji + Sporočilca ni mogoče poslati Izvedba dejanja je spodletela Pokaži napredek prejemanja + Kanal obvestil prejemanja Preverjaj datoteke za spremembe Opazovalnik datotek Pokaži napredek usklajevanja datotek in končana opravila Usklajevanje datotek + Pokaži obvestila za zaznane nove mape predstavnih vsebin in podobno Splošna obvestila + Napredek glasbenega predvajalnika Predvajalnik predstavnih datotek Potisna obvestila Pokaži napredek pošiljanja @@ -414,24 +436,6 @@ Prosim, preveri kasneje Ni vzpostavljene internetne povezave Opravilo 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 programa Koda bo zahtevana vsakič pred zagonom programa. Vnesite kodo PIN @@ -444,6 +448,7 @@ Dovoli Zavrni Za prejemanje oziroma pošiljanje datotek v oblak so zahtevana dodatna dovoljenja. + Ni najdenega programa za nastavitev slike 389 KB vsebnik.txt 12:23:45 @@ -461,11 +466,13 @@ Splošno Več Dnevno usklajevanje stikov + Pokaži obvestila iskanja predstavnih vsebin Obvesti o na novo zaznanih mapah s predstavno vsebino - Pošlji sporočilo razvijalcem programa Splošno Javno dovoljenje GNU GPL, različice 2 Pomoč Natis + Izvorna datoteka bo ... + Izvorna datoteka bo ... Shrani v podmape kot leto in mesec Uporabi podmape Dovoljenje @@ -473,6 +480,7 @@ Poverila naprave so omogočena Ni nastavljenih poveril naprave Brez + Zaščiti program z uporabo Poverila naprave Koda PIN Upravljanje z računi @@ -480,8 +488,11 @@ Pokaži skrite datoteke Pridobi izvorno kodo Mesto shrambe - Lokalna mapa + Krajevna mapa Mapa na strežniku + Tema + Temna + Svetla Predogled slike Ni krajevne datoteke za predogled Te 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 spodletelo Odstranjevanje obvestil je spodletelo. Odstrani @@ -496,6 +508,9 @@ Vnesite novo ime Ni 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 datoteko Obnovi izbrisano datoteko Poteka pridobivanje datoteke ... @@ -503,12 +518,13 @@ Zahtevano je geslo Napačno geslo Prijava s kodo QR + Zaščita podatkov + Brskanje in souporaba + Dejavnosti, souporaba ... Vsi vaši računi na enem mestu Samodejno pošiljanje za vaše slike in posnetke - Usklajevalnik koledarja in stikov - z DAVx5 (prej poimenovan DAVdroid) Iskanje uporabnikov in skupin Izberi vse IZbor predloge @@ -524,31 +540,40 @@ Omogoči souporabo %1$s Pridobi 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 mape Souporaba ni mogoča. Preverite, ali datoteka obstaja. za omogočanje souporabe datoteke. + Vpis gesla na željo Vpis gesla Nastavi datum preteka Nastavi geslo Ni datotek, ki bi jih omogočili za souporabo z drugimi + Zaščiteno z geslom lahko ureja lahko spremeni lahko ustvari lahko izbriše lahko omogoči souporabo + Prekini souporabo %1$s (oddaljeno) + Ime, ID zveznega oblaka ali elektronski naslov ... + Sporočilo za prejemnika Dovoli urejanje Nastavi datum preteka Skrij prejem Skrij spisek datotek + Zaščiti z geslom (%1$s) Zaščiti z geslom + Varovano Omogoči souporabo prek povezave Pošlji povezavo Omogoči souporabo z %1$s Omogoči souporabo … Omogoči souporabo s skupino ali z uporabnikom + Prijava prek ponudnika Razvrsti najprej najnovejše najprej najstarejše @@ -590,6 +615,7 @@ Video Glasba Slike + Pretakaj s programom ... Notranji pretok ni mogoč \"%1$s\" vam je oddan v souporabo Uporabnik %1$s je omogočil souporabo \"%2$s\" @@ -602,6 +628,7 @@ Usklajevanje je spodletelo; zahtevana je ponovna prijava. Vsebina datoteke je že usklajena Nekatere krajevne datoteke so spregledane + Gumb stanja usklajevanja Datoteke Gumb nastavitev Nastavi mape @@ -618,8 +645,12 @@ Nalaganje poteka nepričakovano dolgo ... Izbrisane datoteke Ni 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 priljubljenih Prišlo je do napake med poskusom odstranjevanja souporabe te datoteke ali mape Ni 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 lidhjen Kopjimi/lëvizja në direktorinë e enkriptuar për momentin nuk suportohet. Kopjojeni tek… + Nuk mund të shkarkohet imazhi i plotë Url nuk mund të merrej S’u krijua dot dosja Duke krijuar skedar nga shablloni... @@ -201,6 +202,7 @@ U shkarkua Ende e pashkarkuar Mbyllni sidebar-in + Komuniteti Llogaria e tanishme Llogaria e fundit Imazhi në background i drawer header @@ -221,7 +223,6 @@ Menaxhoni llogaritë Llogari e mesit Hapni sidebar-in - Merni pjesë %1$s e %2$s përdorur %1$s të përdorura Ngarkim automatik @@ -257,12 +258,14 @@ Provë e mënyrës inxhinierike Shtoni ose ngarkoni Kalimi i skedarit për të shkarkuar menaxherin dështoi + Printimi i skedarit dështoi Prapa Rikthehu në metodën e vjetër te identifikimit Shtoje tek të parapëlqyerat E parapëlqyer - Nuk ka asnjë aplikacion në dispozicion për të dërguar mesazhe! + skedari Fshini + Gabim gjatë marrjes së aktiviteteve të skedarit Dështoi ngarkimi i detajeve Skedar Mbaje @@ -330,6 +333,7 @@ Bashkëpunim i sigurtë &skedar i ndryshuar E-mail i thjeshtë për t\'u përdorur në web, kalendari &i kontakteve Ndarja e ekranit, takime në internet & konferenca në web + dosja Dosja ekziston tashmë Krijo Ikona e dosjes @@ -381,19 +385,13 @@ Skedari media s’mund të transmetohet S\'u lexua dot skedari media Skedar 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ë luhej Ndërtuesi i integruar i medias nuk mund të luajë skedarin medial Kodek mediash i pambuluar - Luajtja e %1$s përfundoi Butoni Përpara Lojtësi %1$s i muzikës Butoni Luaje ose Pushoje Butoni Mbrapsht - %1$s (po ngarkohet) %1$s (po luhet) Më i riu në fillim Më i vjetri në fillim @@ -417,6 +415,7 @@ Njoftim i ri U krijua versioni i ri Nuk ka asnjë aplikacion në dispozicion për të trajtuar lidhjet + Vetëm një llogari e lejuar Nuk ka asnjë aplikacion në dispozicion për të trajtuar PDF Dërgo Shënimi nuk mund të dërgohej @@ -443,25 +442,6 @@ Operacioni është anuluar Serveri 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 tuaj Kodkalimi do të kërkohet sa herë që niset aplikacioni Ju 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ë medias Njoftoni për dosjet e mediave të sapo gjetura - Përshtypje GNU General Public License, versioni 2 Ndihmë Imprint @@ -523,6 +502,9 @@ Rruga e hapsirës ruajtëse Dosje lokale Dosje remote + Shabllon + E errët + E çelët Parapamje figure Nuk ka skedar lokal për të shfaqur Kjo 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ështoi Dështoi heqja e njoftimit. Hiq @@ -553,17 +536,13 @@ Fjalëkalim i gabuar Identifikohu nëpërmjet QR code Mbrojtja e të dhënave tuaja - platformë e vetë-pritur e produktivitetit Shfletoni dhe shpërndani të gjitha veprimet në majë të gishtave tuaj - Aktiviteti, aksionet, skedarët jashtë linje gjithçka shpejt e arritshme Të gjitha llogaritë tuaja në një vend Ngarkimi automatik për fotot tuaja & videot - Kalendar i sinkronizuar & kontaktet - me DAVx5 (më parë i njohur si DAVdroid) Kërkoni për grupe dhe përdorues Zgjidhi të gjitha Zgjidh shabllonin @@ -581,6 +560,8 @@ Ndajeni %1$s Merreni 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ëkalim Ndodhi 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 konton Mittenkonto Öppna sidofältet - Hjälp oss att bli bättre %1$s av %2$s använt %1$s använt Automatisk uppladdning @@ -263,7 +262,6 @@ Gå tillbaka till gammal inloggningsmetod Lägg till favoriter Favoritisera - Ingen app finns att skicka e-post! fil Radera Fel vid hämtning av aktiviteter för fil @@ -386,18 +384,13 @@ Media-filen kan inte strömmas Kunde inte läsa mediafilen Mediafilen har felaktig kodning - Filen är inte i ett giltigt konto - Hittar ingen mediefil - Oväntat fel uppstod vid uppspelning av %1$s Försök att spela upp filen tog för lång tid Den inbyggda mediaspelaren kan inte spela upp mediefilen Saknar stöd för denna mediatyp - %1$s uppspelning slutfördes Snappspolningsknapp %1$s musikspelare Spela- / Pausknapp Bakå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 mapp Det är inte möjligt att flytta en mapp till en av dess undermappar Filen finns redan i målmappen - Kunde inte flytta fil. Vänligen kolla om den finns + Kan inte flytta fil. Vänligen kontrollera om det finns Flytta till… Ett fel uppstod i väntan på servern. Kunde inte slutföra operationen Ett fel uppstod under anslutningen till servern @@ -443,30 +436,11 @@ Meddelandeikon Läser in aviseringar… Inga aviseringar - Vänligen kolla igen senare. + Vänligen kontrollera igen senare. Ingen internetanslutning Operationen har avbrutits Servern 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ösenord Koden kommer efterfrågas varje gång du startar appen Vänligen ange ditt lösenord @@ -505,7 +479,6 @@ För att visa mnemonic vänligen aktivera enhetsuppgifter. Visa aviseringar om mediescanning Meddela om nya media-mappar - Feedback GNU General Public License, version 2 Hjälp Imprint @@ -563,17 +536,16 @@ Fel lösenord Logga in via QR-kod Skydda din data - själv-hostad produktivitetsplattform Bläddra och dela alla åtgärder till hands - Aktivitet, delningar, offline-filer + Aktivitet, delningar, … allt snabbt tillgängligt Alla dina konton på ett ställe Automatisk uppladdning för dina bilder & videor - Synkronisera kalender & kontakter - med DAVx5 (tidigare DAVdroid) + Kalender & kontakter + Synka med DAVx5 Sök användare och grupper Välj alla Vä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 finns att uppdatera denna delning Uppdatera delning misslyckades Kan 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 fil Kunde 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 upp Ingen fil att ladda upp Mappnamn 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ı yok Güvenli bağlantı kullanılamıyor. - Sunucu ayarları doğru değil + Sunucu yapılandırması doğru değil Kimlik doğrulanamadı Kimlik doğrulama sunucusu erişime izin vermedi Parola @@ -189,7 +189,7 @@ Yeni bir sürüm yok. Telefonunuz için gerekli algoritma bulunamadı. Devre Dışı Bırak - Yoksay + Yok say Bildirimi yok say Son geliştirici sürümünü indirin %1$s indirmesi tamamlanamadı @@ -223,7 +223,6 @@ Hesap yönetimi Orta hesap Yan çubuğu aç - Katkıda bulunun %1$s / %2$s kullanıldı %1$s kullanılıyor Otomatik 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ılabilir 12 sözcüklü şifreleme parolanızı not edin Parola… @@ -264,7 +263,6 @@ Eski oturum açma yöntemine dön Sık Kullanılanlara Ekle Sık Kullanılanlara Ekle - E-posta göndermek için kullanılabilecek bir uygulama yok! dosya Sil Dosya işlemleri alınırken sorun çıktı @@ -310,8 +308,8 @@ Tamamlandı Değiştirilsin Aktarı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ıyor Ortam kod çözücüsü desteklenmiyor - %1$s oynatıldı İleri Alma Düğmesi %1$s müzik oynatıcı Oynatma ya da Duraklatma Düğmesi Geri Alma Düğmesi - %1$s (yükleniyor) %1$s (oynatılıyor) Yeniden - Eskiye Eskiden Yeniye @@ -449,25 +442,6 @@ İşlem iptal edildi Sunucu ö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ın Parola uygulama her başlatıldığında sorulacak Lü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ülensin Yeni bulunan ortam klasörleri bildirilsin - Geri bildirim GNU Genel Kamu Lisansı 2. Sürüm Yardı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 kaydedilsin Alt klasörler kullanılsın Lisans @@ -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 üretkenlik Gözat ve paylaş tüm işlemler parmaklarınızın ucunda - İşlemler, paylaşımlar, çevrimdışı dosyalar + İşlemler, paylaşımlar, … her şey kolayca erişilebilir Tüm hesaplarınız tek bir yerde Otomatik yükleme fotoğ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şitleme Kullanıcı ve Grup Arama Tü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ệu Quản lý dung lượng Khô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ào Codec của media không được hỗ trợ - %1$s phát lại khi kết thúc Nút đẩy nhanh tốc độ phát %1$s trình nghe nhạc Nút phát hoặc tạm dừng Nút chuyển lại - %1$s (đang chạy) %1$s (đang phát) Mới trước Cũ trước @@ -262,7 +258,6 @@ Tổng hợp hơn Sao lưu hàng ngày danh bạn của bạn - Phản hồi Giúp đỡ Đánh dấu Bậ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 @@ 32dp 32dp 24dp - 72dp - 72dp -3dp 12dp 16sp 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 @@ + true false 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 error Waiting for Wi-Fi Waiting 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-Fi Only upload when charging + Also upload existing files /InstantUpload /AutoUpload File 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 uploading Instant 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 folder Create new custom folder setup + Hide folder + Configure Configure folders @@ -710,12 +718,12 @@ Account not found! Protecting your data - self-hosted productivity platform + self-hosted productivity Browse and share all actions at your fingertips - Activity, shares, offline files + Activity, shares, … everything quickly accessible All your accounts @@ -724,8 +732,8 @@ Automatic upload for your photos & videos - Sync calendar & contacts - with DAVx5 (formerly DAVdroid) + Calendar & contacts + Sync with DAVx5 No personal info set Add name, picture and contact details on your profile page. @@ -868,9 +876,6 @@ Music Documents Downloads - IO error - Operation has been canceled - Authentication Exception Avatar from shared user Shared with you by %1$s Resharing 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