diff --git a/.gitignore b/.gitignore index a18d2645..c4f61d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,11 +31,6 @@ build/ local.properties # fastlane -secret_key_file.json -**/**/fastlane/fastlane/** -**/**/fastlane/metadata/** -**/**/fastlane/report.xml -**/**/fastlane/mappings/** -**/**/fastlane/release_notes/** -**/**/fastlane/latest_versions/** -.env.default +**/fastlane/.env +**/fastlane/metadata/**/images/** +**/fastlane/report.xml diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..448c34f4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +source "https://rubygems.org" + +gem "fastlane" +gem "net-sftp" +gem "ed25519" +gem "bcrypt_pbkdf" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..0e25b8aa --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,221 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + apktools (0.7.4) + rubyzip (~> 2.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.426.0) + aws-sdk-core (3.112.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.42.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.88.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + bcrypt_pbkdf (1.0.1) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.20) + declarative-option (0.1.0) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + ed25519 (1.2.4) + emoji_regex (3.2.1) + excon (0.79.0) + faraday (1.3.0) + faraday-net_http (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-net_http (1.0.1) + faraday_middleware (1.0.0) + faraday (~> 1.0) + fastimage (2.2.2) + fastlane (2.174.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.37.0, < 0.39.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-aws_s3 (1.8.0) + apktools (~> 0.7) + aws-sdk-s3 (~> 1) + mime-types (~> 3.3) + fastlane-plugin-get_version_name (0.2.2) + gh_inspector (1.1.3) + google-api-client (0.38.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-apis-core (0.2.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.14) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + rexml + signet (~> 0.14) + webrick + google-apis-iamcredentials_v1 (0.1.0) + google-apis-core (~> 0.1) + google-apis-storage_v1 (0.2.0) + google-apis-core (~> 0.1) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.4.0) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.30.0) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.15.1) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.14) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.4.0) + json (2.5.1) + jwt (2.2.2) + memoist (0.16.2) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.1104) + mini_magick (4.11.0) + mini_mime (1.0.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.1) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (5.2.0) + os (1.1.1) + plist (3.6.0) + public_suffix (4.0.6) + rake (13.0.3) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.4) + rouge (2.0.7) + ruby2_keywords (0.0.4) + rubyzip (2.3.0) + security (0.1.3) + signet (0.14.1) + addressable (~> 2.3) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + webrick (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.19.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + bcrypt_pbkdf + ed25519 + fastlane + fastlane-plugin-aws_s3 + fastlane-plugin-get_version_name + net-sftp + +BUNDLED WITH + 2.2.5 diff --git a/README.md b/README.md index 48aafde9..303c40e3 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,42 @@ Please read our [contribution guide](.github/CONTRIBUTING.md), if you would like Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](.github/CODE_OF_CONDUCT.md). +## Deployment + +Follow these steps to deploy a release: + +1. Check `TODO`/`FIXME` comments + - Create issue for or delete + - Regexp for "Find in Path": `\W(TODO|FIXME)(?! #[0-9]{1,4}:)` +1. Merge translations +1. Check latest dependencies +1. Create release branch +1. Test database migration +1. Smoke-Test changed or added functionality +1. Update version +1. Create and commit release notes +1. Merge in `master` +1. Create tag and execute deploy app using Fastlane +1. Close GitHub-issues or move them to next milestone +1. Close milestone +1. Update version on website ([cryptomator.org/android](https://cryptomator.org/android/)) + +### Release Notes + +Before tagging the release, create and commit the release notes. For Playstore create [fastlane/metadata/android/de-DE/changelogs/default.txt](https://github.com/cryptomator/android/blob/develop/fastlane/metadata/android/de-DE/changelogs/default.txt), [fastlane/metadata/android/en-US/changelogs/default.txt](https://github.com/cryptomator/android/blob/develop/fastlane/metadata/android/en-US/changelogs/default.txt) and for the website create [fastlane/release_notes_apkstore_en.html](https://github.com/cryptomator/android/blob/develop/fastlane/release_notes_apkstore_en.html). + +### Deploy app using Fastlane + +Deploy production version to Google Play, Website/GitHub-Releases and F-Droid using `fastlane android deploy` or `bundle exec fastlane deploy` + +There are further targets and options like `beta`, see [fastlane/README.md](https://github.com/cryptomator/android/blob/develop/fastlane/README.md) + +### Initial setup Fastlane + +1. Make sure you copied `.default.env` to `.env` in the `fastlane` folder and filled out those variables. +1. Install Ruby (depends on OS, Ubuntu): `sudo apt install ruby-dev` +1. Install fastlane (depends on OS, Ubuntu): `gem install fastlane -N` + ## License This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team. diff --git a/build.gradle b/build.gradle index 10bcf17f..4e29c087 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply from: 'buildsystem/dependencies.gradle' apply plugin: "com.vanniktech.android.junit.jacoco" buildscript { - ext.kotlin_version = '1.4.21' + ext.kotlin_version = '1.4.30' repositories { jcenter() mavenCentral() @@ -42,7 +42,7 @@ allprojects { ext { androidApplicationId = 'org.cryptomator' androidVersionCode = getVersionCode() - androidVersionName = '1.5.11' + androidVersionName = '1.5.12' } repositories { mavenCentral() diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 8a95adc0..889e44f0 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -18,27 +18,26 @@ ext { // support lib androidSupportAnnotationsVersion = '1.1.0' androidSupportAppcompatVersion = '1.2.0' - // check https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview/57968071#57968071 !!!!!! - androidSupportDesignVersion = '1.2.1' + androidSupportDesignVersion = '1.3.0' // app frameworks and utilities - rxJavaVersion = '2.2.20' + rxJavaVersion = '2.2.21' rxAndroidVersion = '2.1.1' rxBindingVersion = '2.2.0' - daggerVersion = '2.31.2' + daggerVersion = '2.32' gsonVersion = '2.8.6' - okHttpVersion = '4.9.0' + okHttpVersion = '4.9.1' okHttpDigestVersion = '2.5' velocityVersion = '1.7' timberVersion = '4.7.1' - zxcvbnVersion = '1.3.3' + zxcvbnVersion = '1.3.6' scaleImageViewVersion = '3.10.0' @@ -58,7 +57,7 @@ ext { googlePlayServicesVersion = '19.0.0' googleClientVersion = '1.31.2' - msgraphVersion = '2.5.0' + msgraphVersion = '2.7.1' msaAuthVersion = '0.10.0' commonsCodecVersion = '1.15' @@ -67,7 +66,7 @@ ext { // testing dependencies - jUnitVersion = '5.7.0' + jUnitVersion = '5.7.1' jUnit4Version = '4.13.1' assertJVersion = '1.7.1' mockitoVersion = '3.7.7' @@ -82,13 +81,13 @@ ext { uiautomatorVersion = '2.2.0' androidxCoreVersion = '1.3.2' - androidxFragmentVersion = '1.2.5' + androidxFragmentVersion = '1.3.0' androidxViewpagerVersion = '1.0.0' androidxSwiperefreshVersion = '1.1.0' androidxPreferenceVersion = '1.0.0' // 1.1.0 and 1.1.2 does have a bug with the text size androidxRecyclerViewVersion = '1.1.0' androidxDocumentfileVersion = '1.0.1' - androidxBiometricVersion = '1.0.1' + androidxBiometricVersion = '1.1.0' androidxTestCoreVersion = '1.3.0' jsonWebTokenApiVersion = '0.11.2' diff --git a/data/build.gradle b/data/build.gradle index d9b3ca1c..7740b19c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -74,7 +74,7 @@ android { } greendao { - schemaVersion 3 + schemaVersion 4 } configurations.all { diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java index 1a7427a6..c3ea26b0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java @@ -1,7 +1,6 @@ package org.cryptomator.data.cloud.local; import android.content.Context; -import android.os.Build; import org.cryptomator.data.cloud.local.file.LocalStorageContentRepository; import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkContentRepository; @@ -43,7 +42,7 @@ public class LocalStorageContentRepositoryFactory implements CloudContentReposit if (!hasPermissions(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE)) { throw new NoAuthenticationProvidedException(cloud); } - if (((LocalStorageCloud) cloud).rootUri() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (((LocalStorageCloud) cloud).rootUri() != null) { return new LocalStorageAccessFrameworkContentRepository(context, mimeTypes, (LocalStorageCloud) cloud); } else { return new LocalStorageContentRepository(context, (LocalStorageCloud) cloud); diff --git a/data/src/main/java/org/cryptomator/data/cloud/onedrive/graph/MSAAuthAndroidAdapter.java b/data/src/main/java/org/cryptomator/data/cloud/onedrive/graph/MSAAuthAndroidAdapter.java index cc901de8..a9b1a723 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/onedrive/graph/MSAAuthAndroidAdapter.java +++ b/data/src/main/java/org/cryptomator/data/cloud/onedrive/graph/MSAAuthAndroidAdapter.java @@ -152,7 +152,7 @@ public abstract class MSAAuthAndroidAdapter implements IAuthenticationAdapter { public void onAuthComplete(final LiveStatus status, final LiveConnectSession session, final Object userState) { Timber.tag("MSAAuthAndroidAdapter").d(String.format("LiveStatus: %s, LiveConnectSession good?: %s, UserState %s", status, session != null, userState)); - if (status == LiveStatus.NOT_CONNECTED) { + if (status == LiveStatus.NOT_CONNECTED && session.getRefreshToken() == null) { Timber.tag("MSAAuthAndroidAdapter").d("Received invalid login failure from silent authentication, ignoring."); return; } diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java index f188e34f..60458e36 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java @@ -21,12 +21,14 @@ class DatabaseUpgrades { public DatabaseUpgrades( // Upgrade0To1 upgrade0To1, // Upgrade1To2 upgrade1To2, // - Upgrade2To3 upgrade2To3) { + Upgrade2To3 upgrade2To3, // + Upgrade3To4 upgrade3To4) { availableUpgrades = defineUpgrades( // upgrade0To1, // upgrade1To2, // - upgrade2To3); + upgrade2To3, // + upgrade3To4); } private Map> defineUpgrades(DatabaseUpgrade... upgrades) { diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt new file mode 100644 index 00000000..8be61f62 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt @@ -0,0 +1,66 @@ +package org.cryptomator.data.db + +import org.cryptomator.data.db.Sql.SqlCreateTableBuilder.ForeignKeyBehaviour +import org.cryptomator.data.db.entities.CloudEntityDao +import org.cryptomator.data.db.entities.VaultEntityDao +import org.greenrobot.greendao.database.Database +import org.greenrobot.greendao.internal.DaoConfig +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class Upgrade3To4 @Inject constructor() : DatabaseUpgrade(3, 4) { + + override fun internalApplyTo(db: Database, origin: Int) { + db.beginTransaction() + try { + addPositionToVaultSchema(db) + initVaultPositionUsingCurrentSortOrder(db) + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } + } + + private fun addPositionToVaultSchema(db: Database) { + Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) + Sql.createTable("VAULT_ENTITY") // + .id() // + .optionalInt("FOLDER_CLOUD_ID") // + .optionalText("FOLDER_PATH") // + .optionalText("FOLDER_NAME") // + .requiredText("CLOUD_TYPE") // + .optionalText("PASSWORD") // + .optionalInt("POSITION") // + .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .executeOn(db) + + Sql.insertInto("VAULT_ENTITY") // + .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_ENTITY.TYPE") // + .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "CLOUD_TYPE") // + .from("VAULT_ENTITY_OLD") // + .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .executeOn(db) + + Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) + + Sql.createUniqueIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") // + .on("VAULT_ENTITY") // + .asc("FOLDER_PATH") // + .asc("FOLDER_CLOUD_ID") // + .executeOn(db) + + Sql.dropTable("VAULT_ENTITY_OLD").executeOn(db) + } + + private fun initVaultPositionUsingCurrentSortOrder(db: Database) { + CloudEntityDao(DaoConfig(db, VaultEntityDao::class.java)) // + .loadAll() // + .map { + Sql.update("VAULT_ENTITY") // + .where("_id", Sql.eq(it.id)) // + .set("POSITION", Sql.toInteger(it.id - 1)) // + .executeOn(db) + } + } +} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java index 75baf600..9201131d 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java @@ -28,6 +28,8 @@ public class VaultEntity extends DatabaseEntity { private String password; + private Integer position; + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. * Entity must attached to an entity context. @@ -152,6 +154,14 @@ public class VaultEntity extends DatabaseEntity { this.password = password; } + public Integer getPosition() { + return this.position; + } + + public void setPosition(Integer position) { + this.position = position; + } + /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 674742652) public void __setDaoSession(DaoSession daoSession) { @@ -159,14 +169,16 @@ public class VaultEntity extends DatabaseEntity { myDao = daoSession != null ? daoSession.getVaultEntityDao() : null; } - @Generated(hash = 1196809909) - public VaultEntity(Long id, Long folderCloudId, String folderPath, String folderName, @NotNull String cloudType, String password) { + @Generated(hash = 825602374) + public VaultEntity(Long id, Long folderCloudId, String folderPath, String folderName, @NotNull String cloudType, String password, + Integer position) { this.id = id; this.folderCloudId = folderCloudId; this.folderPath = folderPath; this.folderName = folderName; this.cloudType = cloudType; this.password = password; + this.position = position; } @Generated(hash = 691253864) diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java index 645ab322..aabbfff1 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/VaultEntityMapper.java @@ -30,6 +30,7 @@ public class VaultEntityMapper extends EntityMapper { .withCloud(cloudFrom(entity)) // .withCloudType(CloudType.valueOf(entity.getCloudType())) // .withSavedPassword(entity.getPassword()) // + .withPosition(entity.getPosition()) // .build(); } @@ -51,6 +52,7 @@ public class VaultEntityMapper extends EntityMapper { } entity.setCloudType(domainObject.getCloudType().name()); entity.setPassword(domainObject.getPassword()); + entity.setPosition(domainObject.getPosition()); return entity; } } diff --git a/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveClientFactory.java b/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveClientFactory.java index 02532be4..13c64b0a 100644 --- a/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveClientFactory.java +++ b/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveClientFactory.java @@ -9,18 +9,49 @@ import com.google.api.services.drive.DriveScopes; import org.cryptomator.data.BuildConfig; import org.cryptomator.domain.exception.FatalBackendException; +import org.cryptomator.util.SharedPreferencesHandler; import java.util.Collections; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import timber.log.Timber; class GoogleDriveClientFactory { private final Context context; + private final SharedPreferencesHandler sharedPreferencesHandler; - GoogleDriveClientFactory(Context context) { + GoogleDriveClientFactory(Context context, SharedPreferencesHandler sharedPreferencesHandler) { this.context = context; + this.sharedPreferencesHandler = sharedPreferencesHandler; } Drive getClient(String accountName) throws FatalBackendException { + if(sharedPreferencesHandler.debugMode()) { + Logger.getLogger("com.google.api.client").setLevel(Level.CONFIG); + Logger.getLogger("com.google.api.client").addHandler(new Handler() { + @Override + public void publish(LogRecord record) { + if(record.getMessage().startsWith("-------------- RESPONSE --------------") + || record.getMessage().startsWith("-------------- REQUEST --------------") + || record.getMessage().startsWith("{\n \"files\": [\n")) { + Timber.tag("GoogleDriveClient").d(record.getMessage()); + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + }); + } + try { FixedGoogleAccountCredential credential = FixedGoogleAccountCredential.usingOAuth2(context, Collections.singleton(DriveScopes.DRIVE)); credential.setAccountName(accountName); diff --git a/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.java b/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.java index 0ccebe23..0bb53f0f 100644 --- a/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.java +++ b/data/src/notFoss/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.java @@ -69,7 +69,7 @@ class GoogleDriveImpl { } private Drive client() { - return new GoogleDriveClientFactory(context) // + return new GoogleDriveClientFactory(context, sharedPreferencesHandler) // .getClient(googleDriveCloud.accessToken()); } diff --git a/domain/src/main/java/org/cryptomator/domain/Vault.java b/domain/src/main/java/org/cryptomator/domain/Vault.java index 6bcd3669..7a1be5e2 100644 --- a/domain/src/main/java/org/cryptomator/domain/Vault.java +++ b/domain/src/main/java/org/cryptomator/domain/Vault.java @@ -19,7 +19,8 @@ public class Vault implements Serializable { .withPath(vault.getPath()) // .withUnlocked(vault.isUnlocked()) // .withSavedPassword(vault.getPassword()) // - .withVersion(vault.getVersion()); + .withVersion(vault.getVersion()) // + .withPosition(vault.getPosition()); } private final Long id; @@ -30,6 +31,7 @@ public class Vault implements Serializable { private final boolean unlocked; private final String password; private final int version; + private final int position; private Vault(Builder builder) { this.id = builder.id; @@ -40,6 +42,7 @@ public class Vault implements Serializable { this.cloudType = builder.cloudType; this.password = builder.password; this.version = builder.version; + this.position = builder.position; } public Long getId() { @@ -74,6 +77,10 @@ public class Vault implements Serializable { return version; } + public int getPosition() { + return position; + } + public static class Builder { private Long id = NOT_SET; @@ -84,6 +91,7 @@ public class Vault implements Serializable { private boolean unlocked; private String password; private int version = -1; + private int position = -1; private Builder() { } @@ -154,6 +162,11 @@ public class Vault implements Serializable { return this; } + public Builder withPosition(int position) { + this.position = position; + return this; + } + public Vault build() { validate(); return new Vault(this); @@ -172,6 +185,9 @@ public class Vault implements Serializable { if (cloudType == null) { throw new IllegalStateException("cloudtype must be set"); } + if (position == -1) { + throw new IllegalStateException("position must be set"); + } } } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/vault/CreateVault.java b/domain/src/main/java/org/cryptomator/domain/usecases/vault/CreateVault.java index 9b73b5f2..6df1bcde 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/vault/CreateVault.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/vault/CreateVault.java @@ -35,7 +35,10 @@ class CreateVault { CloudFolder vaultFolder = cloudContentRepository.folder(folder, vaultName); vaultFolder = cloudContentRepository.create(vaultFolder); cloudRepository.create(vaultFolder, password); - return vaultRepository.store(aVault().thatIsNew().withNamePathAndCloudFrom(vaultFolder).build()); + return vaultRepository.store(aVault() // + .thatIsNew() // + .withNamePathAndCloudFrom(vaultFolder) // + .withPosition(vaultRepository.vaults().size()) // + .build()); } - } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/vault/DeleteVault.java b/domain/src/main/java/org/cryptomator/domain/usecases/vault/DeleteVault.java index 7ff6cb17..7a807060 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/vault/DeleteVault.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/vault/DeleteVault.java @@ -6,6 +6,8 @@ import org.cryptomator.domain.repository.VaultRepository; import org.cryptomator.generator.Parameter; import org.cryptomator.generator.UseCase; +import java.util.List; + @UseCase class DeleteVault { @@ -18,7 +20,12 @@ class DeleteVault { } public Long execute() throws BackendException { - return vaultRepository.delete(vault); + Long vaultId = vaultRepository.delete(vault); + + List reorderVaults = MoveVaultHelper.Companion.reorderVaults(vaultRepository); + MoveVaultHelper.Companion.updateVaultsInDatabase(reorderVaults, vaultRepository); + + return vaultId; } } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultHelper.kt b/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultHelper.kt new file mode 100644 index 00000000..e42c5bbf --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultHelper.kt @@ -0,0 +1,51 @@ +package org.cryptomator.domain.usecases.vault; + +import org.cryptomator.domain.Vault +import org.cryptomator.domain.repository.VaultRepository +import java.util.* + +class MoveVaultHelper { + + companion object { + fun updateVaultPosition(fromPosition: Int, toPosition: Int, vaultRepository: VaultRepository): List { + val vaults = vaultRepository.vaults() + + vaults.sortWith(VaultComparator()) + + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(vaults, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(vaults, i, i - 1) + } + } + return reorderVaults(vaults) + } + + private fun reorderVaults(vaults: MutableList) : List { + for (i in 0 until vaults.size) { + vaults[i] = Vault.aCopyOf(vaults[i]).withPosition(i).build() + } + return vaults; + } + + fun reorderVaults(vaultRepository: VaultRepository) : List { + val vaults = vaultRepository.vaults() + vaults.sortWith(VaultComparator()) + return reorderVaults(vaults) + } + + fun updateVaultsInDatabase(vaults: List, vaultRepository: VaultRepository): List { + vaults.forEach { vault -> vaultRepository.store(vault) } + return vaultRepository.vaults() + } + } + + internal class VaultComparator : Comparator { + override fun compare(o1: Vault, o2: Vault): Int { + return o1.position - o2.position + } + } +} diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultPosition.java b/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultPosition.java new file mode 100644 index 00000000..fd7c43d3 --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/usecases/vault/MoveVaultPosition.java @@ -0,0 +1,28 @@ +package org.cryptomator.domain.usecases.vault; + +import org.cryptomator.domain.Vault; +import org.cryptomator.domain.exception.BackendException; +import org.cryptomator.domain.repository.VaultRepository; +import org.cryptomator.generator.Parameter; +import org.cryptomator.generator.UseCase; + +import java.util.List; + +@UseCase +class MoveVaultPosition { + + private final VaultRepository vaultRepository; + private final int fromPosition; + private final int toPosition; + + public MoveVaultPosition(VaultRepository vaultRepository, @Parameter Integer fromPosition, @Parameter Integer toPosition) { + this.vaultRepository = vaultRepository; + this.fromPosition = fromPosition; + this.toPosition = toPosition; + } + + public List execute() throws BackendException { + List vaults = MoveVaultHelper.Companion.updateVaultPosition(fromPosition, toPosition, vaultRepository); + return MoveVaultHelper.Companion.updateVaultsInDatabase(vaults, vaultRepository); + } +} diff --git a/domain/src/test/java/org/cryptomator/domain/usecases/vault/MoveVaultHelperTest.kt b/domain/src/test/java/org/cryptomator/domain/usecases/vault/MoveVaultHelperTest.kt new file mode 100644 index 00000000..36fd6a4a --- /dev/null +++ b/domain/src/test/java/org/cryptomator/domain/usecases/vault/MoveVaultHelperTest.kt @@ -0,0 +1,100 @@ +package org.cryptomator.domain.usecases.vault + +import org.cryptomator.domain.CloudType +import org.cryptomator.domain.Vault +import org.cryptomator.domain.repository.VaultRepository +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito + + +class MoveVaultHelperTest { + + private lateinit var orderedVaults: ArrayList + private lateinit var unorderedVaults: ArrayList + + private lateinit var vaultRepository: VaultRepository + private lateinit var cloudType: CloudType + + @Test + fun reorderVaults() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(unorderedVaults) + assertEquals(orderedVaults, MoveVaultHelper.Companion.reorderVaults(vaultRepository), "Failed to reorderVaults") + } + + @Test + fun movePositionUp() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(orderedVaults) + + val resultList = ArrayList() + resultList.add(Vault.aVault().withId(2).withPath("").withCloudType(cloudType).withName("foo 5").withPosition(0).build()) + resultList.add(Vault.aVault().withId(3).withPath("").withCloudType(cloudType).withName("foo 10").withPosition(1).build()) + resultList.add(Vault.aVault().withId(24).withPath("").withCloudType(cloudType).withName("foo 1").withPosition(2).build()) + resultList.add(Vault.aVault().withId(4).withPath("").withCloudType(cloudType).withName("foo 15").withPosition(3).build()) + + assertEquals(resultList, MoveVaultHelper.Companion.updateVaultPosition(0, 2, vaultRepository), "Failed to movePositionUp") + } + + @Test + fun movePositionDown() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(orderedVaults) + + val resultList = ArrayList() + resultList.add(Vault.aVault().withId(3).withPath("").withCloudType(cloudType).withName("foo 10").withPosition(0).build()) + resultList.add(Vault.aVault().withId(24).withPath("").withCloudType(cloudType).withName("foo 1").withPosition(1).build()) + resultList.add(Vault.aVault().withId(2).withPath("").withCloudType(cloudType).withName("foo 5").withPosition(2).build()) + resultList.add(Vault.aVault().withId(4).withPath("").withCloudType(cloudType).withName("foo 15").withPosition(3).build()) + + assertEquals(resultList, MoveVaultHelper.Companion.updateVaultPosition(2, 0, vaultRepository), "Failed to movePositionDown") + } + + @Test + fun movePositionToSelf() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(orderedVaults) + + val resultList = ArrayList() + resultList.add(Vault.aVault().withId(24).withPath("").withCloudType(cloudType).withName("foo 1").withPosition(0).build()) + resultList.add(Vault.aVault().withId(2).withPath("").withCloudType(cloudType).withName("foo 5").withPosition(1).build()) + resultList.add(Vault.aVault().withId(3).withPath("").withCloudType(cloudType).withName("foo 10").withPosition(2).build()) + resultList.add(Vault.aVault().withId(4).withPath("").withCloudType(cloudType).withName("foo 15").withPosition(3).build()) + + assertEquals(resultList, MoveVaultHelper.Companion.updateVaultPosition(1, 1, vaultRepository), "Failed to movePositionToSelf") + } + + @Test + fun movePositionOutOfBounds() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(orderedVaults) + Assertions.assertThrows(IndexOutOfBoundsException::class.java) { MoveVaultHelper.Companion.updateVaultPosition(1, 4, vaultRepository) } + } + + @Test + fun verifyStoreInVaultRepo() { + Mockito.`when`(vaultRepository.vaults()).thenReturn(orderedVaults) + val result = MoveVaultHelper.Companion.updateVaultsInDatabase(orderedVaults, vaultRepository) + assertEquals(orderedVaults, result, "Failed to verifyStoreInVaultRepo") + + orderedVaults.forEach { + Mockito.verify(vaultRepository).store(Mockito.eq(it)) + } + } + + @BeforeEach + fun setup() { + vaultRepository = Mockito.mock(VaultRepository::class.java) + cloudType = CloudType.LOCAL + + unorderedVaults = ArrayList() + unorderedVaults.add(Vault.aVault().withId(24).withPath("").withCloudType(cloudType).withName("foo 1").withPosition(1).build()) + unorderedVaults.add(Vault.aVault().withId(3).withPath("").withCloudType(cloudType).withName("foo 10").withPosition(10).build()) + unorderedVaults.add(Vault.aVault().withId(2).withPath("").withCloudType(cloudType).withName("foo 5").withPosition(5).build()) + unorderedVaults.add(Vault.aVault().withId(4).withPath("").withCloudType(cloudType).withName("foo 15").withPosition(15).build()) + + orderedVaults = ArrayList() + orderedVaults.add(Vault.aVault().withId(24).withPath("").withCloudType(cloudType).withName("foo 1").withPosition(0).build()) + orderedVaults.add(Vault.aVault().withId(2).withPath("").withCloudType(cloudType).withName("foo 5").withPosition(1).build()) + orderedVaults.add(Vault.aVault().withId(3).withPath("").withCloudType(cloudType).withName("foo 10").withPosition(2).build()) + orderedVaults.add(Vault.aVault().withId(4).withPath("").withCloudType(cloudType).withName("foo 15").withPosition(3).build()) + } +} diff --git a/fastlane/.default.env b/fastlane/.default.env new file mode 100644 index 00000000..228ca9a5 --- /dev/null +++ b/fastlane/.default.env @@ -0,0 +1,21 @@ +# This file is just a template and should remain untouched +# COPY this file to .env and fill out the env variables + +GOOGLE_PLAYSTORE_PRIVATE_KEY_FILE_PATH= + +SIGNING_KEYSTORE_PATH= +SIGNING_KEYSTORE_PASSWORD= +SIGNING_KEY_ALIAS= +SIGNING_KEY_PASSWORD= + +SIGNING_UPDATE_APK_STORE_KEY_PATH= +SIGNING_UPDATE_APK_STORE_PUB_KEY_PATH= +APK_STORE_BASIC_URL= + +S3_BUCKET= +S3_ENDPOINT= +S3_REGION= +S3_ACCESS_KEY= +S3_SECRET_ACCESS_KEY= + +SLACK_URL= diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000..864808dc --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file(ENV["GOOGLE_PLAYSTORE_PRIVATE_KEY_FILE_PATH"]) +package_name("org.cryptomator") diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000..f219606b --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,183 @@ +fastlane_require 'dotenv' +fastlane_require 'jwt' +fastlane_require 'base64' +fastlane_require 'net/sftp' + +default_platform(:android) + +branch_name = `git rev-parse --abbrev-ref HEAD` +build = `git rev-list --count #{branch_name} | tr -d " \t\n\r"` +build = build.to_i + 1958 # adding 1958 for legacy reasons. Must be in sync with getVersionCode() from build.gradle +version = get_version_name( + gradle_file_path:"build.gradle", + ext_constant_name:"androidVersionName") +version = version.delete "'" + +platform :android do |options| + + desc "Run all the tests" + lane :test do |options| + gradle(task: "test") + end + + desc "Deploy new version to Google Play and APK Store options: beta:false (default)" + lane :deploy do |options| + release_note_path_en = "metadata/android/en-US/changelogs/default.txt" + + # use english-change-log for french language too + FileUtils.cp(release_note_path_en, "metadata/android/fr-FR/changelogs/default.txt") + + deployToPlaystore(beta:options[:beta]) + deployToServer(beta:options[:beta]) + deployToFDroid(beta:options[:beta]) + + slack( + default_payloads: [], # reduce the notification to the minimum + message: ":rocket: Successfully deployed #{version} with code #{build} to the Play Store :cryptomator:", + payload: { + "Changes" => File.read(release_note_path_en) + } + ) + end + + desc "Deploy new version to Play Store" + lane :deployToPlaystore do |options| + deploy_target = "production" + + if options[:beta] + deploy_target = "beta" + end + + gradle(task: "clean") + + gradle( + task: "assemble", + build_type: "Release", + flavor: "playstore", + print_command: false, + properties: { + "android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"], + "android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"], + "android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"], + } + ) + + upload_to_play_store( + track: deploy_target, + apk: lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], + mapping: lane_context[SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH], + version_name: version, + version_code: build, + release_status: "draft", + json_key: ENV["GOOGLE_PLAYSTORE_PRIVATE_KEY_FILE_PATH"], + skip_upload_aab: true, + skip_upload_metadata: false, + skip_upload_images: true, + skip_upload_screenshots: true, + metadata_path: "fastlane/metadata/android" + ) + + FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_playstore_signed.apk") + end + + desc "Deploy new version to server" + lane :deployToServer do |options| + gradle(task: "clean") + + gradle( + task: "assemble", + build_type: "Release", + flavor: "apkstore", + print_command: false, + properties: { + "android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"], + "android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"], + "android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"], + } + ) + + FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}.apk") + + server_host = ENV["APK_STORE_BASIC_URL"] + base_url = "https://#{server_host}/android/" + apk_url = "#{base_url}#{version}/Cryptomator-#{version}.apk" + release_note_url = "#{base_url}#{version}/release-notes.html" + + claims = { + "version": version, + "url": apk_url, + "release_notes": release_note_url + } + + private_key = OpenSSL::PKey.read(File.read(ENV["SIGNING_UPDATE_APK_STORE_KEY_PATH"])) + token = JWT.encode claims, private_key, "ES256" + + latest_version_filename = "latest-version-#{version}.json" + + latest_version_jsn = File.new("latest_versions/#{latest_version_filename}","w") + latest_version_jsn.write(token) + latest_version_jsn.close + + if options[:beta] + puts "Skipping deployment to server cause there isn't currently a beta channel" + else + puts "Uploading APK and release note" + + aws_s3( + bucket: ENV['S3_BUCKET'], + endpoint: ENV['S3_ENDPOINT'], + region: ENV['S3_REGION'], + access_key: ENV['S3_ACCESS_KEY'], + secret_access_key: ENV['S3_SECRET_ACCESS_KEY'], + path: "android/#{version}", + files: [ + "fastlane/release/Cryptomator-#{version}.apk", + "fastlane/release-notes.html" + ], + skip_html_upload: true, + apk: '' + ) + + puts "Uploading #{latest_version_filename} with claims #{claims}" + puts "Rename #{latest_version_filename} to latest-version.json for deployment" + + aws_s3( + bucket: ENV['S3_BUCKET'], + endpoint: ENV['S3_ENDPOINT'], + region: ENV['S3_REGION'], + access_key: ENV['S3_ACCESS_KEY'], + secret_access_key: ENV['S3_SECRET_ACCESS_KEY'], + path: "android", + files: [ + "fastlane/latest_versions/#{latest_version_filename}" + ], + skip_html_upload: true, + apk: '' + ) + end + + FileUtils.mv("release/Cryptomator-#{version}.apk", "release/Cryptomator-#{version}_signed.apk") + end + + desc "Deploy new version to F-Droid" + lane :deployToFDroid do |options| + gradle(task: "clean") + + gradle( + task: "assemble", + build_type: "Release", + flavor: "fdroid", + print_command: false, + properties: { + "android.injected.signing.store.file" => ENV["SIGNING_KEYSTORE_PATH"], + "android.injected.signing.store.password" => ENV["SIGNING_KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["SIGNING_KEY_ALIAS"], + "android.injected.signing.key.password" => ENV["SIGNING_KEY_PASSWORD"], + } + ) + + FileUtils.cp(lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], "release/Cryptomator-#{version}_fdroid_signed.apk") + end +end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 00000000..9b434483 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,6 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-get_version_name' +gem 'fastlane-plugin-aws_s3' diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 00000000..eee28971 --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,49 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew install fastlane` + +# Available Actions +## Android +### android test +``` +fastlane android test +``` +Run all the tests +### android deploy +``` +fastlane android deploy +``` +Deploy new version to Google Play and APK Store options: beta:false (default) +### android deployToPlaystore +``` +fastlane android deployToPlaystore +``` +Deploy new version to Play Store +### android deployToServer +``` +fastlane android deployToServer +``` +Deploy new version to server +### android deployToFDroid +``` +fastlane android deployToFDroid +``` +Deploy new version to F-Droid + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/latest_versions/.gitignore b/fastlane/latest_versions/.gitignore new file mode 100644 index 00000000..5e7d2734 --- /dev/null +++ b/fastlane/latest_versions/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/fastlane/metadata/android/de-DE/changelogs/default.txt b/fastlane/metadata/android/de-DE/changelogs/default.txt new file mode 100644 index 00000000..d723022d --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/default.txt @@ -0,0 +1,4 @@ +- Möglichkeit zum Sortieren der Tresorliste hinzugefügt +- Logging in Google Drive Cloud hinzugefügt +- Verhalten bei Änderung des OneDrive-Passworts verbessert +- CryptoBot-Symbole aufpoliert \ No newline at end of file diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt new file mode 100644 index 00000000..11885796 --- /dev/null +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -0,0 +1,39 @@ +Mit Cryptomator liegt der Schlüssel zu deinen Daten bei dir. Durch Cryptomator ver­schlüsselst du deine Daten schnell und unkompliziert. Anschließend lädst du sie geschützt in deinen Lieblingscloudservice hoch. + +EINFACH + +Cryptomator ist ein einfaches Tool zur digitalen Selbstverteidigung. Es ermöglicht dir, deine Cloud-Daten eigenständig und unabhängig zu schützen. + +• Erstelle einen Tresor und vergib ein Passwort +• Keine komplizierte Konfiguration und keine weiteren Accounts notwendig +• Entsperr Tresore mit deinem Fingerabdruck* + +* ab Android 6.0 and Smartphones mit Fingerabdruck-Sensor + +KOMPATIBEL + +Cryptomator ist kompatibel mit den meistgenutzten Cloudspeichern und verfügbar für alle gängigen Betriebssysteme. + +• Kompatibel mit Dropbox, Google Drive, OneDrive und WebDAV-basierten Cloudspeicher-Diensten +• Erstelle auch Tresore in Androids lokalem Speicher (funktioniert bspw. zusammen mit Sync-Apps von Drittanbietern) +• Greife auf deine Tresore von allen deinen mobilen Endgeräten und Computern zu + +SICHER + +Du musst Cryptomator nicht blind vertrauen, denn die App ist quelloffen, was für dich als Nutzer bedeutet, dass jeder den Code einsehen kann. + +• Verschlüsselung der Dateiinhalte und Dateinamen mit AES und 256-Bit-Schlüssellänge +• Erhöhte Brute-Force-Resistenz des Tresor-Passworts durch Einsatz von scrypt +• Tresore werden automatisch gesperrt, wenn die App in den Hintergrund geschickt wird +• Verschlüsselungsimplementierung ist öffentlich dokumentiert + +PREISGEKRÖNT + +Cryptomator wurde mit dem CeBIT Innovation Award 2016 for Usable Security and Privacy ausgezeichnet. Wir freuen uns, hunderttausenden Cryptomator-Nutzern Sicherheit und Privatsphärenschutz bieten zu können. + +CRYPTOMATOR COMMUNITY + +Tritt der Cryptomator Community bei und tausche dich mit Cryptomator-Nutzern aus: https://community.cryptomator.org + +• Folge uns auf Twitter @Cryptomator +• Like uns auf Facebook /Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt new file mode 100644 index 00000000..591a40e1 --- /dev/null +++ b/fastlane/metadata/android/de-DE/short_description.txt @@ -0,0 +1 @@ +Nimm die Sicherung deiner Cloud-Daten selbst in die Hand \ No newline at end of file diff --git a/fastlane/metadata/android/de-DE/title.txt b/fastlane/metadata/android/de-DE/title.txt new file mode 100644 index 00000000..f9944b4a --- /dev/null +++ b/fastlane/metadata/android/de-DE/title.txt @@ -0,0 +1 @@ +Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/de-DE/video.txt b/fastlane/metadata/android/de-DE/video.txt new file mode 100644 index 00000000..e69de29b diff --git a/fastlane/metadata/android/en-US/changelogs/default.txt b/fastlane/metadata/android/en-US/changelogs/default.txt new file mode 100644 index 00000000..22075f45 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/default.txt @@ -0,0 +1,4 @@ +- Added possibility to sort vault list +- Added logging to Google drive cloud +- Enhanced behavior when OneDrive password changed +- Polished CryptoBot icons \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 00000000..f1841971 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,39 @@ +With Cryptomator, the key to your data is in your hands. Cryptomator encrypts your data quickly and easily. Afterwards you upload them protected to your favorite cloud service. + +EASY-TO-USE + +Cryptomator is a simple tool for digital self-defense. It allows you to protect your cloud data by yourself and independently. + +• Simply create a vault and assign a password +• No additional account or configuration needed +• Unlock vaults with your fingerprint* + +* from Android 6.0 and smartphones with fingerprint sensor + +COMPATIBLE + +Cryptomator is compatible with the most commonly used cloud storages and available for all major operating systems. + +• Compatible with Dropbox, Google Drive, OneDrive, and WebDAV-based cloud storage services +• Create vaults in Android’s local storage (e.g., works with third-party sync apps) +• Access your vaults on all your mobile devices and computers + +SECURE + +You don't have to trust Cryptomator blindly, because it is open source software. For you as a user, this means that everyone can see the code. + +• File content and filename encryption with AES and 256 bit key length +• Vault password is secured with scrypt for enhanced brute-force resistance +• Vaults are automatically locked after sending app to background +• Crypto implementation is publicly documented + +AWARD-WINNING + +Cryptomator received the CeBIT Innovation Award 2016 for Usable Security and Privacy. We're proud to provide security and privacy for hundreds of thousands of Cryptomator users. + +CRYPTOMATOR COMMUNITY + +Join the Cryptomator Community and participate in the conversations with other Cryptomator users. + +• Follow us on Twitter @Cryptomator +• Like us on Facebook /Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 00000000..7a0ae9a7 --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Put a lock on your cloud: Take the security of your data into your own hands \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 00000000..f9944b4a --- /dev/null +++ b/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/video.txt b/fastlane/metadata/android/en-US/video.txt new file mode 100644 index 00000000..e69de29b diff --git a/fastlane/metadata/android/fr-FR/changelogs/default.txt b/fastlane/metadata/android/fr-FR/changelogs/default.txt new file mode 100644 index 00000000..e69de29b diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt new file mode 100644 index 00000000..4bfd34cb --- /dev/null +++ b/fastlane/metadata/android/fr-FR/full_description.txt @@ -0,0 +1,39 @@ +Cryptomator rend votre stockage dans le cloud beaucoup plus sûr. L'application chiffre les fichiers sur votre appareil mobile avant qu'ils ne soient envoyés dans votre cloud. Même si une tierce partie obtient un accès non autorisé à vos fichiers (par exemple, une attaque de pirates informatiques), vos fichiers sont à l'abri des regards indiscrets. + +SIMPLICITÉ + +Cryptomator a été développé en mettant l'accent sur la convivialité. + +• Il suffit de créer un coffre-fort et d'y attribuer un mot de passe +• Aucun compte ou configuration supplémentaire n'est nécessaire +• Déverrouillez les coffres-forts avec votre empreinte digitale*. + +* à partir d'Android 6.0 et sur smartphones avec capteur biométrique d'empreintes digitales + +COMPATIBILITÉ + +Cryptomator est compatible avec les systèmes de stockage dans le cloud les plus couramment utilisés et disponible pour tous les principaux systèmes d'exploitation. + +• Compatible avec Dropbox, Google Drive, OneDrive et les services de stockage dans le nuage basés sur WebDAV +• Créer des coffres-forts dans le stockage local d'Android (par exemple, fonctionne avec des applications de synchronisation tierces) +• Accédez à vos coffres-forts sur tous vos appareils mobiles et ordinateurs + +SÉCURITÉ + +Cryptomator pour Android est basé sur le projet open-source de Cryptomator pour Ordinateur de bureau. + +• Chiffrement du contenu et des noms de fichiers via AES et une longueur de clé de 256 bits +• Le mot de passe du coffre-fort est sécurisé par cryptage pour une meilleure résistance aux attaque par force brut +• Les coffre-forts sont automatiquement verrouillées après la mise en arrière-plan de l'application +• La mise en œuvre de Crypto est basée sur la bibliothèque open-source CryptoLib et est documentée publiquement + +UNE GÉNIALITUDE GÉNÉRAL + +Cryptomator a reçu le prix de l'innovation CeBIT 2016 pour la sécurité pratique et la confidentialité. Nous sommes fiers d'assurer la sécurité et la confidentialité des centaines de milliers d'utilisateurs du Cryptomator. + +LA COMMUNAUTÉ CRYPTOMATOR + +Rejoignez la communauté de Cryptomator et participez aux conversations avec les autres utilisateurs de Cryptomator: https://community.cryptomator.org + +- Suivez-nous sur Twitter @Cryptomator +- Comme nous sur Facebook/Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/short_description.txt b/fastlane/metadata/android/fr-FR/short_description.txt new file mode 100644 index 00000000..0a4252a1 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/short_description.txt @@ -0,0 +1 @@ +Verrouillé votre cloud: Prenez en mains la sécurité de vos données \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/title.txt b/fastlane/metadata/android/fr-FR/title.txt new file mode 100644 index 00000000..f9944b4a --- /dev/null +++ b/fastlane/metadata/android/fr-FR/title.txt @@ -0,0 +1 @@ +Cryptomator \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/video.txt b/fastlane/metadata/android/fr-FR/video.txt new file mode 100644 index 00000000..e69de29b diff --git a/fastlane/release-notes.html b/fastlane/release-notes.html new file mode 100644 index 00000000..839af27d --- /dev/null +++ b/fastlane/release-notes.html @@ -0,0 +1,6 @@ +
    +
  • Added possibility to sort vault list
  • +
  • Added logging to Google drive cloud
  • +
  • Enhanced behavior when OneDrive password changed
  • +
  • Polished CryptoBot icons
  • +
\ No newline at end of file diff --git a/fastlane/release/.gitignore b/fastlane/release/.gitignore new file mode 100644 index 00000000..5e7d2734 --- /dev/null +++ b/fastlane/release/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/presentation/src/debug/res/drawable/ic_launcher_background.xml b/presentation/src/debug/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..adffe5a8 --- /dev/null +++ b/presentation/src/debug/res/drawable/ic_launcher_background.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/presentation/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/debug/res/mipmap-hdpi/ic_launcher.png b/presentation/src/debug/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..06ba5001 Binary files /dev/null and b/presentation/src/debug/res/mipmap-hdpi/ic_launcher.png differ diff --git a/presentation/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/presentation/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..b31fd740 Binary files /dev/null and b/presentation/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/presentation/src/debug/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..59b67a34 Binary files /dev/null and b/presentation/src/debug/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/presentation/src/debug/res/mipmap-mdpi/ic_launcher.png b/presentation/src/debug/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..b6c41cd4 Binary files /dev/null and b/presentation/src/debug/res/mipmap-mdpi/ic_launcher.png differ diff --git a/presentation/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png b/presentation/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..6f85a129 Binary files /dev/null and b/presentation/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/presentation/src/debug/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..18359374 Binary files /dev/null and b/presentation/src/debug/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/presentation/src/debug/res/mipmap-xhdpi/ic_launcher.png b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..8f7d27c2 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..ea9a5fc9 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..3b928c52 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..8db4eec8 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..bd6bc45d Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..cdb10637 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..d3404092 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..2403cf92 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..aaaeac56 Binary files /dev/null and b/presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/presentation/src/debug/res/values/ic_launcher_background.xml b/presentation/src/debug/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..e50a17c8 --- /dev/null +++ b/presentation/src/debug/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F1C40F + \ No newline at end of file diff --git a/presentation/src/debug/res/values/strings.xml b/presentation/src/debug/res/values/strings.xml deleted file mode 100644 index 1ad426c7..00000000 --- a/presentation/src/debug/res/values/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - Debug Cryptomator - - diff --git a/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index 1c76fb07..a8fee1c0 100644 --- a/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -112,7 +112,9 @@ class AuthenticateCloudPresenter @Inject constructor( // } private fun failAuthentication(cloudName: Int) { - view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName))) + activity().runOnUiThread { + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName))) + } finish() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt index a9c3dfcf..ca16deb3 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.model -import android.os.Build import org.cryptomator.domain.CloudType import org.cryptomator.presentation.R @@ -23,13 +22,13 @@ enum class CloudTypeModel(builder: Builder) { LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) // .withCloudImageResource(R.drawable.storage_type_local) // .withCloudImageLargeResource(R.drawable.storage_type_local_large) // - .withMultiInstancesIfLollipopOrLater()); + .withMultiInstances()); - val cloudName: String - val displayNameResource: Int - val cloudImageResource: Int - val cloudImageLargeResource: Int - val isMultiInstance: Boolean + val cloudName: String = builder.cloudName + val displayNameResource: Int = builder.displayNameResource + val cloudImageResource: Int = builder.cloudImageResource + val cloudImageLargeResource: Int = builder.cloudImageLargeResource + val isMultiInstance: Boolean = builder.multiInstances private class Builder(val cloudName: String, val displayNameResource: Int) { var cloudImageResource = 0 @@ -50,11 +49,6 @@ enum class CloudTypeModel(builder: Builder) { multiInstances = true return this } - - fun withMultiInstancesIfLollipopOrLater(): Builder { - multiInstances = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - return this - } } companion object { @@ -66,12 +60,4 @@ enum class CloudTypeModel(builder: Builder) { return CloudType.valueOf(type.name) } } - - init { - cloudName = builder.cloudName - displayNameResource = builder.displayNameResource - cloudImageResource = builder.cloudImageResource - cloudImageLargeResource = builder.cloudImageLargeResource - isMultiInstance = builder.multiInstances - } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/VaultModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/VaultModel.kt index 762d4a95..f15f1c50 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/VaultModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/VaultModel.kt @@ -13,6 +13,8 @@ class VaultModel(private val vault: Vault) : Serializable { get() = vault.path val isLocked: Boolean get() = !vault.isUnlocked + val position: Int + get() = vault.position fun toVault(): Vault { return vault diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/comparator/VaultPositionComparator.kt b/presentation/src/main/java/org/cryptomator/presentation/model/comparator/VaultPositionComparator.kt new file mode 100644 index 00000000..08324c77 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/model/comparator/VaultPositionComparator.kt @@ -0,0 +1,10 @@ +package org.cryptomator.presentation.model.comparator + +import org.cryptomator.presentation.model.VaultModel + +class VaultPositionComparator : Comparator { + + override fun compare(v1: VaultModel, v2: VaultModel): Int { + return v1.position - v2.position + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt index c3f9f7fe..d053b17a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt @@ -673,22 +673,11 @@ class BrowseFilesPresenter @Inject constructor( // } fun onExportFileClicked(cloudFile: CloudFileModel, trigger: ExportOperation) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - exportFileToDownloadDirectory(cloudFile, trigger) - } else { - exportFileToUserSelectedLocation(cloudFile, trigger) - } + exportFileToUserSelectedLocation(cloudFile, trigger) } fun onExportNodesClicked(selectedCloudFiles: ArrayList>, trigger: ExportOperation) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - exportNodesToUserSelectedLocation(selectedCloudFiles, trigger) - } - } - - private fun exportFileToDownloadDirectory(fileToExport: CloudFileModel, exportOperation: ExportOperation) { - requestPermissions(PermissionsResultCallbacks.exportFileToDownloadDirectory(fileToExport, exportOperation), // - R.string.permission_message_export_file, Manifest.permission.WRITE_EXTERNAL_STORAGE) + exportNodesToUserSelectedLocation(selectedCloudFiles, trigger) } @Callback diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index e4e565ac..b4a11094 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -103,7 +103,7 @@ class CloudConnectionListPresenter @Inject constructor( // } private fun releaseUriPermissionForLocalStorageCloud(cloudModel: LocalStorageModel) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && (cloudModel.toCloud() as LocalStorageCloud).rootUri() != null) { + if ((cloudModel.toCloud() as LocalStorageCloud).rootUri() != null) { releaseUriPermission(cloudModel.uri()) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt index 74a3d225..d4e1867b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt @@ -53,6 +53,7 @@ class VaultListPresenter @Inject constructor( // private val addExistingVaultWorkflow: AddExistingVaultWorkflow, // private val createNewVaultWorkflow: CreateNewVaultWorkflow, // private val saveVaultUseCase: SaveVaultUseCase, // + private val moveVaultPositionUseCase: MoveVaultPositionUseCase, // private val changePasswordUseCase: ChangePasswordUseCase, // private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, // private val licenseCheckUseCase: DoLicenseCheckUseCase, // @@ -603,6 +604,25 @@ class VaultListPresenter @Inject constructor( // view?.showDialog(AppIsObscuredInfoDialog.newInstance()) } + fun onRowMoved(fromPosition: Int, toPosition: Int) { + view?.rowMoved(fromPosition, toPosition) + } + + fun onVaultMoved(fromPosition: Int, toPosition: Int) { + moveVaultPositionUseCase + .withFromPosition(fromPosition) // + .andToPosition(toPosition) // + .run(object : DefaultResultHandler>() { + override fun onSuccess(vaults: List) { + view?.vaultMoved(vaults.mapTo(ArrayList()) { VaultModel(it) }) + } + + override fun onError(e: Throwable) { + Timber.tag("VaultListPresenter").e(e, "Failed to execute MoveVaultUseCase") + } + }) + } + fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) { if (changedVaultPassword) { changedVaultPassword = false @@ -690,6 +710,7 @@ class VaultListPresenter @Inject constructor( // lockVaultUseCase, // getVaultListUseCase, // saveVaultUseCase, // + moveVaultPositionUseCase, // removeStoredVaultPasswordsUseCase, // unlockVaultUseCase, // prepareUnlockUseCase, // diff --git a/presentation/src/main/java/org/cryptomator/presentation/service/AutoUploadNotification.kt b/presentation/src/main/java/org/cryptomator/presentation/service/AutoUploadNotification.kt index 08219fec..9d8abcfc 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/service/AutoUploadNotification.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/service/AutoUploadNotification.kt @@ -34,7 +34,7 @@ class AutoUploadNotification(private val context: Context, private val amountOfP this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) // .setContentTitle(context.getString(R.string.notification_auto_upload_title)) // - .setSmallIcon(R.mipmap.ic_launcher) // + .setSmallIcon(R.drawable.background_splash_cryptomator) // .setColor(getColor(R.color.colorPrimary)) // .addAction(cancelNowAction()) .setGroup(NOTIFICATION_GROUP_KEY) diff --git a/presentation/src/main/java/org/cryptomator/presentation/service/OpenWritableFileNotification.kt b/presentation/src/main/java/org/cryptomator/presentation/service/OpenWritableFileNotification.kt index 5b81fa80..ad414e27 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/service/OpenWritableFileNotification.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/service/OpenWritableFileNotification.kt @@ -35,7 +35,7 @@ class OpenWritableFileNotification(private val context: Context, private val uri this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) // .setContentTitle(context.getString(R.string.notification_open_writable_file_title)) // .setContentText(context.getString(R.string.notification_open_writable_file_message)) // - .setSmallIcon(R.mipmap.ic_launcher) // + .setSmallIcon(R.drawable.background_splash_cryptomator) // .setColor(getColor(R.color.colorPrimary)) // .setGroup(NOTIFICATION_GROUP_KEY) .setOngoing(true) diff --git a/presentation/src/main/java/org/cryptomator/presentation/service/PhotoContentJob.kt b/presentation/src/main/java/org/cryptomator/presentation/service/PhotoContentJob.kt index 68ee0e5a..66537386 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/service/PhotoContentJob.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/service/PhotoContentJob.kt @@ -7,12 +7,13 @@ import android.app.job.JobScheduler import android.app.job.JobService import android.content.ComponentName import android.content.Context -import android.database.Cursor +import android.database.MergeCursor import android.net.Uri import android.os.Build import android.os.Handler import android.provider.MediaStore import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI +import android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI import androidx.annotation.RequiresApi import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.presentation.R @@ -21,72 +22,69 @@ import org.cryptomator.presentation.util.ResourceHelper import org.cryptomator.util.file.MimeTypeMap_Factory import org.cryptomator.util.file.MimeTypes import timber.log.Timber -import java.lang.String.format -import java.util.* @RequiresApi(api = Build.VERSION_CODES.N) class PhotoContentJob : JobService() { - private val mHandler = Handler() - private val mWorker: Runnable = Runnable { + private val handler = Handler() + private val worker: Runnable = Runnable { scheduleJob(applicationContext) - jobFinished(mRunningParams, false) + jobFinished(runningParams, false) } - private lateinit var mRunningParams: JobParameters + private lateinit var runningParams: JobParameters override fun onStartJob(params: JobParameters): Boolean { Timber.tag("PhotoContentJob").i("Job started!") val fileUtil = FileUtil(baseContext, MimeTypes(MimeTypeMap_Factory.newInstance())) - mRunningParams = params - if (params.triggeredContentAuthorities != null) { + runningParams = params + + params.triggeredContentAuthorities?.let { if (params.triggeredContentUris != null) { val ids = getIds(params) - if (ids != null && ids.size > 0) { + if (ids != null && ids.isNotEmpty()) { val selection = buildSelection(ids) - var cursor: Cursor? = null - try { - cursor = contentResolver.query(EXTERNAL_CONTENT_URI, PROJECTION, selection, null, null) - cursor?.let { - while (cursor.moveToNext()) { - val dir = cursor.getString(PROJECTION_DATA) - try { - fileUtil.addImageToAutoUploads(dir) - Timber.tag("PhotoContentJob").i("Added file to UploadList") - Timber.tag("PhotoContentJob").d(format("Added file to UploadList %s", dir)) - } catch (e: FatalBackendException) { - Timber.tag("PhotoContentJob").e(e, "Failed to add image to auto upload list") + contentResolver.query(EXTERNAL_CONTENT_URI, PROJECTION, selection, null, null).use { externalCursor -> + contentResolver.query(INTERNAL_CONTENT_URI, PROJECTION, selection, null, null).use { internalCursor -> + MergeCursor(arrayOf(externalCursor, internalCursor)).use { cursor -> + while (cursor.moveToNext()) { + try { + val dir = cursor.getString(PROJECTION_DATA) + fileUtil.addImageToAutoUploads(dir) + Timber.tag("PhotoContentJob").i("Added file to UploadList") + Timber.tag("PhotoContentJob").d(String.format("Added file to UploadList %s", dir)) + } catch (e: FatalBackendException) { + Timber.tag("PhotoContentJob").e(e, "Failed to add image to auto upload list") + } catch (e: SecurityException) { + Timber.tag("PhotoContentJob").e(e, "No access to storage") + } } } - } ?: Timber.tag("PhotoContentJob").e("Error: no access to media!") - } catch (e: SecurityException) { - Timber.tag("PhotoContentJob").e("Error: no access to media!") - } finally { - cursor?.close() + } } + } else { + Timber.tag("PhotoContentJob").d("ids are null or 0: %s", ids) } } else { Timber.tag("PhotoContentJob").w("Photos rescan needed!") return true } - } else { - Timber.tag("PhotoContentJob").w("No photos content") - } + } ?: Timber.tag("PhotoContentJob").w("No photos content") - mHandler.post(mWorker) + handler.post(worker) return false } - private fun getIds(params: JobParameters): ArrayList? { + private fun getIds(params: JobParameters): Set? { return params.triggeredContentUris ?.map { it.pathSegments } - ?.filter { it != null && it.size == EXTERNAL_PATH_SEGMENTS.size + 1 } - ?.mapTo(ArrayList()) { it[it.size - 1] } + ?.filter { it != null && (it.size == EXTERNAL_CONTENT_URI.pathSegments.size + 1 || it.size == INTERNAL_CONTENT_URI.pathSegments.size + 1) } + ?.mapTo(HashSet()) { it[it.size - 1] } } - private fun buildSelection(ids: ArrayList): String { + private fun buildSelection(ids: Set): String { val selection = StringBuilder() ids.indices.forEach { i -> if (selection.isNotEmpty()) { @@ -94,7 +92,7 @@ class PhotoContentJob : JobService() { } selection.append(MediaStore.Images.ImageColumns._ID) selection.append("='") - selection.append(ids[i]) + selection.append(ids.elementAt(i)) selection.append("'") } return selection.toString() @@ -102,7 +100,7 @@ class PhotoContentJob : JobService() { override fun onStopJob(params: JobParameters): Boolean { Timber.tag("PhotoContentJob").i("onStopJob called, must stop, reschedule later") - mHandler.removeCallbacks(mWorker) + handler.removeCallbacks(worker) return true } @@ -114,7 +112,6 @@ class PhotoContentJob : JobService() { companion object { private val MEDIA_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/") - internal val EXTERNAL_PATH_SEGMENTS = EXTERNAL_CONTENT_URI.pathSegments internal val PROJECTION = arrayOf(MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA) internal const val PROJECTION_DATA = 1 @@ -125,6 +122,7 @@ class PhotoContentJob : JobService() { init { val builder = JobInfo.Builder(PHOTOS_CONTENT_JOB, ComponentName(ResourceHelper.getString(R.string.app_id), PhotoContentJob::class.java.name)) builder.addTriggerContentUri(JobInfo.TriggerContentUri(EXTERNAL_CONTENT_URI, FLAG_NOTIFY_FOR_DESCENDANTS)) + builder.addTriggerContentUri(JobInfo.TriggerContentUri(INTERNAL_CONTENT_URI, FLAG_NOTIFY_FOR_DESCENDANTS)) builder.addTriggerContentUri(JobInfo.TriggerContentUri(MEDIA_URI, FLAG_NOTIFY_FOR_DESCENDANTS)) jobInfo = builder.build() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/service/UnlockedNotification.java b/presentation/src/main/java/org/cryptomator/presentation/service/UnlockedNotification.java index d8c53ba2..5b50e415 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/service/UnlockedNotification.java +++ b/presentation/src/main/java/org/cryptomator/presentation/service/UnlockedNotification.java @@ -54,7 +54,7 @@ class UnlockedNotification { } this.builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) // - .setSmallIcon(R.mipmap.ic_launcher) // + .setSmallIcon(R.drawable.background_splash_cryptomator) // .setColor(ResourceHelper.Companion.getColor(R.color.colorPrimary)) // .addAction(lockNowAction()) // .setGroup(NOTIFICATION_GROUP_KEY) // diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt index 4b4f7d59..5d70f3b1 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt @@ -1,7 +1,6 @@ package org.cryptomator.presentation.ui.activity import android.net.Uri -import android.os.Build import android.view.View.* import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment @@ -91,9 +90,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou } private fun setupStatusBar() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - window.statusBarColor = ContextCompat.getColor(this, R.color.colorBlack) - } + window.statusBarColor = ContextCompat.getColor(this, R.color.colorBlack) } private fun setupToolbar(index: Int) { @@ -131,10 +128,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou var newUiOptions = window.decorView.systemUiVisibility newUiOptions = newUiOptions or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION newUiOptions = newUiOptions xor SYSTEM_UI_FLAG_FULLSCREEN - - if (Build.VERSION.SDK_INT >= 19) { - newUiOptions = newUiOptions xor SYSTEM_UI_FLAG_IMMERSIVE_STICKY - } + newUiOptions = newUiOptions xor SYSTEM_UI_FLAG_IMMERSIVE_STICKY window.decorView.systemUiVisibility = newUiOptions } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt index 8d8e4642..b3784a71 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt @@ -150,6 +150,14 @@ class VaultListActivity : BaseActivity(), // return biometricAuthentication?.stoppedBiometricAuthDuringCloudAuthentication() == true } + override fun rowMoved(fromPosition: Int, toPosition: Int) { + vaultListFragment().rowMoved(fromPosition, toPosition) + } + + override fun vaultMoved(vaults: List) { + vaultListFragment().vaultMoved(vaults) + } + override fun showVaultSettingsDialog(vaultModel: VaultModel) { val vaultSettingDialog = // SettingsVaultBottomSheet.newInstance(vaultModel) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/VaultListView.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/VaultListView.kt index 300ea4da..4551f245 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/VaultListView.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/VaultListView.kt @@ -23,5 +23,7 @@ interface VaultListView : View { fun isVaultLocked(vaultModel: VaultModel): Boolean fun cancelBasicAuthIfRunning() fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean + fun rowMoved(fromPosition: Int, toPosition: Int) + fun vaultMoved(vaults: List) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt index e1e2bfef..b01a9050 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt @@ -2,15 +2,18 @@ package org.cryptomator.presentation.ui.adapter import android.view.View import com.google.android.material.switchmaterial.SwitchMaterial -import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.* import org.cryptomator.presentation.R import org.cryptomator.presentation.model.VaultModel +import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.BiometricAuthSettingsAdapter.BiometricAuthSettingsViewHolder import javax.inject.Inject +import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.cloud +import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.toggleBiometricAuth +import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.vaultName class BiometricAuthSettingsAdapter // @Inject -constructor() : RecyclerViewBaseAdapter() { +constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { private var onVaultBiometricAuthSettingsChanged: OnVaultBiometricAuthSettingsChanged? = null diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt index 6ec8b950..28f85a17 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt @@ -1,14 +1,19 @@ package org.cryptomator.presentation.ui.adapter import android.view.View -import kotlinx.android.synthetic.main.item_shareable_location.view.* import org.cryptomator.presentation.R import org.cryptomator.presentation.model.VaultModel +import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.SharedLocationsAdapter.VaultViewHolder import javax.inject.Inject +import kotlinx.android.synthetic.main.item_shareable_location.view.chooseFolderLocation +import kotlinx.android.synthetic.main.item_shareable_location.view.chosenLocation +import kotlinx.android.synthetic.main.item_shareable_location.view.cloudImage +import kotlinx.android.synthetic.main.item_shareable_location.view.selectedVault +import kotlinx.android.synthetic.main.item_shareable_location.view.vaultName class SharedLocationsAdapter @Inject -constructor() : RecyclerViewBaseAdapter() { +constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { private var selectedVault: VaultModel? = null private var selectedLocation: String? = null diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt index 322e4f54..946aebfe 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt @@ -1,21 +1,31 @@ package org.cryptomator.presentation.ui.adapter import android.view.View -import kotlinx.android.synthetic.main.item_vault.view.* import org.cryptomator.presentation.R import org.cryptomator.presentation.model.VaultModel +import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.VaultsAdapter.VaultViewHolder import javax.inject.Inject +import kotlinx.android.synthetic.main.item_vault.view.cloudImage +import kotlinx.android.synthetic.main.item_vault.view.settings +import kotlinx.android.synthetic.main.item_vault.view.unlockedImage +import kotlinx.android.synthetic.main.item_vault.view.vaultName +import kotlinx.android.synthetic.main.item_vault.view.vaultPath class VaultsAdapter @Inject -internal constructor() : RecyclerViewBaseAdapter() { +internal constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()), VaultsMoveListener.Listener { + + interface OnItemInteractionListener { - interface OnItemClickListener { fun onVaultClicked(vaultModel: VaultModel) fun onVaultSettingsClicked(vaultModel: VaultModel) fun onVaultLockClicked(vaultModel: VaultModel) + + fun onRowMoved(fromPosition: Int, toPosition: Int) + + fun onVaultMoved(fromPosition: Int, toPosition: Int) } override fun getItemLayout(viewType: Int): Int { @@ -65,4 +75,12 @@ internal constructor() : RecyclerViewBaseAdapter= Build.VERSION_CODES.LOLLIPOP) { - getString(R.string.screen_settings_last_check_updates_never) - } else { - getString(R.string.screen_settings_last_check_updates_never_pre_marshmallow) - } + getString(R.string.screen_settings_last_check_updates_never) } val date = SpannableString(readableDate) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SharedFilesFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SharedFilesFragment.kt index 9d6f8e51..4a45076b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SharedFilesFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SharedFilesFragment.kt @@ -8,6 +8,7 @@ import org.cryptomator.presentation.R import org.cryptomator.presentation.model.CloudFolderModel import org.cryptomator.presentation.model.SharedFileModel import org.cryptomator.presentation.model.VaultModel +import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.presenter.SharedFilesPresenter import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter.Callback @@ -69,9 +70,10 @@ class SharedFilesFragment : BaseFragment() { } fun displayVaults(vaults: List?) { - if (vaults?.isNotEmpty() == true) { - presenter.selectedVault?.let { presenter.selectedVault = vaults[vaults.indexOf(it)] } - val preselectedVault = presenter.selectedVault ?: vaults[0] + val sortedVaults = vaults?.sortedWith(VaultPositionComparator()) + if (sortedVaults?.isNotEmpty() == true) { + presenter.selectedVault?.let { presenter.selectedVault = sortedVaults[sortedVaults.indexOf(it)] } + val preselectedVault = presenter.selectedVault ?: sortedVaults[0] locationsAdapter.setPreselectedVault(preselectedVault) presenter.onVaultSelected(preselectedVault) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/VaultListFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/VaultListFragment.kt index 73ba7b1f..9069acbc 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/VaultListFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/VaultListFragment.kt @@ -2,6 +2,7 @@ package org.cryptomator.presentation.ui.fragment import android.util.TypedValue import android.view.View +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.fragment_vault_list.* import kotlinx.android.synthetic.main.recycler_view_layout.* @@ -11,6 +12,7 @@ import org.cryptomator.presentation.R import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.presenter.VaultListPresenter import org.cryptomator.presentation.ui.adapter.VaultsAdapter +import org.cryptomator.presentation.ui.adapter.VaultsMoveListener import javax.inject.Inject @Fragment(R.layout.fragment_vault_list) @@ -22,7 +24,9 @@ class VaultListFragment : BaseFragment() { @Inject lateinit var vaultsAdapter: VaultsAdapter - private val onItemClickListener = object : VaultsAdapter.OnItemClickListener { + lateinit var touchHelper: ItemTouchHelper + + private val onItemClickListener = object : VaultsAdapter.OnItemInteractionListener { override fun onVaultClicked(vaultModel: VaultModel) { vaultListPresenter.onVaultClicked(vaultModel) } @@ -34,8 +38,17 @@ class VaultListFragment : BaseFragment() { override fun onVaultLockClicked(vaultModel: VaultModel) { vaultListPresenter.onVaultLockClicked(vaultModel) } + + override fun onRowMoved(fromPosition: Int, toPosition: Int) { + vaultListPresenter.onRowMoved(fromPosition, toPosition) + } + + override fun onVaultMoved(fromPosition: Int, toPosition: Int) { + vaultListPresenter.onVaultMoved(fromPosition, toPosition) + } } + override fun setupView() { setupRecyclerView() fab_vault.setOnClickListener { vaultListPresenter.onCreateVaultClicked() } @@ -48,6 +61,9 @@ class VaultListFragment : BaseFragment() { private fun setupRecyclerView() { vaultsAdapter.setCallback(onItemClickListener) + touchHelper = ItemTouchHelper(VaultsMoveListener(vaultsAdapter)) + touchHelper.attachToRecyclerView(recyclerView) + recyclerView.layoutManager = LinearLayoutManager(context()) recyclerView.adapter = vaultsAdapter recyclerView.setHasFixedSize(true) // smoother scrolling @@ -83,5 +99,14 @@ class VaultListFragment : BaseFragment() { vaultsAdapter.addOrUpdateVault(vaultModel) } + fun vaultMoved(vaults: List) { + vaultsAdapter.clear() + vaultsAdapter.addAll(vaults) + } + + fun rowMoved(fromPosition: Int, toPosition: Int) { + vaultsAdapter.notifyItemMoved(fromPosition, toPosition) + } + fun rootView(): View = coordinatorLayout } diff --git a/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java b/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java index 1ec41190..6548e311 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java +++ b/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java @@ -7,6 +7,7 @@ import org.cryptomator.domain.CloudFolder; import org.cryptomator.domain.Vault; import org.cryptomator.domain.di.PerView; import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase; +import org.cryptomator.domain.usecases.vault.GetVaultListUseCase; import org.cryptomator.domain.usecases.vault.SaveVaultUseCase; import org.cryptomator.generator.Callback; import org.cryptomator.presentation.R; @@ -18,6 +19,7 @@ import org.cryptomator.presentation.model.mappers.CloudModelMapper; import org.cryptomator.presentation.presenter.VaultListPresenter; import java.io.Serializable; +import java.util.List; import javax.inject.Inject; @@ -30,6 +32,7 @@ import static org.cryptomator.presentation.intent.Intents.chooseCloudServiceInte public class AddExistingVaultWorkflow extends Workflow { private final SaveVaultUseCase saveVaultUseCase; + private final GetVaultListUseCase getVaultListUseCase; private final GetRootFolderUseCase getRootFolderUseCase; private final CloudModelMapper cloudModelMapper; private final AuthenticationExceptionHandler authenticationExceptionHandler; @@ -39,12 +42,14 @@ public class AddExistingVaultWorkflow extends Workflow() { - @Override - public void onSuccess(Vault vault) { - ((VaultListPresenter) presenter()).onAddOrCreateVaultCompleted(vault); - } - }); + getVaultListUseCase.run(presenter().new ProgressCompletingResultHandler>() { + @Override + public void onSuccess(List vaults) { + saveVaultUseCase// + .withVault(aVault() // + .withNamePathAndCloudFrom(state().masterkeyFile.getParent()) // + .withPosition(vaults.size()) // + .thatIsNew() // + .build()) // + .run(presenter().new ProgressCompletingResultHandler() { + @Override + public void onSuccess(Vault vault) { + ((VaultListPresenter) presenter()).onAddOrCreateVaultCompleted(vault); + } + }); + } + }); } public static class State implements Serializable { diff --git a/presentation/src/main/res/drawable/ic_launcher_background.xml b/presentation/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..adffe5a8 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/mipmap-hdpi/ic_launcher.png b/presentation/src/main/res/mipmap-hdpi/ic_launcher.png index 3a0dc83f..74979acb 100644 Binary files a/presentation/src/main/res/mipmap-hdpi/ic_launcher.png and b/presentation/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/presentation/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/presentation/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..b31fd740 Binary files /dev/null and b/presentation/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png b/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..3466a1e1 Binary files /dev/null and b/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/presentation/src/main/res/mipmap-mdpi/ic_launcher.png b/presentation/src/main/res/mipmap-mdpi/ic_launcher.png index 514e4ad6..455b917f 100644 Binary files a/presentation/src/main/res/mipmap-mdpi/ic_launcher.png and b/presentation/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/presentation/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/presentation/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..6f85a129 Binary files /dev/null and b/presentation/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png b/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..2ebc053e Binary files /dev/null and b/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png b/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png index ef09a117..9f9dd009 100644 Binary files a/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png and b/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/presentation/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/presentation/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..ea9a5fc9 Binary files /dev/null and b/presentation/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..3134f0f7 Binary files /dev/null and b/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png index 40138188..fec6a14c 100644 Binary files a/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..bd6bc45d Binary files /dev/null and b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..469f6e26 Binary files /dev/null and b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d2c299e..e94212c0 100644 Binary files a/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..2403cf92 Binary files /dev/null and b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..a626af31 Binary files /dev/null and b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml index 6af46287..227eca18 100644 --- a/presentation/src/main/res/values-de/strings.xml +++ b/presentation/src/main/res/values-de/strings.xml @@ -99,7 +99,7 @@ Speicherort Speichern Dateien verschlüsselt - text.txt + @string/dialog_file_name_placeholder Cloud-Dienst Neuen Tresor anlegen @@ -219,7 +219,7 @@ Möchten Sie wirklich beenden, ohne zu speichern? Verwerfen @string/dialog_button_cancel - @string/screen_share_files_new_text_file + text.txt Sind Sie sicher, dass sie den Tresor entfernen wollen? Dieser Vorgang wird den Tresor nur aus dieser Liste entfernen und nicht tatsächlich löschen. Lade hoch… @@ -341,8 +341,7 @@ Intervall der Aktualisierungsprüfung Nach Aktualisierungen suchen Letzte Ausführung %1$s - Noch nie - Noch nie ~ Wenn aus technischen Gründen keine Update-Prüfung durchgeführt werden kann, können Updates manuell über die Website https://cryptomator.org/android/ heruntergeladen und installiert werden. + @string/lock_timeout_never Zwischenspeichergröße pro Cloud Sofort diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index ccdf0340..312cd807 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -350,7 +350,6 @@ Vérifier les mises à jour Dernière exécution %1$s @string/lock_timeout_never - Jamais ~ Si la vérification de mise à jour ne peut être effectué pour des raisons techniques, les mises à jour peuvent être téléchargées et installées manuellement à partir du site https://cryptomator.org/android/. Taille du cache par cloud Instantané diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index c74cf321..43ceaef7 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -342,7 +342,6 @@ Güncellemeleri kontrol et Son çalıştırma %1$s @string/lock_timeout_never - Teknik nedenlerle güncelleme kontrolü yapılamıyorsa, güncellemeler, https://cryptomator.org/android/ web sitesinden manuel olarak indirilebilir ve yüklenebilir. Bulut başına önbellek boyutu Anında diff --git a/presentation/src/main/res/values/ic_launcher_background.xml b/presentation/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..c5d5899f --- /dev/null +++ b/presentation/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index fa95a7bd..240d713f 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -509,8 +509,7 @@ Update check interval Check for updates Last run %1$s - Never - Never ~ If an update check cannot be performed for technical reasons, updates can be downloaded and installed manually from the website https://cryptomator.org/android/. + @string/lock_timeout_never Cache size per Cloud @@ -520,7 +519,7 @@ 2 minutes 5 minutes 10 minutes - @string/screen_settings_last_check_updates_never + Never 50 MB @@ -540,6 +539,6 @@ Once a day Once a week Once a month - @string/screen_settings_last_check_updates_never + @string/lock_timeout_never diff --git a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index 14bcac5a..35095a8f 100644 --- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -116,7 +116,9 @@ class AuthenticateCloudPresenter @Inject constructor( // } private fun failAuthentication(cloudName: Int) { - view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName))) + activity().runOnUiThread { + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName))) + } finish() } diff --git a/presentation/src/test/java/org/cryptomator/presentation/presenter/VaultListPresenterTest.java b/presentation/src/test/java/org/cryptomator/presentation/presenter/VaultListPresenterTest.java index ef80a487..30e66dc2 100644 --- a/presentation/src/test/java/org/cryptomator/presentation/presenter/VaultListPresenterTest.java +++ b/presentation/src/test/java/org/cryptomator/presentation/presenter/VaultListPresenterTest.java @@ -17,6 +17,7 @@ import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase; import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase; import org.cryptomator.domain.usecases.vault.GetVaultListUseCase; import org.cryptomator.domain.usecases.vault.LockVaultUseCase; +import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase; import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase; import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase; import org.cryptomator.domain.usecases.vault.RenameVaultUseCase; @@ -56,6 +57,7 @@ public class VaultListPresenterTest { private static final Vault AN_UNLOCKED_VAULT = Vault.aVault() // .withId(1L) // + .withPosition(1) // .withName("Top Secret") // .withPath("/top secret") // .withCloudType(CloudType.DROPBOX) // @@ -63,6 +65,7 @@ public class VaultListPresenterTest { private static final Vault ANOTHER_VAULT_WITH_CLOUD = Vault.aVault() // .withId(2L) // + .withPosition(2) // .withName("Trip to the moon") // .withPath("/trip to the moon") // .withCloudType(CloudType.ONEDRIVE) // @@ -70,6 +73,7 @@ public class VaultListPresenterTest { private static final Vault A_VAULT_WITH_NEW_NAME = Vault.aVault() // .withId(3L) // + .withPosition(3) // .withName(A_NEW_VAULT_NAME) // .withPath("/trip to the moon") // .withCloudType(CloudType.GOOGLE_DRIVE) // @@ -120,6 +124,8 @@ public class VaultListPresenterTest { private SaveVaultUseCase saveVaultUseCase = Mockito.mock(SaveVaultUseCase.class); + private MoveVaultPositionUseCase moveVaultPositionUseCase = Mockito.mock(MoveVaultPositionUseCase.class); + private ChangePasswordUseCase changePasswordUseCase = Mockito.mock(ChangePasswordUseCase.class); private RemoveStoredVaultPasswordsUseCase removeStoredVaultPasswordsUseCase = Mockito.mock(RemoveStoredVaultPasswordsUseCase.class); @@ -157,6 +163,7 @@ public class VaultListPresenterTest { addExistingVaultWorkflow, // createNewVaultWorkflow, // saveVaultUseCase, // + moveVaultPositionUseCase, // changePasswordUseCase, // removeStoredVaultPasswordsUseCase, // doLicenceCheckUsecase, //