Initial commit: Pezkuwi Wallet Android

Complete rebrand of Nova Wallet for Pezkuwichain ecosystem.

## Features
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Polkadot ecosystem compatibility
- Staking, Governance, DeFi, NFTs
- XCM cross-chain transfers
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications

## Languages
- English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese

Based on Nova Wallet by Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
This commit is contained in:
2026-01-23 01:31:12 +03:00
commit 31c8c5995f
7621 changed files with 425838 additions and 0 deletions
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_cloud_backup_api.di
import io.novafoundation.nova.feature_cloud_backup_api.domain.CloudBackupService
import io.novafoundation.nova.feature_cloud_backup_api.presenter.mixin.CloudBackupChangingWarningMixinFactory
interface CloudBackupFeatureApi {
val cloudBackupService: CloudBackupService
val cloudBackupChangingWarningMixinFactory: CloudBackupChangingWarningMixinFactory
}
@@ -0,0 +1,86 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain
import io.novafoundation.nova.common.utils.flatMap
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.EncryptedCloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.PreCreateValidationStatus
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.WriteBackupRequest
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.DeleteBackupError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.FetchBackupError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.InvalidBackupPasswordError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.PasswordNotSaved
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.WriteBackupError
/**
* Manages cloud backup storage, serialization and encryption
*
* The storing pipeline is the following:
*
* Backup -> serialize private data -> encrypt private data -> serialize public data + encrypted private
*
* This allows to access public data without accessing password
*/
interface CloudBackupService {
/**
* Current user preferences, state known for cloud backup related functionality
*/
val session: CloudBackupSession
/**
* Checks conditions for creating initial backup
*/
suspend fun validateCanCreateBackup(): PreCreateValidationStatus
/**
* Writes a backup to the cloud, overwriting already existing one
*
* @throws WriteBackupError
*/
suspend fun writeBackupToCloud(request: WriteBackupRequest): Result<Unit>
/**
* Check if backup file exists in the cloud
*/
suspend fun isCloudBackupExist(): Result<Boolean>
/**
* @throws FetchBackupError
*/
suspend fun fetchBackup(): Result<EncryptedCloudBackup>
/**
* @throws DeleteBackupError
*/
suspend fun deleteBackup(): Result<Unit>
suspend fun signInToCloud(): Result<Unit>
}
/**
* @throws FetchBackupError
* @throws InvalidBackupPasswordError
*/
suspend fun CloudBackupService.fetchAndDecryptExistingBackup(password: String): Result<CloudBackup> {
return fetchBackup().flatMap { it.decrypt(password) }
}
/**
* @throws PasswordNotSaved
* @throws FetchBackupError
* @throws InvalidBackupPasswordError
*/
suspend fun CloudBackupService.fetchAndDecryptExistingBackupWithSavedPassword(): Result<CloudBackup> {
return fetchBackup().flatMap { encryptedBackup ->
session.getSavedPassword().flatMap { password ->
encryptedBackup.decrypt(password)
}
}
}
suspend fun CloudBackupService.writeCloudBackupWithSavedPassword(cloudBackup: CloudBackup): Result<Unit> {
return session.getSavedPassword()
.flatMap { password ->
writeBackupToCloud(WriteBackupRequest(cloudBackup, password))
}
}
@@ -0,0 +1,51 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.PasswordNotSaved
import kotlinx.coroutines.flow.Flow
import java.util.Date
interface CloudBackupSession {
/**
* Check if user enabled sync with cloud backup in the current application instance
* Enabling this means that the app should write changes of local state to backup on addition, modification or delegation of accounts
*/
suspend fun isSyncWithCloudEnabled(): Boolean
/**
* @see isSyncWithCloudEnabled
*/
suspend fun setSyncingBackupEnabled(enable: Boolean)
/**
* Observe the time of the latest backup sync
*/
fun lastSyncedTimeFlow(): Flow<Date?>
/**
* @see lastSyncedTimeFlow
*/
suspend fun setLastSyncedTime(date: Date)
/**
* @throws PasswordNotSaved
*/
suspend fun getSavedPassword(): Result<String>
suspend fun setSavedPassword(password: String)
fun cloudBackupWasInitialized(): Boolean
fun setBackupWasInitialized()
}
suspend fun CloudBackupSession.setLastSyncedTimeAsNow() {
setLastSyncedTime(Date())
}
suspend fun CloudBackupSession.initEnabledBackup(password: String) {
setBackupWasInitialized()
setSyncingBackupEnabled(true)
setLastSyncedTimeAsNow()
setSavedPassword(password)
}
@@ -0,0 +1,238 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model
import io.novafoundation.nova.common.utils.Identifiable
import io.novafoundation.nova.core.model.CryptoType
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
data class CloudBackup(
val publicData: PublicData,
val privateData: PrivateData
) {
data class PublicData(
val modifiedAt: Long,
val wallets: List<WalletPublicInfo>
)
data class WalletPublicInfo(
val walletId: String,
val substratePublicKey: ByteArray?,
val substrateAccountId: ByteArray?,
val substrateCryptoType: CryptoType?,
val ethereumAddress: ByteArray?,
val ethereumPublicKey: ByteArray?,
val name: String,
val type: Type,
val chainAccounts: Set<ChainAccountInfo>
) : Identifiable {
override val identifier: String = walletId
enum class Type {
SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, LEDGER_GENERIC, POLKADOT_VAULT
}
data class ChainAccountInfo(
val chainId: ChainId,
val publicKey: ByteArray?,
val accountId: ByteArray,
val cryptoType: ChainAccountCryptoType?,
) {
override fun equals(other: Any?): Boolean {
if (other !is ChainAccountInfo) return false
return chainId == other.chainId &&
publicKey.contentEquals(other.publicKey) &&
accountId.contentEquals(other.accountId) &&
cryptoType == other.cryptoType
}
override fun hashCode(): Int {
var result = chainId.hashCode()
result = 31 * result + (publicKey?.contentHashCode() ?: 0)
result = 31 * result + accountId.contentHashCode()
result = 31 * result + (cryptoType?.hashCode() ?: 0)
return result
}
enum class ChainAccountCryptoType {
SR25519, ED25519, ECDSA, ETHEREUM
}
}
override fun equals(other: Any?): Boolean {
if (other !is WalletPublicInfo) return false
return walletId == other.walletId &&
substratePublicKey.contentEquals(other.substratePublicKey) &&
substrateAccountId.contentEquals(other.substrateAccountId) &&
substrateCryptoType == other.substrateCryptoType &&
ethereumAddress.contentEquals(other.ethereumAddress) &&
ethereumPublicKey.contentEquals(other.ethereumPublicKey) &&
name == other.name &&
type == other.type &&
chainAccounts == other.chainAccounts
}
override fun hashCode(): Int {
var result = walletId.hashCode()
result = 31 * result + (substratePublicKey?.contentHashCode() ?: 0)
result = 31 * result + (substrateAccountId?.contentHashCode() ?: 0)
result = 31 * result + (substrateCryptoType?.hashCode() ?: 0)
result = 31 * result + (ethereumAddress?.contentHashCode() ?: 0)
result = 31 * result + (ethereumPublicKey?.contentHashCode() ?: 0)
result = 31 * result + name.hashCode()
result = 31 * result + type.hashCode()
result = 31 * result + chainAccounts.hashCode()
result = 31 * result + identifier.hashCode()
return result
}
}
data class PrivateData(
val wallets: List<WalletPrivateInfo>
)
data class WalletPrivateInfo(
val walletId: String,
val entropy: ByteArray?,
val substrate: SubstrateSecrets?,
val ethereum: EthereumSecrets?,
val chainAccounts: List<ChainAccountSecrets>,
) : Identifiable {
override val identifier: String = walletId
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as WalletPrivateInfo
if (walletId != other.walletId) return false
if (entropy != null) {
if (other.entropy == null) return false
if (!entropy.contentEquals(other.entropy)) return false
} else if (other.entropy != null) return false
if (substrate != other.substrate) return false
if (ethereum != other.ethereum) return false
if (chainAccounts != other.chainAccounts) return false
return identifier == other.identifier
}
override fun hashCode(): Int {
var result = walletId.hashCode()
result = 31 * result + (entropy?.contentHashCode() ?: 0)
result = 31 * result + (substrate?.hashCode() ?: 0)
result = 31 * result + (ethereum?.hashCode() ?: 0)
result = 31 * result + chainAccounts.hashCode()
result = 31 * result + identifier.hashCode()
return result
}
data class ChainAccountSecrets(
val accountId: ByteArray,
val entropy: ByteArray?,
val seed: ByteArray?,
val keypair: KeyPairSecrets?,
val derivationPath: String?,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ChainAccountSecrets
if (!accountId.contentEquals(other.accountId)) return false
if (entropy != null) {
if (other.entropy == null) return false
if (!entropy.contentEquals(other.entropy)) return false
} else if (other.entropy != null) return false
if (seed != null) {
if (other.seed == null) return false
if (!seed.contentEquals(other.seed)) return false
} else if (other.seed != null) return false
if (keypair != other.keypair) return false
return derivationPath == other.derivationPath
}
override fun hashCode(): Int {
var result = accountId.contentHashCode()
result = 31 * result + (entropy?.contentHashCode() ?: 0)
result = 31 * result + (seed?.contentHashCode() ?: 0)
result = 31 * result + (keypair?.hashCode() ?: 0)
result = 31 * result + (derivationPath?.hashCode() ?: 0)
return result
}
}
data class SubstrateSecrets(
val seed: ByteArray?,
val keypair: KeyPairSecrets?,
val derivationPath: String?,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SubstrateSecrets
if (seed != null) {
if (other.seed == null) return false
if (!seed.contentEquals(other.seed)) return false
} else if (other.seed != null) return false
if (keypair != other.keypair) return false
return derivationPath == other.derivationPath
}
override fun hashCode(): Int {
var result = seed?.contentHashCode() ?: 0
result = 31 * result + keypair.hashCode()
result = 31 * result + (derivationPath?.hashCode() ?: 0)
return result
}
}
data class EthereumSecrets(
val keypair: KeyPairSecrets,
val derivationPath: String?,
)
data class KeyPairSecrets(
val publicKey: ByteArray,
val privateKey: ByteArray,
val nonce: ByteArray?
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as KeyPairSecrets
if (!publicKey.contentEquals(other.publicKey)) return false
if (!privateKey.contentEquals(other.privateKey)) return false
if (nonce != null) {
if (other.nonce == null) return false
if (!nonce.contentEquals(other.nonce)) return false
} else if (other.nonce != null) return false
return true
}
override fun hashCode(): Int {
var result = publicKey.contentHashCode()
result = 31 * result + privateKey.contentHashCode()
result = 31 * result + (nonce?.contentHashCode() ?: 0)
return result
}
}
}
}
fun CloudBackup.WalletPrivateInfo.isCompletelyEmpty(): Boolean {
return entropy == null && substrate == null && ethereum == null && chainAccounts.isEmpty()
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.InvalidBackupPasswordError
interface EncryptedCloudBackup {
val publicData: CloudBackup.PublicData
/**
* @throws InvalidBackupPasswordError
*/
suspend fun decrypt(password: String): Result<CloudBackup>
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupExistingBackupFound
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupServiceUnavailable
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupUnknownError
sealed class PreCreateValidationStatus {
object Ok : PreCreateValidationStatus()
object AuthenticationFailed : PreCreateValidationStatus(), CloudBackupAuthFailed
object BackupServiceUnavailable : PreCreateValidationStatus(), CloudBackupServiceUnavailable
object ExistingBackupFound : PreCreateValidationStatus(), CloudBackupExistingBackupFound
object NotEnoughSpace : PreCreateValidationStatus(), CloudBackupNotEnoughSpace
object OtherError : PreCreateValidationStatus(), CloudBackupUnknownError
}
@@ -0,0 +1,6 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model
class WriteBackupRequest(
val cloudBackup: CloudBackup,
val password: String
)
@@ -0,0 +1,87 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup.WalletPublicInfo
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges.WalletsFromCloud
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.BackupDiffStrategyFactory
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.addToCloud
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.addToLocal
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.modifyInCloud
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.modifyLocally
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.removeFromCloud
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy.removeLocally
class CloudBackupDiff(
val localChanges: PerSourceDiff,
val cloudChanges: PerSourceDiff
) {
class PerSourceDiff(
val added: List<WalletPublicInfo>,
val modified: List<WalletPublicInfo>,
val removed: List<WalletPublicInfo>
)
}
fun CloudBackupDiff.PerSourceDiff.isEmpty(): Boolean = added.isEmpty() && modified.isEmpty() && removed.isEmpty()
fun CloudBackupDiff.PerSourceDiff.isNotEmpty(): Boolean = !isEmpty()
fun CloudBackupDiff.PerSourceDiff.isDestructive(): Boolean = removed.isNotEmpty() || modified.isNotEmpty()
fun CloudBackupDiff.PerSourceDiff.isNotDestructive(): Boolean = !isDestructive()
/**
* @see [CloudBackup.PublicData.localVsCloudDiff]
*/
fun CloudBackup.localVsCloudDiff(
cloudVersion: CloudBackup,
strategyFactory: BackupDiffStrategyFactory
): CloudBackupDiff {
return publicData.localVsCloudDiff(cloudVersion.publicData, strategyFactory)
}
/**
* Finds the diff between local and cloud versions
*
* [this] - Local snapshot
* [cloudVersion] - Cloud snapshot
*/
fun CloudBackup.PublicData.localVsCloudDiff(
cloudVersion: CloudBackup.PublicData,
strategyFactory: BackupDiffStrategyFactory
): CloudBackupDiff {
val localVersion = this
val strategy = strategyFactory(localVersion, cloudVersion)
val localToCloudDiff = CollectionDiffer.findDiff(newItems = cloudVersion.wallets, oldItems = localVersion.wallets, forceUseNewItems = false)
val walletsOnlyPresentInCloud = localToCloudDiff.added.asCloudWallets()
val walletsModifiedByCloud = localToCloudDiff.updated.asCloudWallets()
val walletsOnlyPresentLocally = localToCloudDiff.removed.asLocalWallets()
val cloudToLocalDiff = CollectionDiffer.findDiff(newItems = localVersion.wallets, oldItems = cloudVersion.wallets, forceUseNewItems = false)
val walletsModifiedByLocal = cloudToLocalDiff.updated.asLocalWallets()
return CloudBackupDiff(
localChanges = CloudBackupDiff.PerSourceDiff(
added = strategy.addToLocal(walletsOnlyPresentInCloud),
removed = strategy.removeLocally(walletsOnlyPresentLocally),
modified = strategy.modifyLocally(walletsModifiedByCloud)
),
cloudChanges = CloudBackupDiff.PerSourceDiff(
added = strategy.addToCloud(walletsOnlyPresentLocally),
removed = strategy.removeFromCloud(walletsOnlyPresentInCloud),
modified = strategy.modifyInCloud(walletsModifiedByLocal)
)
)
}
private fun List<WalletPublicInfo>.asCloudWallets(): SourcedBackupChanges<WalletsFromCloud> {
return SourcedBackupChanges(this)
}
private fun List<WalletPublicInfo>.asLocalWallets(): SourcedBackupChanges<SourcedBackupChanges.LocalWallets> {
return SourcedBackupChanges(this)
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
class SourcedBackupChanges<SOURCE>(val changes: List<CloudBackup.WalletPublicInfo>) {
object LocalWallets
object WalletsFromCloud
}
@@ -0,0 +1,70 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges.LocalWallets
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges.WalletsFromCloud
typealias BackupDiffStrategyFactory = (localData: CloudBackup.PublicData, remoteData: CloudBackup.PublicData) -> BackupDiffStrategy
interface BackupDiffStrategy {
companion object {
fun importFromCloud(): BackupDiffStrategyFactory = { _, _ ->
ImportFromCloudStrategy()
}
fun syncWithCloud(): BackupDiffStrategyFactory = { local, remote ->
SyncWithCloudStrategy(localTimestamp = local.modifiedAt, cloudTimestamp = remote.modifiedAt)
}
fun overwriteLocal(): BackupDiffStrategyFactory = { _, _ ->
OverwriteLocalStrategy()
}
}
fun shouldAddLocally(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean
fun shouldRemoveFromCloud(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean
fun shouldRemoveLocally(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): Boolean
fun shouldAddToCloud(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): Boolean
fun shouldModifyLocally(modifiedInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean
fun shouldModifyInCloud(modifiedLocally: SourcedBackupChanges<LocalWallets>): Boolean
}
fun BackupDiffStrategy.addToLocal(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): List<CloudBackup.WalletPublicInfo> {
return walletsOnlyPresentInCloud.takeValueOrEmpty(shouldAddLocally(walletsOnlyPresentInCloud))
}
fun BackupDiffStrategy.removeFromCloud(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): List<CloudBackup.WalletPublicInfo> {
return walletsOnlyPresentInCloud.takeValueOrEmpty(shouldRemoveFromCloud(walletsOnlyPresentInCloud))
}
fun BackupDiffStrategy.removeLocally(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): List<CloudBackup.WalletPublicInfo> {
return walletsOnlyPresentLocally.takeValueOrEmpty(shouldRemoveLocally(walletsOnlyPresentLocally))
}
fun BackupDiffStrategy.addToCloud(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): List<CloudBackup.WalletPublicInfo> {
return walletsOnlyPresentLocally.takeValueOrEmpty(shouldAddToCloud(walletsOnlyPresentLocally))
}
fun BackupDiffStrategy.modifyLocally(modifiedInCloud: SourcedBackupChanges<WalletsFromCloud>): List<CloudBackup.WalletPublicInfo> {
return modifiedInCloud.takeValueOrEmpty(shouldModifyLocally(modifiedInCloud))
}
fun BackupDiffStrategy.modifyInCloud(modifiedLocally: SourcedBackupChanges<LocalWallets>): List<CloudBackup.WalletPublicInfo> {
return modifiedLocally.takeValueOrEmpty(shouldModifyInCloud(modifiedLocally))
}
private fun SourcedBackupChanges<*>.takeValueOrEmpty(condition: Boolean): List<CloudBackup.WalletPublicInfo> {
return if (condition) {
changes
} else {
emptyList()
}
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges
/**
* Strategy that retains both local-exclusive and cloud-exclusive wallets
* Modified wallets are taken from local state and updated in cloud
*/
class ImportFromCloudStrategy : BackupDiffStrategy {
override fun shouldAddLocally(walletsOnlyPresentInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return true
}
override fun shouldRemoveFromCloud(walletsOnlyPresentInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return false
}
override fun shouldRemoveLocally(walletsOnlyPresentLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return false
}
override fun shouldAddToCloud(walletsOnlyPresentLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return true
}
override fun shouldModifyLocally(modifiedInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return false
}
override fun shouldModifyInCloud(modifiedLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return true
}
}
@@ -0,0 +1,33 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges
/**
* Strategy that considers cloud version to be the source of truth
*/
class OverwriteLocalStrategy : BackupDiffStrategy {
override fun shouldAddLocally(walletsOnlyPresentInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return true
}
override fun shouldRemoveFromCloud(walletsOnlyPresentInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return false
}
override fun shouldRemoveLocally(walletsOnlyPresentLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return true
}
override fun shouldAddToCloud(walletsOnlyPresentLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return false
}
override fun shouldModifyLocally(modifiedInCloud: SourcedBackupChanges<SourcedBackupChanges.WalletsFromCloud>): Boolean {
return true
}
override fun shouldModifyInCloud(modifiedLocally: SourcedBackupChanges<SourcedBackupChanges.LocalWallets>): Boolean {
return false
}
}
@@ -0,0 +1,41 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.strategy
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges.LocalWallets
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.SourcedBackupChanges.WalletsFromCloud
/**
* Strategy that uses modifiedAt timestamps to identify priority
*/
class SyncWithCloudStrategy(
localTimestamp: Long,
cloudTimestamp: Long
) : BackupDiffStrategy {
private val cloudInPriority = cloudTimestamp > localTimestamp
private val localInPriority = localTimestamp > cloudTimestamp
override fun shouldAddLocally(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean {
return cloudInPriority
}
override fun shouldRemoveFromCloud(walletsOnlyPresentInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean {
return localInPriority
}
override fun shouldRemoveLocally(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): Boolean {
return cloudInPriority
}
override fun shouldAddToCloud(walletsOnlyPresentLocally: SourcedBackupChanges<LocalWallets>): Boolean {
return localInPriority
}
override fun shouldModifyLocally(modifiedInCloud: SourcedBackupChanges<WalletsFromCloud>): Boolean {
return cloudInPriority
}
override fun shouldModifyInCloud(modifiedLocally: SourcedBackupChanges<LocalWallets>): Boolean {
return localInPriority
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.CloudBackupDiff
interface CloudBackupAuthFailed
interface CloudBackupServiceUnavailable
interface CloudBackupExistingBackupFound
interface CloudBackupNotFound
interface CloudBackupNotEnoughSpace
interface CloudBackupUnknownError
interface CorruptedBackupError
class CannotApplyNonDestructiveDiff(val cloudBackupDiff: CloudBackupDiff, val cloudBackup: CloudBackup) : Throwable()
class PasswordNotSaved : Throwable()
class InvalidBackupPasswordError : Throwable()
@@ -0,0 +1,6 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors
sealed class DeleteBackupError : Throwable() {
object Other : DeleteBackupError(), CloudBackupUnknownError
}
@@ -0,0 +1,12 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors
sealed class FetchBackupError : Throwable() {
object AuthFailed : FetchBackupError(), CloudBackupAuthFailed
object BackupNotFound : FetchBackupError(), CloudBackupNotFound
object CorruptedBackup : FetchBackupError(), CorruptedBackupError
object Other : FetchBackupError(), CloudBackupUnknownError
}
@@ -0,0 +1,6 @@
package io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors
sealed class WriteBackupError : Throwable() {
object Other : WriteBackupError(), CloudBackupUnknownError
}
@@ -0,0 +1,171 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.action
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.addColor
import io.novafoundation.nova.common.utils.formatting.spannable.spannableFormatting
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import io.novafoundation.nova.common.view.bottomSheet.action.ButtonPreferences
import io.novafoundation.nova.common.view.bottomSheet.action.negative
import io.novafoundation.nova.common.view.bottomSheet.action.primary
import io.novafoundation.nova.common.view.bottomSheet.action.secondary
import io.novafoundation.nova.feature_cloud_backup_api.R
fun ActionBottomSheetLauncher.launchBackupLostPasswordAction(resourceManager: ResourceManager, onDeleteClicked: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_password,
title = resourceManager.getString(R.string.restore_cloud_backup_delete_backup_title),
subtitle = with(resourceManager) {
val highlightedFirstPart = getString(R.string.restore_cloud_backup_delete_backup_description_highlighted_1)
.addColor(getColor(R.color.text_primary))
val highlightedSecondPart = getString(R.string.restore_cloud_backup_delete_backup_description_highlighted_2)
.addColor(getColor(R.color.text_primary))
getString(R.string.restore_cloud_backup_delete_backup_description).spannableFormatting(highlightedFirstPart, highlightedSecondPart)
},
neutralButtonPreferences = ButtonPreferences.secondary(
resourceManager.getString(
R.string.common_cancel
)
),
actionButtonPreferences = ButtonPreferences.negative(
resourceManager.getString(R.string.cloud_backup_delete_button),
onDeleteClicked
)
)
}
fun ActionBottomSheetLauncher.launchDeleteBackupAction(resourceManager: ResourceManager, onDeleteClicked: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_delete,
title = resourceManager.getString(R.string.cloud_backup_delete_action_title),
subtitle = with(resourceManager) {
val highlightedPart = getString(R.string.cloud_backup_delete_action_subtitle_highlighted)
.addColor(getColor(R.color.text_primary))
getString(R.string.cloud_backup_delete_action_subtitle).spannableFormatting(highlightedPart)
},
neutralButtonPreferences = ButtonPreferences.secondary(
resourceManager.getString(
R.string.common_cancel
)
),
actionButtonPreferences = ButtonPreferences.negative(
resourceManager.getString(R.string.cloud_backup_delete_button),
onDeleteClicked
)
)
}
fun ActionBottomSheetLauncher.launchRememberPasswordWarning(resourceManager: ResourceManager) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_lock,
title = resourceManager.getString(R.string.create_cloud_backup_password_alert_title),
subtitle = with(resourceManager) {
val highlightedPart = getString(R.string.create_cloud_backup_password_alert_subtitle_highlighted)
.addColor(getColor(R.color.text_primary))
getString(R.string.create_cloud_backup_password_alert_subtitle).spannableFormatting(highlightedPart)
},
actionButtonPreferences = ButtonPreferences.primary(resourceManager.getString(R.string.common_got_it))
)
}
fun ActionBottomSheetLauncher.launchCorruptedBackupFoundAction(resourceManager: ResourceManager, onDeleteClicked: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_error,
title = resourceManager.getString(R.string.corrupted_backup_error_title),
subtitle = with(resourceManager) {
val highlightedPart = getString(R.string.corrupted_backup_error_subtitle_highlighted)
.addColor(getColor(R.color.text_primary))
getString(R.string.corrupted_backup_error_subtitle).spannableFormatting(highlightedPart)
},
neutralButtonPreferences = ButtonPreferences.secondary(resourceManager.getString(R.string.common_cancel)),
actionButtonPreferences = ButtonPreferences.negative(
resourceManager.getString(R.string.cloud_backup_delete_button),
onDeleteClicked
)
)
}
fun ActionBottomSheetLauncher.launchExistingCloudBackupAction(resourceManager: ResourceManager, onImportClicked: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_sync,
title = resourceManager.getString(R.string.existing_cloud_backup_found_title),
subtitle = with(resourceManager) {
val highlightedPart = getString(R.string.existing_cloud_backup_found_subtitle_highlight)
.addColor(getColor(R.color.text_primary))
getString(R.string.existing_cloud_backup_found_subtitle).spannableFormatting(highlightedPart)
},
neutralButtonPreferences = ButtonPreferences.secondary(resourceManager.getString(R.string.common_cancel)),
actionButtonPreferences = ButtonPreferences.primary(
resourceManager.getString(R.string.existing_cloud_backup_found_button),
onImportClicked
)
)
}
fun ActionBottomSheetLauncher.launchDeprecatedPasswordAction(resourceManager: ResourceManager, onEnterPasswordClick: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_password,
title = resourceManager.getString(R.string.deprecated_cloud_backup_password_title),
subtitle = with(resourceManager) {
val highlightedPart = getString(R.string.deprecated_cloud_backup_password_subtitle_highlight)
.addColor(getColor(R.color.text_primary))
getString(R.string.deprecated_cloud_backup_password_subtitle).spannableFormatting(highlightedPart)
},
neutralButtonPreferences = ButtonPreferences.secondary(resourceManager.getString(R.string.common_not_now)),
actionButtonPreferences = ButtonPreferences.primary(
resourceManager.getString(R.string.common_enter_password),
onEnterPasswordClick
)
)
}
fun ActionBottomSheetLauncher.launchCloudBackupChangesAction(resourceManager: ResourceManager, onReviewClicked: () -> Unit) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_warning,
title = resourceManager.getString(R.string.cloud_backup_destructive_changes_action_title),
subtitle = resourceManager.getString(R.string.cloud_backup_destructive_changes_action_subtitle),
neutralButtonPreferences = ButtonPreferences.secondary(
resourceManager.getString(R.string.common_not_now)
),
actionButtonPreferences = ButtonPreferences.primary(
resourceManager.getString(R.string.cloud_backup_destructive_changes_button),
onReviewClicked
)
)
}
fun ActionBottomSheetLauncher.launchCloudBackupDestructiveChangesNotApplied(
resourceManager: ResourceManager,
onReviewClicked: () -> Unit
) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_warning,
title = resourceManager.getString(R.string.cloud_backup_destructive_changes_not_applied_title),
subtitle = resourceManager.getString(R.string.cloud_backup_destructive_changes_not_applied_subtitle),
neutralButtonPreferences = ButtonPreferences.secondary(
resourceManager.getString(R.string.common_not_now)
),
actionButtonPreferences = ButtonPreferences.negative(
resourceManager.getString(R.string.cloud_backup_destructive_changes_not_applied_button),
onReviewClicked
)
)
}
fun ActionBottomSheetLauncher.launchCloudBackupDestructiveChangesNotAppliedWithoutRouting(
resourceManager: ResourceManager
) {
launchBottomSheet(
imageRes = R.drawable.ic_cloud_backup_warning,
title = resourceManager.getString(R.string.cloud_backup_destructive_changes_not_applied_title),
subtitle = resourceManager.getString(R.string.cloud_backup_destructive_changes_not_applied_subtitle),
actionButtonPreferences = ButtonPreferences.secondary(
resourceManager.getString(R.string.common_got_it)
)
)
}
@@ -0,0 +1,31 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.confirmation
import io.novafoundation.nova.common.mixin.actionAwaitable.ConfirmationAwaitable
import io.novafoundation.nova.common.mixin.actionAwaitable.ConfirmationDialogInfo
import io.novafoundation.nova.common.mixin.actionAwaitable.fromRes
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.R
suspend fun ConfirmationAwaitable<ConfirmationDialogInfo>.awaitDeleteBackupConfirmation(resourceManager: ResourceManager) {
awaitAction(
ConfirmationDialogInfo.fromRes(
resourceManager,
title = R.string.cloud_backup_delete_backup_confirmation_title,
message = R.string.cloud_backup_delete_backup_confirmation_message,
positiveButton = R.string.cloud_backup_delete_button,
negativeButton = R.string.common_cancel
)
)
}
suspend fun ConfirmationAwaitable<ConfirmationDialogInfo>.awaitBackupDestructiveChangesConfirmation(resourceManager: ResourceManager) {
awaitAction(
ConfirmationDialogInfo.fromRes(
resourceManager,
title = R.string.cloud_backup_destructive_changes_confirmation_title,
message = R.string.cloud_backup_destructive_changes_confirmation_subtitle,
positiveButton = R.string.common_apply,
negativeButton = R.string.common_cancel
)
)
}
@@ -0,0 +1,28 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
import io.novafoundation.nova.common.mixin.api.toCustomDialogPayload
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.InvalidBackupPasswordError
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupInvalidPassword
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapChangePasswordValidationStatusToUi(
resourceManager: ResourceManager,
status: Throwable,
initSignIn: () -> Unit
): CustomDialogDisplayer.Payload {
return when (status) {
is CloudBackupAuthFailed -> handleCloudBackupAuthFailed(resourceManager, initSignIn)
is CloudBackupNotEnoughSpace -> handleCloudBackupNotEnoughSpace(resourceManager).toCustomDialogPayload(resourceManager)
is InvalidBackupPasswordError -> handleCloudBackupInvalidPassword(resourceManager).toCustomDialogPayload(resourceManager)
else -> handleCloudBackupUnknownError(resourceManager).toCustomDialogPayload(resourceManager)
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
import io.novafoundation.nova.common.mixin.api.toCustomDialogPayload
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupServiceUnavailable
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupServiceUnavailable
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapCheckBackupAvailableFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable,
initSignIn: () -> Unit
): CustomDialogDisplayer.Payload {
return when (throwable) {
is CloudBackupAuthFailed -> handleCloudBackupAuthFailed(resourceManager, initSignIn)
is CloudBackupServiceUnavailable -> handleCloudBackupServiceUnavailable(resourceManager).toCustomDialogPayload(resourceManager)
else -> handleCloudBackupUnknownError(resourceManager).toCustomDialogPayload(resourceManager)
}
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
import io.novafoundation.nova.common.mixin.api.toCustomDialogPayload
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.PreCreateValidationStatus
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupServiceUnavailable
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapPreCreateValidationStatusToUi(
resourceManager: ResourceManager,
status: PreCreateValidationStatus,
existingBackupFound: () -> Unit,
initSignIn: () -> Unit
): CustomDialogDisplayer.Payload? {
return when (status) {
is PreCreateValidationStatus.AuthenticationFailed -> handleCloudBackupAuthFailed(resourceManager, initSignIn)
is PreCreateValidationStatus.BackupServiceUnavailable -> handleCloudBackupServiceUnavailable(resourceManager).toCustomDialogPayload(resourceManager)
is PreCreateValidationStatus.ExistingBackupFound -> {
existingBackupFound()
null
}
is PreCreateValidationStatus.NotEnoughSpace -> handleCloudBackupNotEnoughSpace(resourceManager).toCustomDialogPayload(resourceManager)
is PreCreateValidationStatus.OtherError -> handleCloudBackupUnknownError(resourceManager).toCustomDialogPayload(resourceManager)
is PreCreateValidationStatus.Ok -> null
}
}
@@ -0,0 +1,74 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.base.TitleAndMessage
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupNotFound
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupUnknownError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CorruptedBackupError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.InvalidBackupPasswordError
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupInvalidPassword
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupNotFound
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapRestoreBackupFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable,
corruptedBackupFound: () -> Unit
): TitleAndMessage? {
return when (throwable) {
is InvalidBackupPasswordError -> handleCloudBackupInvalidPassword(resourceManager)
is CorruptedBackupError -> {
corruptedBackupFound()
return null
}
is CloudBackupNotFound -> handleCloudBackupNotFound(resourceManager)
is CloudBackupUnknownError -> handleCloudBackupUnknownError(resourceManager)
else -> null
}
}
fun mapDeleteBackupFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable
): TitleAndMessage? {
return when (throwable) {
is CloudBackupUnknownError -> handleCloudBackupUnknownError(resourceManager)
else -> null
}
}
fun mapCheckPasswordFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable
): TitleAndMessage? {
return when (throwable) {
is InvalidBackupPasswordError -> handleCloudBackupInvalidPassword(resourceManager)
else -> null
}
}
fun mapRestorePasswordFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable,
corruptedBackupFound: () -> Unit
): TitleAndMessage? {
return when (throwable) {
is InvalidBackupPasswordError -> handleCloudBackupInvalidPassword(resourceManager)
is CorruptedBackupError -> {
corruptedBackupFound()
null
}
is CloudBackupNotFound -> handleCloudBackupNotFound(resourceManager)
else -> handleCloudBackupUnknownError(resourceManager)
}
}
@@ -0,0 +1,48 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer
import io.novafoundation.nova.common.mixin.api.toCustomDialogPayload
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.CloudBackup
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.diff.CloudBackupDiff
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CannotApplyNonDestructiveDiff
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupUnknownError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CorruptedBackupError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.InvalidBackupPasswordError
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.PasswordNotSaved
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupAuthFailed
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapCloudBackupSyncFailed(
resourceManager: ResourceManager,
state: Throwable,
onPasswordDeprecated: () -> Unit,
onCorruptedBackup: () -> Unit,
initSignIn: () -> Unit,
onDestructiveBackupFound: (CloudBackupDiff, CloudBackup) -> Unit,
): CustomDialogDisplayer.Payload? {
return when (state) {
is CloudBackupAuthFailed -> handleCloudBackupAuthFailed(resourceManager, initSignIn)
is CloudBackupUnknownError -> handleCloudBackupUnknownError(resourceManager)
.toCustomDialogPayload(resourceManager)
is CannotApplyNonDestructiveDiff -> {
onDestructiveBackupFound(state.cloudBackupDiff, state.cloudBackup)
null
}
is CorruptedBackupError -> {
onCorruptedBackup()
null
}
is InvalidBackupPasswordError, is PasswordNotSaved -> {
onPasswordDeprecated()
null
}
else -> null
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling
import io.novafoundation.nova.common.base.TitleAndMessage
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.domain.model.errors.CloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupNotEnoughSpace
import io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers.handleCloudBackupUnknownError
fun mapWriteBackupFailureToUi(
resourceManager: ResourceManager,
throwable: Throwable
): TitleAndMessage {
return when (throwable) {
is CloudBackupNotEnoughSpace -> handleCloudBackupNotEnoughSpace(resourceManager)
else -> handleCloudBackupUnknownError(resourceManager)
}
}
@@ -0,0 +1,50 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers
import io.novafoundation.nova.common.base.TitleAndMessage
import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer.Payload
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_cloud_backup_api.R
fun handleCloudBackupAuthFailed(resourceManager: ResourceManager, onSignInClicked: () -> Unit): Payload {
return Payload(
resourceManager.getString(R.string.cloud_backup_error_google_service_auth_failed_title),
resourceManager.getString(R.string.cloud_backup_error_google_service_auth_failed_message),
Payload.DialogAction(resourceManager.getString(R.string.common_sign_in), onSignInClicked),
Payload.DialogAction.noOp(resourceManager.getString(R.string.common_cancel))
)
}
fun handleCloudBackupServiceUnavailable(resourceManager: ResourceManager): TitleAndMessage {
return TitleAndMessage(
resourceManager.getString(R.string.cloud_backup_error_google_service_not_found_title),
resourceManager.getString(R.string.cloud_backup_error_google_service_not_found_message)
)
}
fun handleCloudBackupNotEnoughSpace(resourceManager: ResourceManager): TitleAndMessage {
return TitleAndMessage(
resourceManager.getString(R.string.cloud_backup_error_google_service_not_enough_space_title),
resourceManager.getString(R.string.cloud_backup_error_google_service_not_enough_space_message)
)
}
fun handleCloudBackupNotFound(resourceManager: ResourceManager): TitleAndMessage {
return TitleAndMessage(
resourceManager.getString(R.string.cloud_backup_error_not_found_title),
resourceManager.getString(R.string.cloud_backup_error_not_found_message)
)
}
fun handleCloudBackupUnknownError(resourceManager: ResourceManager): TitleAndMessage {
return TitleAndMessage(
resourceManager.getString(R.string.cloud_backup_error_google_service_other_title),
resourceManager.getString(R.string.cloud_backup_error_google_service_other_message)
)
}
fun handleCloudBackupInvalidPassword(resourceManager: ResourceManager): TitleAndMessage {
return TitleAndMessage(
resourceManager.getString(R.string.cloud_backup_error_invalid_password_title),
resourceManager.getString(R.string.cloud_backup_error_invalid_password_message),
)
}
@@ -0,0 +1,9 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.errorHandling.handlers
import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.common.base.showError
import io.novafoundation.nova.common.resources.ResourceManager
fun BaseViewModel.showCloudBackupUnknownError(resourceManager: ResourceManager) {
showError(handleCloudBackupUnknownError(resourceManager))
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.mixin
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import kotlinx.coroutines.CoroutineScope
interface CloudBackupChangingWarningMixinFactory {
fun create(coroutineScope: CoroutineScope): CloudBackupChangingWarningMixin
}
interface CloudBackupChangingWarningMixin {
val actionBottomSheetLauncher: ActionBottomSheetLauncher
fun launchChangingConfirmationIfNeeded(onConfirm: () -> Unit)
fun launchRemovingConfirmationIfNeeded(onConfirm: () -> Unit)
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_cloud_backup_api.presenter.mixin
import io.novafoundation.nova.common.base.BaseScreenMixin
import io.novafoundation.nova.common.view.bottomSheet.action.observeActionBottomSheet
fun BaseScreenMixin<*>.observeConfirmationAction(mixinFactory: CloudBackupChangingWarningMixin) {
observeActionBottomSheet(mixinFactory.actionBottomSheetLauncher)
}