From ab54805635a607da584e47c8a26ae2a37380006d Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 3 May 2024 16:29:47 +0200 Subject: [PATCH 1/6] Update dependencies --- buildsystem/dependencies.gradle | 31 +++--- data/build.gradle | 14 +-- .../cloud/dropbox/DropboxCloudNodeFactory.kt | 2 +- .../data/cloud/crypto/VaultConfig.kt | 101 ++++++++---------- .../repository/UpdateCheckRepositoryImpl.java | 24 ++--- domain/build.gradle | 4 +- ...UnsupportedMasterkeyLocationException.java | 6 +- .../vaultconfig/VaultConfigLoadException.java | 6 +- .../domain/usecases/DoLicenseCheck.java | 26 +++-- gradle.properties | 1 + presentation/build.gradle | 9 +- 11 files changed, 108 insertions(+), 116 deletions(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index d2f6a1d1c..f920fef07 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -60,18 +60,18 @@ ext { rxAndroidVersion = '2.1.1' rxBindingVersion = '2.2.0' - daggerVersion = '2.45' + daggerVersion = '2.51.1' gsonVersion = '2.10.1' - okHttpVersion = '4.10.0' - okHttpDigestVersion = '3.0' + okHttpVersion = '4.11.0' + okHttpDigestVersion = '3.1.0' velocityVersion = '2.3' timberVersion = '5.0.1' - zxcvbnVersion = '1.7.0' + zxcvbnVersion = '1.9.0' scaleImageViewVersion = '3.10.1-dev.0002' @@ -83,7 +83,7 @@ ext { // cloud provider libs cryptolibVersion = '2.1.2' - dropboxVersion = '5.4.4' + dropboxVersion = '7.0.0' googleApiServicesVersion = 'v3-rev20220508-1.32.1' googlePlayServicesVersion = '19.2.0' @@ -96,17 +96,17 @@ ext { minIoVersion = '8.5.2' pcloudVersion = '1.9.2-dev.0001' staxVersion = '1.2.0' // needed for minIO - - commonsCodecVersion = '1.15' + commonsCodecVersion = '1.17.0' recyclerViewFastScrollVersion = '2.0.1' // testing dependencies - jUnitVersion = '5.9.2' + jUnitVersion = '5.10.2' assertJVersion = '1.7.1' - mockitoVersion = '5.1.1' - mockitoKotlinVersion = '4.1.0' + mockitoVersion = '5.11.0' + mockitoKotlinVersion = '5.3.1' + mockitoInlineVersion = '5.2.0' hamcrestVersion = '1.3' dexmakerVersion = '1.0' espressoVersion = '3.4.0' @@ -128,7 +128,7 @@ ext { androidxTestCoreVersion = '1.4.0' androidxSplashscreenVersion = '1.0.0-rc01' - jsonWebTokenApiVersion = '0.11.5' + jsonWebTokenVersion = '4.4.0' dependencies = [ android : "com.google.android:android:${androidVersion}", @@ -151,7 +151,8 @@ ext { daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}", design : "com.google.android.material:material:${androidMaterialDesignVersion}", coreDesugaring : "com.android.tools:desugar_jdk_libs:${coreDesugaringVersion}", - dropbox : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}", + dropboxCore : "com.dropbox.core:dropbox-core-sdk:${dropboxVersion}", + dropboxAndroid : "com.dropbox.core:dropbox-android-sdk:${dropboxVersion}", espresso : "androidx.test.espresso:espresso-core:${espressoVersion}", googleApiClientAndroid : "com.google.api-client:google-api-client-android:${googleClientVersion}", googleApiServicesDrive : "com.google.apis:google-api-services-drive:${googleApiServicesVersion}", @@ -169,7 +170,7 @@ ext { junit4Engine : "org.junit.vintage:junit-vintage-engine:${jUnitVersion}", minIo : "io.minio:minio:${minIoVersion}", mockito : "org.mockito:mockito-core:${mockitoVersion}", - mockitoInline : "org.mockito:mockito-inline:${mockitoVersion}", + mockitoInline : "org.mockito:mockito-inline:${mockitoInlineVersion}", mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:${mockitoKotlinVersion}", msgraph : "com.microsoft.graph:microsoft-graph:${msgraphVersion}", msgraphAuth : "com.microsoft.identity.client:msal:${msgraphAuthVersion}", @@ -192,9 +193,7 @@ ext { zxcvbn : "com.nulab-inc:zxcvbn:${zxcvbnVersion}", scaleImageView : "com.github.cryptomator:subsampling-scale-image-view:${scaleImageViewVersion}", lruFileCache : "com.github.solkin:disk-lru-cache:${lruFileCacheVersion}", - jsonWebTokenApi : "io.jsonwebtoken:jjwt-api:${jsonWebTokenApiVersion}", - jsonWebTokenImpl : "io.jsonwebtoken:jjwt-impl:${jsonWebTokenApiVersion}", - jsonWebTokenJson : "io.jsonwebtoken:jjwt-orgjson:${jsonWebTokenApiVersion}" + jsonWebToken : "com.auth0:java-jwt:${jsonWebTokenVersion}" ] } diff --git a/data/build.gradle b/data/build.gradle index 3c0f7ec56..6f51f85be 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -118,14 +118,16 @@ dependencies { annotationProcessor dependencies.daggerCompiler implementation dependencies.dagger - api dependencies.jsonWebTokenApi - implementation dependencies.jsonWebTokenImpl - implementation dependencies.jsonWebTokenJson + implementation dependencies.jsonWebToken // cloud - playstoreImplementation dependencies.dropbox - apkstoreImplementation dependencies.dropbox - fdroidImplementation dependencies.dropbox + playstoreImplementation dependencies.dropboxCore + playstoreImplementation dependencies.dropboxAndroid + apkstoreImplementation dependencies.dropboxCore + apkstoreImplementation dependencies.dropboxAndroid + fdroidImplementation dependencies.dropboxCore + fdroidImplementation dependencies.dropboxAndroid + playstoreImplementation dependencies.msgraphAuth apkstoreImplementation dependencies.msgraphAuth diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt index 05c97e749..bce99457b 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt @@ -7,7 +7,7 @@ import com.dropbox.core.v2.files.Metadata internal object DropboxCloudNodeFactory { fun from(parent: DropboxFolder, metadata: FileMetadata): DropboxFile { - return DropboxFile(parent, metadata.name, metadata.pathDisplay, metadata.size, metadata.clientModified) + return DropboxFile(parent, metadata.name, metadata.pathLower!!, metadata.size, metadata.clientModified) } @JvmStatic diff --git a/data/src/main/java/org/cryptomator/data/cloud/crypto/VaultConfig.kt b/data/src/main/java/org/cryptomator/data/cloud/crypto/VaultConfig.kt index d7c082aaf..2a324f142 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/crypto/VaultConfig.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/crypto/VaultConfig.kt @@ -1,24 +1,18 @@ package org.cryptomator.data.cloud.crypto +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import com.auth0.jwt.exceptions.InvalidClaimException +import com.auth0.jwt.exceptions.JWTVerificationException +import com.auth0.jwt.exceptions.SignatureVerificationException +import com.auth0.jwt.interfaces.DecodedJWT import org.cryptomator.cryptolib.api.CryptorProvider import org.cryptomator.domain.UnverifiedVaultConfig import org.cryptomator.domain.exception.vaultconfig.VaultConfigLoadException import org.cryptomator.domain.exception.vaultconfig.VaultKeyInvalidException import org.cryptomator.domain.exception.vaultconfig.VaultVersionMismatchException import java.net.URI -import java.security.Key import java.util.UUID -import io.jsonwebtoken.Claims -import io.jsonwebtoken.IncorrectClaimException -import io.jsonwebtoken.JwsHeader -import io.jsonwebtoken.JwtException -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.MissingClaimException -import io.jsonwebtoken.SignatureAlgorithm -import io.jsonwebtoken.SigningKeyResolverAdapter -import io.jsonwebtoken.security.Keys -import io.jsonwebtoken.security.SignatureException -import kotlin.properties.Delegates class VaultConfig private constructor(builder: VaultConfigBuilder) { @@ -29,14 +23,13 @@ class VaultConfig private constructor(builder: VaultConfigBuilder) { val shorteningThreshold: Int fun toToken(rawKey: ByteArray): String { - return Jwts.builder() - .setHeaderParam(JSON_KEY_ID, keyId.toASCIIString()) // - .setId(id) // - .claim(JSON_KEY_VAULTFORMAT, vaultFormat) // - .claim(JSON_KEY_CIPHERCONFIG, cipherCombo.name) // - .claim(JSON_KEY_SHORTENING_THRESHOLD, shorteningThreshold) // - .signWith(Keys.hmacShaKeyFor(rawKey), SignatureAlgorithm.HS256) // - .compact() + return JWT.create() // + .withKeyId(keyId.toString()) // + .withJWTId(id) // + .withClaim(JSON_KEY_VAULTFORMAT, vaultFormat) // + .withClaim(JSON_KEY_CIPHERCONFIG, cipherCombo.name) // + .withClaim(JSON_KEY_SHORTENING_THRESHOLD, shorteningThreshold) // + .sign(Algorithm.HMAC256(rawKey)) } class VaultConfigBuilder { @@ -87,42 +80,46 @@ class VaultConfig private constructor(builder: VaultConfigBuilder) { @JvmStatic @Throws(VaultConfigLoadException::class) fun decode(token: String): UnverifiedVaultConfig { - val unverifiedSigningKeyResolver = UnverifiedSigningKeyResolver() - - // At this point we can't verify the signature because we don't have the masterkey yet. - try { - Jwts.parserBuilder().setSigningKeyResolver(unverifiedSigningKeyResolver).build().parse(token) - } catch (e: IllegalArgumentException) { - return UnverifiedVaultConfig(token, unverifiedSigningKeyResolver.keyId, unverifiedSigningKeyResolver.vaultFormat) - } - throw VaultConfigLoadException("Failed to load vaultconfig") + val unverifiedJwt = JWT.decode(token) + val vaultFormat = unverifiedJwt.getClaim(JSON_KEY_VAULTFORMAT).asInt() + val keyId = URI.create(unverifiedJwt.keyId) + return UnverifiedVaultConfig(token, keyId, vaultFormat) } @JvmStatic @Throws(VaultKeyInvalidException::class, VaultVersionMismatchException::class, VaultConfigLoadException::class) fun verify(rawKey: ByteArray, unverifiedVaultConfig: UnverifiedVaultConfig): VaultConfig { return try { - val parser = Jwts // - .parserBuilder() // - .setSigningKey(rawKey) // - .require(JSON_KEY_VAULTFORMAT, unverifiedVaultConfig.vaultFormat) // - .build() // - .parseClaimsJws(unverifiedVaultConfig.jwt) + val unverifiedJwt = JWT.decode(unverifiedVaultConfig.jwt) + val verifier = JWT.require(initAlgorithm(rawKey, unverifiedJwt)) // + .withClaim(JSON_KEY_VAULTFORMAT, unverifiedVaultConfig.vaultFormat) // + .build() + val verifiedJwt = verifier.verify(unverifiedJwt) val vaultConfigBuilder = createVaultConfig() // - .keyId(unverifiedVaultConfig.keyId) - .id(parser.header[JSON_KEY_ID] as String) // - .cipherCombo(CryptorProvider.Scheme.valueOf(parser.body.get(JSON_KEY_CIPHERCONFIG, String::class.java))) // - .vaultFormat(unverifiedVaultConfig.vaultFormat) // - .shorteningThreshold(parser.body[JSON_KEY_SHORTENING_THRESHOLD] as Int) + .keyId(URI.create(verifiedJwt.keyId)) // + .id(verifiedJwt.getHeaderClaim(JSON_KEY_ID).asString()) // + .cipherCombo(CryptorProvider.Scheme.valueOf(verifiedJwt.getClaim(JSON_KEY_CIPHERCONFIG).asString())) // + .vaultFormat(verifiedJwt.getClaim(JSON_KEY_VAULTFORMAT).asInt()) // + .shorteningThreshold(verifiedJwt.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt()) // VaultConfig(vaultConfigBuilder) - } catch (e: JwtException) { - when (e) { - is MissingClaimException, is IncorrectClaimException -> throw VaultVersionMismatchException("Vault config not for version " + unverifiedVaultConfig.vaultFormat) - is SignatureException -> throw VaultKeyInvalidException() - else -> throw VaultConfigLoadException(e) - } + } catch (e: SignatureVerificationException) { + throw VaultKeyInvalidException() + } catch (e: InvalidClaimException) { + throw VaultVersionMismatchException("Vault config not for version $unverifiedVaultConfig.vaultFormat") + } catch (e: JWTVerificationException) { + throw VaultConfigLoadException("Failed to verify vault config") + } + } + + @Throws(VaultConfigLoadException::class) + private fun initAlgorithm(rawKey: ByteArray, jwt: DecodedJWT): Algorithm { + return when (val algo = jwt.algorithm) { + "HS256" -> Algorithm.HMAC256(rawKey) + "HS384" -> Algorithm.HMAC384(rawKey) + "HS512" -> Algorithm.HMAC512(rawKey) + else -> throw VaultConfigLoadException("Unsupported signature algorithm: $algo") } } @@ -132,18 +129,6 @@ class VaultConfig private constructor(builder: VaultConfigBuilder) { } } - private class UnverifiedSigningKeyResolver : SigningKeyResolverAdapter() { - - lateinit var keyId: URI - var vaultFormat: Int by Delegates.notNull() - - override fun resolveSigningKey(jwsHeader: JwsHeader<*>, claims: Claims): Key? { - keyId = URI.create(jwsHeader.keyId) - vaultFormat = claims[JSON_KEY_VAULTFORMAT] as Int - return null - } - } - init { id = builder.id keyId = builder.keyId diff --git a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java index 0d0018996..431438e41 100644 --- a/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/UpdateCheckRepositoryImpl.java @@ -3,6 +3,10 @@ import android.content.Context; import android.net.Uri; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; import com.google.common.base.Optional; import com.google.common.io.BaseEncoding; @@ -32,8 +36,6 @@ import javax.inject.Inject; import javax.inject.Singleton; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -263,16 +265,14 @@ private class LatestVersion { LatestVersion(String json) throws GeneralUpdateErrorException { try { - Claims jws = Jwts // - .parserBuilder().setSigningKey(getPublicKey()) // - .build() // - .parseClaimsJws(json) // - .getBody(); - - version = jws.get("version", String.class); - urlApk = jws.get("url", String.class); - apkSha256 = jws.get("apk_sha_256", String.class); - urlReleaseNote = jws.get("release_notes", String.class); + Algorithm algorithm = Algorithm.ECDSA256(getPublicKey(), null); + JWTVerifier verifier = JWT.require(algorithm).build(); + DecodedJWT jwt = verifier.verify(json); + + version = jwt.getClaim("version").asString(); + urlApk = jwt.getClaim("url").asString(); + apkSha256 = jwt.getClaim("apk_sha_256").asString(); + urlReleaseNote = jwt.getClaim("release_notes").asString(); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new GeneralUpdateErrorException("Failed to parse latest version", e); } diff --git a/domain/build.gradle b/domain/build.gradle index 2d896054d..00a11254c 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -54,9 +54,7 @@ dependencies { implementation dependencies.appcompat - api dependencies.jsonWebTokenApi - implementation dependencies.jsonWebTokenImpl - implementation dependencies.jsonWebTokenJson + implementation dependencies.jsonWebToken // test testImplementation dependencies.junit diff --git a/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/UnsupportedMasterkeyLocationException.java b/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/UnsupportedMasterkeyLocationException.java index 12c8b0d00..cae8834d4 100644 --- a/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/UnsupportedMasterkeyLocationException.java +++ b/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/UnsupportedMasterkeyLocationException.java @@ -1,10 +1,10 @@ package org.cryptomator.domain.exception.vaultconfig; +import com.auth0.jwt.exceptions.JWTVerificationException; + import org.cryptomator.domain.UnverifiedVaultConfig; import org.cryptomator.domain.exception.BackendException; -import io.jsonwebtoken.JwtException; - public class UnsupportedMasterkeyLocationException extends BackendException { UnverifiedVaultConfig unverifiedVaultConfig; @@ -13,7 +13,7 @@ public UnsupportedMasterkeyLocationException(UnverifiedVaultConfig unverifiedVau this.unverifiedVaultConfig = unverifiedVaultConfig; } - public UnsupportedMasterkeyLocationException(String message, JwtException e) { + public UnsupportedMasterkeyLocationException(String message, JWTVerificationException e) { super(message, e); } } diff --git a/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/VaultConfigLoadException.java b/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/VaultConfigLoadException.java index 4fe159f29..088bfa708 100644 --- a/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/VaultConfigLoadException.java +++ b/domain/src/main/java/org/cryptomator/domain/exception/vaultconfig/VaultConfigLoadException.java @@ -1,8 +1,8 @@ package org.cryptomator.domain.exception.vaultconfig; -import org.cryptomator.domain.exception.BackendException; +import com.auth0.jwt.exceptions.JWTVerificationException; -import io.jsonwebtoken.JwtException; +import org.cryptomator.domain.exception.BackendException; public class VaultConfigLoadException extends BackendException { @@ -10,7 +10,7 @@ public VaultConfigLoadException(String message) { super(message); } - public VaultConfigLoadException(String message, JwtException e) { + public VaultConfigLoadException(String message, JWTVerificationException e) { super(message, e); } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java b/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java index dd624e061..b36dc0a82 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java @@ -1,5 +1,10 @@ package org.cryptomator.domain.usecases; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; import com.google.common.io.BaseEncoding; import org.cryptomator.domain.exception.BackendException; @@ -18,11 +23,6 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.SignatureException; - @UseCase public class DoLicenseCheck { @@ -45,10 +45,12 @@ public class DoLicenseCheck { public LicenseCheck execute() throws BackendException { license = useLicenseOrRetrieveFromDb(license); try { - final Claims claims = Jwts.parserBuilder().setSigningKey(getPublicKey(ANDROID_PUB_KEY)).build().parseClaimsJws(license).getBody(); - return claims::getSubject; - } catch (JwtException | FatalBackendException e) { - if (e instanceof SignatureException && isDesktopSupporterCertificate(license)) { + Algorithm algorithm = Algorithm.ECDSA512(getPublicKey(ANDROID_PUB_KEY), null); + JWTVerifier verifier = JWT.require(algorithm).build(); + DecodedJWT jwt = verifier.verify(license); + return jwt::getSubject; + } catch (SignatureVerificationException | FatalBackendException e) { + if (e instanceof SignatureVerificationException && isDesktopSupporterCertificate(license)) { throw new DesktopSupporterCertificateException(license); } throw new LicenseNotValidException(license); @@ -81,9 +83,11 @@ private ECPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmExcepti private boolean isDesktopSupporterCertificate(String license) { try { - Jwts.parserBuilder().setSigningKey(getPublicKey(DESKTOP_SUPPORTER_CERTIFICATE_PUB_KEY)).build().parseClaimsJws(license); + Algorithm algorithm = Algorithm.ECDSA512(getPublicKey(DESKTOP_SUPPORTER_CERTIFICATE_PUB_KEY), null); + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(license); return true; - } catch (JwtException | NoSuchAlgorithmException | InvalidKeySpecException e) { + } catch (SignatureVerificationException | NoSuchAlgorithmException | InvalidKeySpecException e) { return false; } } diff --git a/gradle.properties b/gradle.properties index 605acdb7f..14a5b6a7c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true android.enableJetifier=true +android.jetifier.ignorelist=jackson-core,fastdoubleparser kapt.incremental.apt=false diff --git a/presentation/build.gradle b/presentation/build.gradle index 2c99bc67d..00566795a 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -161,9 +161,12 @@ dependencies { implementation dependencies.androidxBiometric // cloud - playstoreImplementation dependencies.dropbox - apkstoreImplementation dependencies.dropbox - fdroidImplementation dependencies.dropbox + playstoreImplementation dependencies.dropboxCore + playstoreImplementation dependencies.dropboxAndroid + apkstoreImplementation dependencies.dropboxCore + apkstoreImplementation dependencies.dropboxAndroid + fdroidImplementation dependencies.dropboxCore + fdroidImplementation dependencies.dropboxAndroid playstoreImplementation dependencies.msgraphAuth apkstoreImplementation dependencies.msgraphAuth From f82f151a3aa59d370d3b2636493258ec9dacc955 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 22 May 2024 17:23:45 +0200 Subject: [PATCH 2/6] Update dependencies and migrate to Jetpack view binding Kotlin upgrade was required due to some dependencies that uses OkHttp. This Kotlin upgrade required Gradle upgrade which required migration to Jetpack view binding. As we touched a lot of the UI code due to the migration, we now use as ID always snake case and not camel case and snake case mixed. --- build.gradle | 8 +- buildsystem/Dockerfile | 8 +- buildsystem/dependencies.gradle | 14 +- buildsystem/docker/dependencies.txt | 2 +- buildsystem/docker/sources.list | 2 +- data/build.gradle | 4 +- .../MasterkeyCryptoCloudProviderTest.kt | 2 +- domain/build.gradle | 4 +- .../domain/usecases/DoLicenseCheck.java | 3 +- generator-api/build.gradle | 4 +- .../org/cryptomator/generator/Activity.java | 2 - .../org/cryptomator/generator/Dialog.java | 2 - .../org/cryptomator/generator/Fragment.java | 2 - generator/build.gradle | 4 +- .../generator/ActivityProcessor.java | 2 +- .../generator/CallbackProcessor.java | 2 +- .../generator/FragmentProcessor.java | 2 +- .../generator/InstanceStateProcessor.java | 2 +- .../generator/IntentProcessor.java | 2 +- .../generator/UseCaseProcessor.java | 2 +- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 29 ++-- gradlew.bat | 20 +-- presentation/build.gradle | 14 +- .../presentation/model/ImagePreviewFile.kt | 2 +- .../presenter/CloudConnectionListPresenter.kt | 3 +- .../ui/activity/AuthenticateCloudActivity.kt | 8 +- .../ui/activity/AuthenticatePCloudActivity.kt | 3 +- .../activity/AutoUploadChooseVaultActivity.kt | 14 +- .../AutoUploadRefreshTokenActivity.kt | 6 +- .../presentation/ui/activity/BaseActivity.kt | 30 ++-- .../activity/BiometricAuthSettingsActivity.kt | 12 +- .../ui/activity/BrowseFilesActivity.kt | 18 +-- .../ui/activity/ChooseCloudServiceActivity.kt | 12 +- .../activity/CloudConnectionListActivity.kt | 10 +- .../ui/activity/CloudSettingsActivity.kt | 10 +- .../ui/activity/CreateVaultActivity.kt | 23 ++- .../activity/CryptomatorVariantsActivity.kt | 39 ++---- .../ui/activity/ImagePreviewActivity.kt | 37 +++-- .../ui/activity/LicenseCheckActivity.kt | 17 +-- .../ui/activity/LicensesActivity.kt | 10 +- .../ui/activity/S3AddOrChangeActivity.kt | 10 +- .../ui/activity/SetPasswordActivity.kt | 8 +- .../ui/activity/SettingsActivity.kt | 10 +- .../ui/activity/SharedFilesActivity.kt | 10 +- .../ui/activity/TextEditorActivity.kt | 10 +- .../ui/activity/UnlockVaultActivity.kt | 7 +- .../ui/activity/VaultListActivity.kt | 15 +- .../ui/activity/WebDavAddOrChangeActivity.kt | 10 +- .../adapter/BiometricAuthSettingsAdapter.kt | 28 ++-- .../ui/adapter/BrowseFilesAdapter.kt | 131 ++++++++---------- .../ui/adapter/CloudConnectionListAdapter.kt | 50 ++++--- .../ui/adapter/CloudSettingsAdapter.kt | 38 ++--- .../presentation/ui/adapter/CloudsAdapter.kt | 26 ++-- .../ui/adapter/RecyclerViewBaseAdapter.java | 19 +-- .../ui/adapter/SharedFilesAdapter.kt | 33 +++-- .../ui/adapter/SharedLocationsAdapter.kt | 51 ++++--- .../presentation/ui/adapter/VaultsAdapter.kt | 39 +++--- .../ui/bottomsheet/AddVaultBottomSheet.kt | 12 +- .../ui/bottomsheet/BaseBottomSheet.kt | 8 +- .../CloudConnectionSettingsBottomSheet.kt | 40 +++--- .../ui/bottomsheet/FileSettingsBottomSheet.kt | 32 ++--- .../bottomsheet/FolderSettingsBottomSheet.kt | 26 ++-- .../bottomsheet/SettingsVaultBottomSheet.kt | 26 ++-- .../VaultContentActionBottomSheet.kt | 15 +- .../ui/dialog/AppIsObscuredInfoDialog.kt | 8 +- .../ui/dialog/AskForLockScreenDialog.kt | 8 +- .../AskIgnoreBatteryOptimizationsDialog.kt | 11 +- .../ui/dialog/AssignSslCertificateDialog.kt | 22 ++- .../presentation/ui/dialog/BaseDialog.kt | 22 +-- .../ui/dialog/BaseProgressErrorDialog.kt | 34 +++-- .../ui/dialog/BetaConfirmationDialog.kt | 5 +- .../BiometricAuthKeyInvalidatedDialog.kt | 5 +- .../ui/dialog/ChangePasswordDialog.kt | 59 ++++---- .../ui/dialog/CloudNodeRenameDialog.kt | 47 ++++--- .../ui/dialog/ConfirmDeleteCloudNodeDialog.kt | 10 +- .../ui/dialog/CreateFolderDialog.kt | 37 +++-- .../ui/dialog/DebugModeDisclaimerDialog.kt | 5 +- .../DeleteCloudConnectionWithVaultsDialog.kt | 5 +- .../DisableAppWhenObscuredDisclaimerDialog.kt | 8 +- .../DisableSecureScreenDisclaimerDialog.kt | 8 +- .../ui/dialog/EnrollSystemBiometricDialog.kt | 5 +- .../ui/dialog/EnterPasswordDialog.kt | 35 +++-- .../ui/dialog/ExistingFileDialog.kt | 8 +- .../ui/dialog/ExportCloudFilesDialog.kt | 26 +++- .../presentation/ui/dialog/FileNameDialog.kt | 37 +++-- .../ui/dialog/FileTypeNotSupportedDialog.kt | 5 +- .../ui/dialog/GenericProgressDialog.kt | 12 +- .../ui/dialog/LicenseConfirmationDialog.kt | 8 +- .../MicrosoftWorkaroundDisclaimerDialog.kt | 5 +- .../ui/dialog/NoDirFileOrEmptyDialog.kt | 8 +- .../ui/dialog/NotEnoughVaultsDialog.kt | 17 ++- .../dialog/PCloudCredentialsUpdatedDialog.kt | 9 +- .../presentation/ui/dialog/SymLinkDialog.kt | 8 +- .../ui/dialog/UpdateAppAvailableDialog.kt | 25 +++- .../presentation/ui/dialog/UpdateAppDialog.kt | 27 +++- .../ui/dialog/UpdateLicenseDialog.kt | 44 ++++-- .../ui/dialog/UploadCloudFileDialog.kt | 29 ++-- .../dialog/VaultDeleteConfirmationDialog.kt | 5 +- .../dialog/VaultIsRootFolderOfCloudDialog.kt | 5 +- .../ui/dialog/VaultRenameDialog.kt | 37 +++-- .../VaultsRemovedDuringMigrationDialog.kt | 9 +- .../ui/dialog/WebDavAskForHttpDialog.kt | 8 +- .../fragment/AutoUploadChooseVaultFragment.kt | 16 +-- .../presentation/ui/fragment/BaseFragment.kt | 10 +- .../fragment/BiometricAuthSettingsFragment.kt | 33 ++--- .../ui/fragment/BrowseFilesFragment.kt | 71 +++++----- .../ui/fragment/ChooseCloudServiceFragment.kt | 13 +- .../fragment/CloudConnectionListFragment.kt | 23 ++- .../ui/fragment/CloudSettingsFragment.kt | 13 +- .../ui/fragment/ImagePreviewFragment.kt | 19 +-- .../ui/fragment/S3AddOrChangeFragment.kt | 43 +++--- .../ui/fragment/SetPasswordFragment.kt | 29 ++-- .../ui/fragment/SettingsFragment.kt | 2 +- .../ui/fragment/SharedFilesFragment.kt | 23 ++- .../ui/fragment/TextEditorFragment.kt | 21 ++- .../ui/fragment/UnlockVaultFragment.kt | 6 +- .../ui/fragment/VaultListFragment.kt | 30 ++-- .../ui/fragment/WebDavAddOrChangeFragment.kt | 30 ++-- .../main/res/layout/activity_create_vault.xml | 8 +- .../layout/activity_cryptomator_variants.xml | 67 ++++----- .../src/main/res/layout/activity_empty.xml | 2 +- .../res/layout/activity_image_preview.xml | 16 +-- .../src/main/res/layout/activity_layout.xml | 8 +- .../layout/activity_layout_obscure_aware.xml | 8 +- .../src/main/res/layout/activity_licenses.xml | 5 +- .../src/main/res/layout/activity_settings.xml | 5 +- .../main/res/layout/activity_unlock_vault.xml | 4 +- .../main/res/layout/content_create_vault.xml | 4 +- .../src/main/res/layout/dialog_app_update.xml | 4 +- .../dialog_bottom_sheet_cloud_settings.xml | 4 +- .../dialog_bottom_sheet_file_settings.xml | 2 +- .../dialog_bottom_sheet_folder_settings.xml | 2 +- .../res/layout/dialog_change_password.xml | 10 +- .../main/res/layout/dialog_create_folder.xml | 2 + .../main/res/layout/dialog_enter_license.xml | 8 +- .../main/res/layout/dialog_enter_password.xml | 6 +- .../src/main/res/layout/dialog_file_name.xml | 6 +- .../res/layout/dialog_generic_progress.xml | 1 + .../layout/dialog_handle_ssl_certificate.xml | 2 +- .../src/main/res/layout/dialog_rename.xml | 6 +- .../main/res/layout/dialog_upload_loading.xml | 2 + .../layout/floating_action_button_layout.xml | 2 +- .../fragment_auto_upload_choose_vault.xml | 4 +- .../fragment_browse_cloud_connections.xml | 7 +- .../main/res/layout/fragment_browse_files.xml | 15 +- .../layout/fragment_choose_cloud_service.xml | 8 +- .../res/layout/fragment_cloud_settings.xml | 4 +- .../res/layout/fragment_image_preview.xml | 4 +- .../main/res/layout/fragment_set_password.xml | 11 +- .../fragment_settings_biometric_auth.xml | 9 +- .../src/main/res/layout/fragment_setup_s3.xml | 14 +- .../main/res/layout/fragment_setup_webdav.xml | 8 +- .../main/res/layout/fragment_shared_files.xml | 6 +- .../main/res/layout/fragment_text_editor.xml | 6 +- .../main/res/layout/fragment_vault_list.xml | 12 +- .../res/layout/item_biometric_auth_vault.xml | 10 +- .../item_browse_cloud_model_connections.xml | 9 +- .../res/layout/item_browse_files_node.xml | 16 ++- .../src/main/res/layout/item_cloud.xml | 8 +- .../main/res/layout/item_cloud_setting.xml | 10 +- .../res/layout/item_shareable_location.xml | 18 +-- .../src/main/res/layout/item_shared_files.xml | 4 +- .../src/main/res/layout/item_vault.xml | 16 +-- .../main/res/layout/recycler_view_layout.xml | 2 +- ...ew_browses_files_extra_text_and_button.xml | 10 +- .../layout/view_cloud_connection_content.xml | 4 +- .../res/layout/view_cloud_file_content.xml | 11 +- .../res/layout/view_cloud_file_progress.xml | 2 +- .../res/layout/view_cloud_folder_content.xml | 6 +- .../view_dialog_intermediate_progress.xml | 6 +- .../layout/view_empty_cloud_connections.xml | 2 +- .../src/main/res/layout/view_empty_folder.xml | 2 +- .../view_password_strength_indicator.xml | 6 +- .../res/layout/view_receive_save_button.xml | 4 +- .../src/main/res/layout/view_retry.xml | 2 +- .../res/layout/view_vault_creation_hint.xml | 2 +- presentation/src/main/res/xml/preferences.xml | 2 +- util/build.gradle | 4 +- .../org/cryptomator/util/ExceptionUtil.java | 11 +- .../util/SharedPreferencesHandler.kt | 2 +- 183 files changed, 1335 insertions(+), 1224 deletions(-) diff --git a/build.gradle b/build.gradle index bf48a96c0..3c04bf51c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,14 @@ apply from: 'buildsystem/dependencies.gradle' -apply plugin: "com.vanniktech.android.junit.jacoco" buildscript { - ext.kotlin_version = '1.7.20' + ext.kotlin_version = '1.9.24' repositories { mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0' - classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0' + classpath 'com.android.tools.build:gradle:8.4.1' + classpath 'org.greenrobot:greendao-gradle-plugin:3.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1" } diff --git a/buildsystem/Dockerfile b/buildsystem/Dockerfile index 9c1b1bf0a..41aad9c18 100644 --- a/buildsystem/Dockerfile +++ b/buildsystem/Dockerfile @@ -19,9 +19,9 @@ RUN apt-get update -y && apt-get install -y apt-utils wget git unzip RUN apt-get update -y && apt-get install -y $(cat docker/dependencies.txt) RUN docker/print-versions.sh docker/dependencies.txt -ENV ANDROID_COMMAND_LINE_TOOLS_FILENAME commandlinetools-linux-9477386_latest.zip -ENV ANDROID_API_LEVELS android-33 -ENV ANDROID_BUILD_TOOLS_VERSION 33.0.2 +ENV ANDROID_COMMAND_LINE_TOOLS_FILENAME commandlinetools-linux-10406996_latest.zip +ENV ANDROID_API_LEVELS android-34 +ENV ANDROID_BUILD_TOOLS_VERSION 34.0.0 ENV ANDROID_HOME /usr/local/android-sdk-linux ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/cmdline-tools/bin @@ -35,6 +35,6 @@ RUN yes | sdkmanager --update --sdk_root="${ANDROID_HOME}" RUN yes | sdkmanager --sdk_root="${ANDROID_HOME}" "platforms;${ANDROID_API_LEVELS}" "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" RUN yes | sdkmanager --licenses --sdk_root="${ANDROID_HOME}" -RUN update-java-alternatives -s java-1.11.0-openjdk-amd64 +RUN update-java-alternatives -s java-1.17.0-openjdk-amd64 RUN rm -rf ${ANDROID_HOME}/tools diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index f920fef07..387a69409 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -37,10 +37,10 @@ allprojects { } ext { - androidBuildToolsVersion = "33.0.2" + androidBuildToolsVersion = "34.0.0" androidMinSdkVersion = 26 - androidTargetSdkVersion = 33 - androidCompileSdkVersion = 33 + androidTargetSdkVersion = 34 + androidCompileSdkVersion = 34 // android and java libs androidVersion = '4.1.1.4' @@ -62,9 +62,9 @@ ext { daggerVersion = '2.51.1' - gsonVersion = '2.10.1' + gsonVersion = '2.11.0' - okHttpVersion = '4.11.0' + okHttpVersion = '4.12.0' okHttpDigestVersion = '3.1.0' velocityVersion = '2.3' @@ -93,7 +93,7 @@ ext { msgraphVersion = '5.47.0' msgraphAuthVersion = '4.0.5' // contains com.microsoft.identity:common lib which added opentelemetry in 9.0.0, do we need to fork another lib before updating to >=4.2.0 ??? - minIoVersion = '8.5.2' + minIoVersion = '8.5.10' pcloudVersion = '1.9.2-dev.0001' staxVersion = '1.2.0' // needed for minIO commonsCodecVersion = '1.17.0' @@ -104,7 +104,7 @@ ext { jUnitVersion = '5.10.2' assertJVersion = '1.7.1' - mockitoVersion = '5.11.0' + mockitoVersion = '5.12.0' mockitoKotlinVersion = '5.3.1' mockitoInlineVersion = '5.2.0' hamcrestVersion = '1.3' diff --git a/buildsystem/docker/dependencies.txt b/buildsystem/docker/dependencies.txt index d184c22c2..29371773d 100644 --- a/buildsystem/docker/dependencies.txt +++ b/buildsystem/docker/dependencies.txt @@ -1 +1 @@ -openjdk-11-jdk=11.0.16+8-1 +openjdk-17-jdk=17.0.11+9-1 diff --git a/buildsystem/docker/sources.list b/buildsystem/docker/sources.list index 37218b643..b7a8e6ecd 100644 --- a/buildsystem/docker/sources.list +++ b/buildsystem/docker/sources.list @@ -1,4 +1,4 @@ # From https://github.com/signalapp/Signal-Android #deb http://snapshot.debian.org/archive/debian-security/20220816T110409Z/ bullseye/updates main -deb http://snapshot.debian.org/archive/debian/20220816T041409Z/ unstable main +deb http://snapshot.debian.org/archive/debian/20240522T144944Z/ unstable main diff --git a/data/build.gradle b/data/build.gradle index 6f51f85be..f28de6329 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -20,8 +20,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 coreLibraryDesugaringEnabled true } diff --git a/data/src/test/java/org/cryptomator/data/cloud/crypto/MasterkeyCryptoCloudProviderTest.kt b/data/src/test/java/org/cryptomator/data/cloud/crypto/MasterkeyCryptoCloudProviderTest.kt index 67d3bb4de..bc668c8c8 100644 --- a/data/src/test/java/org/cryptomator/data/cloud/crypto/MasterkeyCryptoCloudProviderTest.kt +++ b/data/src/test/java/org/cryptomator/data/cloud/crypto/MasterkeyCryptoCloudProviderTest.kt @@ -57,7 +57,7 @@ internal class MasterkeyCryptoCloudProviderTest { private val masterkeyV7 = "{ \"version\": 7, \"scryptSalt\": \"AAAAAAAAAAA=\", \"scryptCostParam\": 32768, \"scryptBlockSize\": 8, \"primaryMasterKey\": \"D2kc+xBoAcVY+M7s74YBEy6l7ga2+Nz+HS5o0TQY3JMW1uQ5jTlLIQ==\", \"hmacMasterKey\": \"D2kc+xBoAcVY+M7s74YBEy6l7ga2+Nz+HS5o0TQY3JMW1uQ5jTlLIQ==\", \"versionMac\": \"cn2sAK6l9p1/w9deJVUuW3h7br056mpv5srvALiYw+g=\"}" private val vaultConfig = - "eyJraWQiOiJtYXN0ZXJrZXlmaWxlOm1hc3RlcmtleS5jcnlwdG9tYXRvciIsImFsZyI6IkhTMjU2In0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwiY2lwaGVyQ29tYm8iOiJTSVZfR0NNIn0.-tDU6GyH7Iv-LAOnFdhpxei2Qyd7DLbx4hfY9Wywc_Y" + "eyJraWQiOiJtYXN0ZXJrZXlmaWxlOm1hc3RlcmtleS5jcnlwdG9tYXRvciIsImFsZyI6IkhTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiIiLCJmb3JtYXQiOjgsImNpcGhlckNvbWJvIjoiU0lWX0dDTSIsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMH0.HHMkCqK-kFv_TXAhrzgp8vuPhws_2NswhaAHB9hkKBs" private var context: Context = mock() private var cloud: Cloud = mock() diff --git a/domain/build.gradle b/domain/build.gradle index 00a11254c..7b76c4b8e 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -19,8 +19,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 coreLibraryDesugaringEnabled true } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java b/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java index b36dc0a82..c9497a4af 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/DoLicenseCheck.java @@ -2,6 +2,7 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; @@ -49,7 +50,7 @@ public LicenseCheck execute() throws BackendException { JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(license); return jwt::getSubject; - } catch (SignatureVerificationException | FatalBackendException e) { + } catch (SignatureVerificationException | JWTDecodeException | FatalBackendException e) { if (e instanceof SignatureVerificationException && isDesktopSupporterCertificate(license)) { throw new DesktopSupporterCertificateException(license); } diff --git a/generator-api/build.gradle b/generator-api/build.gradle index c560571c9..45cabb4c4 100644 --- a/generator-api/build.gradle +++ b/generator-api/build.gradle @@ -1,9 +1,9 @@ apply plugin: 'java' //noinspection GroovyUnusedAssignment -sourceCompatibility = 1.8 +sourceCompatibility = 17 //noinspection GroovyUnusedAssignment -targetCompatibility = 1.8 +targetCompatibility = 17 dependencies { def dependencies = rootProject.ext.dependencies diff --git a/generator-api/src/main/java/org/cryptomator/generator/Activity.java b/generator-api/src/main/java/org/cryptomator/generator/Activity.java index 3dfc56bf5..5a9393bab 100644 --- a/generator-api/src/main/java/org/cryptomator/generator/Activity.java +++ b/generator-api/src/main/java/org/cryptomator/generator/Activity.java @@ -10,8 +10,6 @@ @Target(TYPE) public @interface Activity { - int layout() default -1; - boolean secure() default true; } diff --git a/generator-api/src/main/java/org/cryptomator/generator/Dialog.java b/generator-api/src/main/java/org/cryptomator/generator/Dialog.java index 72afe2f55..757b3e5b0 100644 --- a/generator-api/src/main/java/org/cryptomator/generator/Dialog.java +++ b/generator-api/src/main/java/org/cryptomator/generator/Dialog.java @@ -10,8 +10,6 @@ @Target(TYPE) public @interface Dialog { - int value(); - boolean secure() default true; } diff --git a/generator-api/src/main/java/org/cryptomator/generator/Fragment.java b/generator-api/src/main/java/org/cryptomator/generator/Fragment.java index 946fb24a5..34532098d 100644 --- a/generator-api/src/main/java/org/cryptomator/generator/Fragment.java +++ b/generator-api/src/main/java/org/cryptomator/generator/Fragment.java @@ -10,6 +10,4 @@ @Target(TYPE) public @interface Fragment { - int value(); - } diff --git a/generator/build.gradle b/generator/build.gradle index 3dc33c03f..9fffdf15e 100644 --- a/generator/build.gradle +++ b/generator/build.gradle @@ -1,9 +1,9 @@ apply plugin: 'java' //noinspection GroovyUnusedAssignment -sourceCompatibility = 1.8 +sourceCompatibility = 17 //noinspection GroovyUnusedAssignment -targetCompatibility = 1.8 +targetCompatibility = 17 repositories { mavenCentral() diff --git a/generator/src/main/java/org/cryptomator/generator/ActivityProcessor.java b/generator/src/main/java/org/cryptomator/generator/ActivityProcessor.java index 92d41342d..7c99c56b6 100644 --- a/generator/src/main/java/org/cryptomator/generator/ActivityProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/ActivityProcessor.java @@ -18,7 +18,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.Activity") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class ActivityProcessor extends BaseProcessor { @Override diff --git a/generator/src/main/java/org/cryptomator/generator/CallbackProcessor.java b/generator/src/main/java/org/cryptomator/generator/CallbackProcessor.java index d31155d13..09b692a48 100644 --- a/generator/src/main/java/org/cryptomator/generator/CallbackProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/CallbackProcessor.java @@ -22,7 +22,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.Callback") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class CallbackProcessor extends BaseProcessor { @Override diff --git a/generator/src/main/java/org/cryptomator/generator/FragmentProcessor.java b/generator/src/main/java/org/cryptomator/generator/FragmentProcessor.java index 8eb902cac..8ab84be61 100644 --- a/generator/src/main/java/org/cryptomator/generator/FragmentProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/FragmentProcessor.java @@ -18,7 +18,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.Fragment") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class FragmentProcessor extends BaseProcessor { @Override diff --git a/generator/src/main/java/org/cryptomator/generator/InstanceStateProcessor.java b/generator/src/main/java/org/cryptomator/generator/InstanceStateProcessor.java index 1d9d93ed8..86b55b446 100644 --- a/generator/src/main/java/org/cryptomator/generator/InstanceStateProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/InstanceStateProcessor.java @@ -19,7 +19,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.InstanceState") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class InstanceStateProcessor extends BaseProcessor { @Override diff --git a/generator/src/main/java/org/cryptomator/generator/IntentProcessor.java b/generator/src/main/java/org/cryptomator/generator/IntentProcessor.java index afdbb95d8..28d31a290 100644 --- a/generator/src/main/java/org/cryptomator/generator/IntentProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/IntentProcessor.java @@ -22,7 +22,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.Intent") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class IntentProcessor extends BaseProcessor { @Override diff --git a/generator/src/main/java/org/cryptomator/generator/UseCaseProcessor.java b/generator/src/main/java/org/cryptomator/generator/UseCaseProcessor.java index ef8b10d72..9d2321fbd 100644 --- a/generator/src/main/java/org/cryptomator/generator/UseCaseProcessor.java +++ b/generator/src/main/java/org/cryptomator/generator/UseCaseProcessor.java @@ -15,7 +15,7 @@ import javax.tools.JavaFileObject; @SupportedAnnotationTypes("org.cryptomator.generator.UseCase") -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_17) public class UseCaseProcessor extends BaseProcessor { @Override diff --git a/gradle.properties b/gradle.properties index 14a5b6a7c..5db8c731d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,3 +3,6 @@ android.useAndroidX=true android.enableJetifier=true android.jetifier.ignorelist=jackson-core,fastdoubleparser kapt.incremental.apt=false +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 508322917..a80b22ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/presentation/build.gradle b/presentation/build.gradle index 00566795a..25a82f613 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-parcelize' apply plugin: 'de.mannodermaus.android-junit5' if (!liteFlavor()) { @@ -38,12 +38,16 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 coreLibraryDesugaringEnabled true } + buildFeatures { + viewBinding true + } + buildTypes { release { crunchPngs false @@ -269,10 +273,6 @@ configurations { all*.exclude group: 'com.google.guava', module: 'listenablefuture' } -androidExtensions { - experimental = true -} - def liteFlavor() { gradle.startParameter.taskNames.stream().filter(t -> t.toLowerCase().contains("lite")).findAny().isPresent() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/ImagePreviewFile.kt b/presentation/src/main/java/org/cryptomator/presentation/model/ImagePreviewFile.kt index 7431c0f94..97616e16c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/ImagePreviewFile.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/ImagePreviewFile.kt @@ -3,7 +3,7 @@ package org.cryptomator.presentation.model import android.annotation.SuppressLint import android.net.Uri import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @SuppressLint("ParcelCreator") diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index 4403af7e3..6c3f205b3 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -34,6 +34,7 @@ import org.cryptomator.util.ExceptionUtil import org.cryptomator.util.crypto.CredentialCryptor import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject +import kotlin.jvm.optionals.getOrNull import timber.log.Timber @PerView @@ -136,7 +137,7 @@ class CloudConnectionListPresenter @Inject constructor( // OnedriveAuthentication.getAuthenticatedOnedriveCloud(activity(), { cloud -> saveOnedriveCloud(cloud) }, { e -> - ExceptionUtil.extract(e, NetworkConnectionException::class.java).orNull()?.let { showError(it) } ?: showError(e) + ExceptionUtil.extract(e, NetworkConnectionException::class.java).getOrNull()?.let { showError(it) } ?: showError(e) }) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticateCloudActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticateCloudActivity.kt index 4fd756c05..fad8f3548 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticateCloudActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticateCloudActivity.kt @@ -3,18 +3,16 @@ package org.cryptomator.presentation.ui.activity import org.cryptomator.domain.WebDavCloud import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent -import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityEmptyBinding import org.cryptomator.presentation.intent.AuthenticateCloudIntent import org.cryptomator.presentation.presenter.AuthenticateCloudPresenter import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView import org.cryptomator.presentation.ui.dialog.AssignSslCertificateDialog - import java.security.cert.X509Certificate - import javax.inject.Inject -@Activity(layout = R.layout.activity_empty) -class AuthenticateCloudActivity : BaseActivity(), +@Activity +class AuthenticateCloudActivity : BaseActivity(ActivityEmptyBinding::inflate), AuthenticateCloudView, AssignSslCertificateDialog.Callback { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticatePCloudActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticatePCloudActivity.kt index 59b610eeb..de7cce9a4 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticatePCloudActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AuthenticatePCloudActivity.kt @@ -7,12 +7,13 @@ import android.widget.Toast import org.cryptomator.generator.Activity import org.cryptomator.presentation.BuildConfig import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.presenter.CloudConnectionListPresenter import java.util.TreeMap import timber.log.Timber @Activity -class AuthenticatePCloudActivity : BaseActivity() { +class AuthenticatePCloudActivity : BaseActivity(ActivityLayoutBinding::inflate) { private val startAuthenticationRequestCode = 1232 private val redirectTimeoutAfterAuthenticationAndResumed = 1000L diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadChooseVaultActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadChooseVaultActivity.kt index f218e0f15..184274803 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadChooseVaultActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadChooseVaultActivity.kt @@ -3,6 +3,7 @@ package org.cryptomator.presentation.ui.activity import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.model.CloudFolderModel import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.presenter.AutoUploadChooseVaultPresenter @@ -10,10 +11,9 @@ import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView import org.cryptomator.presentation.ui.dialog.NotEnoughVaultsDialog import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class AutoUploadChooseVaultActivity : BaseActivity(), // +class AutoUploadChooseVaultActivity : BaseActivity(ActivityLayoutBinding::inflate), // AutoUploadChooseVaultView, // NotEnoughVaultsDialog.Callback { @@ -25,8 +25,8 @@ class AutoUploadChooseVaultActivity : BaseActivity(), // } private fun setupToolbar() { - toolbar.title = getString(R.string.screen_settings_auto_photo_upload_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_settings_auto_photo_upload_title) + setSupportActionBar(binding.mtToolbar.toolbar) supportActionBar?.let { it.setDisplayHomeAsUpEnabled(true) it.setHomeAsUpIndicator(R.drawable.ic_clear) @@ -50,8 +50,8 @@ class AutoUploadChooseVaultActivity : BaseActivity(), // override fun displayDialogUnableToUploadFiles() { NotEnoughVaultsDialog // - .withContext(this) // - .andTitle(getString(R.string.dialog_unable_to_auto_upload_files_title)) // + .withContext(context()) // + .andTitle(R.string.dialog_unable_to_auto_upload_files_title) // .show() } @@ -70,5 +70,5 @@ class AutoUploadChooseVaultActivity : BaseActivity(), // autoUploadChooseVaultFragment().showChosenLocation(location) } - private fun autoUploadChooseVaultFragment(): AutoUploadChooseVaultFragment = getCurrentFragment(R.id.fragmentContainer) as AutoUploadChooseVaultFragment + private fun autoUploadChooseVaultFragment(): AutoUploadChooseVaultFragment = getCurrentFragment(R.id.fragment_container) as AutoUploadChooseVaultFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadRefreshTokenActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadRefreshTokenActivity.kt index 1fce32ea3..4401c92ae 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadRefreshTokenActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/AutoUploadRefreshTokenActivity.kt @@ -3,14 +3,14 @@ package org.cryptomator.presentation.ui.activity import android.os.Bundle import org.cryptomator.domain.exception.authentication.AuthenticationException import org.cryptomator.generator.Activity -import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityEmptyBinding import org.cryptomator.presentation.presenter.AutoUploadRefreshTokenPresenter import org.cryptomator.presentation.ui.activity.view.AutoUploadRefreshTokenView import javax.inject.Inject import timber.log.Timber -@Activity(layout = R.layout.activity_empty) -class AutoUploadRefreshTokenActivity : BaseActivity(), AutoUploadRefreshTokenView { +@Activity +class AutoUploadRefreshTokenActivity : BaseActivity(ActivityEmptyBinding::inflate), AutoUploadRefreshTokenView { @Inject lateinit var presenter: AutoUploadRefreshTokenPresenter diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BaseActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BaseActivity.kt index 25f4c3ba3..fc725e351 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BaseActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BaseActivity.kt @@ -6,6 +6,7 @@ import android.content.res.Configuration import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.os.Bundle +import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.WindowManager @@ -14,6 +15,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import org.cryptomator.generator.Activity import org.cryptomator.presentation.BuildConfig @@ -37,7 +39,7 @@ import javax.inject.Inject import kotlin.reflect.KClass import timber.log.Timber -abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnRequestPermissionsResultCallback, HasComponent { +abstract class BaseActivity(val bindingFactory: (LayoutInflater) -> VB) : AppCompatActivity(), View, ActivityCompat.OnRequestPermissionsResultCallback, HasComponent { @Inject lateinit var exceptionMappings: ExceptionHandlers @@ -45,6 +47,8 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques @Inject lateinit var sharedPreferencesHandler: SharedPreferencesHandler + protected val binding: VB by lazy { bindingFactory(layoutInflater) } + private var activityComponent: ActivityComponent? = null private var presenter: Presenter<*>? = null @@ -70,7 +74,7 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques this.activityComponent = initializeDagger() javaClass.getAnnotation(Activity::class.java)?.let { - setContentView(getContentLayout(it)) + setContentView(binding.root) Activities.setIntent(this) this.presenter = Activities.initializePresenter(this) @@ -107,7 +111,7 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques logLifecycleAsInfo("onResume") // not using android extensions to access activityRootVIew because the view might be from different layouts with different type - findViewById(R.id.activityRootView)?.filterTouchesWhenObscured = sharedPreferencesHandler.disableAppWhenObscured() + findViewById(R.id.activity_root_view)?.filterTouchesWhenObscured = sharedPreferencesHandler.disableAppWhenObscured() val config = javaClass.getAnnotation(Activity::class.java) if (config?.secure == true && sharedPreferencesHandler.secureScreen() && !BuildConfig.DEBUG) { @@ -152,14 +156,6 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques presenter?.destroy() } - private fun getContentLayout(config: Activity): Int { - return if (config.layout == -1) { - R.layout.activity_layout - } else { - config.layout - } - } - private fun initializeDagger(): ActivityComponent { val activityComponent = DaggerActivityComponent.builder() .applicationComponent(applicationComponent) @@ -171,7 +167,7 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques private fun createAndAddFragment() { val fragment = createFragment() - fragment?.let { addFragment(R.id.fragmentContainer, it) } + fragment?.let { addFragment(R.id.fragment_container, it) } } private fun afterIntentInjected() { @@ -222,7 +218,7 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques internal fun replaceFragment(fragment: Fragment, fragmentAnimation: FragmentAnimation, addToBackStack: Boolean = true) { val transaction = supportFragmentManager.beginTransaction() transaction.setCustomAnimations(fragmentAnimation.enter, fragmentAnimation.exit, fragmentAnimation.popEnter, fragmentAnimation.popExit) - transaction.replace(R.id.fragmentContainer, fragment) + transaction.replace(R.id.fragment_container, fragment) if (addToBackStack) { transaction.addToBackStack(null) } @@ -315,9 +311,9 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques } internal open fun snackbarView(): android.view.View { - return activity().findViewById(R.id.locationsRecyclerView) as android.view.View? - ?: activity().findViewById(R.id.rlChooseCloudService) as android.view.View? - ?: return activity().findViewById(R.id.coordinatorLayout) + return activity().findViewById(R.id.locations_recycler_view) + ?: activity().findViewById(R.id.rl_choose_cloud_service) + ?: return activity().findViewById(R.id.coordinator_layout) } internal fun getCurrentFragment(fragmentContainer: Int): Fragment? = supportFragmentManager.findFragmentById(fragmentContainer) @@ -374,7 +370,7 @@ abstract class BaseActivity : AppCompatActivity(), View, ActivityCompat.OnReques Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } - internal enum class FragmentAnimation constructor( + internal enum class FragmentAnimation( val enter: Int, val exit: Int, val popEnter: Int, diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BiometricAuthSettingsActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BiometricAuthSettingsActivity.kt index 0cc223e63..6e63b6c19 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BiometricAuthSettingsActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BiometricAuthSettingsActivity.kt @@ -4,16 +4,16 @@ import androidx.biometric.BiometricManager import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.presenter.BiometricAuthSettingsPresenter import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView import org.cryptomator.presentation.ui.dialog.EnrollSystemBiometricDialog import org.cryptomator.presentation.ui.fragment.BiometricAuthSettingsFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class BiometricAuthSettingsActivity : BaseActivity(), // +class BiometricAuthSettingsActivity : BaseActivity(ActivityLayoutBinding::inflate), // BiometricAuthSettingsView, // EnrollSystemBiometricDialog.Callback { @@ -21,8 +21,8 @@ class BiometricAuthSettingsActivity : BaseActivity(), // lateinit var presenter: BiometricAuthSettingsPresenter override fun setupView() { - toolbar.setTitle(R.string.screen_settings_biometric_auth) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_settings_biometric_auth) + setSupportActionBar(binding.mtToolbar.toolbar) showSetupBiometricAuthDialog() } @@ -36,7 +36,7 @@ class BiometricAuthSettingsActivity : BaseActivity(), // } } - override fun createFragment(): Fragment? = BiometricAuthSettingsFragment() + override fun createFragment(): Fragment = BiometricAuthSettingsFragment() override fun renderVaultList(vaultModelCollection: List) { biometricAuthSettingsFragment().showVaults(vaultModelCollection) @@ -46,7 +46,7 @@ class BiometricAuthSettingsActivity : BaseActivity(), // biometricAuthSettingsFragment().clearVaultList() } - private fun biometricAuthSettingsFragment(): BiometricAuthSettingsFragment = getCurrentFragment(R.id.fragmentContainer) as BiometricAuthSettingsFragment + private fun biometricAuthSettingsFragment(): BiometricAuthSettingsFragment = getCurrentFragment(R.id.fragment_container) as BiometricAuthSettingsFragment override fun onSetupBiometricAuthInSystemClicked() { presenter.onSetupBiometricAuthInSystemClicked() diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt index 2a3c30c1a..f3d42fa02 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/BrowseFilesActivity.kt @@ -15,6 +15,7 @@ import org.cryptomator.domain.exception.ParentFolderIsNullException import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.BrowseFilesIntent import org.cryptomator.presentation.intent.ChooseCloudNodeSettings import org.cryptomator.presentation.intent.ChooseCloudNodeSettings.NavigationMode.BROWSE_FILES @@ -51,10 +52,9 @@ import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog import org.cryptomator.presentation.ui.fragment.BrowseFilesFragment import java.util.regex.Pattern import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class BrowseFilesActivity : BaseActivity(), // +class BrowseFilesActivity : BaseActivity(ActivityLayoutBinding::inflate), // BrowseFilesView, // BrowseFilesCallback, // ReplaceDialog.Callback, // @@ -264,9 +264,9 @@ class BrowseFilesActivity : BaseActivity(), // } private fun setupToolbar() { - toolbar.title = effectiveTitle(browseFilesIntent.folder()) - toolbar.subtitle = effectiveSubtitle() - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = effectiveTitle(browseFilesIntent.folder()) + binding.mtToolbar.toolbar.subtitle = effectiveSubtitle() + setSupportActionBar(binding.mtToolbar.toolbar) if (hasCloudNodeSettings()) { effectiveToolbarIcon(browseFilesIntent.chooseCloudNodeSettings().extraToolbarIcon()) } @@ -389,9 +389,9 @@ class BrowseFilesActivity : BaseActivity(), // override fun updateSelectionTitle(numberSelected: Int) { if (numberSelected == 0) { - toolbar.title = getString(R.string.screen_file_browser_selection_mode_title_zero_elements) + binding.mtToolbar.toolbar.setTitle(R.string.screen_file_browser_selection_mode_title_zero_elements) } else { - toolbar.title = getString(R.string.screen_file_browser_selection_mode_title_one_or_more_elements, numberSelected) + binding.mtToolbar.toolbar.title = getString(R.string.screen_file_browser_selection_mode_title_one_or_more_elements, numberSelected) } } @@ -436,7 +436,7 @@ class BrowseFilesActivity : BaseActivity(), // } override fun updateTitle(folder: CloudFolderModel) { - toolbar.title = effectiveTitle(folder) + binding.mtToolbar.toolbar.title = effectiveTitle(folder) } override fun hasExcludedFolder(): Boolean { @@ -553,7 +553,7 @@ class BrowseFilesActivity : BaseActivity(), // browseFilesFragment().showLoading(loading) } - private fun browseFilesFragment(): BrowseFilesFragment = getCurrentFragment(R.id.fragmentContainer) as BrowseFilesFragment + private fun browseFilesFragment(): BrowseFilesFragment = getCurrentFragment(R.id.fragment_container) as BrowseFilesFragment override fun onCreateNewTextFileClicked(fileName: String) { browseFilesPresenter.onCreateNewTextFileClicked(browseFilesFragment().folder, fileName) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt index c5e1bc3c4..90f8be0a8 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt @@ -5,6 +5,7 @@ import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.ChooseCloudServiceIntent import org.cryptomator.presentation.intent.Intents.cloudSettingsIntent import org.cryptomator.presentation.model.CloudTypeModel @@ -12,10 +13,9 @@ import org.cryptomator.presentation.presenter.ChooseCloudServicePresenter import org.cryptomator.presentation.ui.activity.view.ChooseCloudServiceView import org.cryptomator.presentation.ui.fragment.ChooseCloudServiceFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView { +class ChooseCloudServiceActivity : BaseActivity(ActivityLayoutBinding::inflate), ChooseCloudServiceView { @Inject lateinit var presenter: ChooseCloudServicePresenter @@ -24,9 +24,9 @@ class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView { lateinit var chooseCloudServiceIntent: ChooseCloudServiceIntent override fun setupView() { - toolbar.setTitle(R.string.screen_choose_cloud_service_title) - toolbar.subtitle = chooseCloudServiceIntent.subtitle() - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_choose_cloud_service_title) + binding.mtToolbar.toolbar.subtitle = chooseCloudServiceIntent.subtitle() + setSupportActionBar(binding.mtToolbar.toolbar) } override fun createFragment(): Fragment = ChooseCloudServiceFragment() @@ -42,5 +42,5 @@ class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView { chooseCloudServiceFragment().render(cloudModels) } - private fun chooseCloudServiceFragment(): ChooseCloudServiceFragment = getCurrentFragment(R.id.fragmentContainer) as ChooseCloudServiceFragment + private fun chooseCloudServiceFragment(): ChooseCloudServiceFragment = getCurrentFragment(R.id.fragment_container) as ChooseCloudServiceFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudConnectionListActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudConnectionListActivity.kt index f545b4405..4e0dba610 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudConnectionListActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudConnectionListActivity.kt @@ -5,6 +5,7 @@ import org.cryptomator.domain.Vault import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.CloudConnectionListIntent import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.presenter.CloudConnectionListPresenter @@ -14,10 +15,9 @@ import org.cryptomator.presentation.ui.dialog.DeleteCloudConnectionWithVaultsDia import org.cryptomator.presentation.ui.dialog.PCloudCredentialsUpdatedDialog import org.cryptomator.presentation.ui.fragment.CloudConnectionListFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class CloudConnectionListActivity : BaseActivity(), +class CloudConnectionListActivity : BaseActivity(ActivityLayoutBinding::inflate), CloudConnectionListView, CloudConnectionSettingsBottomSheet.Callback, DeleteCloudConnectionWithVaultsDialog.Callback, @@ -33,8 +33,8 @@ class CloudConnectionListActivity : BaseActivity(), get() = cloudConnectionListIntent.finishOnCloudItemClick() override fun setupView() { - toolbar.title = cloudConnectionListIntent.dialogTitle() - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = cloudConnectionListIntent.dialogTitle() + setSupportActionBar(binding.mtToolbar.toolbar) } override fun setupPresenter() { @@ -50,7 +50,7 @@ class CloudConnectionListActivity : BaseActivity(), connectionListFragment().show(cloudNodes) } - private fun connectionListFragment(): CloudConnectionListFragment = getCurrentFragment(R.id.fragmentContainer) as CloudConnectionListFragment + private fun connectionListFragment(): CloudConnectionListFragment = getCurrentFragment(R.id.fragment_container) as CloudConnectionListFragment override fun createFragment(): Fragment = CloudConnectionListFragment() diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudSettingsActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudSettingsActivity.kt index f572bc5de..3735d55a2 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudSettingsActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CloudSettingsActivity.kt @@ -3,22 +3,22 @@ package org.cryptomator.presentation.ui.activity import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.presenter.CloudSettingsPresenter import org.cryptomator.presentation.ui.activity.view.CloudSettingsView import org.cryptomator.presentation.ui.fragment.CloudSettingsFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class CloudSettingsActivity : BaseActivity(), CloudSettingsView { +class CloudSettingsActivity : BaseActivity(ActivityLayoutBinding::inflate), CloudSettingsView { @Inject lateinit var cloudSettingsPresenter: CloudSettingsPresenter override fun setupView() { - toolbar.setTitle(R.string.screen_cloud_settings_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_cloud_settings_title) + setSupportActionBar(binding.mtToolbar.toolbar) } override fun createFragment(): Fragment = CloudSettingsFragment() @@ -31,5 +31,5 @@ class CloudSettingsActivity : BaseActivity(), CloudSettingsView { cloudSettingsFragment().update(cloud) } - private fun cloudSettingsFragment(): CloudSettingsFragment = getCurrentFragment(R.id.fragmentContainer) as CloudSettingsFragment + private fun cloudSettingsFragment(): CloudSettingsFragment = getCurrentFragment(R.id.fragment_container) as CloudSettingsFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CreateVaultActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CreateVaultActivity.kt index 2a194ac2d..87b94713c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CreateVaultActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CreateVaultActivity.kt @@ -3,37 +3,34 @@ package org.cryptomator.presentation.ui.activity import android.view.inputmethod.EditorInfo import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityCreateVaultBinding import org.cryptomator.presentation.presenter.CreateVaultPresenter import org.cryptomator.presentation.ui.activity.view.CreateVaultView import javax.inject.Inject -import kotlinx.android.synthetic.main.content_create_vault.createVaultButton -import kotlinx.android.synthetic.main.content_create_vault.vaultNameEditText -import kotlinx.android.synthetic.main.toolbar_layout.toolbar -@Activity(layout = R.layout.activity_create_vault) -class CreateVaultActivity : BaseActivity(), CreateVaultView { +@Activity +class CreateVaultActivity : BaseActivity(ActivityCreateVaultBinding::inflate), CreateVaultView { @Inject lateinit var createVaultPresenter: CreateVaultPresenter override fun setupView() { - createVaultButton.setOnClickListener { - createVaultPresenter.onCreateVaultClicked(vaultNameEditText.text.toString()) + binding.llContentCreateVault.createVaultButton.setOnClickListener { + createVaultPresenter.onCreateVaultClicked(binding.llContentCreateVault.vaultNameEditText.text.toString()) } - createVaultButton.setOnEditorActionListener { _, actionId, _ -> + binding.llContentCreateVault.createVaultButton.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { - createVaultPresenter.onCreateVaultClicked(vaultNameEditText.text.toString()) + createVaultPresenter.onCreateVaultClicked(binding.llContentCreateVault.vaultNameEditText.text.toString()) } false } setupToolbar() - - vaultNameEditText.requestFocus() + binding.llContentCreateVault.vaultNameEditText.requestFocus() } private fun setupToolbar() { - toolbar.setTitle(R.string.screen_enter_vault_name_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_enter_vault_name_title) + setSupportActionBar(binding.mtToolbar.toolbar) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CryptomatorVariantsActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CryptomatorVariantsActivity.kt index 97eedf540..063335bd4 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CryptomatorVariantsActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/CryptomatorVariantsActivity.kt @@ -2,48 +2,39 @@ package org.cryptomator.presentation.ui.activity import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityCryptomatorVariantsBinding import org.cryptomator.presentation.presenter.CryptomatorVariantsPresenter import org.cryptomator.presentation.ui.activity.view.CryptomatorVariantsView import javax.inject.Inject -import kotlinx.android.synthetic.main.activity_cryptomator_variants.btnAddRepo -import kotlinx.android.synthetic.main.activity_cryptomator_variants.btnInstallFDroidVariant -import kotlinx.android.synthetic.main.activity_cryptomator_variants.btnInstallLiteVariant -import kotlinx.android.synthetic.main.activity_cryptomator_variants.btnInstallWebsiteVariant -import kotlinx.android.synthetic.main.activity_cryptomator_variants.tvFdroidCustomSupported -import kotlinx.android.synthetic.main.activity_cryptomator_variants.tvFdroidCustomUnsupported -import kotlinx.android.synthetic.main.activity_cryptomator_variants.tvLiteSupported -import kotlinx.android.synthetic.main.activity_cryptomator_variants.tvLiteUnsupported -import kotlinx.android.synthetic.main.activity_cryptomator_variants.tvWebsiteSupported -import kotlinx.android.synthetic.main.toolbar_layout.toolbar - -@Activity(layout = R.layout.activity_cryptomator_variants) -class CryptomatorVariantsActivity : BaseActivity(), CryptomatorVariantsView { + +@Activity +class CryptomatorVariantsActivity : BaseActivity(ActivityCryptomatorVariantsBinding::inflate), CryptomatorVariantsView { @Inject lateinit var presenter: CryptomatorVariantsPresenter override fun setupView() { - toolbar.title = getString(R.string.screen_cryptomator_variants_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_cryptomator_variants_title) + setSupportActionBar(binding.mtToolbar.toolbar) - tvLiteSupported.text = "WebDAV, S3, Local Storage" - tvLiteUnsupported.text = "Dropbox, Google Drive, OneDrive, pCloud" + binding.tvLiteSupported.text = "WebDAV, S3, Local Storage" + binding.tvLiteUnsupported.text = "Dropbox, Google Drive, OneDrive, pCloud" - tvFdroidCustomSupported.text = "Dropbox, OneDrive, pCloud, WebDAV, S3, Local Storage" - tvFdroidCustomUnsupported.text = "Google Drive" + binding.tvFdroidCustomSupported.text = "Dropbox, OneDrive, pCloud, WebDAV, S3, Local Storage" + binding.tvFdroidCustomUnsupported.text = "Google Drive" - tvWebsiteSupported.text = "Dropbox, Google Drive, OneDrive, pCloud, WebDAV, S3, Local Storage" + binding.tvWebsiteSupported.text = "Dropbox, Google Drive, OneDrive, pCloud, WebDAV, S3, Local Storage" - btnInstallLiteVariant.setOnClickListener { + binding.btnInstallLiteVariant.setOnClickListener { presenter.onInstallMainFDroidVariantClicked() } - btnAddRepo.setOnClickListener { + binding.btnAddRepo.setOnClickListener { presenter.onAddRepoClicked() } - btnInstallFDroidVariant.setOnClickListener { + binding.btnInstallFdroidVariant.setOnClickListener { presenter.onInstallFDroidVariantClicked() } - btnInstallWebsiteVariant.setOnClickListener { + binding.btnInstallWebsiteVariant.setOnClickListener { presenter.onInstallWebsiteVariantClicked() } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt index 901b67cb0..7b7ea88b0 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt @@ -17,6 +17,7 @@ import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityImagePreviewBinding import org.cryptomator.presentation.intent.ImagePreviewIntent import org.cryptomator.presentation.model.CloudNodeModel import org.cryptomator.presentation.model.ImagePreviewFile @@ -25,15 +26,9 @@ import org.cryptomator.presentation.ui.activity.view.ImagePreviewView import org.cryptomator.presentation.ui.dialog.ConfirmDeleteCloudNodeDialog import org.cryptomator.presentation.ui.fragment.ImagePreviewFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.activity_image_preview.controlView -import kotlinx.android.synthetic.main.activity_image_preview.deleteImage -import kotlinx.android.synthetic.main.activity_image_preview.exportImage -import kotlinx.android.synthetic.main.activity_image_preview.shareImage -import kotlinx.android.synthetic.main.activity_image_preview.toolbar -import kotlinx.android.synthetic.main.activity_image_preview.viewPager -@Activity(layout = R.layout.activity_image_preview) -class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteCloudNodeDialog.Callback { +@Activity +class ImagePreviewActivity : BaseActivity(ActivityImagePreviewBinding::inflate), ImagePreviewView, ConfirmDeleteCloudNodeDialog.Callback { @Inject lateinit var presenter: ImagePreviewPresenter @@ -46,7 +41,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou lateinit var imagePreviewFiles: ArrayList private val currentImageUri: Uri? - get() = imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)].uri + get() = imagePreviewFiles[imagePreviewSliderAdapter.getIndex(binding.viewPager.currentItem)].uri private val pageChangeListener = object : ViewPager.SimpleOnPageChangeListener() { @@ -62,13 +57,13 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou val index = imagePreviewFileStore.index imagePreviewFiles = presenter.getImagePreviewFiles(imagePreviewFileStore, index) - deleteImage.setOnClickListener { - presenter.onDeleteImageClicked(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)]) + binding.deleteImage.setOnClickListener { + presenter.onDeleteImageClicked(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(binding.viewPager.currentItem)]) } - exportImage.setOnClickListener { + binding.exportImage.setOnClickListener { currentImageUri?.let { presenter.exportImageToUserSelectedLocation(it) } } - shareImage.setOnClickListener { + binding.shareImage.setOnClickListener { currentImageUri?.let { presenter.onShareImageClicked(it) } } @@ -85,10 +80,10 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou private fun setupViewPager(index: Int) { imagePreviewSliderAdapter = ImagePreviewSliderAdapter(supportFragmentManager) - viewPager.adapter = imagePreviewSliderAdapter - viewPager.currentItem = index - viewPager.addOnPageChangeListener(pageChangeListener) - viewPager.pageMargin = 50 + binding.viewPager.adapter = imagePreviewSliderAdapter + binding.viewPager.currentItem = index + binding.viewPager.addOnPageChangeListener(pageChangeListener) + binding.viewPager.pageMargin = 50 } override fun onWindowFocusChanged(hasFocus: Boolean) { @@ -104,14 +99,14 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou private fun setupToolbar(index: Int) { updateTitle(index) - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_clear) } private fun updateTitle(position: Int) { - toolbar.title = imagePreviewFiles[imagePreviewSliderAdapter.getIndex(position)].cloudFileModel.name + binding.toolbar.title = imagePreviewFiles[imagePreviewSliderAdapter.getIndex(position)].cloudFileModel.name } override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) { @@ -189,7 +184,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou } override fun onDeleteCloudNodeConfirmed(nodes: List>) { - presenter.onDeleteImageConfirmed(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)], viewPager.currentItem) + presenter.onDeleteImageConfirmed(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(binding.viewPager.currentItem)], binding.viewPager.currentItem) } override fun onImageDeleted(index: Int) { @@ -208,7 +203,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou } private fun setControlViewVisibility(visibility: Int) { - controlView.visibility = visibility + binding.controlView.visibility = visibility } private fun onImageChanged(position: Int) { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicenseCheckActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicenseCheckActivity.kt index f7602a647..95cc9e44b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicenseCheckActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicenseCheckActivity.kt @@ -5,6 +5,7 @@ import android.net.Uri import android.os.Bundle import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutObscureAwareBinding import org.cryptomator.presentation.intent.Intents.vaultListIntent import org.cryptomator.presentation.presenter.LicenseCheckPresenter import org.cryptomator.presentation.ui.activity.view.UpdateLicenseView @@ -13,19 +14,19 @@ import org.cryptomator.presentation.ui.dialog.UpdateLicenseDialog import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout import javax.inject.Inject import kotlin.system.exitProcess -import kotlinx.android.synthetic.main.activity_layout_obscure_aware.activityRootView -import kotlinx.android.synthetic.main.toolbar_layout.toolbar -@Activity(layout = R.layout.activity_layout_obscure_aware) -class LicenseCheckActivity : BaseActivity(), UpdateLicenseDialog.Callback, LicenseConfirmationDialog.Callback, UpdateLicenseView { +@Activity +class LicenseCheckActivity : BaseActivity(ActivityLayoutObscureAwareBinding::inflate), // + UpdateLicenseDialog.Callback, // + LicenseConfirmationDialog.Callback, // + UpdateLicenseView { @Inject lateinit var licenseCheckPresenter: LicenseCheckPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - activityRootView.setOnFilteredTouchEventForSecurityListener(object : ObscuredAwareCoordinatorLayout.Listener { + binding.activityRootView.setOnFilteredTouchEventForSecurityListener(object : ObscuredAwareCoordinatorLayout.Listener { override fun onFilteredTouchEventForSecurity() { licenseCheckPresenter.onFilteredTouchEventForSecurity() } @@ -67,8 +68,8 @@ class LicenseCheckActivity : BaseActivity(), UpdateLicenseDialog.Callback, Licen } private fun setupToolbar() { - toolbar.title = getString(R.string.app_name).uppercase() - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = getString(R.string.app_name).uppercase() + setSupportActionBar(binding.mtToolbar.toolbar) } override fun showConfirmationDialog(mail: String) { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicensesActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicensesActivity.kt index 970ef25c3..3dd063588 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicensesActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/LicensesActivity.kt @@ -2,18 +2,18 @@ package org.cryptomator.presentation.ui.activity import org.cryptomator.generator.Activity import org.cryptomator.presentation.R -import kotlinx.android.synthetic.main.toolbar_layout.toolbar +import org.cryptomator.presentation.databinding.ActivityLicensesBinding -@Activity(layout = R.layout.activity_licenses) -class LicensesActivity : BaseActivity() { +@Activity +class LicensesActivity : BaseActivity(ActivityLicensesBinding::inflate) { override fun setupView() { setupToolbar() } private fun setupToolbar() { - toolbar.setTitle(R.string.screen_licenses_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_licenses_title) + setSupportActionBar(binding.mtToolbar.toolbar) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/S3AddOrChangeActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/S3AddOrChangeActivity.kt index 39d00be39..584abf0df 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/S3AddOrChangeActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/S3AddOrChangeActivity.kt @@ -4,15 +4,15 @@ import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.S3AddOrChangeIntent import org.cryptomator.presentation.presenter.S3AddOrChangePresenter import org.cryptomator.presentation.ui.activity.view.S3AddOrChangeView import org.cryptomator.presentation.ui.fragment.S3AddOrChangeFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class S3AddOrChangeActivity : BaseActivity(), S3AddOrChangeView { +class S3AddOrChangeActivity : BaseActivity(ActivityLayoutBinding::inflate), S3AddOrChangeView { @Inject lateinit var s3AddOrChangePresenter: S3AddOrChangePresenter @@ -21,8 +21,8 @@ class S3AddOrChangeActivity : BaseActivity(), S3AddOrChangeView { lateinit var s3AddOrChangeIntent: S3AddOrChangeIntent override fun setupView() { - toolbar.setTitle(R.string.screen_s3_settings_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_s3_settings_title) + setSupportActionBar(binding.mtToolbar.toolbar) } override fun createFragment(): Fragment = S3AddOrChangeFragment.newInstance(s3AddOrChangeIntent.s3Cloud()) @@ -32,6 +32,6 @@ class S3AddOrChangeActivity : BaseActivity(), S3AddOrChangeView { s3AddOrChangePresenter.authenticate(accessKey, secretKey, bucket, endpoint, region, cloudId, displayName) } - private fun s3AddOrChangeFragment(): S3AddOrChangeFragment = getCurrentFragment(R.id.fragmentContainer) as S3AddOrChangeFragment + private fun s3AddOrChangeFragment(): S3AddOrChangeFragment = getCurrentFragment(R.id.fragment_container) as S3AddOrChangeFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SetPasswordActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SetPasswordActivity.kt index c684b6fd1..00e068522 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SetPasswordActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SetPasswordActivity.kt @@ -3,21 +3,21 @@ package org.cryptomator.presentation.ui.activity import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.presenter.SetPasswordPresenter import org.cryptomator.presentation.ui.activity.view.SetPasswordView import org.cryptomator.presentation.ui.fragment.SetPasswordFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class SetPasswordActivity : BaseActivity(), SetPasswordView { +class SetPasswordActivity : BaseActivity(ActivityLayoutBinding::inflate), SetPasswordView { @Inject lateinit var setPasswordPresenter: SetPasswordPresenter override fun setupView() { - toolbar.setTitle(R.string.screen_set_password_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_set_password_title) + setSupportActionBar(binding.mtToolbar.toolbar) } override fun createFragment(): Fragment = SetPasswordFragment() diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt index 4c43a2ecb..5fa190c17 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SettingsActivity.kt @@ -5,6 +5,7 @@ import android.net.Uri import android.view.View import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivitySettingsBinding import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.presenter.SettingsPresenter import org.cryptomator.presentation.ui.activity.view.SettingsView @@ -17,10 +18,9 @@ import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog import org.cryptomator.presentation.ui.dialog.UpdateAppDialog import org.cryptomator.presentation.ui.fragment.SettingsFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar -@Activity(layout = R.layout.activity_settings) -class SettingsActivity : BaseActivity(), +@Activity +class SettingsActivity : BaseActivity(ActivitySettingsBinding::inflate), SettingsView, AskIgnoreBatteryOptimizationsDialog.Callback, DebugModeDisclaimerDialog.Callback, @@ -39,8 +39,8 @@ class SettingsActivity : BaseActivity(), } private fun setupToolbar() { - toolbar.setTitle(R.string.screen_settings_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_settings_title) + setSupportActionBar(binding.mtToolbar.toolbar) } fun presenter(): SettingsPresenter = presenter diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SharedFilesActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SharedFilesActivity.kt index c7fc14647..6b318c880 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SharedFilesActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SharedFilesActivity.kt @@ -8,6 +8,7 @@ import android.net.Uri import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.model.CloudFolderModel import org.cryptomator.presentation.model.ProgressModel.Companion.COMPLETED import org.cryptomator.presentation.model.SharedFileModel @@ -20,11 +21,10 @@ import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog import org.cryptomator.presentation.ui.fragment.SharedFilesFragment import java.lang.String.format import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar import timber.log.Timber @Activity -class SharedFilesActivity : BaseActivity(), // +class SharedFilesActivity : BaseActivity(ActivityLayoutBinding::inflate), // SharedFilesView, // ReplaceDialog.Callback, // NotEnoughVaultsDialog.Callback, // @@ -110,8 +110,8 @@ class SharedFilesActivity : BaseActivity(), // } private fun setupToolbar() { - toolbar.title = format(getString(R.string.screen_share_files_title), contentName) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = format(getString(R.string.screen_share_files_title), contentName) + setSupportActionBar(binding.mtToolbar.toolbar) supportActionBar?.let { it.setDisplayHomeAsUpEnabled(true) it.setHomeAsUpIndicator(R.drawable.ic_clear) @@ -140,7 +140,7 @@ class SharedFilesActivity : BaseActivity(), // //UnableToShareFilesDialog.withContext(this).show() } - private fun sharedFilesFragment(): SharedFilesFragment = getCurrentFragment(R.id.fragmentContainer) as SharedFilesFragment + private fun sharedFilesFragment(): SharedFilesFragment = getCurrentFragment(R.id.fragment_container) as SharedFilesFragment override fun showReplaceDialog(existingFiles: List, size: Int) { ReplaceDialog.withContext(this).show(existingFiles, size) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/TextEditorActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/TextEditorActivity.kt index 205185b8b..541a44a3c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/TextEditorActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/TextEditorActivity.kt @@ -7,16 +7,16 @@ import androidx.fragment.app.Fragment import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.TextEditorIntent import org.cryptomator.presentation.presenter.TextEditorPresenter import org.cryptomator.presentation.ui.activity.view.TextEditorView import org.cryptomator.presentation.ui.dialog.UnsavedChangesDialog import org.cryptomator.presentation.ui.fragment.TextEditorFragment import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class TextEditorActivity : BaseActivity(), +class TextEditorActivity : BaseActivity(ActivityLayoutBinding::inflate), TextEditorView, UnsavedChangesDialog.Callback, SearchView.OnQueryTextListener { @@ -101,8 +101,8 @@ class TextEditorActivity : BaseActivity(), } private fun setupToolbar() { - toolbar.title = textEditorIntent.textFile().name - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = textEditorIntent.textFile().name + setSupportActionBar(binding.mtToolbar.toolbar) } override fun performBackPressed() { @@ -129,6 +129,6 @@ class TextEditorActivity : BaseActivity(), finish() } - private fun textEditorFragment(): TextEditorFragment = getCurrentFragment(R.id.fragmentContainer) as TextEditorFragment + private fun textEditorFragment(): TextEditorFragment = getCurrentFragment(R.id.fragment_container) as TextEditorFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt index ee6ed627f..1271df05d 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt @@ -7,6 +7,7 @@ import org.cryptomator.domain.Vault import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityUnlockVaultBinding import org.cryptomator.presentation.intent.UnlockVaultIntent import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.presenter.UnlockVaultPresenter @@ -19,8 +20,8 @@ import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment import org.cryptomator.presentation.util.BiometricAuthentication import javax.inject.Inject -@Activity(layout = R.layout.activity_unlock_vault) -class UnlockVaultActivity : BaseActivity(), // +@Activity +class UnlockVaultActivity : BaseActivity(ActivityUnlockVaultBinding::inflate), // UnlockVaultView, // BiometricAuthentication.Callback, ChangePasswordDialog.Callback, @@ -100,7 +101,7 @@ class UnlockVaultActivity : BaseActivity(), // } private fun unlockVaultFragment(): UnlockVaultFragment = // - getCurrentFragment(R.id.fragmentContainer) as UnlockVaultFragment + getCurrentFragment(R.id.fragment_container) as UnlockVaultFragment override fun showChangePasswordDialog(vaultModel: VaultModel, unverifiedVaultConfig: UnverifiedVaultConfig?) { showDialog(ChangePasswordDialog.newInstance(vaultModel, unverifiedVaultConfig)) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt index 7fe53a3b6..fd124b2a6 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt @@ -10,6 +10,7 @@ import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.CryptomatorApp import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutObscureAwareBinding import org.cryptomator.presentation.intent.Intents.browseFilesIntent import org.cryptomator.presentation.intent.Intents.settingsIntent import org.cryptomator.presentation.intent.VaultListIntent @@ -31,11 +32,9 @@ import org.cryptomator.presentation.ui.dialog.VaultRenameDialog import org.cryptomator.presentation.ui.fragment.VaultListFragment import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout.Listener import javax.inject.Inject -import kotlinx.android.synthetic.main.activity_layout_obscure_aware.activityRootView -import kotlinx.android.synthetic.main.toolbar_layout.toolbar -@Activity(layout = R.layout.activity_layout_obscure_aware) -class VaultListActivity : BaseActivity(), // +@Activity +class VaultListActivity : BaseActivity(ActivityLayoutObscureAwareBinding::inflate), // VaultListView, // VaultListCallback, // AskForLockScreenDialog.Callback, // @@ -62,7 +61,7 @@ class VaultListActivity : BaseActivity(), // override fun setupView() { setupToolbar() vaultListPresenter.prepareView() - activityRootView.setOnFilteredTouchEventForSecurityListener(object : Listener { + binding.activityRootView.setOnFilteredTouchEventForSecurityListener(object : Listener { override fun onFilteredTouchEventForSecurity() { vaultListPresenter.onFilteredTouchEventForSecurity() } @@ -106,8 +105,8 @@ class VaultListActivity : BaseActivity(), // } private fun setupToolbar() { - toolbar.title = getString(R.string.app_name).uppercase() - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.title = getString(R.string.app_name).uppercase() + setSupportActionBar(binding.mtToolbar.toolbar) } override fun showAddVaultBottomSheet() { @@ -198,7 +197,7 @@ class VaultListActivity : BaseActivity(), // } private fun vaultListFragment(): VaultListFragment = // - getCurrentFragment(R.id.fragmentContainer) as VaultListFragment + getCurrentFragment(R.id.fragment_container) as VaultListFragment override fun onUpdateAppDialogLoaded() { showProgress(ProgressModel.GENERIC) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/WebDavAddOrChangeActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/WebDavAddOrChangeActivity.kt index 30077acec..5391362a3 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/WebDavAddOrChangeActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/WebDavAddOrChangeActivity.kt @@ -5,6 +5,7 @@ import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.generator.Activity import org.cryptomator.generator.InjectIntent import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ActivityLayoutBinding import org.cryptomator.presentation.intent.WebDavAddOrChangeIntent import org.cryptomator.presentation.presenter.WebDavAddOrChangePresenter import org.cryptomator.presentation.ui.activity.view.WebDavAddOrChangeView @@ -13,10 +14,9 @@ import org.cryptomator.presentation.ui.fragment.WebDavAddOrChangeFragment import java.net.URI import java.net.URISyntaxException import javax.inject.Inject -import kotlinx.android.synthetic.main.toolbar_layout.toolbar @Activity -class WebDavAddOrChangeActivity : BaseActivity(), +class WebDavAddOrChangeActivity : BaseActivity(ActivityLayoutBinding::inflate), WebDavAddOrChangeView, WebDavAskForHttpDialog.Callback { @@ -27,8 +27,8 @@ class WebDavAddOrChangeActivity : BaseActivity(), lateinit var webDavAddOrChangeIntent: WebDavAddOrChangeIntent override fun setupView() { - toolbar.setTitle(R.string.screen_webdav_settings_title) - setSupportActionBar(toolbar) + binding.mtToolbar.toolbar.setTitle(R.string.screen_webdav_settings_title) + setSupportActionBar(binding.mtToolbar.toolbar) } override fun createFragment(): Fragment = WebDavAddOrChangeFragment.newInstance(webDavAddOrChangeIntent.webDavCloud()) @@ -50,6 +50,6 @@ class WebDavAddOrChangeActivity : BaseActivity(), webDavAddOrChangePresenter.authenticate(username, password, url, cloudId, certificate) } - private fun webDavAddOrChangeFragment(): WebDavAddOrChangeFragment = getCurrentFragment(R.id.fragmentContainer) as WebDavAddOrChangeFragment + private fun webDavAddOrChangeFragment(): WebDavAddOrChangeFragment = getCurrentFragment(R.id.fragment_container) as WebDavAddOrChangeFragment } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt index 2e593ad57..cb5ab746e 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt @@ -1,19 +1,17 @@ package org.cryptomator.presentation.ui.adapter -import android.view.View +import android.view.LayoutInflater +import android.view.ViewGroup import android.widget.Switch -import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ItemBiometricAuthVaultBinding import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.BiometricAuthSettingsAdapter.BiometricAuthSettingsViewHolder import javax.inject.Inject -import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.cloud -import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.toggleBiometricAuth -import kotlinx.android.synthetic.main.item_biometric_auth_vault.view.vaultName class BiometricAuthSettingsAdapter // @Inject -constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { +constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { private var onVaultBiometricAuthSettingsChanged: OnVaultBiometricAuthSettingsChanged? = null @@ -30,30 +28,30 @@ constructor() : RecyclerViewBaseAdapter.ItemViewHolder(itemView) { + inner class BiometricAuthSettingsViewHolder(private val binding: ItemBiometricAuthVaultBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { override fun bind(position: Int) { val vaultModel = getItem(position) - itemView.vaultName.text = vaultModel.name - itemView.cloud.setImageResource(vaultModel.cloudType.vaultImageResource) + binding.vaultName.text = vaultModel.name + binding.cloud.setImageResource(vaultModel.cloudType.vaultImageResource) - itemView.toggleBiometricAuth.isChecked = vaultModel.password != null + binding.toggleBiometricAuth.isChecked = vaultModel.password != null //itemView.toggleBiometricAuth.setOnCheckedChangeListener doesn't work because bind can be executed multiple times - itemView.toggleBiometricAuth.setOnClickListener { switch -> + binding.toggleBiometricAuth.setOnClickListener { switch -> onVaultBiometricAuthSettingsChanged?.onVaultBiometricAuthSettingsChanged(vaultModel, (switch as Switch).isChecked) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BrowseFilesAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BrowseFilesAdapter.kt index 8e31b644a..7b05f62fc 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BrowseFilesAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BrowseFilesAdapter.kt @@ -1,12 +1,15 @@ package org.cryptomator.presentation.ui.adapter import android.os.PatternMatcher +import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import android.view.ViewGroup import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import org.cryptomator.domain.CloudNode import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ItemBrowseFilesNodeBinding import org.cryptomator.presentation.intent.ChooseCloudNodeSettings import org.cryptomator.presentation.intent.ChooseCloudNodeSettings.NavigationMode.BROWSE_FILES import org.cryptomator.presentation.intent.ChooseCloudNodeSettings.NavigationMode.SELECT_ITEMS @@ -28,18 +31,6 @@ import org.cryptomator.presentation.util.FileUtil import org.cryptomator.presentation.util.ResourceHelper.Companion.getDrawable import org.cryptomator.util.SharedPreferencesHandler import javax.inject.Inject -import kotlinx.android.synthetic.main.item_browse_files_node.view.cloudNodeImage -import kotlinx.android.synthetic.main.item_browse_files_node.view.itemCheckBox -import kotlinx.android.synthetic.main.item_browse_files_node.view.settings -import kotlinx.android.synthetic.main.view_cloud_file_content.view.cloudFileContent -import kotlinx.android.synthetic.main.view_cloud_file_content.view.cloudFileProgress -import kotlinx.android.synthetic.main.view_cloud_file_content.view.cloudFileSubText -import kotlinx.android.synthetic.main.view_cloud_file_content.view.cloudFileText -import kotlinx.android.synthetic.main.view_cloud_file_content.view.progressIcon -import kotlinx.android.synthetic.main.view_cloud_file_progress.view.cloudFile -import kotlinx.android.synthetic.main.view_cloud_folder_content.view.cloudFolderActionText -import kotlinx.android.synthetic.main.view_cloud_folder_content.view.cloudFolderContent -import kotlinx.android.synthetic.main.view_cloud_folder_content.view.cloudFolderText class BrowseFilesAdapter @Inject constructor( @@ -47,7 +38,7 @@ constructor( private val fileSizeHelper: FileSizeHelper, // private val fileUtil: FileUtil, // private val sharedPreferencesHandler: SharedPreferencesHandler -) : RecyclerViewBaseAdapter, BrowseFilesAdapter.ItemClickListener, VaultContentViewHolder>(CloudNodeModelNameAZComparator()), FastScrollRecyclerView.SectionedAdapter { +) : RecyclerViewBaseAdapter, BrowseFilesAdapter.ItemClickListener, VaultContentViewHolder, ItemBrowseFilesNodeBinding>(CloudNodeModelNameAZComparator()), FastScrollRecyclerView.SectionedAdapter { private var chooseCloudNodeSettings: ChooseCloudNodeSettings? = null private var navigationMode: ChooseCloudNodeSettings.NavigationMode? = null @@ -55,12 +46,12 @@ constructor( private val isInSelectionMode: Boolean get() = chooseCloudNodeSettings != null - override fun getItemLayout(viewType: Int): Int { - return R.layout.item_browse_files_node + override fun createViewHolder(binding: ItemBrowseFilesNodeBinding, viewType: Int): VaultContentViewHolder { + return VaultContentViewHolder(binding) } - override fun createViewHolder(view: View, viewType: Int): VaultContentViewHolder { - return VaultContentViewHolder(view) + override fun getItemBinding(inflater: LayoutInflater, parent: ViewGroup?, viewType: Int): ItemBrowseFilesNodeBinding { + return ItemBrowseFilesNodeBinding.inflate(inflater, parent, false) } fun addOrReplaceCloudNode(cloudNodeModel: CloudNodeModel<*>) { @@ -123,7 +114,7 @@ constructor( } } - inner class VaultContentViewHolder internal constructor(itemView: View) : RecyclerViewBaseAdapter<*, *, *>.ItemViewHolder(itemView) { + inner class VaultContentViewHolder internal constructor(private val binding: ItemBrowseFilesNodeBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { private var uiState: UiStateTest? = null @@ -144,7 +135,7 @@ constructor( } private fun bindNodeImage(node: CloudNodeModel<*>) { - itemView.cloudNodeImage.setImageResource(bindCloudNodeImage(node)) + binding.cloudNodeImage.setImageResource(bindCloudNodeImage(node)) } private fun bindCloudNodeImage(cloudNodeModel: CloudNodeModel<*>): Int { @@ -157,7 +148,7 @@ constructor( } private fun bindSettings(node: CloudNodeModel<*>) { - itemView.settings.setOnClickListener { callback.onNodeSettingsClicked(node) } + binding.settings.setOnClickListener { callback.onNodeSettingsClicked(node) } } private fun bindLongNodeClick(node: CloudNodeModel<*>) { @@ -185,8 +176,8 @@ constructor( } private fun bindFile(file: CloudFileModel) { - itemView.cloudFileText.text = file.name - itemView.cloudFileSubText.text = fileDetails(file) + binding.llCloudFileContent.cloudFileText.text = file.name + binding.llCloudFileContent.cloudFileSubText.text = fileDetails(file) enableNodeClick { callback.onFileClicked(file) } } @@ -196,8 +187,8 @@ constructor( disableNodeLongClick() hideSettings() if (!isSelectable(file)) { - itemView.cloudFileSubText.visibility = GONE - itemView.cloudFileSubText.text = "" + binding.llCloudFileContent.cloudFileSubText.visibility = GONE + binding.llCloudFileContent.cloudFileSubText.text = "" itemView.isEnabled = false } } @@ -228,7 +219,7 @@ constructor( } private fun bindFolder(folder: CloudFolderModel) { - itemView.cloudFolderText.text = folder.name + binding.llCloudFolderContent.cloudFolderText.text = folder.name enableNodeClick { callback.onFolderClicked(folder) } } @@ -243,17 +234,17 @@ constructor( } private fun hideSettings() { - itemView.settings.visibility = GONE + binding.settings.visibility = GONE } private fun bindNodeSelection(cloudNodeModel: CloudNodeModel<*>) { - itemView.itemCheckBox.setOnCheckedChangeListener { _, isChecked -> + binding.itemCheckBox.setOnCheckedChangeListener { _, isChecked -> cloudNodeModel.isSelected = isChecked callback.onSelectedNodesChanged(selectedCloudNodes().size) } - enableNodeClick { itemView.itemCheckBox.toggle() } + enableNodeClick { binding.itemCheckBox.toggle() } - itemView.itemCheckBox.isChecked = cloudNodeModel.isSelected + binding.itemCheckBox.isChecked = cloudNodeModel.isSelected } private fun fileDetails(cloudFile: CloudFileModel): String { @@ -281,9 +272,9 @@ constructor( private fun showIndeterminateProgress(progress: ProgressModel) { uiState?.let { switchTo(it.indeterminateProgress()) } if (uiState?.isForFile == true) { - itemView.cloudFileSubText.setText(progress.state().textResourceId()) + binding.llCloudFileContent.cloudFileSubText.setText(progress.state().textResourceId()) } else { - itemView.cloudFolderActionText.setText(progress.state().textResourceId()) + binding.llCloudFolderContent.cloudFolderActionText.setText(progress.state().textResourceId()) } if (!progress.state().isSelectable) { @@ -293,7 +284,7 @@ constructor( private fun disableNodeActions() { itemView.isEnabled = false - itemView.settings.visibility = GONE + binding.settings.visibility = GONE } private fun enableNodeClick(clickListener: View.OnClickListener) { @@ -312,14 +303,14 @@ constructor( uiState?.let { switchTo(it.determinateProgress()) } if (uiState?.isForFile == true) { disableNodeActions() - itemView.cloudFile.progress = progress.progress() + binding.llCloudFileContent.rlCloudFileProgress.cloudFile.progress = progress.progress() if (currentProgressIcon != progress.state().imageResourceId()) { currentProgressIcon = progress.state().imageResourceId() - itemView.progressIcon.setImageDrawable(getDrawable(currentProgressIcon)) + binding.llCloudFileContent.progressIcon.setImageDrawable(getDrawable(currentProgressIcon)) } } else { // no determinate progress for folders - itemView.cloudFolderActionText.setText(progress.state().textResourceId()) + binding.llCloudFolderContent.cloudFolderActionText.setText(progress.state().textResourceId()) } } @@ -336,7 +327,7 @@ constructor( } fun selectNode(checked: Boolean) { - itemView.itemCheckBox.isChecked = checked + binding.itemCheckBox.isChecked = checked } abstract inner class UiStateTest(val isForFile: Boolean) { @@ -372,13 +363,13 @@ constructor( override fun apply() { itemView.isEnabled = true - itemView.cloudFolderContent.visibility = GONE - itemView.cloudFileContent.visibility = VISIBLE - itemView.cloudFileText.visibility = VISIBLE - itemView.cloudFileSubText.visibility = VISIBLE - itemView.cloudFileProgress.visibility = GONE - itemView.settings.visibility = VISIBLE - itemView.itemCheckBox.visibility = GONE + binding.llCloudFolderContent.cloudFolderContent.visibility = GONE + binding.llCloudFileContent.cloudFileContent.visibility = VISIBLE + binding.llCloudFileContent.cloudFileText.visibility = VISIBLE + binding.llCloudFileContent.cloudFileSubText.visibility = VISIBLE + binding.llCloudFileContent.cloudFileProgress.visibility = GONE + binding.settings.visibility = VISIBLE + binding.itemCheckBox.visibility = GONE } } @@ -386,36 +377,36 @@ constructor( override fun apply() { itemView.isEnabled = true - itemView.cloudFileContent.visibility = GONE - itemView.cloudFolderContent.visibility = VISIBLE - itemView.cloudFolderText.visibility = VISIBLE - itemView.cloudFolderActionText.visibility = GONE - itemView.settings.visibility = VISIBLE - itemView.itemCheckBox.visibility = GONE + binding.llCloudFileContent.cloudFileContent.visibility = GONE + binding.llCloudFolderContent.cloudFolderContent.visibility = VISIBLE + binding.llCloudFolderContent.cloudFolderText.visibility = VISIBLE + binding.llCloudFolderContent.cloudFolderActionText.visibility = GONE + binding.settings.visibility = VISIBLE + binding.itemCheckBox.visibility = GONE } } inner class FileDeterminateProgress : UiStateTest(true) { override fun apply() { - itemView.cloudFolderContent.visibility = GONE - itemView.cloudFileContent.visibility = VISIBLE - itemView.cloudFileText.visibility = VISIBLE - itemView.cloudFileSubText.visibility = GONE - itemView.cloudFileProgress.visibility = VISIBLE - itemView.itemCheckBox.visibility = GONE + binding.llCloudFolderContent.cloudFolderContent.visibility = GONE + binding.llCloudFileContent.cloudFileContent.visibility = VISIBLE + binding.llCloudFileContent.cloudFileText.visibility = VISIBLE + binding.llCloudFileContent.cloudFileSubText.visibility = GONE + binding.llCloudFileContent.cloudFileProgress.visibility = VISIBLE + binding.itemCheckBox.visibility = GONE } } inner class FileIndeterminateProgress : UiStateTest(true) { override fun apply() { - itemView.cloudFolderContent.visibility = GONE - itemView.cloudFileContent.visibility = VISIBLE - itemView.cloudFileText.visibility = VISIBLE - itemView.cloudFileSubText.visibility = VISIBLE - itemView.cloudFileProgress.visibility = GONE - itemView.itemCheckBox.visibility = GONE + binding.llCloudFolderContent.cloudFolderContent.visibility = GONE + binding.llCloudFileContent.cloudFileContent.visibility = VISIBLE + binding.llCloudFileContent.cloudFileText.visibility = VISIBLE + binding.llCloudFileContent.cloudFileSubText.visibility = VISIBLE + binding.llCloudFileContent.cloudFileProgress.visibility = GONE + binding.itemCheckBox.visibility = GONE } } @@ -423,27 +414,27 @@ constructor( inner class FolderIndeterminateProgress : UiStateTest(false) { override fun apply() { - itemView.cloudFileContent.visibility = GONE - itemView.cloudFolderContent.visibility = VISIBLE - itemView.cloudFolderText.visibility = VISIBLE - itemView.cloudFolderActionText.visibility = VISIBLE - itemView.itemCheckBox.visibility = GONE + binding.llCloudFileContent.cloudFileContent.visibility = GONE + binding.llCloudFolderContent.cloudFolderContent.visibility = VISIBLE + binding.llCloudFolderContent.cloudFolderText.visibility = VISIBLE + binding.llCloudFolderContent.cloudFolderActionText.visibility = VISIBLE + binding.itemCheckBox.visibility = GONE } } inner class FileSelection : UiStateTest(true) { override fun apply() { - itemView.itemCheckBox.visibility = VISIBLE - itemView.settings.visibility = GONE + binding.itemCheckBox.visibility = VISIBLE + binding.settings.visibility = GONE } } inner class FolderSelection : UiStateTest(false) { override fun apply() { - itemView.itemCheckBox.visibility = VISIBLE - itemView.settings.visibility = GONE + binding.itemCheckBox.visibility = VISIBLE + binding.settings.visibility = GONE } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt index ea6c6f706..79ade9c33 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt @@ -2,9 +2,11 @@ package org.cryptomator.presentation.ui.adapter import android.content.Context import android.net.Uri +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import org.cryptomator.domain.exception.FatalBackendException -import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ItemBrowseCloudModelConnectionsBinding import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.OnedriveCloudModel @@ -15,13 +17,9 @@ import org.cryptomator.presentation.model.comparator.CloudModelComparator import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder import java.net.URISyntaxException import javax.inject.Inject -import kotlinx.android.synthetic.main.item_browse_cloud_model_connections.view.cloudImage -import kotlinx.android.synthetic.main.item_browse_cloud_model_connections.view.settings -import kotlinx.android.synthetic.main.view_cloud_connection_content.view.cloudSubText -import kotlinx.android.synthetic.main.view_cloud_connection_content.view.cloudText class CloudConnectionListAdapter @Inject -internal constructor(context: Context) : RecyclerViewBaseAdapter(CloudModelComparator(context)) { +internal constructor(context: Context) : RecyclerViewBaseAdapter(CloudModelComparator(context)) { interface Callback { @@ -30,28 +28,28 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter.ItemViewHolder(itemView) { + inner class CloudConnectionHolder(private val binding: ItemBrowseCloudModelConnectionsBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { override fun bind(position: Int) { internalBind(getItem(position)) } private fun internalBind(cloudModel: CloudModel) { - itemView.settings.setOnClickListener { callback.onCloudSettingsClicked(cloudModel) } + binding.settings.setOnClickListener { callback.onCloudSettingsClicked(cloudModel) } - itemView.cloudImage.setImageResource(cloudModel.cloudType().cloudImageResource) + binding.cloudImage.setImageResource(cloudModel.cloudType().cloudImageResource) itemView.setOnClickListener { callback.onCloudConnectionClicked(cloudModel) } @@ -76,39 +74,39 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter() { +constructor(private val context: Context) : RecyclerViewBaseAdapter() { interface OnItemClickListener { fun onCloudClicked(cloudModel: CloudModel) } - override fun getItemLayout(viewType: Int): Int { - return R.layout.item_cloud_setting + override fun getItemBinding(inflater: LayoutInflater, parent: ViewGroup?, viewType: Int): ItemCloudSettingBinding { + return ItemCloudSettingBinding.inflate(inflater, parent, false) } - override fun createViewHolder(view: View, viewType: Int): CloudSettingViewHolder { - return CloudSettingViewHolder(view) + override fun createViewHolder(binding: ItemCloudSettingBinding, viewType: Int): CloudSettingViewHolder { + return CloudSettingViewHolder(binding) } fun notifyCloudChanged(changedCloud: CloudModel?) { @@ -34,26 +34,26 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter.ItemViewHolder(itemView) { + inner class CloudSettingViewHolder(private val binding: ItemCloudSettingBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { override fun bind(position: Int) { val cloudModel = getItem(position) - itemView.cloudImage.setImageResource(cloudModel.cloudType().cloudImageResource) + binding.cloudImage.setImageResource(cloudModel.cloudType().cloudImageResource) when (cloudModel.cloudType()) { - CloudTypeModel.ONEDRIVE -> itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_onedrive_connections) - CloudTypeModel.PCLOUD -> itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_pcloud_connections) - CloudTypeModel.S3 -> itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_s3_connections) - CloudTypeModel.WEBDAV -> itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections) - CloudTypeModel.LOCAL -> itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations) + CloudTypeModel.ONEDRIVE -> binding.cloudName.text = context.getString(R.string.screen_cloud_settings_onedrive_connections) + CloudTypeModel.PCLOUD -> binding.cloudName.text = context.getString(R.string.screen_cloud_settings_pcloud_connections) + CloudTypeModel.S3 -> binding.cloudName.text = context.getString(R.string.screen_cloud_settings_s3_connections) + CloudTypeModel.WEBDAV -> binding.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections) + CloudTypeModel.LOCAL -> binding.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations) else -> { - itemView.cloudName.text = getCloudNameText(isAlreadyLoggedIn(cloudModel), cloudModel) + binding.cloudName.text = getCloudNameText(isAlreadyLoggedIn(cloudModel), cloudModel) if (isAlreadyLoggedIn(cloudModel)) { - itemView.cloudUsername.text = cloudModel.username() - itemView.cloudUsername.visibility = View.VISIBLE + binding.cloudUsername.text = cloudModel.username() + binding.cloudUsername.visibility = View.VISIBLE } else { - itemView.cloudUsername.visibility = View.GONE + binding.cloudUsername.visibility = View.GONE } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudsAdapter.kt index 72444b575..1babe9b23 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudsAdapter.kt @@ -1,37 +1,35 @@ package org.cryptomator.presentation.ui.adapter -import android.view.View -import org.cryptomator.presentation.R +import android.view.LayoutInflater +import android.view.ViewGroup +import org.cryptomator.presentation.databinding.ItemCloudBinding import org.cryptomator.presentation.model.CloudTypeModel -import org.cryptomator.presentation.ui.adapter.CloudsAdapter.CloudViewHolder import javax.inject.Inject -import kotlinx.android.synthetic.main.item_cloud.view.cloudImage -import kotlinx.android.synthetic.main.item_cloud.view.cloudName class CloudsAdapter @Inject -constructor() : RecyclerViewBaseAdapter() { +constructor() : RecyclerViewBaseAdapter() { interface OnItemClickListener { fun onCloudClicked(cloudTypeModel: CloudTypeModel) } - override fun getItemLayout(viewType: Int): Int { - return R.layout.item_cloud + override fun getItemBinding(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): ItemCloudBinding { + return ItemCloudBinding.inflate(inflater, parent, false) } - override fun createViewHolder(view: View, viewType: Int): CloudViewHolder { - return CloudViewHolder(view) + override fun createViewHolder(binding: ItemCloudBinding, viewType: Int): CloudViewHolder { + return CloudViewHolder(binding) } - inner class CloudViewHolder(itemView: View) : RecyclerViewBaseAdapter<*, *, *>.ItemViewHolder(itemView) { + inner class CloudViewHolder(private val binding: ItemCloudBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { override fun bind(position: Int) { val cloudTypeModel = getItem(position) - itemView.cloudImage.setImageResource(cloudTypeModel.cloudImageResource) - itemView.cloudName.setText(cloudTypeModel.displayNameResource) + binding.cloudImage.setImageResource(cloudTypeModel.cloudImageResource) + binding.cloudName.setText(cloudTypeModel.displayNameResource) - itemView.setOnClickListener { callback.onCloudClicked(cloudTypeModel) } + binding.root.setOnClickListener { callback.onCloudClicked(cloudTypeModel) } } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/RecyclerViewBaseAdapter.java b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/RecyclerViewBaseAdapter.java index 3d94339f5..a8ad4d919 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/RecyclerViewBaseAdapter.java +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/RecyclerViewBaseAdapter.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; import java.util.ArrayList; import java.util.Collection; @@ -13,19 +14,19 @@ import java.util.Comparator; import java.util.List; -public abstract class RecyclerViewBaseAdapter extends RecyclerView.Adapter { +public abstract class RecyclerViewBaseAdapter extends RecyclerView.Adapter { final List itemCollection; - Callback callback; + protected Callback callback; private Comparator comparator; - RecyclerViewBaseAdapter() { + protected RecyclerViewBaseAdapter() { this.itemCollection = new ArrayList<>(); } - RecyclerViewBaseAdapter(Comparator comparator) { + protected RecyclerViewBaseAdapter(Comparator comparator) { this.itemCollection = new ArrayList<>(); this.comparator = comparator; } @@ -33,8 +34,8 @@ public abstract class RecyclerViewBaseAdapter getComparator() { public abstract class ItemViewHolder extends RecyclerView.ViewHolder { - ItemViewHolder(View itemView) { + protected ItemViewHolder(View itemView) { super(itemView); } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedFilesAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedFilesAdapter.kt index 41b4ae926..ae3f7fdde 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedFilesAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedFilesAdapter.kt @@ -3,32 +3,31 @@ package org.cryptomator.presentation.ui.adapter import android.content.Context import android.text.Editable import android.text.TextWatcher -import android.view.View +import android.view.LayoutInflater +import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources -import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.ItemSharedFilesBinding import org.cryptomator.presentation.model.SharedFileModel import org.cryptomator.presentation.ui.adapter.SharedFilesAdapter.FileViewHolder import org.cryptomator.presentation.util.FileIcon import org.cryptomator.presentation.util.FileUtil import org.cryptomator.util.Comparators import javax.inject.Inject -import kotlinx.android.synthetic.main.item_shared_files.view.fileName -import kotlinx.android.synthetic.main.item_shared_files.view.til_file_name class SharedFilesAdapter @Inject -constructor(private val fileUtil: FileUtil, private val context: Context) : RecyclerViewBaseAdapter(Comparators.naturalOrder()) { +constructor(private val fileUtil: FileUtil, private val context: Context) : RecyclerViewBaseAdapter(Comparators.naturalOrder()) { interface Callback { fun onFileNameConflict(hasFileNameConflict: Boolean) } - override fun getItemLayout(viewType: Int): Int { - return R.layout.item_shared_files + override fun getItemBinding(inflater: LayoutInflater, parent: ViewGroup?, viewType: Int): ItemSharedFilesBinding { + return ItemSharedFilesBinding.inflate(inflater, parent, false) } - override fun createViewHolder(view: View, viewType: Int): FileViewHolder { - return FileViewHolder(view) + override fun createViewHolder(binding: ItemSharedFilesBinding, viewType: Int): FileViewHolder { + return FileViewHolder(binding) } fun show(files: List?) { @@ -47,18 +46,18 @@ constructor(private val fileUtil: FileUtil, private val context: Context) : Recy return false } - inner class FileViewHolder(itemView: View) : RecyclerViewBaseAdapter<*, *, *>.ItemViewHolder(itemView) { + inner class FileViewHolder(private val binding: ItemSharedFilesBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { - private var et_file_name_watcher: TextWatcher? = null + private var etFileNameWatcher: TextWatcher? = null override fun bind(position: Int) { - if (et_file_name_watcher != null) { - itemView.fileName.removeTextChangedListener(et_file_name_watcher) + if (etFileNameWatcher != null) { + binding.fileName.removeTextChangedListener(etFileNameWatcher) } val file = getItem(position) - itemView.til_file_name.startIconDrawable = AppCompatResources.getDrawable(context, FileIcon.fileIconFor(file.fileName, fileUtil).iconResource) - itemView.fileName.setText(file.fileName) - et_file_name_watcher = object : TextWatcher { + binding.tilFileName.startIconDrawable = AppCompatResources.getDrawable(context, FileIcon.fileIconFor(file.fileName, fileUtil).iconResource) + binding.fileName.setText(file.fileName) + etFileNameWatcher = object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { } @@ -72,7 +71,7 @@ constructor(private val fileUtil: FileUtil, private val context: Context) : Recy callback.onFileNameConflict(hasFileNameConflict()) } } - itemView.fileName.addTextChangedListener(et_file_name_watcher) + binding.fileName.addTextChangedListener(etFileNameWatcher) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt index 3f720ecc1..549ccc01a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/SharedLocationsAdapter.kt @@ -1,19 +1,16 @@ package org.cryptomator.presentation.ui.adapter +import android.view.LayoutInflater import android.view.View -import org.cryptomator.presentation.R +import android.view.ViewGroup +import org.cryptomator.presentation.databinding.ItemShareableLocationBinding import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.SharedLocationsAdapter.VaultViewHolder import javax.inject.Inject -import kotlinx.android.synthetic.main.item_shareable_location.view.chooseFolderLocation -import kotlinx.android.synthetic.main.item_shareable_location.view.chosenLocation -import kotlinx.android.synthetic.main.item_shareable_location.view.cloudImage -import kotlinx.android.synthetic.main.item_shareable_location.view.selectedVault -import kotlinx.android.synthetic.main.item_shareable_location.view.vaultName class SharedLocationsAdapter @Inject -constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { +constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()) { private var selectedVault: VaultModel? = null private var selectedLocation: String? = null @@ -39,12 +36,12 @@ constructor() : RecyclerViewBaseAdapter.ItemViewHolder(itemView) { + inner class VaultViewHolder(private val binding: ItemShareableLocationBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { private var boundVault: VaultModel? = null @@ -67,24 +64,24 @@ constructor() : RecyclerViewBaseAdapter + binding.selectedVault.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { selectVault(boundVault) } } - itemView.chooseFolderLocation.setOnClickListener { callback.onChooseLocationPressed() } + binding.chooseFolderLocation.setOnClickListener { callback.onChooseLocationPressed() } } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt index 07c4d65f0..cd9c1d3d1 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt @@ -1,19 +1,16 @@ package org.cryptomator.presentation.ui.adapter +import android.view.LayoutInflater import android.view.View -import org.cryptomator.presentation.R +import android.view.ViewGroup +import org.cryptomator.presentation.databinding.ItemVaultBinding import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.comparator.VaultPositionComparator import org.cryptomator.presentation.ui.adapter.VaultsAdapter.VaultViewHolder import javax.inject.Inject -import kotlinx.android.synthetic.main.item_vault.view.cloudImage -import kotlinx.android.synthetic.main.item_vault.view.settings -import kotlinx.android.synthetic.main.item_vault.view.unlockedImage -import kotlinx.android.synthetic.main.item_vault.view.vaultName -import kotlinx.android.synthetic.main.item_vault.view.vaultPath class VaultsAdapter @Inject -internal constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()), VaultsMoveListener.Listener { +internal constructor() : RecyclerViewBaseAdapter(VaultPositionComparator()), VaultsMoveListener.Listener { interface OnItemInteractionListener { @@ -28,12 +25,12 @@ internal constructor() : RecyclerViewBaseAdapter.ItemViewHolder(itemView) { + inner class VaultViewHolder(private val binding: ItemVaultBinding) : RecyclerViewBaseAdapter<*, *, *, *>.ItemViewHolder(binding.root) { override fun bind(position: Int) { val vaultModel = getItem(position) - itemView.vaultName.text = vaultModel.name - itemView.vaultPath.text = vaultModel.path + binding.vaultName.text = vaultModel.name + binding.vaultPath.text = vaultModel.path - itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultImageResource) + binding.cloudImage.setImageResource(vaultModel.cloudType.vaultImageResource) if (vaultModel.isLocked) { - itemView.unlockedImage.visibility = View.GONE + binding.unlockedImage.visibility = View.GONE } else { - itemView.unlockedImage.visibility = View.VISIBLE + binding.unlockedImage.visibility = View.VISIBLE } itemView.setOnClickListener { - itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource) + binding.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource) callback.onVaultClicked(vaultModel) } - itemView.unlockedImage.setOnClickListener { callback.onVaultLockClicked(vaultModel) } + binding.unlockedImage.setOnClickListener { callback.onVaultLockClicked(vaultModel) } - itemView.settings.setOnClickListener { - itemView.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource) + binding.settings.setOnClickListener { + binding.cloudImage.setImageResource(vaultModel.cloudType.vaultSelectedImageResource) callback.onVaultSettingsClicked(vaultModel) } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/AddVaultBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/AddVaultBottomSheet.kt index 62c7ef0bd..d62deb73c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/AddVaultBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/AddVaultBottomSheet.kt @@ -2,12 +2,10 @@ package org.cryptomator.presentation.ui.bottomsheet import org.cryptomator.generator.BottomSheet import org.cryptomator.presentation.R -import kotlinx.android.synthetic.main.dialog_bottom_sheet_add_vault.add_existing_vault -import kotlinx.android.synthetic.main.dialog_bottom_sheet_add_vault.create_new_vault -import kotlinx.android.synthetic.main.dialog_bottom_sheet_add_vault.title +import org.cryptomator.presentation.databinding.DialogBottomSheetAddVaultBinding @BottomSheet(R.layout.dialog_bottom_sheet_add_vault) -class AddVaultBottomSheet : BaseBottomSheet() { +class AddVaultBottomSheet : BaseBottomSheet(DialogBottomSheetAddVaultBinding::inflate) { interface Callback { @@ -16,8 +14,8 @@ class AddVaultBottomSheet : BaseBottomSheet() { } override fun setupView() { - title.text = getString(R.string.screen_vault_list_actions_title) - create_new_vault.setOnClickListener { callback?.onCreateVault() } - add_existing_vault.setOnClickListener { callback?.onAddExistingVault() } + binding.title.setText(R.string.screen_vault_list_actions_title) + binding.createNewVault.setOnClickListener { callback?.onCreateVault() } + binding.addExistingVault.setOnClickListener { callback?.onAddExistingVault() } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/BaseBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/BaseBottomSheet.kt index a42309105..024da0533 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/BaseBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/BaseBottomSheet.kt @@ -7,16 +7,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.viewbinding.ViewBinding import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.cryptomator.generator.BottomSheet import org.cryptomator.presentation.BuildConfig import org.cryptomator.presentation.R import org.cryptomator.util.SharedPreferencesHandler -abstract class BaseBottomSheet : BottomSheetDialogFragment() { +abstract class BaseBottomSheet(val bindingFactory: (LayoutInflater, ViewGroup?, Boolean) -> VB) : BottomSheetDialogFragment() { protected abstract fun setupView() + protected lateinit var binding: VB + var callback: Callback? = null override fun onAttach(context: Context) { @@ -32,7 +35,8 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() { // Need to return the view here or onViewCreated won't be called by DialogFragment, sigh override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return requireActivity().layoutInflater.inflate(bottomSheetContent, null, false) + binding = bindingFactory(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt index ffdc61674..b13df542a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.View import org.cryptomator.generator.BottomSheet import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.DialogBottomSheetCloudSettingsBinding import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.LocalStorageModel @@ -11,14 +12,9 @@ import org.cryptomator.presentation.model.OnedriveCloudModel import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.S3CloudModel import org.cryptomator.presentation.model.WebDavCloudModel -import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.change_cloud -import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.delete_cloud -import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.iv_cloud_image -import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.tv_cloud_name -import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.tv_cloud_subtext @BottomSheet(R.layout.dialog_bottom_sheet_cloud_settings) -class CloudConnectionSettingsBottomSheet : BaseBottomSheet() { +class CloudConnectionSettingsBottomSheet : BaseBottomSheet(DialogBottomSheetCloudSettingsBinding::inflate) { interface Callback { @@ -38,12 +34,12 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet throw IllegalStateException("Cloud model is not binded in the view") } - iv_cloud_image.setImageResource(cloudModel.cloudType().cloudImageResource) - change_cloud.setOnClickListener { + binding.ivCloudImage.setImageResource(cloudModel.cloudType().cloudImageResource) + binding.changeCloud.setOnClickListener { callback?.onChangeCloudClicked(cloudModel) dismiss() } - delete_cloud.setOnClickListener { + binding.deleteCloud.setOnClickListener { callback?.onDeleteCloudClicked(cloudModel) dismiss() } @@ -51,33 +47,33 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet() { +class FileSettingsBottomSheet : BaseBottomSheet(DialogBottomSheetFileSettingsBinding::inflate) { interface Callback { @@ -33,36 +25,36 @@ class FileSettingsBottomSheet : BaseBottomSheet() { +class FolderSettingsBottomSheet : BaseBottomSheet(DialogBottomSheetFolderSettingsBinding::inflate) { interface Callback { @@ -30,27 +24,27 @@ class FolderSettingsBottomSheet : BaseBottomSheet() { +class SettingsVaultBottomSheet : BaseBottomSheet(DialogBottomSheetVaultSettingsBinding::inflate) { interface Callback { @@ -28,26 +22,26 @@ class SettingsVaultBottomSheet : BaseBottomSheet() { +class VaultContentActionBottomSheet : BaseBottomSheet(DialogBottomSheetVaultActionBinding::inflate) { interface Callback { @@ -22,17 +19,17 @@ class VaultContentActionBottomSheet : BaseBottomSheet() { +@Dialog +class AppIsObscuredInfoDialog : BaseDialog(DialogAppIsObscuredInfoBinding::inflate) { public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { return builder // @@ -24,7 +24,7 @@ class AppIsObscuredInfoDialog : BaseDialog() { } public override fun setupView() { - tv_app_is_obscured_info.movementMethod = LinkMovementMethod.getInstance() + binding.tvAppIsObscuredInfo.movementMethod = LinkMovementMethod.getInstance() } companion object { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskForLockScreenDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskForLockScreenDialog.kt index fa2163ff6..562b4d5c4 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskForLockScreenDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskForLockScreenDialog.kt @@ -5,10 +5,10 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import org.cryptomator.generator.Dialog import org.cryptomator.presentation.R -import kotlinx.android.synthetic.main.dialog_no_screen_lock_set.cb_select_screen_lock +import org.cryptomator.presentation.databinding.DialogNoScreenLockSetBinding -@Dialog(R.layout.dialog_no_screen_lock_set) -class AskForLockScreenDialog : BaseDialog() { +@Dialog +class AskForLockScreenDialog : BaseDialog(DialogNoScreenLockSetBinding::inflate) { interface Callback { @@ -18,7 +18,7 @@ class AskForLockScreenDialog : BaseDialog() { override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { builder // .setTitle(R.string.dialog_no_screen_lock_title) // - .setNeutralButton(getString(R.string.dialog_unable_to_share_positive_button)) { _: DialogInterface, _: Int -> callback?.onAskForLockScreenFinished(cb_select_screen_lock.isChecked) } + .setNeutralButton(getString(R.string.dialog_unable_to_share_positive_button)) { _: DialogInterface, _: Int -> callback?.onAskForLockScreenFinished(binding.cbSelectScreenLock.isChecked) } return builder.create() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskIgnoreBatteryOptimizationsDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskIgnoreBatteryOptimizationsDialog.kt index b6c58476c..f5c75361c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskIgnoreBatteryOptimizationsDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AskIgnoreBatteryOptimizationsDialog.kt @@ -5,11 +5,10 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import org.cryptomator.generator.Dialog import org.cryptomator.presentation.R -import kotlinx.android.synthetic.main.dialog_ask_ignore_battery_optimizations.cb_ask_ignore_battery_optimizations -import kotlinx.android.synthetic.main.dialog_ask_ignore_battery_optimizations.tv_ask_ignore_battery_optimizations +import org.cryptomator.presentation.databinding.DialogAskIgnoreBatteryOptimizationsBinding -@Dialog(R.layout.dialog_ask_ignore_battery_optimizations) -class AskIgnoreBatteryOptimizationsDialog : BaseDialog() { +@Dialog +class AskIgnoreBatteryOptimizationsDialog : BaseDialog(DialogAskIgnoreBatteryOptimizationsBinding::inflate) { interface Callback { @@ -21,12 +20,12 @@ class AskIgnoreBatteryOptimizationsDialog : BaseDialog callback?.onAskIgnoreBatteryOptimizationsAccepted() } // - .setNegativeButton(getString(R.string.dialog_ask_ignore_battery_optimizations_negative_button)) { _: DialogInterface, _: Int -> callback?.onAskIgnoreBatteryOptimizationsRejected(!cb_ask_ignore_battery_optimizations.isChecked) } // + .setNegativeButton(getString(R.string.dialog_ask_ignore_battery_optimizations_negative_button)) { _: DialogInterface, _: Int -> callback?.onAskIgnoreBatteryOptimizationsRejected(!binding.cbAskIgnoreBatteryOptimizations.isChecked) } // .create() } public override fun setupView() { - tv_ask_ignore_battery_optimizations.text = String.format( + binding.tvAskIgnoreBatteryOptimizations.text = String.format( getString(R.string.dialog_ask_ignore_battery_optimizations_hint), // getString(R.string.app_name), // getString(R.string.dialog_ask_ignore_battery_optimizations_neutral_button) // diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AssignSslCertificateDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AssignSslCertificateDialog.kt index b697bdeac..bcb831520 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AssignSslCertificateDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AssignSslCertificateDialog.kt @@ -9,15 +9,12 @@ import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.generator.Dialog import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.DialogHandleSslCertificateBinding import java.security.cert.CertificateException import java.security.cert.X509Certificate -import kotlinx.android.synthetic.main.dialog_handle_ssl_certificate.cb_accept_certificate -import kotlinx.android.synthetic.main.dialog_handle_ssl_certificate.certificate_details -import kotlinx.android.synthetic.main.dialog_handle_ssl_certificate.show_certificate -import kotlinx.android.synthetic.main.dialog_handle_ssl_certificate.tv_finger_print_text -@Dialog(R.layout.dialog_handle_ssl_certificate) -class AssignSslCertificateDialog : BaseDialog() { +@Dialog +class AssignSslCertificateDialog : BaseDialog(DialogHandleSslCertificateBinding::inflate) { private lateinit var certificate: X509Certificate @@ -43,20 +40,19 @@ class AssignSslCertificateDialog : BaseDialog + binding.cbAcceptCertificate.setOnCheckedChangeListener { _, isChecked -> run { alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = isChecked } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/BaseDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/BaseDialog.kt index 2a465c4be..ccab631ad 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/BaseDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/BaseDialog.kt @@ -16,16 +16,19 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager +import androidx.viewbinding.ViewBinding import org.cryptomator.generator.Dialog import org.cryptomator.presentation.BuildConfig import org.cryptomator.presentation.util.KeyboardHelper import org.cryptomator.util.SharedPreferencesHandler import java.util.function.Supplier -abstract class BaseDialog : DialogFragment() { +abstract class BaseDialog(val bindingFactory: (LayoutInflater, ViewGroup?, Boolean) -> VB) : DialogFragment() { private lateinit var customDialog: View + protected lateinit var binding: VB + var callback: Callback? = null protected abstract fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog @@ -41,17 +44,21 @@ abstract class BaseDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): android.app.Dialog { + val inflater = LayoutInflater.from(context) + binding = bindingFactory(inflater, null, false) + val builder = AlertDialog.Builder(requireActivity()) - customDialog = requireActivity().layoutInflater.inflate(dialogContent, null) - builder.setView(customDialog) - val dialog = setupDialog(builder) + builder.setView(binding.root) + setupDialog(builder) + + val dialog = builder.create() dialog.window?.decorView?.filterTouchesWhenObscured = disableDialogWhenObscured() + return dialog } - // Need to return the view here or onViewCreated won't be called by DialogFragment, sigh override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return customDialog + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -116,9 +123,6 @@ abstract class BaseDialog : DialogFragment() { KeyboardHelper.hideKeyboard(requireActivity(), view) } - private val dialogContent: Int - get() = javaClass.getAnnotation(org.cryptomator.generator.Dialog::class.java)!!.value - protected fun registerOnEditorDoneActionAndPerformButtonClick(editText: EditText, positiveButton: Supplier