From 675d13ab6f1543f6d2ad8d45da7529d43ecc2231 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Mar 2024 09:51:23 +0800 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + data/feedback/.gitignore | 1 + data/feedback/build.gradle.kts | 42 +++++++ .../feedback/DefaultFeedbackRepository.kt | 104 ++++++++++++++++++ .../converters/BlockchainInfoConverter.kt | 43 ++++++++ .../feedback/converters/CardInfoConverter.kt | 29 +++++ .../feedback/di/FeedbackRepositoryModule.kt | 30 +++++ .../settings/DefaultSettingsRepository.kt | 7 -- .../domain/feedback/FeedbackDataBuilder.kt | 33 +++++- .../domain/feedback/models/AppLogModel.kt | 3 + .../domain/feedback/models/BlockchainInfo.kt | 14 ++- .../feedback/repository/FeedbackRepository.kt | 20 ++++ .../domain/settings/models/AppLogsModel.kt | 3 - .../repositories/SettingsRepository.kt | 5 - settings.gradle.kts | 1 + 15 files changed, 316 insertions(+), 20 deletions(-) create mode 100644 data/feedback/.gitignore create mode 100644 data/feedback/build.gradle.kts create mode 100644 data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt create mode 100644 data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt create mode 100644 data/feedback/src/main/java/com/tangem/data/feedback/converters/CardInfoConverter.kt create mode 100644 data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/models/AppLogModel.kt create mode 100644 domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt delete mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/models/AppLogsModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 654eaa2a15..4543073e2a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,6 +94,7 @@ dependencies { implementation(projects.data.visa) implementation(projects.data.promo) implementation(projects.data.onboarding) + implementation(projects.data.feedback) /** Features */ implementation(projects.features.onboarding) diff --git a/data/feedback/.gitignore b/data/feedback/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/data/feedback/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/feedback/build.gradle.kts b/data/feedback/build.gradle.kts new file mode 100644 index 0000000000..306603b7c2 --- /dev/null +++ b/data/feedback/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +android { + namespace = "com.tangem.data.feedback" +} + +dependencies { + + // region AndroidX libraries + implementation(deps.androidx.datastore) + // endregion + + // region DI + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + // endregion + + // region Tangem libraries + implementation(deps.tangem.blockchain) + implementation(deps.tangem.card.core) + // endregion + + // Other libraries + implementation(deps.timber) + // endregion + + // region Core modules + implementation(projects.core.datasource) + // endregion + + implementation(projects.core.utils) + + implementation(projects.domain.feedback) + implementation(projects.domain.legacy) + implementation(projects.domain.models) + implementation(projects.domain.wallets.models) +} \ No newline at end of file diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt new file mode 100644 index 0000000000..580c5fb4ab --- /dev/null +++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt @@ -0,0 +1,104 @@ +package com.tangem.data.feedback + +import android.content.Context +import android.content.pm.PackageInfo +import android.os.Build +import com.tangem.data.feedback.converters.BlockchainInfoConverter +import com.tangem.data.feedback.converters.CardInfoConverter +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.datasource.local.walletmanager.WalletManagersStore +import com.tangem.domain.feedback.models.AppLogModel +import com.tangem.domain.feedback.models.BlockchainInfo +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.feedback.models.PhoneInfo +import com.tangem.domain.feedback.repository.FeedbackRepository +import com.tangem.domain.wallets.models.UserWallet +import timber.log.Timber +import java.io.File +import java.io.FileWriter +import java.io.StringWriter + +/** + * Implementation of [FeedbackRepository] + * + * @property appPreferencesStore application preferences store + * @property userWalletsStore user wallets store + * @property walletManagersStore wallet managers store + * @property context context for getting app version + * +[REDACTED_AUTHOR] + */ +internal class DefaultFeedbackRepository( + private val appPreferencesStore: AppPreferencesStore, + private val userWalletsStore: UserWalletsStore, + private val walletManagersStore: WalletManagersStore, + private val context: Context, +) : FeedbackRepository { + + override suspend fun getCardInfo(): CardInfo { + return CardInfoConverter.convert(value = getSelectedUserWallet()) + } + + override suspend fun getBlockchainInfoList(): List { + return walletManagersStore + .getAllSync(userWalletId = getSelectedUserWallet().walletId) + .map(BlockchainInfoConverter::convert) + } + + override fun getPhoneInfo(): PhoneInfo { + return PhoneInfo( + phoneModel = Build.MODEL, + osVersion = Build.VERSION.SDK_INT.toString(), + appVersion = getAppVersion(), + ) + } + + override suspend fun getAppLogs(): List { + return appPreferencesStore.getObjectMap(key = PreferencesKeys.APP_LOGS_KEY) + .map { AppLogModel(timestamp = it.key.toLong(), message = it.value) } + } + + override suspend fun createLogFile(logs: List): File? { + return try { + val file = File(context.filesDir, LOGS_FILE) + file.delete() + file.createNewFile() + + val stringWriter = StringWriter() + + logs.forEach(stringWriter::append) + + val fileWriter = FileWriter(file) + fileWriter.write(stringWriter.toString()) + fileWriter.close() + + file + } catch (ex: Exception) { + Timber.e(ex, "Logs file isn't created") + null + } + } + + private fun getAppVersion(): String { + return runCatching { context.packageManager.getPackageInfo(context.packageName, 0) } + .fold( + onSuccess = PackageInfo::versionName, + onFailure = { + Timber.e(it) + "x.y.z" + }, + ) + } + + private fun getSelectedUserWallet(): UserWallet { + return userWalletsStore.selectedUserWalletOrNull + ?: error("UserWallet is not selected") + } + + private companion object { + const val LOGS_FILE = "logs.txt" + } +} \ No newline at end of file diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt b/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt new file mode 100644 index 0000000000..de7f9b6d04 --- /dev/null +++ b/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt @@ -0,0 +1,43 @@ +package com.tangem.data.feedback.converters + +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.address.Address +import com.tangem.domain.feedback.models.BlockchainInfo +import com.tangem.domain.feedback.models.BlockchainInfo.Addresses.Multiple.AddressInfo +import com.tangem.utils.converter.Converter +import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainAddresses + +/** + * Converter from [WalletManager] to [BlockchainInfo] + * +[REDACTED_AUTHOR] + */ +internal object BlockchainInfoConverter : Converter { + + override fun convert(value: WalletManager): BlockchainInfo { + return BlockchainInfo( + blockchain = value.wallet.blockchain.fullName, + derivationPath = value.wallet.publicKey.derivationPath?.rawPath ?: "", + outputsCount = value.outputsCount?.toString(), + host = value.currentHost, + addresses = value.wallet.mapAddresses(Address::value), + explorerLinks = value.wallet.mapAddresses { value.wallet.getExploreUrl(it.value) }, + tokens = value.cardTokens.map { token -> + BlockchainInfo.TokenInfo(id = token.id, name = token.name, contractAddress = token.contractAddress) + }, + ) + } + + private inline fun Wallet.mapAddresses(map: (Address) -> String): BlockchainAddresses { + return if (addresses.size == 1) { + BlockchainAddresses.Single(value = map(addresses.first())) + } else { + BlockchainAddresses.Multiple( + addresses.map { + AddressInfo(type = it.type.name, value = map(it)) + }, + ) + } + } +} \ No newline at end of file diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/converters/CardInfoConverter.kt b/data/feedback/src/main/java/com/tangem/data/feedback/converters/CardInfoConverter.kt new file mode 100644 index 0000000000..e9a1a61019 --- /dev/null +++ b/data/feedback/src/main/java/com/tangem/data/feedback/converters/CardInfoConverter.kt @@ -0,0 +1,29 @@ +package com.tangem.data.feedback.converters + +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.utils.converter.Converter + +/** + * Converter from [UserWallet] to [CardInfo] + * +[REDACTED_AUTHOR] + */ +internal object CardInfoConverter : Converter { + + override fun convert(value: UserWallet): CardInfo { + return with(value.scanResponse) { + CardInfo( + userWalletId = value.walletId.stringValue, + cardId = card.cardId, + firmwareVersion = card.firmwareVersion.stringValue, + cardBlockchain = walletData?.blockchain, + signedHashesList = card.wallets.map { + CardInfo.SignedHashes(curve = it.curve.curve, total = it.totalSignedHashes?.toString()) + }, + isStart2Coin = value.scanResponse.card.isStart2Coin, + ) + } + } +} \ No newline at end of file diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt new file mode 100644 index 0000000000..5a00938959 --- /dev/null +++ b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackRepositoryModule.kt @@ -0,0 +1,30 @@ +package com.tangem.data.feedback.di + +import android.content.Context +import com.tangem.data.feedback.DefaultFeedbackRepository +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.datasource.local.walletmanager.WalletManagersStore +import com.tangem.domain.feedback.repository.FeedbackRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object FeedbackRepositoryModule { + + @Provides + @Singleton + fun provideFeedbackRepository( + appPreferencesStore: AppPreferencesStore, + userWalletsStore: UserWalletsStore, + walletManagersStore: WalletManagersStore, + @ApplicationContext context: Context, + ): FeedbackRepository { + return DefaultFeedbackRepository(appPreferencesStore, userWalletsStore, walletManagersStore, context) + } +} \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index 4e7ff03252..308e823b90 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -3,10 +3,8 @@ package com.tangem.data.settings import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store -import com.tangem.domain.settings.models.AppLogsModel import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext @@ -36,11 +34,6 @@ internal class DefaultSettingsRepository( ) } - override suspend fun getAppLogs(): List { - return appPreferencesStore.getObjectMap(key = PreferencesKeys.APP_LOGS_KEY) - .map { AppLogsModel(timestamp = it.key.toLong(), message = it.value) } - } - override suspend fun updateAppLogs(message: String) { val newLogs = DateTime.now().millis.toString() to message diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt index 5023782e64..35740e1357 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt @@ -4,6 +4,7 @@ import com.tangem.domain.feedback.models.BlockchainInfo import com.tangem.domain.feedback.models.CardInfo import com.tangem.domain.feedback.models.PhoneInfo import com.tangem.domain.feedback.utils.breakLine +import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainAddresses internal class FeedbackDataBuilder { @@ -40,8 +41,16 @@ internal class FeedbackDataBuilder { } builder.appendKeyValue("Host", host) - builder.appendKeyValue("Wallet address", addresses) - builder.appendKeyValue("Explorer link", explorerLink) + + builder.appendAddresses( + key = "Wallet address${addresses.isMultiple(suffix = "es")}", + addresses = addresses, + ) + + builder.appendAddresses( + key = "Explorer link${explorerLinks.isMultiple(suffix = "s")}", + addresses = explorerLinks, + ) if (!isLastIndex) builder.appendDelimiter() } @@ -55,7 +64,7 @@ internal class FeedbackDataBuilder { fun addDelimiter(): StringBuilder = builder.appendDelimiter() - fun build(): String = builder.toString() + fun build(): String = builder.trimEnd().toString() private fun StringBuilder.appendKeyValue(key: String, value: String?) { if (value.isNullOrBlank()) return @@ -70,6 +79,24 @@ internal class FeedbackDataBuilder { } } + private fun StringBuilder.appendAddresses(key: String, addresses: BlockchainAddresses) { + appendKeyValue( + key = key, + value = when (addresses) { + is BlockchainInfo.Addresses.Multiple -> { + addresses.values.joinToString(separator = "\n", prefix = "\n") { + "${it.type} — ${it.value}" + } + } + is BlockchainInfo.Addresses.Single -> addresses.value + }, + ) + } + + private fun BlockchainAddresses.isMultiple(suffix: String): String { + return if (this is BlockchainAddresses.Multiple) suffix else "" + } + private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n") private inline fun List.forEachBlockchain(action: BlockchainInfo.(Boolean) -> Unit) { diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/AppLogModel.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/AppLogModel.kt new file mode 100644 index 0000000000..6688c29a1f --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/AppLogModel.kt @@ -0,0 +1,3 @@ +package com.tangem.domain.feedback.models + +data class AppLogModel(val timestamp: Long, val message: String) \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainInfo.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainInfo.kt index 3daa9d9927..da0a0df2a5 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainInfo.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/BlockchainInfo.kt @@ -5,11 +5,21 @@ data class BlockchainInfo( val derivationPath: String, val outputsCount: String?, val host: String, - val addresses: String, - val explorerLink: String, + val addresses: Addresses, + val explorerLinks: Addresses, val tokens: List, ) { + sealed class Addresses { + + data class Single(val value: String) : Addresses() + + data class Multiple(val values: List) : Addresses() { + + data class AddressInfo(val type: String, val value: String) + } + } + data class TokenInfo( val id: String?, val name: String, diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt new file mode 100644 index 0000000000..246afde319 --- /dev/null +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/repository/FeedbackRepository.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.feedback.repository + +import com.tangem.domain.feedback.models.AppLogModel +import com.tangem.domain.feedback.models.BlockchainInfo +import com.tangem.domain.feedback.models.CardInfo +import com.tangem.domain.feedback.models.PhoneInfo +import java.io.File + +interface FeedbackRepository { + + suspend fun getCardInfo(): CardInfo + + suspend fun getBlockchainInfoList(): List + + fun getPhoneInfo(): PhoneInfo + + suspend fun getAppLogs(): List + + suspend fun createLogFile(logs: List): File? +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/models/AppLogsModel.kt b/domain/settings/src/main/java/com/tangem/domain/settings/models/AppLogsModel.kt deleted file mode 100644 index 746974930f..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/models/AppLogsModel.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.domain.settings.models - -data class AppLogsModel(val timestamp: Long, val message: String) \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index f686561f3e..48e6bb5ca2 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -1,7 +1,5 @@ package com.tangem.domain.settings.repositories -import com.tangem.domain.settings.models.AppLogsModel - interface SettingsRepository { suspend fun shouldShowSaveUserWalletScreen(): Boolean @@ -10,9 +8,6 @@ interface SettingsRepository { suspend fun setWalletScrollPreviewAvailability(isEnabled: Boolean) - @Throws - suspend fun getAppLogs(): List - @Throws suspend fun updateAppLogs(message: String) diff --git a/settings.gradle.kts b/settings.gradle.kts index bb2e6c46ba..a7380d73d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -157,4 +157,5 @@ include(":data:transaction") include(":data:visa") include(":data:promo") include(":data:onboarding") +include(":data:feedback") // endregion Data modules \ No newline at end of file