Merge branch 'release/1.5.12'
11
.gitignore
vendored
@ -31,11 +31,6 @@ build/
|
|||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
# fastlane
|
# fastlane
|
||||||
secret_key_file.json
|
**/fastlane/.env
|
||||||
**/**/fastlane/fastlane/**
|
**/fastlane/metadata/**/images/**
|
||||||
**/**/fastlane/metadata/**
|
**/fastlane/report.xml
|
||||||
**/**/fastlane/report.xml
|
|
||||||
**/**/fastlane/mappings/**
|
|
||||||
**/**/fastlane/release_notes/**
|
|
||||||
**/**/fastlane/latest_versions/**
|
|
||||||
.env.default
|
|
||||||
|
9
Gemfile
Normal file
@ -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)
|
221
Gemfile.lock
Normal file
@ -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
|
36
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).
|
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
|
## 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.
|
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.
|
||||||
|
@ -3,7 +3,7 @@ apply from: 'buildsystem/dependencies.gradle'
|
|||||||
apply plugin: "com.vanniktech.android.junit.jacoco"
|
apply plugin: "com.vanniktech.android.junit.jacoco"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.4.21'
|
ext.kotlin_version = '1.4.30'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -42,7 +42,7 @@ allprojects {
|
|||||||
ext {
|
ext {
|
||||||
androidApplicationId = 'org.cryptomator'
|
androidApplicationId = 'org.cryptomator'
|
||||||
androidVersionCode = getVersionCode()
|
androidVersionCode = getVersionCode()
|
||||||
androidVersionName = '1.5.11'
|
androidVersionName = '1.5.12'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -18,27 +18,26 @@ ext {
|
|||||||
// support lib
|
// support lib
|
||||||
androidSupportAnnotationsVersion = '1.1.0'
|
androidSupportAnnotationsVersion = '1.1.0'
|
||||||
androidSupportAppcompatVersion = '1.2.0'
|
androidSupportAppcompatVersion = '1.2.0'
|
||||||
// check https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview/57968071#57968071 !!!!!!
|
androidSupportDesignVersion = '1.3.0'
|
||||||
androidSupportDesignVersion = '1.2.1'
|
|
||||||
|
|
||||||
// app frameworks and utilities
|
// app frameworks and utilities
|
||||||
|
|
||||||
rxJavaVersion = '2.2.20'
|
rxJavaVersion = '2.2.21'
|
||||||
rxAndroidVersion = '2.1.1'
|
rxAndroidVersion = '2.1.1'
|
||||||
rxBindingVersion = '2.2.0'
|
rxBindingVersion = '2.2.0'
|
||||||
|
|
||||||
daggerVersion = '2.31.2'
|
daggerVersion = '2.32'
|
||||||
|
|
||||||
gsonVersion = '2.8.6'
|
gsonVersion = '2.8.6'
|
||||||
|
|
||||||
okHttpVersion = '4.9.0'
|
okHttpVersion = '4.9.1'
|
||||||
okHttpDigestVersion = '2.5'
|
okHttpDigestVersion = '2.5'
|
||||||
|
|
||||||
velocityVersion = '1.7'
|
velocityVersion = '1.7'
|
||||||
|
|
||||||
timberVersion = '4.7.1'
|
timberVersion = '4.7.1'
|
||||||
|
|
||||||
zxcvbnVersion = '1.3.3'
|
zxcvbnVersion = '1.3.6'
|
||||||
|
|
||||||
scaleImageViewVersion = '3.10.0'
|
scaleImageViewVersion = '3.10.0'
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ ext {
|
|||||||
googlePlayServicesVersion = '19.0.0'
|
googlePlayServicesVersion = '19.0.0'
|
||||||
googleClientVersion = '1.31.2'
|
googleClientVersion = '1.31.2'
|
||||||
|
|
||||||
msgraphVersion = '2.5.0'
|
msgraphVersion = '2.7.1'
|
||||||
msaAuthVersion = '0.10.0'
|
msaAuthVersion = '0.10.0'
|
||||||
|
|
||||||
commonsCodecVersion = '1.15'
|
commonsCodecVersion = '1.15'
|
||||||
@ -67,7 +66,7 @@ ext {
|
|||||||
|
|
||||||
// testing dependencies
|
// testing dependencies
|
||||||
|
|
||||||
jUnitVersion = '5.7.0'
|
jUnitVersion = '5.7.1'
|
||||||
jUnit4Version = '4.13.1'
|
jUnit4Version = '4.13.1'
|
||||||
assertJVersion = '1.7.1'
|
assertJVersion = '1.7.1'
|
||||||
mockitoVersion = '3.7.7'
|
mockitoVersion = '3.7.7'
|
||||||
@ -82,13 +81,13 @@ ext {
|
|||||||
uiautomatorVersion = '2.2.0'
|
uiautomatorVersion = '2.2.0'
|
||||||
|
|
||||||
androidxCoreVersion = '1.3.2'
|
androidxCoreVersion = '1.3.2'
|
||||||
androidxFragmentVersion = '1.2.5'
|
androidxFragmentVersion = '1.3.0'
|
||||||
androidxViewpagerVersion = '1.0.0'
|
androidxViewpagerVersion = '1.0.0'
|
||||||
androidxSwiperefreshVersion = '1.1.0'
|
androidxSwiperefreshVersion = '1.1.0'
|
||||||
androidxPreferenceVersion = '1.0.0' // 1.1.0 and 1.1.2 does have a bug with the text size
|
androidxPreferenceVersion = '1.0.0' // 1.1.0 and 1.1.2 does have a bug with the text size
|
||||||
androidxRecyclerViewVersion = '1.1.0'
|
androidxRecyclerViewVersion = '1.1.0'
|
||||||
androidxDocumentfileVersion = '1.0.1'
|
androidxDocumentfileVersion = '1.0.1'
|
||||||
androidxBiometricVersion = '1.0.1'
|
androidxBiometricVersion = '1.1.0'
|
||||||
androidxTestCoreVersion = '1.3.0'
|
androidxTestCoreVersion = '1.3.0'
|
||||||
|
|
||||||
jsonWebTokenApiVersion = '0.11.2'
|
jsonWebTokenApiVersion = '0.11.2'
|
||||||
|
@ -74,7 +74,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
greendao {
|
greendao {
|
||||||
schemaVersion 3
|
schemaVersion 4
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.data.cloud.local;
|
package org.cryptomator.data.cloud.local;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.cryptomator.data.cloud.local.file.LocalStorageContentRepository;
|
import org.cryptomator.data.cloud.local.file.LocalStorageContentRepository;
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkContentRepository;
|
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)) {
|
if (!hasPermissions(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE)) {
|
||||||
throw new NoAuthenticationProvidedException(cloud);
|
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);
|
return new LocalStorageAccessFrameworkContentRepository(context, mimeTypes, (LocalStorageCloud) cloud);
|
||||||
} else {
|
} else {
|
||||||
return new LocalStorageContentRepository(context, (LocalStorageCloud) cloud);
|
return new LocalStorageContentRepository(context, (LocalStorageCloud) cloud);
|
||||||
|
@ -152,7 +152,7 @@ public abstract class MSAAuthAndroidAdapter implements IAuthenticationAdapter {
|
|||||||
public void onAuthComplete(final LiveStatus status, final LiveConnectSession session, final Object userState) {
|
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));
|
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.");
|
Timber.tag("MSAAuthAndroidAdapter").d("Received invalid login failure from silent authentication, ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,14 @@ class DatabaseUpgrades {
|
|||||||
public DatabaseUpgrades( //
|
public DatabaseUpgrades( //
|
||||||
Upgrade0To1 upgrade0To1, //
|
Upgrade0To1 upgrade0To1, //
|
||||||
Upgrade1To2 upgrade1To2, //
|
Upgrade1To2 upgrade1To2, //
|
||||||
Upgrade2To3 upgrade2To3) {
|
Upgrade2To3 upgrade2To3, //
|
||||||
|
Upgrade3To4 upgrade3To4) {
|
||||||
|
|
||||||
availableUpgrades = defineUpgrades( //
|
availableUpgrades = defineUpgrades( //
|
||||||
upgrade0To1, //
|
upgrade0To1, //
|
||||||
upgrade1To2, //
|
upgrade1To2, //
|
||||||
upgrade2To3);
|
upgrade2To3, //
|
||||||
|
upgrade3To4);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Integer, List<DatabaseUpgrade>> defineUpgrades(DatabaseUpgrade... upgrades) {
|
private Map<Integer, List<DatabaseUpgrade>> defineUpgrades(DatabaseUpgrade... upgrades) {
|
||||||
|
66
data/src/main/java/org/cryptomator/data/db/Upgrade3To4.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,8 @@ public class VaultEntity extends DatabaseEntity {
|
|||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
private Integer position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
|
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
|
||||||
* Entity must attached to an entity context.
|
* Entity must attached to an entity context.
|
||||||
@ -152,6 +154,14 @@ public class VaultEntity extends DatabaseEntity {
|
|||||||
this.password = password;
|
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. */
|
/** called by internal mechanisms, do not call yourself. */
|
||||||
@Generated(hash = 674742652)
|
@Generated(hash = 674742652)
|
||||||
public void __setDaoSession(DaoSession daoSession) {
|
public void __setDaoSession(DaoSession daoSession) {
|
||||||
@ -159,14 +169,16 @@ public class VaultEntity extends DatabaseEntity {
|
|||||||
myDao = daoSession != null ? daoSession.getVaultEntityDao() : null;
|
myDao = daoSession != null ? daoSession.getVaultEntityDao() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Generated(hash = 1196809909)
|
@Generated(hash = 825602374)
|
||||||
public VaultEntity(Long id, Long folderCloudId, String folderPath, String folderName, @NotNull String cloudType, String password) {
|
public VaultEntity(Long id, Long folderCloudId, String folderPath, String folderName, @NotNull String cloudType, String password,
|
||||||
|
Integer position) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.folderCloudId = folderCloudId;
|
this.folderCloudId = folderCloudId;
|
||||||
this.folderPath = folderPath;
|
this.folderPath = folderPath;
|
||||||
this.folderName = folderName;
|
this.folderName = folderName;
|
||||||
this.cloudType = cloudType;
|
this.cloudType = cloudType;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Generated(hash = 691253864)
|
@Generated(hash = 691253864)
|
||||||
|
@ -30,6 +30,7 @@ public class VaultEntityMapper extends EntityMapper<VaultEntity, Vault> {
|
|||||||
.withCloud(cloudFrom(entity)) //
|
.withCloud(cloudFrom(entity)) //
|
||||||
.withCloudType(CloudType.valueOf(entity.getCloudType())) //
|
.withCloudType(CloudType.valueOf(entity.getCloudType())) //
|
||||||
.withSavedPassword(entity.getPassword()) //
|
.withSavedPassword(entity.getPassword()) //
|
||||||
|
.withPosition(entity.getPosition()) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ public class VaultEntityMapper extends EntityMapper<VaultEntity, Vault> {
|
|||||||
}
|
}
|
||||||
entity.setCloudType(domainObject.getCloudType().name());
|
entity.setCloudType(domainObject.getCloudType().name());
|
||||||
entity.setPassword(domainObject.getPassword());
|
entity.setPassword(domainObject.getPassword());
|
||||||
|
entity.setPosition(domainObject.getPosition());
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,49 @@ import com.google.api.services.drive.DriveScopes;
|
|||||||
|
|
||||||
import org.cryptomator.data.BuildConfig;
|
import org.cryptomator.data.BuildConfig;
|
||||||
import org.cryptomator.domain.exception.FatalBackendException;
|
import org.cryptomator.domain.exception.FatalBackendException;
|
||||||
|
import org.cryptomator.util.SharedPreferencesHandler;
|
||||||
|
|
||||||
import java.util.Collections;
|
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 {
|
class GoogleDriveClientFactory {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
private final SharedPreferencesHandler sharedPreferencesHandler;
|
||||||
|
|
||||||
GoogleDriveClientFactory(Context context) {
|
GoogleDriveClientFactory(Context context, SharedPreferencesHandler sharedPreferencesHandler) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.sharedPreferencesHandler = sharedPreferencesHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drive getClient(String accountName) throws FatalBackendException {
|
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 {
|
try {
|
||||||
FixedGoogleAccountCredential credential = FixedGoogleAccountCredential.usingOAuth2(context, Collections.singleton(DriveScopes.DRIVE));
|
FixedGoogleAccountCredential credential = FixedGoogleAccountCredential.usingOAuth2(context, Collections.singleton(DriveScopes.DRIVE));
|
||||||
credential.setAccountName(accountName);
|
credential.setAccountName(accountName);
|
||||||
|
@ -69,7 +69,7 @@ class GoogleDriveImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Drive client() {
|
private Drive client() {
|
||||||
return new GoogleDriveClientFactory(context) //
|
return new GoogleDriveClientFactory(context, sharedPreferencesHandler) //
|
||||||
.getClient(googleDriveCloud.accessToken());
|
.getClient(googleDriveCloud.accessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ public class Vault implements Serializable {
|
|||||||
.withPath(vault.getPath()) //
|
.withPath(vault.getPath()) //
|
||||||
.withUnlocked(vault.isUnlocked()) //
|
.withUnlocked(vault.isUnlocked()) //
|
||||||
.withSavedPassword(vault.getPassword()) //
|
.withSavedPassword(vault.getPassword()) //
|
||||||
.withVersion(vault.getVersion());
|
.withVersion(vault.getVersion()) //
|
||||||
|
.withPosition(vault.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Long id;
|
private final Long id;
|
||||||
@ -30,6 +31,7 @@ public class Vault implements Serializable {
|
|||||||
private final boolean unlocked;
|
private final boolean unlocked;
|
||||||
private final String password;
|
private final String password;
|
||||||
private final int version;
|
private final int version;
|
||||||
|
private final int position;
|
||||||
|
|
||||||
private Vault(Builder builder) {
|
private Vault(Builder builder) {
|
||||||
this.id = builder.id;
|
this.id = builder.id;
|
||||||
@ -40,6 +42,7 @@ public class Vault implements Serializable {
|
|||||||
this.cloudType = builder.cloudType;
|
this.cloudType = builder.cloudType;
|
||||||
this.password = builder.password;
|
this.password = builder.password;
|
||||||
this.version = builder.version;
|
this.version = builder.version;
|
||||||
|
this.position = builder.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
@ -74,6 +77,10 @@ public class Vault implements Serializable {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private Long id = NOT_SET;
|
private Long id = NOT_SET;
|
||||||
@ -84,6 +91,7 @@ public class Vault implements Serializable {
|
|||||||
private boolean unlocked;
|
private boolean unlocked;
|
||||||
private String password;
|
private String password;
|
||||||
private int version = -1;
|
private int version = -1;
|
||||||
|
private int position = -1;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
@ -154,6 +162,11 @@ public class Vault implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Vault build() {
|
public Vault build() {
|
||||||
validate();
|
validate();
|
||||||
return new Vault(this);
|
return new Vault(this);
|
||||||
@ -172,6 +185,9 @@ public class Vault implements Serializable {
|
|||||||
if (cloudType == null) {
|
if (cloudType == null) {
|
||||||
throw new IllegalStateException("cloudtype must be set");
|
throw new IllegalStateException("cloudtype must be set");
|
||||||
}
|
}
|
||||||
|
if (position == -1) {
|
||||||
|
throw new IllegalStateException("position must be set");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,10 @@ class CreateVault {
|
|||||||
CloudFolder vaultFolder = cloudContentRepository.folder(folder, vaultName);
|
CloudFolder vaultFolder = cloudContentRepository.folder(folder, vaultName);
|
||||||
vaultFolder = cloudContentRepository.create(vaultFolder);
|
vaultFolder = cloudContentRepository.create(vaultFolder);
|
||||||
cloudRepository.create(vaultFolder, password);
|
cloudRepository.create(vaultFolder, password);
|
||||||
return vaultRepository.store(aVault().thatIsNew().withNamePathAndCloudFrom(vaultFolder).build());
|
return vaultRepository.store(aVault() //
|
||||||
|
.thatIsNew() //
|
||||||
|
.withNamePathAndCloudFrom(vaultFolder) //
|
||||||
|
.withPosition(vaultRepository.vaults().size()) //
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import org.cryptomator.domain.repository.VaultRepository;
|
|||||||
import org.cryptomator.generator.Parameter;
|
import org.cryptomator.generator.Parameter;
|
||||||
import org.cryptomator.generator.UseCase;
|
import org.cryptomator.generator.UseCase;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@UseCase
|
@UseCase
|
||||||
class DeleteVault {
|
class DeleteVault {
|
||||||
|
|
||||||
@ -18,7 +20,12 @@ class DeleteVault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Long execute() throws BackendException {
|
public Long execute() throws BackendException {
|
||||||
return vaultRepository.delete(vault);
|
Long vaultId = vaultRepository.delete(vault);
|
||||||
|
|
||||||
|
List<Vault> reorderVaults = MoveVaultHelper.Companion.reorderVaults(vaultRepository);
|
||||||
|
MoveVaultHelper.Companion.updateVaultsInDatabase(reorderVaults, vaultRepository);
|
||||||
|
|
||||||
|
return vaultId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<Vault> {
|
||||||
|
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<Vault>) : List<Vault> {
|
||||||
|
for (i in 0 until vaults.size) {
|
||||||
|
vaults[i] = Vault.aCopyOf(vaults[i]).withPosition(i).build()
|
||||||
|
}
|
||||||
|
return vaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reorderVaults(vaultRepository: VaultRepository) : List<Vault> {
|
||||||
|
val vaults = vaultRepository.vaults()
|
||||||
|
vaults.sortWith(VaultComparator())
|
||||||
|
return reorderVaults(vaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateVaultsInDatabase(vaults: List<Vault>, vaultRepository: VaultRepository): List<Vault> {
|
||||||
|
vaults.forEach { vault -> vaultRepository.store(vault) }
|
||||||
|
return vaultRepository.vaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class VaultComparator : Comparator<Vault> {
|
||||||
|
override fun compare(o1: Vault, o2: Vault): Int {
|
||||||
|
return o1.position - o2.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Vault> execute() throws BackendException {
|
||||||
|
List<Vault> vaults = MoveVaultHelper.Companion.updateVaultPosition(fromPosition, toPosition, vaultRepository);
|
||||||
|
return MoveVaultHelper.Companion.updateVaultsInDatabase(vaults, vaultRepository);
|
||||||
|
}
|
||||||
|
}
|
@ -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<Vault>
|
||||||
|
private lateinit var unorderedVaults: ArrayList<Vault>
|
||||||
|
|
||||||
|
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<Vault>()
|
||||||
|
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<Vault>()
|
||||||
|
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<Vault>()
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
21
fastlane/.default.env
Normal file
@ -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=
|
2
fastlane/Appfile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
json_key_file(ENV["GOOGLE_PLAYSTORE_PRIVATE_KEY_FILE_PATH"])
|
||||||
|
package_name("org.cryptomator")
|
183
fastlane/Fastfile
Normal file
@ -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
|
6
fastlane/Pluginfile
Normal file
@ -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'
|
49
fastlane/README.md
Normal file
@ -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).
|
4
fastlane/latest_versions/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
4
fastlane/metadata/android/de-DE/changelogs/default.txt
Normal file
@ -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
|
39
fastlane/metadata/android/de-DE/full_description.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
Mit Cryptomator liegt der Schlüssel zu deinen Daten bei dir. Durch Cryptomator verschlüsselst du deine Daten schnell und unkompliziert. Anschließend lädst du sie geschützt in deinen Lieblingscloudservice hoch.
|
||||||
|
|
||||||
|
<b>EINFACH</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>KOMPATIBEL</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>SICHER</b>
|
||||||
|
|
||||||
|
Du musst Cryptomator nicht blind vertrauen, denn <a href="https://github.com/cryptomator/android">die App ist quelloffen</a>, 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
|
||||||
|
|
||||||
|
<b>PREISGEKRÖNT</b>
|
||||||
|
|
||||||
|
Cryptomator wurde mit dem <b>CeBIT Innovation Award 2016 for Usable Security and Privacy</b> ausgezeichnet. Wir freuen uns, hunderttausenden Cryptomator-Nutzern Sicherheit und Privatsphärenschutz bieten zu können.
|
||||||
|
|
||||||
|
<b>CRYPTOMATOR COMMUNITY</b>
|
||||||
|
|
||||||
|
Tritt der Cryptomator Community bei und tausche dich mit Cryptomator-Nutzern aus: <a href="https://community.cryptomator.org">https://community.cryptomator.org</a>
|
||||||
|
|
||||||
|
• Folge uns auf Twitter <a href="https://twitter.com/Cryptomator">@Cryptomator</a>
|
||||||
|
• Like uns auf Facebook <a href="https://facebook.com/Cryptomator">/Cryptomator</a>
|
1
fastlane/metadata/android/de-DE/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Nimm die Sicherung deiner Cloud-Daten selbst in die Hand
|
1
fastlane/metadata/android/de-DE/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Cryptomator
|
0
fastlane/metadata/android/de-DE/video.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/default.txt
Normal file
@ -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
|
39
fastlane/metadata/android/en-US/full_description.txt
Normal file
@ -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.
|
||||||
|
|
||||||
|
<b>EASY-TO-USE</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>COMPATIBLE</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>SECURE</b>
|
||||||
|
|
||||||
|
You don't have to trust Cryptomator blindly, because <a href="https://github.com/cryptomator/android">it is open source software</a>. 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
|
||||||
|
|
||||||
|
<b>AWARD-WINNING</b>
|
||||||
|
|
||||||
|
Cryptomator received the <b>CeBIT Innovation Award 2016 for Usable Security and Privacy</b>. We're proud to provide security and privacy for hundreds of thousands of Cryptomator users.
|
||||||
|
|
||||||
|
<b>CRYPTOMATOR COMMUNITY</b>
|
||||||
|
|
||||||
|
Join the <a href="https://community.cryptomator.org">Cryptomator Community</a> and participate in the conversations with other Cryptomator users.
|
||||||
|
|
||||||
|
• Follow us on Twitter <a href="https://twitter.com/Cryptomator">@Cryptomator</a>
|
||||||
|
• Like us on Facebook <a href="https://facebook.com/Cryptomator">/Cryptomator</a>
|
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Put a lock on your cloud: Take the security of your data into your own hands
|
1
fastlane/metadata/android/en-US/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Cryptomator
|
0
fastlane/metadata/android/en-US/video.txt
Normal file
39
fastlane/metadata/android/fr-FR/full_description.txt
Normal file
@ -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.
|
||||||
|
|
||||||
|
<b>SIMPLICITÉ</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>COMPATIBILITÉ</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>SÉCURITÉ</b>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<b>UNE GÉNIALITUDE GÉNÉRAL</b>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<b>LA COMMUNAUTÉ CRYPTOMATOR</b>
|
||||||
|
|
||||||
|
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
|
1
fastlane/metadata/android/fr-FR/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Verrouillé votre cloud: Prenez en mains la sécurité de vos données
|
1
fastlane/metadata/android/fr-FR/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Cryptomator
|
0
fastlane/metadata/android/fr-FR/video.txt
Normal file
6
fastlane/release-notes.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<ul>
|
||||||
|
<li>Added possibility to sort vault list</li>
|
||||||
|
<li>Added logging to Google drive cloud</li>
|
||||||
|
<li>Enhanced behavior when OneDrive password changed</li>
|
||||||
|
<li>Polished CryptoBot icons</li>
|
||||||
|
</ul>
|
4
fastlane/release/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.77"
|
||||||
|
android:scaleY="0.77"
|
||||||
|
android:translateX="12.42"
|
||||||
|
android:translateY="12.42">
|
||||||
|
<path android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
presentation/src/debug/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
BIN
presentation/src/debug/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
presentation/src/debug/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
BIN
presentation/src/debug/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
presentation/src/debug/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 6.7 KiB |
BIN
presentation/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
presentation/src/debug/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 11 KiB |
BIN
presentation/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
BIN
presentation/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#F1C40F</color>
|
||||||
|
</resources>
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="app_name" translatable="false">Debug Cryptomator</string>
|
|
||||||
|
|
||||||
</resources>
|
|
@ -112,7 +112,9 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun failAuthentication(cloudName: Int) {
|
private fun failAuthentication(cloudName: Int) {
|
||||||
|
activity().runOnUiThread {
|
||||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName)))
|
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName)))
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.presentation.model
|
package org.cryptomator.presentation.model
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import org.cryptomator.domain.CloudType
|
import org.cryptomator.domain.CloudType
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
|
|
||||||
@ -23,13 +22,13 @@ enum class CloudTypeModel(builder: Builder) {
|
|||||||
LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) //
|
LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) //
|
||||||
.withCloudImageResource(R.drawable.storage_type_local) //
|
.withCloudImageResource(R.drawable.storage_type_local) //
|
||||||
.withCloudImageLargeResource(R.drawable.storage_type_local_large) //
|
.withCloudImageLargeResource(R.drawable.storage_type_local_large) //
|
||||||
.withMultiInstancesIfLollipopOrLater());
|
.withMultiInstances());
|
||||||
|
|
||||||
val cloudName: String
|
val cloudName: String = builder.cloudName
|
||||||
val displayNameResource: Int
|
val displayNameResource: Int = builder.displayNameResource
|
||||||
val cloudImageResource: Int
|
val cloudImageResource: Int = builder.cloudImageResource
|
||||||
val cloudImageLargeResource: Int
|
val cloudImageLargeResource: Int = builder.cloudImageLargeResource
|
||||||
val isMultiInstance: Boolean
|
val isMultiInstance: Boolean = builder.multiInstances
|
||||||
|
|
||||||
private class Builder(val cloudName: String, val displayNameResource: Int) {
|
private class Builder(val cloudName: String, val displayNameResource: Int) {
|
||||||
var cloudImageResource = 0
|
var cloudImageResource = 0
|
||||||
@ -50,11 +49,6 @@ enum class CloudTypeModel(builder: Builder) {
|
|||||||
multiInstances = true
|
multiInstances = true
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withMultiInstancesIfLollipopOrLater(): Builder {
|
|
||||||
multiInstances = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -66,12 +60,4 @@ enum class CloudTypeModel(builder: Builder) {
|
|||||||
return CloudType.valueOf(type.name)
|
return CloudType.valueOf(type.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
cloudName = builder.cloudName
|
|
||||||
displayNameResource = builder.displayNameResource
|
|
||||||
cloudImageResource = builder.cloudImageResource
|
|
||||||
cloudImageLargeResource = builder.cloudImageLargeResource
|
|
||||||
isMultiInstance = builder.multiInstances
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ class VaultModel(private val vault: Vault) : Serializable {
|
|||||||
get() = vault.path
|
get() = vault.path
|
||||||
val isLocked: Boolean
|
val isLocked: Boolean
|
||||||
get() = !vault.isUnlocked
|
get() = !vault.isUnlocked
|
||||||
|
val position: Int
|
||||||
|
get() = vault.position
|
||||||
|
|
||||||
fun toVault(): Vault {
|
fun toVault(): Vault {
|
||||||
return vault
|
return vault
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.cryptomator.presentation.model.comparator
|
||||||
|
|
||||||
|
import org.cryptomator.presentation.model.VaultModel
|
||||||
|
|
||||||
|
class VaultPositionComparator : Comparator<VaultModel> {
|
||||||
|
|
||||||
|
override fun compare(v1: VaultModel, v2: VaultModel): Int {
|
||||||
|
return v1.position - v2.position
|
||||||
|
}
|
||||||
|
}
|
@ -673,23 +673,12 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onExportFileClicked(cloudFile: CloudFileModel, trigger: ExportOperation) {
|
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<CloudNodeModel<*>>, trigger: ExportOperation) {
|
fun onExportNodesClicked(selectedCloudFiles: ArrayList<CloudNodeModel<*>>, trigger: ExportOperation) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
exportNodesToUserSelectedLocation(selectedCloudFiles, trigger)
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
fun exportFileToDownloadDirectory(result: PermissionsResult, fileToExport: CloudFileModel, exportOperation: ExportOperation) {
|
fun exportFileToDownloadDirectory(result: PermissionsResult, fileToExport: CloudFileModel, exportOperation: ExportOperation) {
|
||||||
|
@ -103,7 +103,7 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun releaseUriPermissionForLocalStorageCloud(cloudModel: LocalStorageModel) {
|
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())
|
releaseUriPermission(cloudModel.uri())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
||||||
private val createNewVaultWorkflow: CreateNewVaultWorkflow, //
|
private val createNewVaultWorkflow: CreateNewVaultWorkflow, //
|
||||||
private val saveVaultUseCase: SaveVaultUseCase, //
|
private val saveVaultUseCase: SaveVaultUseCase, //
|
||||||
|
private val moveVaultPositionUseCase: MoveVaultPositionUseCase, //
|
||||||
private val changePasswordUseCase: ChangePasswordUseCase, //
|
private val changePasswordUseCase: ChangePasswordUseCase, //
|
||||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
||||||
private val licenseCheckUseCase: DoLicenseCheckUseCase, //
|
private val licenseCheckUseCase: DoLicenseCheckUseCase, //
|
||||||
@ -603,6 +604,25 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
view?.showDialog(AppIsObscuredInfoDialog.newInstance())
|
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<List<Vault>>() {
|
||||||
|
override fun onSuccess(vaults: List<Vault>) {
|
||||||
|
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) {
|
fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) {
|
||||||
if (changedVaultPassword) {
|
if (changedVaultPassword) {
|
||||||
changedVaultPassword = false
|
changedVaultPassword = false
|
||||||
@ -690,6 +710,7 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
lockVaultUseCase, //
|
lockVaultUseCase, //
|
||||||
getVaultListUseCase, //
|
getVaultListUseCase, //
|
||||||
saveVaultUseCase, //
|
saveVaultUseCase, //
|
||||||
|
moveVaultPositionUseCase, //
|
||||||
removeStoredVaultPasswordsUseCase, //
|
removeStoredVaultPasswordsUseCase, //
|
||||||
unlockVaultUseCase, //
|
unlockVaultUseCase, //
|
||||||
prepareUnlockUseCase, //
|
prepareUnlockUseCase, //
|
||||||
|
@ -34,7 +34,7 @@ class AutoUploadNotification(private val context: Context, private val amountOfP
|
|||||||
|
|
||||||
this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) //
|
this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) //
|
||||||
.setContentTitle(context.getString(R.string.notification_auto_upload_title)) //
|
.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)) //
|
.setColor(getColor(R.color.colorPrimary)) //
|
||||||
.addAction(cancelNowAction())
|
.addAction(cancelNowAction())
|
||||||
.setGroup(NOTIFICATION_GROUP_KEY)
|
.setGroup(NOTIFICATION_GROUP_KEY)
|
||||||
|
@ -35,7 +35,7 @@ class OpenWritableFileNotification(private val context: Context, private val uri
|
|||||||
this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) //
|
this.builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) //
|
||||||
.setContentTitle(context.getString(R.string.notification_open_writable_file_title)) //
|
.setContentTitle(context.getString(R.string.notification_open_writable_file_title)) //
|
||||||
.setContentText(context.getString(R.string.notification_open_writable_file_message)) //
|
.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)) //
|
.setColor(getColor(R.color.colorPrimary)) //
|
||||||
.setGroup(NOTIFICATION_GROUP_KEY)
|
.setGroup(NOTIFICATION_GROUP_KEY)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
@ -7,12 +7,13 @@ import android.app.job.JobScheduler
|
|||||||
import android.app.job.JobService
|
import android.app.job.JobService
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.MergeCursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
import android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
import org.cryptomator.domain.exception.FatalBackendException
|
||||||
import org.cryptomator.presentation.R
|
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.MimeTypeMap_Factory
|
||||||
import org.cryptomator.util.file.MimeTypes
|
import org.cryptomator.util.file.MimeTypes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.String.format
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
class PhotoContentJob : JobService() {
|
class PhotoContentJob : JobService() {
|
||||||
|
|
||||||
private val mHandler = Handler()
|
private val handler = Handler()
|
||||||
private val mWorker: Runnable = Runnable {
|
private val worker: Runnable = Runnable {
|
||||||
scheduleJob(applicationContext)
|
scheduleJob(applicationContext)
|
||||||
jobFinished(mRunningParams, false)
|
jobFinished(runningParams, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mRunningParams: JobParameters
|
private lateinit var runningParams: JobParameters
|
||||||
|
|
||||||
override fun onStartJob(params: JobParameters): Boolean {
|
override fun onStartJob(params: JobParameters): Boolean {
|
||||||
Timber.tag("PhotoContentJob").i("Job started!")
|
Timber.tag("PhotoContentJob").i("Job started!")
|
||||||
|
|
||||||
val fileUtil = FileUtil(baseContext, MimeTypes(MimeTypeMap_Factory.newInstance()))
|
val fileUtil = FileUtil(baseContext, MimeTypes(MimeTypeMap_Factory.newInstance()))
|
||||||
|
|
||||||
mRunningParams = params
|
runningParams = params
|
||||||
if (params.triggeredContentAuthorities != null) {
|
|
||||||
|
params.triggeredContentAuthorities?.let {
|
||||||
if (params.triggeredContentUris != null) {
|
if (params.triggeredContentUris != null) {
|
||||||
val ids = getIds(params)
|
val ids = getIds(params)
|
||||||
if (ids != null && ids.size > 0) {
|
if (ids != null && ids.isNotEmpty()) {
|
||||||
val selection = buildSelection(ids)
|
val selection = buildSelection(ids)
|
||||||
var cursor: Cursor? = null
|
contentResolver.query(EXTERNAL_CONTENT_URI, PROJECTION, selection, null, null).use { externalCursor ->
|
||||||
try {
|
contentResolver.query(INTERNAL_CONTENT_URI, PROJECTION, selection, null, null).use { internalCursor ->
|
||||||
cursor = contentResolver.query(EXTERNAL_CONTENT_URI, PROJECTION, selection, null, null)
|
MergeCursor(arrayOf(externalCursor, internalCursor)).use { cursor ->
|
||||||
cursor?.let {
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val dir = cursor.getString(PROJECTION_DATA)
|
|
||||||
try {
|
try {
|
||||||
|
val dir = cursor.getString(PROJECTION_DATA)
|
||||||
fileUtil.addImageToAutoUploads(dir)
|
fileUtil.addImageToAutoUploads(dir)
|
||||||
Timber.tag("PhotoContentJob").i("Added file to UploadList")
|
Timber.tag("PhotoContentJob").i("Added file to UploadList")
|
||||||
Timber.tag("PhotoContentJob").d(format("Added file to UploadList %s", dir))
|
Timber.tag("PhotoContentJob").d(String.format("Added file to UploadList %s", dir))
|
||||||
} catch (e: FatalBackendException) {
|
} catch (e: FatalBackendException) {
|
||||||
Timber.tag("PhotoContentJob").e(e, "Failed to add image to auto upload list")
|
Timber.tag("PhotoContentJob").e(e, "Failed to add image to auto upload list")
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: Timber.tag("PhotoContentJob").e("Error: no access to media!")
|
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
Timber.tag("PhotoContentJob").e("Error: no access to media!")
|
Timber.tag("PhotoContentJob").e(e, "No access to storage")
|
||||||
} finally {
|
|
||||||
cursor?.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.tag("PhotoContentJob").d("ids are null or 0: %s", ids)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.tag("PhotoContentJob").w("Photos rescan needed!")
|
Timber.tag("PhotoContentJob").w("Photos rescan needed!")
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIds(params: JobParameters): ArrayList<String>? {
|
private fun getIds(params: JobParameters): Set<String>? {
|
||||||
return params.triggeredContentUris
|
return params.triggeredContentUris
|
||||||
?.map { it.pathSegments }
|
?.map { it.pathSegments }
|
||||||
?.filter { it != null && it.size == EXTERNAL_PATH_SEGMENTS.size + 1 }
|
?.filter { it != null && (it.size == EXTERNAL_CONTENT_URI.pathSegments.size + 1 || it.size == INTERNAL_CONTENT_URI.pathSegments.size + 1) }
|
||||||
?.mapTo(ArrayList()) { it[it.size - 1] }
|
?.mapTo(HashSet()) { it[it.size - 1] }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSelection(ids: ArrayList<String>): String {
|
private fun buildSelection(ids: Set<String>): String {
|
||||||
val selection = StringBuilder()
|
val selection = StringBuilder()
|
||||||
ids.indices.forEach { i ->
|
ids.indices.forEach { i ->
|
||||||
if (selection.isNotEmpty()) {
|
if (selection.isNotEmpty()) {
|
||||||
@ -94,7 +92,7 @@ class PhotoContentJob : JobService() {
|
|||||||
}
|
}
|
||||||
selection.append(MediaStore.Images.ImageColumns._ID)
|
selection.append(MediaStore.Images.ImageColumns._ID)
|
||||||
selection.append("='")
|
selection.append("='")
|
||||||
selection.append(ids[i])
|
selection.append(ids.elementAt(i))
|
||||||
selection.append("'")
|
selection.append("'")
|
||||||
}
|
}
|
||||||
return selection.toString()
|
return selection.toString()
|
||||||
@ -102,7 +100,7 @@ class PhotoContentJob : JobService() {
|
|||||||
|
|
||||||
override fun onStopJob(params: JobParameters): Boolean {
|
override fun onStopJob(params: JobParameters): Boolean {
|
||||||
Timber.tag("PhotoContentJob").i("onStopJob called, must stop, reschedule later")
|
Timber.tag("PhotoContentJob").i("onStopJob called, must stop, reschedule later")
|
||||||
mHandler.removeCallbacks(mWorker)
|
handler.removeCallbacks(worker)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +112,6 @@ class PhotoContentJob : JobService() {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val MEDIA_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/")
|
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 val PROJECTION = arrayOf(MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA)
|
||||||
|
|
||||||
internal const val PROJECTION_DATA = 1
|
internal const val PROJECTION_DATA = 1
|
||||||
@ -125,6 +122,7 @@ class PhotoContentJob : JobService() {
|
|||||||
init {
|
init {
|
||||||
val builder = JobInfo.Builder(PHOTOS_CONTENT_JOB, ComponentName(ResourceHelper.getString(R.string.app_id), PhotoContentJob::class.java.name))
|
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(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))
|
builder.addTriggerContentUri(JobInfo.TriggerContentUri(MEDIA_URI, FLAG_NOTIFY_FOR_DESCENDANTS))
|
||||||
jobInfo = builder.build()
|
jobInfo = builder.build()
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class UnlockedNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) //
|
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)) //
|
.setColor(ResourceHelper.Companion.getColor(R.color.colorPrimary)) //
|
||||||
.addAction(lockNowAction()) //
|
.addAction(lockNowAction()) //
|
||||||
.setGroup(NOTIFICATION_GROUP_KEY) //
|
.setGroup(NOTIFICATION_GROUP_KEY) //
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.presentation.ui.activity
|
package org.cryptomator.presentation.ui.activity
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.view.View.*
|
import android.view.View.*
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@ -91,10 +90,8 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupStatusBar() {
|
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) {
|
private fun setupToolbar(index: Int) {
|
||||||
updateTitle(index)
|
updateTitle(index)
|
||||||
@ -131,10 +128,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou
|
|||||||
var newUiOptions = window.decorView.systemUiVisibility
|
var newUiOptions = window.decorView.systemUiVisibility
|
||||||
newUiOptions = newUiOptions or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
newUiOptions = newUiOptions or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
newUiOptions = newUiOptions xor SYSTEM_UI_FLAG_FULLSCREEN
|
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
|
window.decorView.systemUiVisibility = newUiOptions
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,14 @@ class VaultListActivity : BaseActivity(), //
|
|||||||
return biometricAuthentication?.stoppedBiometricAuthDuringCloudAuthentication() == true
|
return biometricAuthentication?.stoppedBiometricAuthDuringCloudAuthentication() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun rowMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
vaultListFragment().rowMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun vaultMoved(vaults: List<VaultModel>) {
|
||||||
|
vaultListFragment().vaultMoved(vaults)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showVaultSettingsDialog(vaultModel: VaultModel) {
|
override fun showVaultSettingsDialog(vaultModel: VaultModel) {
|
||||||
val vaultSettingDialog = //
|
val vaultSettingDialog = //
|
||||||
SettingsVaultBottomSheet.newInstance(vaultModel)
|
SettingsVaultBottomSheet.newInstance(vaultModel)
|
||||||
|
@ -23,5 +23,7 @@ interface VaultListView : View {
|
|||||||
fun isVaultLocked(vaultModel: VaultModel): Boolean
|
fun isVaultLocked(vaultModel: VaultModel): Boolean
|
||||||
fun cancelBasicAuthIfRunning()
|
fun cancelBasicAuthIfRunning()
|
||||||
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
|
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
|
||||||
|
fun rowMoved(fromPosition: Int, toPosition: Int)
|
||||||
|
fun vaultMoved(vaults: List<VaultModel>)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,18 @@ package org.cryptomator.presentation.ui.adapter
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
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.R
|
||||||
import org.cryptomator.presentation.model.VaultModel
|
import org.cryptomator.presentation.model.VaultModel
|
||||||
|
import org.cryptomator.presentation.model.comparator.VaultPositionComparator
|
||||||
import org.cryptomator.presentation.ui.adapter.BiometricAuthSettingsAdapter.BiometricAuthSettingsViewHolder
|
import org.cryptomator.presentation.ui.adapter.BiometricAuthSettingsAdapter.BiometricAuthSettingsViewHolder
|
||||||
import javax.inject.Inject
|
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 //
|
class BiometricAuthSettingsAdapter //
|
||||||
@Inject
|
@Inject
|
||||||
constructor() : RecyclerViewBaseAdapter<VaultModel, BiometricAuthSettingsAdapter.OnVaultBiometricAuthSettingsChanged, BiometricAuthSettingsViewHolder>() {
|
constructor() : RecyclerViewBaseAdapter<VaultModel, BiometricAuthSettingsAdapter.OnVaultBiometricAuthSettingsChanged, BiometricAuthSettingsViewHolder>(VaultPositionComparator()) {
|
||||||
|
|
||||||
private var onVaultBiometricAuthSettingsChanged: OnVaultBiometricAuthSettingsChanged? = null
|
private var onVaultBiometricAuthSettingsChanged: OnVaultBiometricAuthSettingsChanged? = null
|
||||||
|
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
package org.cryptomator.presentation.ui.adapter
|
package org.cryptomator.presentation.ui.adapter
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import kotlinx.android.synthetic.main.item_shareable_location.view.*
|
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.model.VaultModel
|
import org.cryptomator.presentation.model.VaultModel
|
||||||
|
import org.cryptomator.presentation.model.comparator.VaultPositionComparator
|
||||||
import org.cryptomator.presentation.ui.adapter.SharedLocationsAdapter.VaultViewHolder
|
import org.cryptomator.presentation.ui.adapter.SharedLocationsAdapter.VaultViewHolder
|
||||||
import javax.inject.Inject
|
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
|
class SharedLocationsAdapter @Inject
|
||||||
constructor() : RecyclerViewBaseAdapter<VaultModel, SharedLocationsAdapter.Callback, VaultViewHolder>() {
|
constructor() : RecyclerViewBaseAdapter<VaultModel, SharedLocationsAdapter.Callback, VaultViewHolder>(VaultPositionComparator()) {
|
||||||
|
|
||||||
private var selectedVault: VaultModel? = null
|
private var selectedVault: VaultModel? = null
|
||||||
private var selectedLocation: String? = null
|
private var selectedLocation: String? = null
|
||||||
|
@ -1,21 +1,31 @@
|
|||||||
package org.cryptomator.presentation.ui.adapter
|
package org.cryptomator.presentation.ui.adapter
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import kotlinx.android.synthetic.main.item_vault.view.*
|
|
||||||
import org.cryptomator.presentation.R
|
import org.cryptomator.presentation.R
|
||||||
import org.cryptomator.presentation.model.VaultModel
|
import org.cryptomator.presentation.model.VaultModel
|
||||||
|
import org.cryptomator.presentation.model.comparator.VaultPositionComparator
|
||||||
import org.cryptomator.presentation.ui.adapter.VaultsAdapter.VaultViewHolder
|
import org.cryptomator.presentation.ui.adapter.VaultsAdapter.VaultViewHolder
|
||||||
import javax.inject.Inject
|
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
|
class VaultsAdapter @Inject
|
||||||
internal constructor() : RecyclerViewBaseAdapter<VaultModel, VaultsAdapter.OnItemClickListener, VaultViewHolder>() {
|
internal constructor() : RecyclerViewBaseAdapter<VaultModel, VaultsAdapter.OnItemInteractionListener, VaultViewHolder>(VaultPositionComparator()), VaultsMoveListener.Listener {
|
||||||
|
|
||||||
|
interface OnItemInteractionListener {
|
||||||
|
|
||||||
interface OnItemClickListener {
|
|
||||||
fun onVaultClicked(vaultModel: VaultModel)
|
fun onVaultClicked(vaultModel: VaultModel)
|
||||||
|
|
||||||
fun onVaultSettingsClicked(vaultModel: VaultModel)
|
fun onVaultSettingsClicked(vaultModel: VaultModel)
|
||||||
|
|
||||||
fun onVaultLockClicked(vaultModel: VaultModel)
|
fun onVaultLockClicked(vaultModel: VaultModel)
|
||||||
|
|
||||||
|
fun onRowMoved(fromPosition: Int, toPosition: Int)
|
||||||
|
|
||||||
|
fun onVaultMoved(fromPosition: Int, toPosition: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemLayout(viewType: Int): Int {
|
override fun getItemLayout(viewType: Int): Int {
|
||||||
@ -65,4 +75,12 @@ internal constructor() : RecyclerViewBaseAdapter<VaultModel, VaultsAdapter.OnIte
|
|||||||
itemView.settings.setOnClickListener { callback.onVaultSettingsClicked(vaultModel) }
|
itemView.settings.setOnClickListener { callback.onVaultSettingsClicked(vaultModel) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onVaultMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
callback.onVaultMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
callback.onRowMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package org.cryptomator.presentation.ui.adapter
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
class VaultsMoveListener(val adapter: VaultsAdapter) : ItemTouchHelper.Callback() {
|
||||||
|
|
||||||
|
var dragFrom = -1
|
||||||
|
var dragTo = -1
|
||||||
|
|
||||||
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||||
|
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
|
return makeMovementFlags(dragFlags, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isItemViewSwipeEnabled(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isLongPressDragEnabled(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||||
|
val fromPosition = viewHolder.adapterPosition
|
||||||
|
val toPosition = target.adapterPosition
|
||||||
|
|
||||||
|
if (dragFrom == -1) {
|
||||||
|
dragFrom = fromPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragTo = toPosition;
|
||||||
|
|
||||||
|
adapter.onRowMoved(viewHolder.adapterPosition, target.adapterPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder)
|
||||||
|
|
||||||
|
if (dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
|
||||||
|
adapter.onVaultMoved(dragFrom, dragTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
dragTo = -1
|
||||||
|
dragFrom = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onRowMoved(fromPosition: Int, toPosition: Int)
|
||||||
|
fun onVaultMoved(fromPosition: Int, toPosition: Int)
|
||||||
|
}
|
||||||
|
}
|
@ -179,11 +179,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
val strDate: String = dateFormatUser.format(lastUpdateCheck)
|
val strDate: String = dateFormatUser.format(lastUpdateCheck)
|
||||||
format(getString(R.string.screen_settings_last_check_updates), strDate)
|
format(getString(R.string.screen_settings_last_check_updates), strDate)
|
||||||
} else {
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
getString(R.string.screen_settings_last_check_updates_never)
|
getString(R.string.screen_settings_last_check_updates_never)
|
||||||
} else {
|
|
||||||
getString(R.string.screen_settings_last_check_updates_never_pre_marshmallow)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val date = SpannableString(readableDate)
|
val date = SpannableString(readableDate)
|
||||||
|
@ -8,6 +8,7 @@ import org.cryptomator.presentation.R
|
|||||||
import org.cryptomator.presentation.model.CloudFolderModel
|
import org.cryptomator.presentation.model.CloudFolderModel
|
||||||
import org.cryptomator.presentation.model.SharedFileModel
|
import org.cryptomator.presentation.model.SharedFileModel
|
||||||
import org.cryptomator.presentation.model.VaultModel
|
import org.cryptomator.presentation.model.VaultModel
|
||||||
|
import org.cryptomator.presentation.model.comparator.VaultPositionComparator
|
||||||
import org.cryptomator.presentation.presenter.SharedFilesPresenter
|
import org.cryptomator.presentation.presenter.SharedFilesPresenter
|
||||||
import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter
|
import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter
|
||||||
import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter.Callback
|
import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter.Callback
|
||||||
@ -69,9 +70,10 @@ class SharedFilesFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun displayVaults(vaults: List<VaultModel>?) {
|
fun displayVaults(vaults: List<VaultModel>?) {
|
||||||
if (vaults?.isNotEmpty() == true) {
|
val sortedVaults = vaults?.sortedWith(VaultPositionComparator())
|
||||||
presenter.selectedVault?.let { presenter.selectedVault = vaults[vaults.indexOf(it)] }
|
if (sortedVaults?.isNotEmpty() == true) {
|
||||||
val preselectedVault = presenter.selectedVault ?: vaults[0]
|
presenter.selectedVault?.let { presenter.selectedVault = sortedVaults[sortedVaults.indexOf(it)] }
|
||||||
|
val preselectedVault = presenter.selectedVault ?: sortedVaults[0]
|
||||||
locationsAdapter.setPreselectedVault(preselectedVault)
|
locationsAdapter.setPreselectedVault(preselectedVault)
|
||||||
presenter.onVaultSelected(preselectedVault)
|
presenter.onVaultSelected(preselectedVault)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.cryptomator.presentation.ui.fragment
|
|||||||
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.fragment_vault_list.*
|
import kotlinx.android.synthetic.main.fragment_vault_list.*
|
||||||
import kotlinx.android.synthetic.main.recycler_view_layout.*
|
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.model.VaultModel
|
||||||
import org.cryptomator.presentation.presenter.VaultListPresenter
|
import org.cryptomator.presentation.presenter.VaultListPresenter
|
||||||
import org.cryptomator.presentation.ui.adapter.VaultsAdapter
|
import org.cryptomator.presentation.ui.adapter.VaultsAdapter
|
||||||
|
import org.cryptomator.presentation.ui.adapter.VaultsMoveListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Fragment(R.layout.fragment_vault_list)
|
@Fragment(R.layout.fragment_vault_list)
|
||||||
@ -22,7 +24,9 @@ class VaultListFragment : BaseFragment() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var vaultsAdapter: VaultsAdapter
|
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) {
|
override fun onVaultClicked(vaultModel: VaultModel) {
|
||||||
vaultListPresenter.onVaultClicked(vaultModel)
|
vaultListPresenter.onVaultClicked(vaultModel)
|
||||||
}
|
}
|
||||||
@ -34,8 +38,17 @@ class VaultListFragment : BaseFragment() {
|
|||||||
override fun onVaultLockClicked(vaultModel: VaultModel) {
|
override fun onVaultLockClicked(vaultModel: VaultModel) {
|
||||||
vaultListPresenter.onVaultLockClicked(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() {
|
override fun setupView() {
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
fab_vault.setOnClickListener { vaultListPresenter.onCreateVaultClicked() }
|
fab_vault.setOnClickListener { vaultListPresenter.onCreateVaultClicked() }
|
||||||
@ -48,6 +61,9 @@ class VaultListFragment : BaseFragment() {
|
|||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
vaultsAdapter.setCallback(onItemClickListener)
|
vaultsAdapter.setCallback(onItemClickListener)
|
||||||
|
touchHelper = ItemTouchHelper(VaultsMoveListener(vaultsAdapter))
|
||||||
|
touchHelper.attachToRecyclerView(recyclerView)
|
||||||
|
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context())
|
recyclerView.layoutManager = LinearLayoutManager(context())
|
||||||
recyclerView.adapter = vaultsAdapter
|
recyclerView.adapter = vaultsAdapter
|
||||||
recyclerView.setHasFixedSize(true) // smoother scrolling
|
recyclerView.setHasFixedSize(true) // smoother scrolling
|
||||||
@ -83,5 +99,14 @@ class VaultListFragment : BaseFragment() {
|
|||||||
vaultsAdapter.addOrUpdateVault(vaultModel)
|
vaultsAdapter.addOrUpdateVault(vaultModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun vaultMoved(vaults: List<VaultModel>) {
|
||||||
|
vaultsAdapter.clear()
|
||||||
|
vaultsAdapter.addAll(vaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rowMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
vaultsAdapter.notifyItemMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
|
|
||||||
fun rootView(): View = coordinatorLayout
|
fun rootView(): View = coordinatorLayout
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import org.cryptomator.domain.CloudFolder;
|
|||||||
import org.cryptomator.domain.Vault;
|
import org.cryptomator.domain.Vault;
|
||||||
import org.cryptomator.domain.di.PerView;
|
import org.cryptomator.domain.di.PerView;
|
||||||
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase;
|
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase;
|
||||||
|
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase;
|
||||||
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase;
|
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase;
|
||||||
import org.cryptomator.generator.Callback;
|
import org.cryptomator.generator.Callback;
|
||||||
import org.cryptomator.presentation.R;
|
import org.cryptomator.presentation.R;
|
||||||
@ -18,6 +19,7 @@ import org.cryptomator.presentation.model.mappers.CloudModelMapper;
|
|||||||
import org.cryptomator.presentation.presenter.VaultListPresenter;
|
import org.cryptomator.presentation.presenter.VaultListPresenter;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ import static org.cryptomator.presentation.intent.Intents.chooseCloudServiceInte
|
|||||||
public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.State> {
|
public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.State> {
|
||||||
|
|
||||||
private final SaveVaultUseCase saveVaultUseCase;
|
private final SaveVaultUseCase saveVaultUseCase;
|
||||||
|
private final GetVaultListUseCase getVaultListUseCase;
|
||||||
private final GetRootFolderUseCase getRootFolderUseCase;
|
private final GetRootFolderUseCase getRootFolderUseCase;
|
||||||
private final CloudModelMapper cloudModelMapper;
|
private final CloudModelMapper cloudModelMapper;
|
||||||
private final AuthenticationExceptionHandler authenticationExceptionHandler;
|
private final AuthenticationExceptionHandler authenticationExceptionHandler;
|
||||||
@ -39,12 +42,14 @@ public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.
|
|||||||
public AddExistingVaultWorkflow( //
|
public AddExistingVaultWorkflow( //
|
||||||
Context context, //
|
Context context, //
|
||||||
SaveVaultUseCase saveVaultUseCase, //
|
SaveVaultUseCase saveVaultUseCase, //
|
||||||
|
GetVaultListUseCase getVaultListUseCase, //
|
||||||
GetRootFolderUseCase getRootFolderUseCase, //
|
GetRootFolderUseCase getRootFolderUseCase, //
|
||||||
CloudModelMapper cloudModelMapper, //
|
CloudModelMapper cloudModelMapper, //
|
||||||
AuthenticationExceptionHandler authenticationExceptionHandler) {
|
AuthenticationExceptionHandler authenticationExceptionHandler) {
|
||||||
super(new State());
|
super(new State());
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.saveVaultUseCase = saveVaultUseCase;
|
this.saveVaultUseCase = saveVaultUseCase;
|
||||||
|
this.getVaultListUseCase = getVaultListUseCase;
|
||||||
this.getRootFolderUseCase = getRootFolderUseCase;
|
this.getRootFolderUseCase = getRootFolderUseCase;
|
||||||
this.cloudModelMapper = cloudModelMapper;
|
this.cloudModelMapper = cloudModelMapper;
|
||||||
this.authenticationExceptionHandler = authenticationExceptionHandler;
|
this.authenticationExceptionHandler = authenticationExceptionHandler;
|
||||||
@ -117,9 +122,13 @@ public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.
|
|||||||
@Override
|
@Override
|
||||||
void completed() {
|
void completed() {
|
||||||
presenter().getView().showProgress(ProgressModel.GENERIC);
|
presenter().getView().showProgress(ProgressModel.GENERIC);
|
||||||
|
getVaultListUseCase.run(presenter().new ProgressCompletingResultHandler<List<Vault>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Vault> vaults) {
|
||||||
saveVaultUseCase//
|
saveVaultUseCase//
|
||||||
.withVault(aVault() //
|
.withVault(aVault() //
|
||||||
.withNamePathAndCloudFrom(state().masterkeyFile.getParent()) //
|
.withNamePathAndCloudFrom(state().masterkeyFile.getParent()) //
|
||||||
|
.withPosition(vaults.size()) //
|
||||||
.thatIsNew() //
|
.thatIsNew() //
|
||||||
.build()) //
|
.build()) //
|
||||||
.run(presenter().new ProgressCompletingResultHandler<Vault>() {
|
.run(presenter().new ProgressCompletingResultHandler<Vault>() {
|
||||||
@ -129,6 +138,8 @@ public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static class State implements Serializable {
|
public static class State implements Serializable {
|
||||||
|
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.77"
|
||||||
|
android:scaleY="0.77"
|
||||||
|
android:translateX="12.42"
|
||||||
|
android:translateY="12.42">
|
||||||
|
<path android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
presentation/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
presentation/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.7 KiB |
BIN
presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 11 KiB |