From 4b474ebb4d70bd61241c7c7b14bf4d1e497b27f6 Mon Sep 17 00:00:00 2001 From: Goldmaster <7105970+Goldmaster@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:14:41 +0100 Subject: [PATCH 1/9] Start work on SMB implementation. --- .idea/appInsightsSettings.xml | 26 ++++ .idea/deploymentTargetSelector.xml | 18 +++ .idea/misc.xml | 2 +- .idea/planningMode.xml | 10 ++ .../CloudContentRepositoryFactories.java | 3 + .../CloudContentRepositoryFactories.java | 3 + .../CloudContentRepositoryFactories.java | 7 +- .../cloud/smb/SmbCloudContentRepository.kt | 91 +++++++++++ .../smb/SmbCloudContentRepositoryFactory.java | 41 +++++ .../org/cryptomator/data/cloud/smb/SmbFile.kt | 17 ++ .../cryptomator/data/cloud/smb/SmbFolder.kt | 19 +++ .../org/cryptomator/data/cloud/smb/SmbNode.kt | 22 +++ .../data/db/mappers/CloudEntityMapper.java | 14 ++ .../java/org/cryptomator/domain/CloudType.kt | 2 +- .../java/org/cryptomator/domain/SmbCloud.java | 145 ++++++++++++++++++ .../presentation/model/CloudTypeModel.kt | 7 + .../presentation/model/SmbCloudModel.kt | 40 +++++ .../model/mappers/CloudModelMapper.kt | 2 + .../presenter/AuthenticateCloudPresenter.kt | 15 ++ .../presenter/CloudConnectionListPresenter.kt | 1 + .../presenter/CloudSettingsPresenter.kt | 11 +- .../activity/CryptomatorVariantsActivity.kt | 6 +- presentation/src/main/res/values/strings.xml | 1 + 23 files changed, 495 insertions(+), 8 deletions(-) create mode 100644 .idea/appInsightsSettings.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/planningMode.xml create mode 100644 data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepository.kt create mode 100644 data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepositoryFactory.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/smb/SmbFile.kt create mode 100644 data/src/main/java/org/cryptomator/data/cloud/smb/SmbFolder.kt create mode 100644 data/src/main/java/org/cryptomator/data/cloud/smb/SmbNode.kt create mode 100644 domain/src/main/java/org/cryptomator/domain/SmbCloud.java create mode 100644 presentation/src/main/java/org/cryptomator/presentation/model/SmbCloudModel.kt diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000000..371f2e299f --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000000..8c1575c2be --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 1d3e3ba73f..edb77fab24 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -54,7 +54,7 @@ - + diff --git a/.idea/planningMode.xml b/.idea/planningMode.xml new file mode 100644 index 0000000000..affd64cc89 --- /dev/null +++ b/.idea/planningMode.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java index 918d4dfb20..51fb48a863 100644 --- a/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java +++ b/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java @@ -7,6 +7,7 @@ import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory; import org.cryptomator.data.cloud.pcloud.PCloudContentRepositoryFactory; import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory; +import org.cryptomator.data.cloud.smb.SmbCloudContentRepositoryFactory; import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory; import org.cryptomator.data.repository.CloudContentRepositoryFactory; import org.jetbrains.annotations.NotNull; @@ -29,6 +30,7 @@ public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory drop OnedriveCloudContentRepositoryFactory oneDriveFactory, // PCloudContentRepositoryFactory pCloudFactory, // S3CloudContentRepositoryFactory s3Factory, // + SmbCloudContentRepositoryFactory smbFactory, // CryptoCloudContentRepositoryFactory cryptoFactory, // LocalStorageContentRepositoryFactory localStorageFactory, // WebDavCloudContentRepositoryFactory webDavFactory) { @@ -38,6 +40,7 @@ public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory drop oneDriveFactory, // pCloudFactory, // s3Factory, // + smbFactory, // cryptoFactory, // localStorageFactory, // webDavFactory); diff --git a/data/src/fdroidAccrescent/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/fdroidAccrescent/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java index 4511d33771..1d3e577f3d 100644 --- a/data/src/fdroidAccrescent/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java +++ b/data/src/fdroidAccrescent/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java @@ -6,6 +6,7 @@ import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory; import org.cryptomator.data.cloud.pcloud.PCloudContentRepositoryFactory; import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory; +import org.cryptomator.data.cloud.smb.SmbCloudContentRepositoryFactory; import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory; import org.cryptomator.data.repository.CloudContentRepositoryFactory; import org.jetbrains.annotations.NotNull; @@ -27,6 +28,7 @@ public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory drop OnedriveCloudContentRepositoryFactory oneDriveFactory, // PCloudContentRepositoryFactory pCloudFactory, // S3CloudContentRepositoryFactory s3Factory, // + SmbCloudContentRepositoryFactory smbFactory, // CryptoCloudContentRepositoryFactory cryptoFactory, // LocalStorageContentRepositoryFactory localStorageFactory, // WebDavCloudContentRepositoryFactory webDavFactory) { @@ -35,6 +37,7 @@ public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory drop oneDriveFactory, // pCloudFactory, // s3Factory, // + smbFactory, // cryptoFactory, // localStorageFactory, // webDavFactory); diff --git a/data/src/lite/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/lite/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java index b058c305b4..91805dae92 100644 --- a/data/src/lite/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java +++ b/data/src/lite/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java @@ -5,6 +5,7 @@ import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory; import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory; import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory; +import org.cryptomator.data.cloud.smb.SmbCloudContentRepositoryFactory; import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory; import org.cryptomator.data.repository.CloudContentRepositoryFactory; import org.jetbrains.annotations.NotNull; @@ -23,12 +24,14 @@ public class CloudContentRepositoryFactories implements Iterable(Intercepted(cloud)) { + + @Throws(BackendException::class) + override fun throwWrappedIfRequired(e: Exception) { + // Not yet implemented + } + + private class Intercepted(private val cloud: SmbCloud) : CloudContentRepository { + + override fun root(cloud: SmbCloud): SmbFolder { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun resolve(cloud: SmbCloud, path: String): SmbFolder { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun file(parent: SmbFolder, name: String): SmbFile { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun file(parent: SmbFolder, name: String, size: Long?): SmbFile { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun folder(parent: SmbFolder, name: String): SmbFolder { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun exists(node: SmbNode): Boolean { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun list(folder: SmbFolder): List { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun create(folder: SmbFolder): SmbFolder { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun move(source: SmbFolder, target: SmbFolder): SmbFolder { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun move(source: SmbFile, target: SmbFile): SmbFile { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun write(file: SmbFile, data: DataSource, progressAware: ProgressAware, replace: Boolean, size: Long): SmbFile { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun read(file: SmbFile, encryptedTmpFile: File?, data: OutputStream, progressAware: ProgressAware) { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun delete(node: SmbNode) { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun checkAuthenticationAndRetrieveCurrentAccount(cloud: SmbCloud): String { + throw UnsupportedOperationException("SMB not yet implemented") + } + + override fun logout(cloud: SmbCloud) { + throw UnsupportedOperationException("SMB not yet implemented") + } + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepositoryFactory.java b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepositoryFactory.java new file mode 100644 index 0000000000..6beffa8916 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepositoryFactory.java @@ -0,0 +1,41 @@ +package org.cryptomator.data.cloud.smb; + +import android.content.Context; + +import org.cryptomator.data.repository.CloudContentRepositoryFactory; +import org.cryptomator.domain.Cloud; +import org.cryptomator.domain.SmbCloud; +import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException; +import org.cryptomator.domain.repository.CloudContentRepository; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import static org.cryptomator.domain.CloudType.SMB; + +/** + * SMB Cloud content repository factory. + * Skeleton for the first step of SMB support. + */ +@Singleton +public class SmbCloudContentRepositoryFactory implements CloudContentRepositoryFactory { + + private final Context context; + + @Inject + public SmbCloudContentRepositoryFactory(Context context) { + this.context = context; + } + + @Override + public boolean supports(Cloud cloud) { + return cloud.type() == SMB; + } + + @Override + public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) { + SmbCloud smbCloud = (SmbCloud) cloud; + // Authentication check will be added when SMB implementation is ready + return new SmbCloudContentRepository(smbCloud, context); + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFile.kt b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFile.kt new file mode 100644 index 0000000000..1f5aec6467 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFile.kt @@ -0,0 +1,17 @@ +package org.cryptomator.data.cloud.smb + +import org.cryptomator.domain.Cloud +import org.cryptomator.domain.CloudFile +import java.util.Date + +/** + * SMB Cloud file implementation. + * Skeleton for the first step of SMB support. + */ +class SmbFile( + override val parent: SmbFolder, + override val name: String, + override val cloud: Cloud, + override val size: Long? = null, + override val modified: Date? = null +) : SmbNode, CloudFile diff --git a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFolder.kt b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFolder.kt new file mode 100644 index 0000000000..09a4fd78a4 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbFolder.kt @@ -0,0 +1,19 @@ +package org.cryptomator.data.cloud.smb + +import org.cryptomator.domain.Cloud +import org.cryptomator.domain.CloudFolder + +/** + * SMB Cloud folder implementation. + * Skeleton for the first step of SMB support. + */ +open class SmbFolder( + override val parent: SmbFolder?, + override val name: String, + override val cloud: Cloud +) : SmbNode, CloudFolder { + + override fun withCloud(cloud: Cloud?): CloudFolder? { + return cloud?.let { SmbFolder(parent, name, it) } + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbNode.kt b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbNode.kt new file mode 100644 index 0000000000..9fc63926ec --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbNode.kt @@ -0,0 +1,22 @@ +package org.cryptomator.data.cloud.smb + +import org.cryptomator.domain.Cloud +import org.cryptomator.domain.CloudNode + +/** + * SMB Cloud node interface. + * Skeleton for the first step of SMB support. + */ +interface SmbNode : CloudNode { + override val parent: SmbFolder? + + override val path: String + get() { + val parentPath = parent?.path ?: "" + return if (parentPath.endsWith("/") || parentPath.isEmpty()) { + parentPath + name + } else { + "$parentPath/$name" + } + } +} diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index 5dd41c4d61..f89ba6aa90 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -9,6 +9,7 @@ import org.cryptomator.domain.OnedriveCloud; import org.cryptomator.domain.PCloud; import org.cryptomator.domain.S3Cloud; +import org.cryptomator.domain.SmbCloud; import org.cryptomator.domain.WebDavCloud; import javax.inject.Inject; @@ -20,6 +21,7 @@ import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud; import static org.cryptomator.domain.PCloud.aPCloud; import static org.cryptomator.domain.S3Cloud.aS3Cloud; +import static org.cryptomator.domain.SmbCloud.aSmbCloud; import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud; @Singleton @@ -79,6 +81,13 @@ public Cloud fromEntity(CloudEntity entity) { .withPassword(entity.getAccessToken()) // .withCertificate(entity.getWebdavCertificate()) // .build(); + case SMB: + return aSmbCloud() // + .withId(entity.getId()) // + .withUrl(entity.getUrl()) // + .withUsername(entity.getUsername()) // + .withPassword(entity.getAccessToken()) // + .build(); default: throw new IllegalStateException("Unhandled enum constant " + type); } @@ -123,6 +132,11 @@ public CloudEntity toEntity(Cloud domainObject) { result.setUsername(((WebDavCloud) domainObject).username()); result.setWebdavCertificate(((WebDavCloud) domainObject).certificate()); break; + case SMB: + result.setAccessToken(((SmbCloud) domainObject).password()); + result.setUrl(((SmbCloud) domainObject).url()); + result.setUsername(((SmbCloud) domainObject).username()); + break; default: throw new IllegalStateException("Unhandled enum constant " + domainObject.type()); } diff --git a/domain/src/main/java/org/cryptomator/domain/CloudType.kt b/domain/src/main/java/org/cryptomator/domain/CloudType.kt index a003c02565..558f159ff1 100644 --- a/domain/src/main/java/org/cryptomator/domain/CloudType.kt +++ b/domain/src/main/java/org/cryptomator/domain/CloudType.kt @@ -1,5 +1,5 @@ package org.cryptomator.domain enum class CloudType { - DROPBOX, GOOGLE_DRIVE, ONEDRIVE, PCLOUD, WEBDAV, LOCAL, S3, CRYPTO + DROPBOX, GOOGLE_DRIVE, ONEDRIVE, PCLOUD, WEBDAV, LOCAL, S3, CRYPTO, SMB } diff --git a/domain/src/main/java/org/cryptomator/domain/SmbCloud.java b/domain/src/main/java/org/cryptomator/domain/SmbCloud.java new file mode 100644 index 0000000000..43651ed42d --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/SmbCloud.java @@ -0,0 +1,145 @@ +package org.cryptomator.domain; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * SMB Cloud implementation. + * Currently just a skeleton for the first step of SMB support. + */ +public class SmbCloud implements Cloud { + + private final Long id; + private final String url; + private final String username; + private final String password; + + private SmbCloud(Builder builder) { + this.id = builder.id; + this.url = builder.url; + this.username = builder.username; + this.password = builder.password; + } + + public static Builder aSmbCloud() { + return new Builder(); + } + + public static Builder aCopyOf(SmbCloud smbCloud) { + return new Builder() // + .withId(smbCloud.id()) // + .withUrl(smbCloud.url()) // + .withUsername(smbCloud.username()) // + .withPassword(smbCloud.password()); + } + + @Override + public Long id() { + return id; + } + + @Override + public boolean configurationMatches(Cloud cloud) { + return cloud instanceof SmbCloud && configurationMatches((SmbCloud) cloud); + } + + private boolean configurationMatches(SmbCloud cloud) { + return Objects.equals(url, cloud.url) && Objects.equals(username, cloud.username); + } + + @Override + public CloudType type() { + return CloudType.SMB; + } + + public String password() { + return password; + } + + public String url() { + return url; + } + + public String username() { + return username; + } + + @Override + public boolean persistent() { + return true; + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @NotNull + @Override + public String toString() { + return "SMB"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + if (obj == this) { + return true; + } + return internalEquals((SmbCloud) obj); + } + + @Override + public int hashCode() { + return id == null ? 0 : id.hashCode(); + } + + private boolean internalEquals(SmbCloud obj) { + return Objects.equals(id, obj.id); + } + + public static class Builder { + + private Long id; + private String password; + private String url; + private String username; + + private Builder() { + } + + public Builder withId(Long id) { + this.id = id; + return this; + } + + public Builder withUsername(String username) { + this.username = username; + return this; + } + + public Builder withPassword(String password) { + this.password = password; + return this; + } + + public Builder withUrl(String url) { + this.url = url; + return this; + } + + public SmbCloud build() { + return new SmbCloud(this); + } + + } + +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt index 60d1d97989..1c1d582372 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt @@ -46,6 +46,13 @@ enum class CloudTypeModel(builder: Builder) { .withVaultSelectedImageResource(R.drawable.s3_vault_selected) // .withMultiInstances() ), // + SMB( + Builder("SMB", R.string.cloud_names_smb) // + .withCloudImageResource(R.drawable.webdav) // + .withVaultImageResource(R.drawable.webdav_vault) // + .withVaultSelectedImageResource(R.drawable.webdav_vault_selected) // + .withMultiInstances() + ), // LOCAL( Builder("LOCAL", R.string.cloud_names_local_storage) // .withCloudImageResource(R.drawable.local_fs) // diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/SmbCloudModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/SmbCloudModel.kt new file mode 100644 index 0000000000..b524362a71 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/model/SmbCloudModel.kt @@ -0,0 +1,40 @@ +package org.cryptomator.presentation.model + +import org.cryptomator.domain.Cloud +import org.cryptomator.domain.SmbCloud +import org.cryptomator.presentation.R + +/** + * SMB Cloud model. + * Currently just a skeleton for the first step of SMB support. + */ +class SmbCloudModel(cloud: Cloud) : CloudModel(cloud) { + + override fun name(): Int { + return R.string.cloud_names_smb + } + + override fun username(): String? { + return smbCloud().username() + } + + override fun cloudType(): CloudTypeModel { + return CloudTypeModel.SMB + } + + fun url(): String? { + return smbCloud().url() + } + + fun password(): String? { + return smbCloud().password() + } + + fun id(): Long? { + return smbCloud().id() + } + + private fun smbCloud(): SmbCloud { + return toCloud() as SmbCloud + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/mappers/CloudModelMapper.kt b/presentation/src/main/java/org/cryptomator/presentation/model/mappers/CloudModelMapper.kt index 0006b4571e..4aa977f097 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/mappers/CloudModelMapper.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/mappers/CloudModelMapper.kt @@ -11,6 +11,7 @@ import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.OnedriveCloudModel import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.S3CloudModel +import org.cryptomator.presentation.model.SmbCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import javax.inject.Inject @@ -31,6 +32,7 @@ class CloudModelMapper @Inject constructor() : ModelMapper() CloudTypeModel.S3 -> S3CloudModel(domainObject) CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject) CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject) + CloudTypeModel.SMB -> SmbCloudModel(domainObject) null -> throw IllegalStateException("The type of the object shouldn't be null") } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index 73dbce2a69..92e9c1d95a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -13,6 +13,7 @@ import org.cryptomator.domain.DropboxCloud import org.cryptomator.domain.GoogleDriveCloud import org.cryptomator.domain.OnedriveCloud import org.cryptomator.domain.PCloud +import org.cryptomator.domain.SmbCloud import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.di.PerView import org.cryptomator.domain.exception.FatalBackendException @@ -69,6 +70,7 @@ class AuthenticateCloudPresenter @Inject constructor( // OnedriveAuthStrategy(), // PCloudAuthStrategy(), // WebDAVAuthStrategy(), // + SmbAuthStrategy(), // S3AuthStrategy(), // LocalStorageAuthStrategy() // ) @@ -117,6 +119,7 @@ class AuthenticateCloudPresenter @Inject constructor( // return when (cloud.type()) { CloudType.DROPBOX -> DropboxCloud.aCopyOf(cloud as DropboxCloud).withUsername(username).build() CloudType.ONEDRIVE -> OnedriveCloud.aCopyOf(cloud as OnedriveCloud).withUsername(username).build() + CloudType.SMB -> SmbCloud.aCopyOf(cloud as SmbCloud).withUsername(username).build() else -> throw IllegalStateException("Cloud " + cloud.type() + " is not supported") } } @@ -395,6 +398,18 @@ class AuthenticateCloudPresenter @Inject constructor( // } } + private inner class SmbAuthStrategy : AuthStrategy { + + override fun supports(cloud: CloudModel): Boolean { + return cloud.cloudType() == CloudTypeModel.SMB + } + + override fun resumed(intent: AuthenticateCloudIntent) { + // SMB authentication is not yet implemented + failAuthentication(intent.cloud().name()) + } + } + fun onAcceptWebDavCertificateClicked(cloud: WebDavCloud, certificate: X509Certificate) { try { val webDavCloudWithAcceptedCert = WebDavCloud.aCopyOf(cloud) // 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 6c3f205b30..ee7285ec5b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -128,6 +128,7 @@ class CloudConnectionListPresenter @Inject constructor( // CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeMultiCloud(), Intents.webDavAddOrChangeIntent()) CloudTypeModel.PCLOUD -> requestActivityResult(ActivityResultCallbacks.pCloudAuthenticationFinished(), Intents.authenticatePCloudIntent()) CloudTypeModel.S3 -> requestActivityResult(ActivityResultCallbacks.addChangeMultiCloud(), Intents.s3AddOrChangeIntent()) + CloudTypeModel.SMB -> Toast.makeText(activity().applicationContext, "SMB not yet implemented", Toast.LENGTH_SHORT).show() CloudTypeModel.LOCAL -> openDocumentTree() else -> throw IllegalStateException("Cloud type is not supported") } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt index f72c032ead..39b6fc21bb 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt @@ -5,6 +5,7 @@ import org.cryptomator.domain.LocalStorageCloud import org.cryptomator.domain.OnedriveCloud import org.cryptomator.domain.PCloud import org.cryptomator.domain.S3Cloud +import org.cryptomator.domain.SmbCloud import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.di.PerView import org.cryptomator.domain.exception.FatalBackendException @@ -22,6 +23,7 @@ import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.OnedriveCloudModel import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.S3CloudModel +import org.cryptomator.presentation.model.SmbCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.mappers.CloudModelMapper import org.cryptomator.presentation.ui.activity.view.CloudSettingsView @@ -44,7 +46,8 @@ class CloudSettingsPresenter @Inject constructor( // CloudTypeModel.ONEDRIVE, // CloudTypeModel.PCLOUD, // CloudTypeModel.S3, // - CloudTypeModel.WEBDAV + CloudTypeModel.WEBDAV, // + CloudTypeModel.SMB ) fun loadClouds() { @@ -101,6 +104,7 @@ class CloudSettingsPresenter @Inject constructor( // CloudTypeModel.ONEDRIVE -> return context().getString(R.string.screen_cloud_settings_onedrive_connections) CloudTypeModel.PCLOUD -> return context().getString(R.string.screen_cloud_settings_pcloud_connections) CloudTypeModel.WEBDAV -> return context().getString(R.string.screen_cloud_settings_webdav_connections) + CloudTypeModel.SMB -> return context().getString(R.string.cloud_names_smb) CloudTypeModel.S3 -> return context().getString(R.string.screen_cloud_settings_s3_connections) CloudTypeModel.LOCAL -> return context().getString(R.string.screen_cloud_settings_local_storage_locations) else -> {} @@ -138,6 +142,7 @@ class CloudSettingsPresenter @Inject constructor( // it.add(aOnedriveCloud()) it.add(aPCloud()) it.add(aWebdavCloud()) + it.add(aSmbCloud()) it.add(aS3Cloud()) it.add(aLocalCloud()) } @@ -175,6 +180,10 @@ class CloudSettingsPresenter @Inject constructor( // return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build()) } + private fun aSmbCloud(): SmbCloudModel { + return SmbCloudModel(SmbCloud.aSmbCloud().build()) + } + private fun aS3Cloud(): S3CloudModel { return S3CloudModel(S3Cloud.aS3Cloud().build()) } 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 a1033bcd84..9448385b71 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 @@ -18,13 +18,13 @@ class CryptomatorVariantsActivity : BaseActivitypCloud WebDAV S3 + SMB Local storage From 50fde8d9641910d4e29519e33c3ce916d6e655f1 Mon Sep 17 00:00:00 2001 From: Goldmaster <7105970+Goldmaster@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:41:10 +0100 Subject: [PATCH 2/9] Add SMB settings interface. --- .../cloud/smb/SmbCloudContentRepository.kt | 6 +- .../domain/usecases/cloud/ConnectToSmb.java | 23 ++++ presentation/src/main/AndroidManifest.xml | 3 + .../di/component/ActivityComponent.java | 6 + .../intent/SmbAddOrChangeIntent.java | 14 +++ .../presenter/CloudConnectionListPresenter.kt | 10 +- .../presenter/SmbAddOrChangePresenter.kt | 105 ++++++++++++++++++ .../ui/activity/SmbAddOrChangeActivity.kt | 38 +++++++ .../ui/activity/view/SmbAddOrChangeView.kt | 9 ++ .../ui/fragment/SmbAddOrChangeFragment.kt | 85 ++++++++++++++ .../main/res/layout/fragment_setup_smb.xml | 64 +++++++++++ 11 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToSmb.java create mode 100644 presentation/src/main/java/org/cryptomator/presentation/intent/SmbAddOrChangeIntent.java create mode 100644 presentation/src/main/java/org/cryptomator/presentation/presenter/SmbAddOrChangePresenter.kt create mode 100644 presentation/src/main/java/org/cryptomator/presentation/ui/activity/SmbAddOrChangeActivity.kt create mode 100644 presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/SmbAddOrChangeView.kt create mode 100644 presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SmbAddOrChangeFragment.kt create mode 100644 presentation/src/main/res/layout/fragment_setup_smb.xml diff --git a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepository.kt b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepository.kt index 0b67f19b9d..a4bee46bbd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepository.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/smb/SmbCloudContentRepository.kt @@ -80,12 +80,12 @@ internal class SmbCloudContentRepository( throw UnsupportedOperationException("SMB not yet implemented") } - override fun checkAuthenticationAndRetrieveCurrentAccount(cloud: SmbCloud): String { + override fun logout(cloud: SmbCloud) { throw UnsupportedOperationException("SMB not yet implemented") } - override fun logout(cloud: SmbCloud) { - throw UnsupportedOperationException("SMB not yet implemented") + override fun checkAuthenticationAndRetrieveCurrentAccount(cloud: SmbCloud): String { + throw UnsupportedOperationException("SMB protocol is not yet implemented. This is just a UI placeholder.") } } } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToSmb.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToSmb.java new file mode 100644 index 0000000000..28bd38d49c --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToSmb.java @@ -0,0 +1,23 @@ +package org.cryptomator.domain.usecases.cloud; + +import org.cryptomator.domain.SmbCloud; +import org.cryptomator.domain.exception.BackendException; +import org.cryptomator.domain.repository.CloudContentRepository; +import org.cryptomator.generator.Parameter; +import org.cryptomator.generator.UseCase; + +@UseCase +public class ConnectToSmb { + + private final CloudContentRepository cloudContentRepository; + private final SmbCloud cloud; + + public ConnectToSmb(CloudContentRepository cloudContentRepository, @Parameter SmbCloud cloud) { + this.cloudContentRepository = cloudContentRepository; + this.cloud = cloud; + } + + public void execute() throws BackendException { + cloudContentRepository.checkAuthenticationAndRetrieveCurrentAccount(cloud); + } +} diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index ca2c57f1c2..d8a5692355 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -126,6 +126,9 @@ + diff --git a/presentation/src/main/java/org/cryptomator/presentation/di/component/ActivityComponent.java b/presentation/src/main/java/org/cryptomator/presentation/di/component/ActivityComponent.java index a47963aed3..81e9d7bdf6 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/di/component/ActivityComponent.java +++ b/presentation/src/main/java/org/cryptomator/presentation/di/component/ActivityComponent.java @@ -22,6 +22,7 @@ import org.cryptomator.presentation.ui.activity.SetPasswordActivity; import org.cryptomator.presentation.ui.activity.SettingsActivity; import org.cryptomator.presentation.ui.activity.SharedFilesActivity; +import org.cryptomator.presentation.ui.activity.SmbAddOrChangeActivity; import org.cryptomator.presentation.ui.activity.TextEditorActivity; import org.cryptomator.presentation.ui.activity.UnlockVaultActivity; import org.cryptomator.presentation.ui.activity.VaultListActivity; @@ -36,6 +37,7 @@ import org.cryptomator.presentation.ui.fragment.ImagePreviewFragment; import org.cryptomator.presentation.ui.fragment.S3AddOrChangeFragment; import org.cryptomator.presentation.ui.fragment.SetPasswordFragment; +import org.cryptomator.presentation.ui.fragment.SmbAddOrChangeFragment; import org.cryptomator.presentation.ui.fragment.SharedFilesFragment; import org.cryptomator.presentation.ui.fragment.TextEditorFragment; import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment; @@ -130,6 +132,10 @@ public interface ActivityComponent { void inject(S3AddOrChangeFragment s3AddOrChangeFragment); + void inject(SmbAddOrChangeActivity smbAddOrChangeActivity); + + void inject(SmbAddOrChangeFragment smbAddOrChangeFragment); + void inject(CryptomatorVariantsActivity cryptomatorVariantsActivity); void inject(WelcomeActivity welcomeActivity); diff --git a/presentation/src/main/java/org/cryptomator/presentation/intent/SmbAddOrChangeIntent.java b/presentation/src/main/java/org/cryptomator/presentation/intent/SmbAddOrChangeIntent.java new file mode 100644 index 0000000000..4d807e4893 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/intent/SmbAddOrChangeIntent.java @@ -0,0 +1,14 @@ +package org.cryptomator.presentation.intent; + +import org.cryptomator.generator.Intent; +import org.cryptomator.generator.Optional; +import org.cryptomator.presentation.model.SmbCloudModel; +import org.cryptomator.presentation.ui.activity.SmbAddOrChangeActivity; + +@Intent(SmbAddOrChangeActivity.class) +public interface SmbAddOrChangeIntent { + + @Optional + SmbCloudModel smbCloud(); + +} 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 ee7285ec5b..48ff06e08e 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -25,6 +25,7 @@ import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.S3CloudModel +import org.cryptomator.presentation.model.SmbCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.mappers.CloudModelMapper import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView @@ -128,7 +129,7 @@ class CloudConnectionListPresenter @Inject constructor( // CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeMultiCloud(), Intents.webDavAddOrChangeIntent()) CloudTypeModel.PCLOUD -> requestActivityResult(ActivityResultCallbacks.pCloudAuthenticationFinished(), Intents.authenticatePCloudIntent()) CloudTypeModel.S3 -> requestActivityResult(ActivityResultCallbacks.addChangeMultiCloud(), Intents.s3AddOrChangeIntent()) - CloudTypeModel.SMB -> Toast.makeText(activity().applicationContext, "SMB not yet implemented", Toast.LENGTH_SHORT).show() + CloudTypeModel.SMB -> requestActivityResult(ActivityResultCallbacks.addChangeMultiCloud(), Intents.smbAddOrChangeIntent()) CloudTypeModel.LOCAL -> openDocumentTree() else -> throw IllegalStateException("Cloud type is not supported") } @@ -185,6 +186,13 @@ class CloudConnectionListPresenter @Inject constructor( // .withWebDavCloud(cloudModel as WebDavCloudModel) ) } + cloudModel.cloudType() == CloudTypeModel.SMB -> { + requestActivityResult( + ActivityResultCallbacks.addChangeMultiCloud(), // + Intents.smbAddOrChangeIntent() // + .withSmbCloud(cloudModel as SmbCloudModel) + ) + } cloudModel.cloudType() == CloudTypeModel.S3 -> { requestActivityResult( ActivityResultCallbacks.addChangeMultiCloud(), // diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/SmbAddOrChangePresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/SmbAddOrChangePresenter.kt new file mode 100644 index 0000000000..2b6d0af6f3 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/SmbAddOrChangePresenter.kt @@ -0,0 +1,105 @@ +package org.cryptomator.presentation.presenter + +import android.widget.Toast +import org.cryptomator.domain.Cloud +import org.cryptomator.domain.SmbCloud +import org.cryptomator.domain.di.PerView +import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase +import org.cryptomator.domain.usecases.cloud.ConnectToSmbUseCase +import org.cryptomator.presentation.R +import org.cryptomator.presentation.exception.ExceptionHandlers +import org.cryptomator.presentation.model.ProgressModel +import org.cryptomator.presentation.model.ProgressStateModel +import org.cryptomator.presentation.ui.activity.view.SmbAddOrChangeView +import org.cryptomator.util.crypto.CredentialCryptor +import javax.inject.Inject + +@PerView +class SmbAddOrChangePresenter @Inject internal constructor( // + private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, // + private val connectToSmbUseCase: ConnectToSmbUseCase, // + exceptionMappings: ExceptionHandlers +) : Presenter(exceptionMappings) { + + fun checkUserInput(urlPort: String, username: String, password: String, cloudId: Long?) { + var statusMessage: String? = null + + if (password.isEmpty()) { + statusMessage = getString(R.string.screen_webdav_settings_msg_password_must_not_be_empty) + } + if (username.isEmpty()) { + statusMessage = getString(R.string.screen_webdav_settings_msg_username_must_not_be_empty) + } + if (urlPort.isEmpty()) { + statusMessage = getString(R.string.screen_webdav_settings_msg_url_must_not_be_empty) + } else if (!isValid(urlPort)) { + statusMessage = getString(R.string.screen_webdav_settings_msg_url_is_invalid) + } + if (statusMessage != null) { + Toast.makeText(context(), statusMessage, Toast.LENGTH_SHORT).show() + } else { + val urlPortWithoutTrailingSlash = if (urlPort.endsWith("/")) urlPort.substring(0, urlPort.length - 1) else urlPort + val encryptedPassword = encryptPassword(password) + view?.onCheckUserInputSucceeded(urlPortWithoutTrailingSlash, username, encryptedPassword, cloudId) + } + } + + private fun encryptPassword(password: String): String { + return CredentialCryptor // + .getInstance(context()) // + .encrypt(password) + } + + private fun isValid(urlPort: String): Boolean { + return urlPort.startsWith("smb://", ignoreCase = true) + } + + private fun mapToCloud(username: String, password: String, hostPort: String, id: Long?): SmbCloud { + var builder = SmbCloud // + .aSmbCloud() // + .withUrl(hostPort) // + .withUsername(username) // + .withPassword(password) + + if (id != null) { + builder = builder.withId(id) + } + + return builder.build() + } + + fun authenticate(username: String, password: String, urlPort: String, cloudId: Long?) { + authenticate(mapToCloud(username, password, urlPort, cloudId)) + } + + private fun authenticate(cloud: SmbCloud) { + view?.showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION)) + connectToSmbUseCase // + .withCloud(cloud) // + .run(object : DefaultResultHandler() { + override fun onSuccess(void: Void?) { + onCloudAuthenticated(cloud) + } + + override fun onError(e: Throwable) { + view?.showProgress(ProgressModel.COMPLETED) + super.onError(e) + } + }) + } + + private fun onCloudAuthenticated(cloud: Cloud) { + save(cloud) + finishWithResult(CloudConnectionListPresenter.SELECTED_CLOUD, cloud) + } + + private fun save(cloud: Cloud) { + addOrChangeCloudConnectionUseCase // + .withCloud(cloud) // + .run(DefaultResultHandler()) + } + + init { + unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, connectToSmbUseCase) + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SmbAddOrChangeActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SmbAddOrChangeActivity.kt new file mode 100644 index 0000000000..0e0e6bc80c --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/SmbAddOrChangeActivity.kt @@ -0,0 +1,38 @@ +package org.cryptomator.presentation.ui.activity + +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.SmbAddOrChangeIntent +import org.cryptomator.presentation.presenter.SmbAddOrChangePresenter +import org.cryptomator.presentation.ui.activity.view.SmbAddOrChangeView +import org.cryptomator.presentation.ui.fragment.SmbAddOrChangeFragment +import javax.inject.Inject + +@Activity +class SmbAddOrChangeActivity : BaseActivity(ActivityLayoutBinding::inflate), + SmbAddOrChangeView { + + @Inject + lateinit var smbAddOrChangePresenter: SmbAddOrChangePresenter + + @InjectIntent + lateinit var smbAddOrChangeIntent: SmbAddOrChangeIntent + + override fun setupView() { + binding.mtToolbar.toolbar.setTitle(R.string.cloud_names_smb) + setSupportActionBar(binding.mtToolbar.toolbar) + } + + override fun createFragment(): Fragment = SmbAddOrChangeFragment.newInstance(smbAddOrChangeIntent.smbCloud()) + + override fun onCheckUserInputSucceeded(urlPort: String, username: String, password: String, cloudId: Long?) { + smbAddOrChangeFragment().hideKeyboard() + smbAddOrChangePresenter.authenticate(username, password, urlPort, cloudId) + } + + private fun smbAddOrChangeFragment(): SmbAddOrChangeFragment = getCurrentFragment(R.id.fragment_container) as SmbAddOrChangeFragment + +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/SmbAddOrChangeView.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/SmbAddOrChangeView.kt new file mode 100644 index 0000000000..c2173e9782 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/view/SmbAddOrChangeView.kt @@ -0,0 +1,9 @@ +package org.cryptomator.presentation.ui.activity.view + +import org.cryptomator.presentation.model.ProgressModel + +interface SmbAddOrChangeView : View { + + fun onCheckUserInputSucceeded(urlPort: String, username: String, password: String, cloudId: Long?) + +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SmbAddOrChangeFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SmbAddOrChangeFragment.kt new file mode 100644 index 0000000000..6c76a8c83b --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SmbAddOrChangeFragment.kt @@ -0,0 +1,85 @@ +package org.cryptomator.presentation.ui.fragment + +import android.os.Bundle +import android.view.inputmethod.EditorInfo +import org.cryptomator.generator.Fragment +import org.cryptomator.presentation.databinding.FragmentSetupSmbBinding +import org.cryptomator.presentation.model.SmbCloudModel +import org.cryptomator.presentation.presenter.SmbAddOrChangePresenter +import org.cryptomator.util.crypto.CredentialCryptor +import org.cryptomator.util.crypto.FatalCryptoException +import javax.inject.Inject +import timber.log.Timber + +@Fragment +class SmbAddOrChangeFragment : BaseFragment(FragmentSetupSmbBinding::inflate) { + + @Inject + lateinit var smbAddOrChangePresenter: SmbAddOrChangePresenter + + private var cloudId: Long? = null + + private val smbCloudModel: SmbCloudModel? + get() = arguments?.getSerializable(ARG_SMB_CLOUD) as? SmbCloudModel + + override fun setupView() { + binding.createCloudButton.setOnClickListener { createCloud() } + binding.createCloudButton.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + createCloud() + } + false + } + + binding.urlPortEditText.text?.length?.let { binding.urlPortEditText.setSelection(it) } + showEditableCloudContent(smbCloudModel) + } + + private fun showEditableCloudContent(smbCloudModel: SmbCloudModel?) { + if (smbCloudModel != null) { + binding.urlPortEditText.setText(smbCloudModel.url()) + binding.userNameEditText.setText(smbCloudModel.username()) + binding.passwordEditText.setText(getPassword(smbCloudModel.password())) + cloudId = smbCloudModel.id() + } + } + + private fun getPassword(password: String?): String { + return if (password != null) { + try { + CredentialCryptor // + .getInstance(activity?.applicationContext) // + .decrypt(password) + } catch (e: FatalCryptoException) { + Timber.tag("SmbAddOrChangeFragment").e(e, "Unable to decrypt password, clearing it") + "" + } + } else "" + } + + private fun createCloud() { + val urlPort = binding.urlPortEditText.text.toString().trim() + val username = binding.userNameEditText.text.toString().trim() + val password = binding.passwordEditText.text.toString().trim() + + smbAddOrChangePresenter.checkUserInput(urlPort, username, password, cloudId) + } + + fun hideKeyboard() { + hideKeyboard(binding.passwordEditText) + } + + companion object { + + private const val ARG_SMB_CLOUD = "SMB_CLOUD" + + fun newInstance(cloudModel: SmbCloudModel?): SmbAddOrChangeFragment { + val result = SmbAddOrChangeFragment() + val args = Bundle() + args.putSerializable(ARG_SMB_CLOUD, cloudModel) + result.arguments = args + return result + } + } + +} diff --git a/presentation/src/main/res/layout/fragment_setup_smb.xml b/presentation/src/main/res/layout/fragment_setup_smb.xml new file mode 100644 index 0000000000..2537396fed --- /dev/null +++ b/presentation/src/main/res/layout/fragment_setup_smb.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + +