From f358f572ec4fafa21834315c10d52b21219a6d78 Mon Sep 17 00:00:00 2001 From: polstianka Date: Tue, 12 Nov 2024 05:05:39 -0500 Subject: [PATCH] safemode and nft manager --- .../main/java/com/tonapps/wallet/api/API.kt | 4 + .../wallet/api/internal/InternalApi.kt | 19 ++ .../collectibles/CollectiblesRepository.kt | 5 +- .../data/settings/SettingsRepository.kt | 18 +- .../com/tonapps/tonkeeper/client/DeleteMe.kt | 1 + .../client/safemode/BadDomainsEntity.kt | 26 +++ .../client/safemode/SafeModeClient.kt | 78 +++++++ .../com/tonapps/tonkeeper/koin/KoinModule.kt | 2 + .../tonkeeper/koin/viewModelWalletModule.kt | 6 +- .../tonkeeper/manager/assets/AssetsManager.kt | 4 +- .../manager/tonconnect/TonConnectManager.kt | 19 ++ .../ui/screen/browser/dapp/DAppScreen.kt | 7 +- .../ui/screen/collectibles/list/Adapter.kt | 27 --- .../{ => main}/CollectiblesScreen.kt | 11 +- .../{ => main}/CollectiblesViewModel.kt | 13 +- .../screen/collectibles/main/list/Adapter.kt | 41 ++++ .../collectibles/{ => main}/list/Item.kt | 2 +- .../{ => main}/list/holder/Holder.kt | 4 +- .../{ => main}/list/holder/NftHolder.kt | 6 +- .../{ => main}/list/holder/SkeletonHolder.kt | 4 +- .../manage/CollectiblesManageScreen.kt | 53 +++++ .../manage/CollectiblesManageViewModel.kt | 190 ++++++++++++++++++ .../manage/CollectionSpamDialog.kt | 41 ++++ .../collectibles/manage/list/Adapter.kt | 27 +++ .../screen/collectibles/manage/list/Item.kt | 31 +++ .../manage/list/holder/AllHolder.kt | 23 +++ .../manage/list/holder/CollectionHolder.kt | 52 +++++ .../collectibles/manage/list/holder/Holder.kt | 11 + .../manage/list/holder/SpaceHolder.kt | 13 ++ .../manage/list/holder/TitleHolder.kt | 15 ++ .../tonkeeper/ui/screen/main/MainScreen.kt | 2 +- .../tonkeeper/ui/screen/nft/NftViewModel.kt | 3 +- .../settings/security/SecurityScreen.kt | 19 +- .../settings/security/SecurityViewModel.kt | 12 +- .../tonconnect/TonConnectSafeModeDialog.kt | 28 +++ .../wallet/manage/TokensManageViewModel.kt | 4 +- .../drawable-nodpi/safemode_stories_1.webp | Bin 0 -> 45856 bytes .../drawable-nodpi/safemode_stories_2.webp | Bin 0 -> 79754 bytes .../drawable-nodpi/safemode_stories_3.webp | Bin 0 -> 56108 bytes .../src/main/res/layout/dialog_token_spam.xml | 109 ++++++++++ .../res/layout/dialog_tonconnect_safemode.xml | 45 +++++ .../src/main/res/layout/fragment_security.xml | 38 ++-- .../res/layout/fragment_send_transaction.xml | 4 +- .../main/res/layout/fragment_tonconnect.xml | 2 + .../main/res/layout/view_item_show_all.xml | 18 ++ .../res/layout/view_manage_collection.xml | 60 ++++++ .../src/main/res/values-bg/strings.xml | 11 + .../src/main/res/values-es/strings.xml | 11 + .../src/main/res/values-in/strings.xml | 11 + .../src/main/res/values-ru/strings.xml | 10 + .../src/main/res/values-tr/strings.xml | 10 + .../src/main/res/values-uk/strings.xml | 10 + .../src/main/res/values-uz/strings.xml | 10 + .../src/main/res/values-zh/strings.xml | 10 + .../src/main/res/values/strings.xml | 16 ++ .../uikit/widget/stories/BaseStoriesScreen.kt | 26 +++ .../widget/stories/StoriesProgressDrawable.kt | 46 +++++ .../widget/stories/StoriesProgressView.kt | 27 +++ .../src/main/res/layout/fragment_stories.xml | 36 ++++ .../icon/src/main/res/drawable/ic_minus.xml | 9 + .../icon/src/main/res/drawable/ic_plus.xml | 10 + .../src/main/res/drawable/ic_sliders_16.xml | 14 ++ 62 files changed, 1245 insertions(+), 119 deletions(-) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/DeleteMe.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/BadDomainsEntity.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt delete mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Adapter.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/CollectiblesScreen.kt (90%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/CollectiblesViewModel.kt (88%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Adapter.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/list/Item.kt (94%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/list/holder/Holder.kt (62%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/list/holder/NftHolder.kt (93%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/{ => main}/list/holder/SkeletonHolder.kt (62%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageScreen.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectionSpamDialog.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/Adapter.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/Item.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/holder/AllHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/holder/CollectionHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/holder/Holder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/holder/SpaceHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/list/holder/TitleHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/tonconnect/TonConnectSafeModeDialog.kt create mode 100644 apps/wallet/instance/app/src/main/res/drawable-nodpi/safemode_stories_1.webp create mode 100644 apps/wallet/instance/app/src/main/res/drawable-nodpi/safemode_stories_2.webp create mode 100644 apps/wallet/instance/app/src/main/res/drawable-nodpi/safemode_stories_3.webp create mode 100644 apps/wallet/instance/app/src/main/res/layout/dialog_token_spam.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/dialog_tonconnect_safemode.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_item_show_all.xml create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_manage_collection.xml create mode 100644 ui/uikit/core/src/main/java/uikit/widget/stories/BaseStoriesScreen.kt create mode 100644 ui/uikit/core/src/main/java/uikit/widget/stories/StoriesProgressDrawable.kt create mode 100644 ui/uikit/core/src/main/java/uikit/widget/stories/StoriesProgressView.kt create mode 100644 ui/uikit/core/src/main/res/layout/fragment_stories.xml create mode 100644 ui/uikit/icon/src/main/res/drawable/ic_minus.xml create mode 100644 ui/uikit/icon/src/main/res/drawable/ic_plus.xml create mode 100644 ui/uikit/icon/src/main/res/drawable/ic_sliders_16.xml diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt index 727137d37..e2ebfea72 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt @@ -793,6 +793,10 @@ class API( } } + suspend fun getScamDomains(): Array = withContext(Dispatchers.IO) { + internalApi.getScamDomains() + } + fun loadChart( token: String, currency: String, diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt index c92e02ed1..cfa7a22e3 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/internal/InternalApi.kt @@ -7,6 +7,7 @@ import android.util.Log import com.tonapps.extensions.appVersionName import com.tonapps.extensions.isDebug import com.tonapps.extensions.locale +import com.tonapps.extensions.toUriOrNull import com.tonapps.network.get import com.tonapps.wallet.api.entity.ConfigEntity import com.tonapps.wallet.api.entity.NotificationEntity @@ -63,6 +64,24 @@ internal class InternalApi( return list.toList() } + fun getScamDomains(): Array { + val array = withRetry { + okHttpClient.get("https://scam.tonkeeper.com/v1/scam/domains") + }?.let { JSONObject(it).getJSONArray("items") } ?: return emptyArray() + + val domains = mutableListOf() + for (i in 0 until array.length()) { + var url = array.getJSONObject(i).getString("url") + if (url.startsWith("www.")) { + url = url.substring(5) + } else if (url.startsWith("@")) { + continue + } + domains.add(url) + } + return domains.toTypedArray() + } + fun getBrowserApps(testnet: Boolean, locale: Locale): JSONObject { val data = request("apps/popular", testnet, locale = locale) return data.getJSONObject("data") diff --git a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt index e9abbfa7a..ef5be2757 100644 --- a/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt +++ b/apps/wallet/data/collectibles/src/main/java/com/tonapps/wallet/data/collectibles/CollectiblesRepository.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.collectibles import android.content.Context import android.util.Log +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.wallet.api.API import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.data.collectibles.entities.NftListResult @@ -45,7 +46,9 @@ class CollectiblesRepository( val remote = getRemoteNftItems(address, testnet) ?: return@flow emit(NftListResult(cache = false, list = remote)) } - } catch (ignored: Throwable) { } + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + } }.cancellable() private fun getLocalNftItems( diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt index 244dc8783..aea2e3e85 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SettingsRepository.kt @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.settings import android.content.Context import android.icu.util.Currency +import android.util.Log import androidx.core.os.LocaleListCompat import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.clear @@ -52,8 +53,7 @@ class SettingsRepository( private const val ENCRYPTED_COMMENT_MODAL_KEY = "encrypted_comment_modal" private const val BATTERY_VIEWED_KEY = "battery_viewed" private const val CHART_PERIOD_KEY = "chart_period" - private const val ONLY_VERIFY_NFTS_KEY = "only_verify_nfts" - private const val ONLY_VERIFY_TOKENS_KEY = "only_verify_tokens" + private const val SAFE_MODE_KEY = "safe_mode" } private val _currencyFlow = MutableEffectFlow() @@ -227,21 +227,11 @@ class SettingsRepository( } - var onlyVerifyNFTs: Boolean = prefs.getBoolean(ONLY_VERIFY_NFTS_KEY, false) + var safeMode: Boolean = prefs.getBoolean(SAFE_MODE_KEY, true) set(value) { if (value != field) { - prefs.edit().putBoolean(ONLY_VERIFY_NFTS_KEY, value).apply() + prefs.edit().putBoolean(SAFE_MODE_KEY, value).apply() field = value - walletPrefsFolder.notifyChanged() - } - } - - var onlyVerifyTokens: Boolean = prefs.getBoolean(ONLY_VERIFY_TOKENS_KEY, false) - set(value) { - if (value != field) { - prefs.edit().putBoolean(ONLY_VERIFY_TOKENS_KEY, value).apply() - field = value - walletPrefsFolder.notifyChanged() } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/DeleteMe.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/DeleteMe.kt new file mode 100644 index 000000000..9d3d07b87 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/DeleteMe.kt @@ -0,0 +1 @@ +package com.tonapps.tonkeeper.client diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/BadDomainsEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/BadDomainsEntity.kt new file mode 100644 index 000000000..14e30f38a --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/BadDomainsEntity.kt @@ -0,0 +1,26 @@ +package com.tonapps.tonkeeper.client.safemode + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class BadDomainsEntity( + val array: Array +): Parcelable { + + val isEmpty: Boolean + get() = array.isEmpty() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BadDomainsEntity + + return array.contentEquals(other.array) + } + + override fun hashCode(): Int { + return array.contentHashCode() + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt new file mode 100644 index 000000000..46a4472f2 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/client/safemode/SafeModeClient.kt @@ -0,0 +1,78 @@ +package com.tonapps.tonkeeper.client.safemode + +import android.content.Context +import android.net.Uri +import android.util.Log +import com.tonapps.icu.Coins +import com.tonapps.wallet.api.API +import com.tonapps.wallet.data.core.BlobDataSource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import java.util.concurrent.ConcurrentHashMap + +class SafeModeClient( + private val context: Context, + private val api: API, + private val scope: CoroutineScope +) { + + private val scamDomains = ConcurrentHashMap(3, 1.0f, 2) + private val blobCache = BlobDataSource.simple(context, "safemode") + private val badDomainsFlow = flow { + getCachedBadDomains()?.let { + emit(it) + } + + loadBadDomains()?.let { + emit(it) + } + }.distinctUntilChanged() + + init { + badDomainsFlow.onEach { + for (domain in it.array) { + scamDomains[domain] = true + } + }.launchIn(scope) + } + + fun isHasScamUris(vararg uris: Uri): Boolean { + for (uri in uris) { + if (uri == Uri.EMPTY) { + continue + } + var host = uri.host ?: continue + if (host.startsWith("www.")) { + host = host.substring(4) + } + if (scamDomains.containsKey(host)) { + return true + } + } + return false + } + + private fun getCachedBadDomains(): BadDomainsEntity? { + val entity = blobCache.getCache("scam_domains") + if (entity == null || entity.isEmpty) { + return null + } + return entity + } + + private suspend fun loadBadDomains(): BadDomainsEntity? { + val domains = api.getScamDomains() + if (domains.isEmpty()) { + return null + } + val entity = BadDomainsEntity(domains) + blobCache.setCache("scam_domains", entity) + return entity + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt index ed5149ca9..286d525b2 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/KoinModule.kt @@ -3,6 +3,7 @@ package com.tonapps.tonkeeper.koin import com.tonapps.network.NetworkMonitor import com.tonapps.tonkeeper.Environment import com.tonapps.tonkeeper.billing.BillingManager +import com.tonapps.tonkeeper.client.safemode.SafeModeClient import com.tonapps.tonkeeper.manager.assets.AssetsManager import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.core.history.HistoryHelper @@ -55,6 +56,7 @@ val koinModel = module { singleOf(::TransactionManager) singleOf(::TonConnectManager) singleOf(::PushManager) + singleOf(::SafeModeClient) factoryOf(::SignUseCase) factoryOf(::EmulationUseCase) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt index 211600efc..4c1311e71 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/koin/viewModelWalletModule.kt @@ -5,7 +5,7 @@ import com.tonapps.tonkeeper.ui.screen.wallet.main.WalletViewModel import com.tonapps.tonkeeper.ui.screen.settings.main.SettingsViewModel import com.tonapps.tonkeeper.ui.screen.name.edit.EditNameViewModel import com.tonapps.tonkeeper.ui.screen.events.EventsViewModel -import com.tonapps.tonkeeper.ui.screen.collectibles.CollectiblesViewModel +import com.tonapps.tonkeeper.ui.screen.collectibles.main.CollectiblesViewModel import com.tonapps.tonkeeper.ui.screen.browser.explore.BrowserExploreViewModel import com.tonapps.tonkeeper.ui.screen.browser.connected.BrowserConnectedViewModel import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppViewModel @@ -19,6 +19,8 @@ import com.tonapps.tonkeeper.ui.screen.token.picker.TokenPickerViewModel import com.tonapps.tonkeeper.ui.screen.battery.settings.BatterySettingsViewModel import com.tonapps.tonkeeper.ui.screen.battery.refill.BatteryRefillViewModel import com.tonapps.tonkeeper.ui.screen.battery.recharge.BatteryRechargeViewModel +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.CollectiblesManageScreen +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.CollectiblesManageViewModel import com.tonapps.tonkeeper.ui.screen.send.contacts.main.SendContactsViewModel import com.tonapps.tonkeeper.ui.screen.purchase.PurchaseViewModel import com.tonapps.tonkeeper.ui.screen.nft.NftViewModel @@ -33,6 +35,7 @@ import com.tonapps.tonkeeper.ui.screen.staking.withdraw.StakeWithdrawViewModel import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModelOf + val viewModelWalletModule = module { viewModelOf(::WalletViewModel) viewModelOf(::SettingsViewModel) @@ -63,4 +66,5 @@ val viewModelWalletModule = module { viewModelOf(::AddContactViewModel) viewModelOf(::EditContactViewModel) viewModelOf(::AppsViewModel) + viewModelOf(::CollectiblesManageViewModel) } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt index 1c3d9a908..5646867f4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/assets/AssetsManager.kt @@ -54,9 +54,9 @@ class AssetsManager( currency: WalletCurrency = settingsRepository.currency, refresh: Boolean, ): List { - val onlyVerifyTokens = settingsRepository.onlyVerifyTokens + val safeMode = settingsRepository.safeMode val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet, refresh) ?: return emptyList() - return if (onlyVerifyTokens) { + return if (safeMode) { tokens.filter { it.verified }.map { AssetsEntity.Token(it) } } else { tokens.map { AssetsEntity.Token(it) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt index ed3e6c6e0..e72e24114 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/manager/tonconnect/TonConnectManager.kt @@ -16,8 +16,10 @@ import com.tonapps.extensions.flatter import com.tonapps.extensions.hasQuery import com.tonapps.extensions.isEmptyQuery import com.tonapps.extensions.mapList +import com.tonapps.extensions.toUriOrNull import com.tonapps.network.simple import com.tonapps.security.CryptoBox +import com.tonapps.tonkeeper.client.safemode.SafeModeClient import com.tonapps.tonkeeper.core.DevSettings import com.tonapps.tonkeeper.extensions.showToast import com.tonapps.tonkeeper.manager.push.PushManager @@ -27,6 +29,7 @@ import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeError import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeEvent import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeMethod import com.tonapps.tonkeeper.manager.tonconnect.exceptions.ManifestException +import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectSafeModeDialog import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectScreen import com.tonapps.tonkeeper.worker.DAppPushToggleWorker import com.tonapps.wallet.api.API @@ -34,6 +37,7 @@ import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.dapps.DAppsRepository import com.tonapps.wallet.data.dapps.entities.AppConnectEntity import com.tonapps.wallet.data.dapps.entities.AppEntity +import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -60,6 +64,8 @@ class TonConnectManager( private val api: API, private val dAppsRepository: DAppsRepository, private val pushManager: PushManager, + private val safeModeClient: SafeModeClient, + private val settingsRepository: SettingsRepository, ) { private val bridge: Bridge = Bridge(api) @@ -228,6 +234,11 @@ class TonConnectManager( returnUri = returnUri, fromPackageName = fromPackageName ) + + if (isScam(context, uri, normalizedUri, tonConnect.manifestUrl.toUri())) { + return null + } + scope.launch { connectRemoteApp(activity, tonConnect) } @@ -311,6 +322,14 @@ class TonConnectManager( } } + fun isScam(context: Context, vararg uris: Uri): Boolean { + if (settingsRepository.safeMode && safeModeClient.isHasScamUris(*uris)) { + TonConnectSafeModeDialog(context).show() + return true + } + return false + } + private suspend fun readManifest(url: String): AppEntity { return fetchManifest(url) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt index 277f29c4e..5a6f054e7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/browser/dapp/DAppScreen.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.os.Bundle -import android.util.Log import android.view.View import android.view.ViewGroup import android.webkit.WebResourceRequest @@ -22,7 +21,6 @@ import com.tonapps.tonkeeper.deeplink.DeepLink import com.tonapps.tonkeeper.deeplink.DeepLinkRoute import com.tonapps.tonkeeper.extensions.copyToClipboard import com.tonapps.tonkeeper.extensions.normalizeTONSites -import com.tonapps.tonkeeper.helper.BrowserHelper import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.manager.tonconnect.ConnectRequest import com.tonapps.tonkeeper.manager.tonconnect.TonConnect @@ -36,7 +34,6 @@ import com.tonapps.tonkeeper.popup.ActionSheet import com.tonapps.tonkeeper.ui.base.WalletContextScreen import com.tonapps.tonkeeper.ui.screen.root.RootViewModel import com.tonapps.tonkeeper.ui.screen.send.transaction.SendTransactionScreen -import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.tabBarActiveIconColor import com.tonapps.uikit.icon.UIKitIcon @@ -286,6 +283,10 @@ class DAppScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_da return JsonBuilder.connectEventError(BridgeError.badRequest("Version $version is not supported")) } val activity = requireContext().activity ?: return JsonBuilder.connectEventError(BridgeError.unknown("internal client error")) + if (tonConnectManager.isScam(requireContext(), request.manifestUrl.toUri(), webView.url!!.toUri(), args.url)) { + return JsonBuilder.connectEventError(BridgeError.unknown("internal client error")) + } + return tonConnectManager.launchConnectFlow( activity = activity, tonConnect = TonConnect.fromJsInject(request, webView.url?.toUri()), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Adapter.kt deleted file mode 100644 index 8b6ed3e5d..000000000 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Adapter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles.list - -import android.view.ViewGroup -import com.tonapps.tonkeeper.ui.screen.collectibles.list.holder.NftHolder -import com.tonapps.tonkeeper.ui.screen.collectibles.list.holder.SkeletonHolder -import com.tonapps.uikit.list.BaseListAdapter -import com.tonapps.uikit.list.BaseListHolder -import com.tonapps.uikit.list.BaseListItem - -class Adapter: BaseListAdapter() { - - init { - applySkeleton() - } - - fun applySkeleton() { - submitList(listOf(Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton(), Item.Skeleton())) - } - - override fun createHolder(parent: ViewGroup, viewType: Int): BaseListHolder { - return when(viewType) { - Item.TYPE_NFT -> NftHolder(parent) - Item.TYPE_SKELETON -> SkeletonHolder(parent) - else -> throw IllegalArgumentException("Unknown view type: $viewType") - } - } -} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt similarity index 90% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesScreen.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt index 12b2cb0fa..0716b4318 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesScreen.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles +package com.tonapps.tonkeeper.ui.screen.collectibles.main import android.os.Bundle import android.view.View @@ -8,17 +8,16 @@ import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.tonapps.tonkeeper.koin.walletViewModel import com.tonapps.tonkeeper.ui.base.UiListState -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Adapter -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Item +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.Adapter +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.CollectiblesManageScreen import com.tonapps.tonkeeper.ui.screen.main.MainScreen import com.tonapps.tonkeeper.ui.screen.qr.QRScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.backgroundTransparentColor +import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.localization.Localization -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import uikit.drawable.BarDrawable import uikit.extensions.collectFlow import uikit.widget.EmptyLayout @@ -40,6 +39,8 @@ class CollectiblesScreen(wallet: WalletEntity): MainScreen.Child(R.layout.fragme headerView = view.findViewById(R.id.header) headerView.title = getString(Localization.collectibles) headerView.setColor(requireContext().backgroundTransparentColor) + headerView.setAction(UIKitIcon.ic_sliders_16) + headerView.doOnActionClick = { navigation?.add(CollectiblesManageScreen.newInstance(wallet)) } refreshView = view.findViewById(R.id.refresh) refreshView.setOnRefreshListener { viewModel.refresh() } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt similarity index 88% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesViewModel.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt index 06ca3b7e6..75e50e0d3 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/CollectiblesViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/CollectiblesViewModel.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles +package com.tonapps.tonkeeper.ui.screen.collectibles.main import android.app.Application import androidx.lifecycle.viewModelScope @@ -8,27 +8,22 @@ import com.tonapps.tonkeeper.extensions.with import com.tonapps.tonkeeper.manager.tx.TransactionManager import com.tonapps.tonkeeper.ui.base.UiListState import com.tonapps.tonkeeper.ui.base.BaseWalletVM -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Item +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.Item import com.tonapps.wallet.data.account.entities.WalletEntity -import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.collectibles.CollectiblesRepository import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.take class CollectiblesViewModel( app: Application, @@ -75,10 +70,10 @@ class CollectiblesViewModel( hiddenBalances: Boolean, isOnline: Boolean, ): Flow = collectiblesRepository.getFlow(wallet.address, wallet.testnet, isOnline).map { result -> - val onlyVerifyNFTs = settingsRepository.onlyVerifyNFTs + val safeMode = settingsRepository.safeMode val uiItems = mutableListOf() for (nft in result.list) { - if (onlyVerifyNFTs && !nft.verified) { + if (safeMode && !nft.verified) { continue } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Adapter.kt new file mode 100644 index 000000000..40d31b7ff --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Adapter.kt @@ -0,0 +1,41 @@ +package com.tonapps.tonkeeper.ui.screen.collectibles.main.list + +import android.view.ViewGroup +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.holder.NftHolder +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.holder.SkeletonHolder +import com.tonapps.uikit.list.BaseListAdapter +import com.tonapps.uikit.list.BaseListHolder +import com.tonapps.uikit.list.BaseListItem + +class Adapter: BaseListAdapter() { + + init { + applySkeleton() + } + + fun applySkeleton() { + submitList(listOf( + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton(), + Item.Skeleton() + )) + } + + override fun createHolder(parent: ViewGroup, viewType: Int): BaseListHolder { + return when(viewType) { + Item.TYPE_NFT -> NftHolder(parent) + Item.TYPE_SKELETON -> SkeletonHolder(parent) + else -> throw IllegalArgumentException("Unknown view type: $viewType") + } + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt similarity index 94% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Item.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt index f1e57c3e6..9ae81bea4 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/Item.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles.list +package com.tonapps.tonkeeper.ui.screen.collectibles.main.list import android.net.Uri import com.tonapps.uikit.list.BaseListItem diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/Holder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/Holder.kt similarity index 62% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/Holder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/Holder.kt index 37c595608..c6c29898e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/Holder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/Holder.kt @@ -1,8 +1,8 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles.list.holder +package com.tonapps.tonkeeper.ui.screen.collectibles.main.list.holder import android.view.ViewGroup import androidx.annotation.LayoutRes -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Item +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.Item import com.tonapps.uikit.list.BaseListHolder abstract class Holder( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/NftHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/NftHolder.kt similarity index 93% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/NftHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/NftHolder.kt index 4a9142b88..65211ff7a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/NftHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/NftHolder.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles.list.holder +package com.tonapps.tonkeeper.ui.screen.collectibles.main.list.holder import android.graphics.drawable.RippleDrawable import android.net.Uri @@ -9,16 +9,14 @@ import androidx.appcompat.widget.AppCompatTextView import com.facebook.drawee.generic.RoundingParams import com.facebook.imagepipeline.common.ResizeOptions import com.facebook.imagepipeline.postprocessors.BlurPostProcessor -import com.facebook.imagepipeline.request.ImageRequest import com.facebook.imagepipeline.request.ImageRequestBuilder -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Item +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.Item import com.tonapps.tonkeeper.ui.screen.nft.NftScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentOrangeColor import com.tonapps.uikit.color.backgroundHighlightedColor import com.tonapps.uikit.color.stateList import com.tonapps.uikit.color.textSecondaryColor -import com.tonapps.uikit.list.BaseListHolder import com.tonapps.wallet.data.core.HIDDEN_BALANCE import com.tonapps.wallet.data.core.Trust import com.tonapps.wallet.localization.Localization diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/SkeletonHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/SkeletonHolder.kt similarity index 62% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/SkeletonHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/SkeletonHolder.kt index b0ba40f84..1c08ae43d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/list/holder/SkeletonHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/main/list/holder/SkeletonHolder.kt @@ -1,7 +1,7 @@ -package com.tonapps.tonkeeper.ui.screen.collectibles.list.holder +package com.tonapps.tonkeeper.ui.screen.collectibles.main.list.holder import android.view.ViewGroup -import com.tonapps.tonkeeper.ui.screen.collectibles.list.Item +import com.tonapps.tonkeeper.ui.screen.collectibles.main.list.Item import com.tonapps.tonkeeperx.R class SkeletonHolder(parent: ViewGroup): Holder(parent, R.layout.view_collectibles_skeleton) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageScreen.kt new file mode 100644 index 000000000..5bf7316d1 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageScreen.kt @@ -0,0 +1,53 @@ +package com.tonapps.tonkeeper.ui.screen.collectibles.manage + +import android.os.Bundle +import android.view.View +import com.tonapps.tonkeeper.koin.walletViewModel +import com.tonapps.tonkeeper.ui.base.BaseListWalletScreen +import com.tonapps.tonkeeper.ui.base.ScreenContext +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.list.Adapter +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.list.Item +import com.tonapps.wallet.data.account.entities.WalletEntity +import com.tonapps.wallet.localization.Localization +import uikit.base.BaseFragment +import uikit.extensions.collectFlow + +class CollectiblesManageScreen(wallet: WalletEntity): BaseListWalletScreen(ScreenContext.Wallet(wallet)), BaseFragment.SwipeBack { + + private val spamDialog: CollectionSpamDialog by lazy { CollectionSpamDialog(requireContext()) } + + override val viewModel: CollectiblesManageViewModel by walletViewModel() + + private val adapter = Adapter( + onClick = { + if (it.spam) { + showSpamDialog(it) + } else { + viewModel.toggle(it) + } + }, + showAllClick = { viewModel.showAll() } + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + collectFlow(viewModel.uiItemsFlow, adapter::submitList) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setTitle(getString(Localization.manage)) + setAdapter(adapter) + } + + private fun showSpamDialog(item: Item.Collection) { + spamDialog.show(item) { + viewModel.notSpam(item) + } + } + + companion object { + + fun newInstance(wallet: WalletEntity) = CollectiblesManageScreen(wallet) + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt new file mode 100644 index 000000000..5dc836e45 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectiblesManageViewModel.kt @@ -0,0 +1,190 @@ +package com.tonapps.tonkeeper.ui.screen.collectibles.manage + +import android.app.Application +import android.util.Log +import androidx.lifecycle.viewModelScope +import com.tonapps.blockchain.ton.extensions.equalsAddress +import com.tonapps.extensions.MutableEffectFlow +import com.tonapps.extensions.filterList +import com.tonapps.extensions.mapList +import com.tonapps.tonkeeper.ui.base.BaseWalletVM +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.list.Item +import com.tonapps.uikit.list.ListCell +import com.tonapps.wallet.data.account.entities.WalletEntity +import com.tonapps.wallet.data.collectibles.CollectiblesRepository +import com.tonapps.wallet.data.collectibles.entities.NftCollectionEntity +import com.tonapps.wallet.data.collectibles.entities.NftEntity +import com.tonapps.wallet.data.core.Trust +import com.tonapps.wallet.data.settings.SettingsRepository +import com.tonapps.wallet.data.settings.entities.TokenPrefsEntity.State +import com.tonapps.wallet.localization.Localization +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class CollectiblesManageViewModel( + app: Application, + private val wallet: WalletEntity, + private val collectiblesRepository: CollectiblesRepository, + private val settingsRepository: SettingsRepository, +): BaseWalletVM(app) { + + private val _showedAllFlow = MutableStateFlow(false) + private val showedAllFlow = _showedAllFlow.asStateFlow() + + private val _toggleFlow = MutableEffectFlow() + private val toggleFlow = _toggleFlow.asSharedFlow() + + private val collectiblesFlow = collectiblesRepository.getFlow( + address = wallet.address, + testnet = wallet.testnet, + isOnline = true + ).map { it.list }.filterList { it.collection != null } + + private val sortedCollectionFlow = combine( + collectiblesFlow, + toggleFlow, + ) { it, _ -> + val collectionItems = collectionItems(it) + + val visibleCollection = mutableListOf() + val hiddenCollection = mutableListOf() + val spamCollection = mutableListOf() + + for (item in collectionItems) { + val pref = settingsRepository.getTokenPrefs(wallet.id, item.address) + if (pref.state == State.TRUST) { + if (item.visible) { + visibleCollection.add(item) + } else { + hiddenCollection.add(item) + } + } else if (pref.state == State.SPAM || item.spam) { + spamCollection.add(item) + } else if (pref.isHidden) { + hiddenCollection.add(item) + } else { + visibleCollection.add(item) + } + } + + Triple(visibleCollection, hiddenCollection, spamCollection) + } + + val uiItemsFlow = combine( + sortedCollectionFlow, + showedAllFlow, + ){ (visibleCollection, hiddenCollection, spamCollection), showedAll -> + val uiItems = mutableListOf() + if (visibleCollection.isNotEmpty()) { + uiItems.add(Item.Title(getString(Localization.visible))) + uiItems.add(Item.Space) + val count = if (showedAll) visibleCollection.size else 3 + for ((index, item) in visibleCollection.withIndex()) { + val isLast = index == count - 1 + uiItems.add(item.copy( + position = ListCell.getPosition(count, index), + visible = true, + spam = false + )) + if (isLast && !showedAll) { + break + } + } + if (visibleCollection.size > 3 && !showedAll) { + uiItems.add(Item.All) + } + uiItems.add(Item.Space) + } + + if (hiddenCollection.isNotEmpty()) { + uiItems.add(Item.Title(getString(Localization.hidden))) + uiItems.add(Item.Space) + for ((index, item) in hiddenCollection.withIndex()) { + uiItems.add(item.copy( + position = ListCell.getPosition(hiddenCollection.size, index), + visible = false, + spam = false + )) + } + uiItems.add(Item.Space) + } + + if (spamCollection.isNotEmpty()) { + uiItems.add(Item.Title(getString(Localization.spam))) + uiItems.add(Item.Space) + for ((index, item) in spamCollection.withIndex()) { + uiItems.add(item.copy( + position = ListCell.getPosition(spamCollection.size, index), + spam = true + )) + } + uiItems.add(Item.Space) + } + + uiItems.toList() + } + + init { + _toggleFlow.tryEmit(Unit) + } + + fun showAll() { + _showedAllFlow.value = true + } + + fun toggle(item: Item.Collection) { + viewModelScope.launch { + settingsRepository.setTokenHidden(wallet.id, item.address, item.visible) + _toggleFlow.tryEmit(Unit) + if (!item.visible) { + showAll() + } + } + } + + fun notSpam(item: Item.Collection) { + viewModelScope.launch { + settingsRepository.setTokenState(wallet.id, item.address, State.TRUST) + _toggleFlow.tryEmit(Unit) + showAll() + } + } + + private suspend fun collectionItems(collectibles: List): List { + val items = mutableListOf() + for (nft in collectibles) { + val collection = nft.collection ?: continue + val index = items.indexOfFirst { + it.address.equalsAddress(collection.address) + } + if (index == -1) { + val state = settingsRepository.getTokenPrefs(wallet.id, nft.address).state + val spam = if (state == State.TRUST) { + false + } else { + nft.trust == Trust.blacklist + } + items.add(Item.Collection( + address = collection.address, + title = collection.name, + imageUri = nft.thumbUri, + count = 1, + spam = spam + )) + } else { + items[index] = items[index].copy( + count = items[index].count + 1 + ) + } + } + return items.toList() + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectionSpamDialog.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectionSpamDialog.kt new file mode 100644 index 000000000..474313d24 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/collectibles/manage/CollectionSpamDialog.kt @@ -0,0 +1,41 @@ +package com.tonapps.tonkeeper.ui.screen.collectibles.manage + +import android.content.Context +import android.view.View +import android.widget.Button +import androidx.appcompat.widget.AppCompatTextView +import com.tonapps.extensions.short8 +import com.tonapps.tonkeeper.extensions.copyToClipboard +import com.tonapps.tonkeeper.ui.screen.collectibles.manage.list.Item +import com.tonapps.tonkeeperx.R +import uikit.dialog.modal.ModalDialog +import uikit.widget.FrescoView +import uikit.widget.ModalHeader + +class CollectionSpamDialog(context: Context): ModalDialog(context, R.layout.dialog_token_spam) { + + private val headerView = findViewById(R.id.header)!! + private val nameView = findViewById(R.id.name)!! + private val iconView = findViewById(R.id.icon)!! + private val addressView = findViewById(R.id.address)!! + private val button = findViewById