From d7046698985f9c404845b7d7df0204e4c9c96e7e Mon Sep 17 00:00:00 2001 From: polstianka <peer_id@yahoo.com> Date: Thu, 15 Aug 2024 16:28:48 -0400 Subject: [PATCH] bug fixeds --- .../main/java/com/tonapps/wallet/api/API.kt | 74 +++++---- .../wallet/api/internal/InternalApi.kt | 11 +- .../wallet/data/events/EventsRepository.kt | 6 + .../wallet/data/passcode/PasscodeBiometric.kt | 2 +- .../wallet/data/push/GooglePushService.kt | 1 + .../com/tonapps/wallet/data/push/Module.kt | 2 +- .../tonapps/wallet/data/push/PushManager.kt | 44 +++-- .../com/tonapps/wallet/data/rn/RNLegacy.kt | 63 ++++++- .../wallet/data/rn/data/RNSpamTransactions.kt | 7 + .../data/settings/SettingsRepository.kt | 83 +++++---- .../data/settings/SpamTransactionState.kt | 5 + .../data/settings/entities/NftPrefsEntity.kt | 6 - .../settings/entities/TokenPrefsEntity.kt | 29 +++- .../settings/folder/BaseSettingsFolder.kt | 11 +- .../settings/folder/ImportLegacyFolder.kt | 20 --- .../data/settings/folder/NftPrefsFolder.kt | 42 ----- .../data/settings/folder/TokenPrefsFolder.kt | 51 ++++-- .../data/settings/folder/WalletPrefsFolder.kt | 30 +++- .../data/tonconnect/TonConnectRepository.kt | 22 ++- .../tonconnect/entities/DAppRequestEntity.kt | 24 ++- .../instance/app/src/main/AndroidManifest.xml | 1 + .../com/tonapps/tonkeeper/api/Extensions.kt | 9 +- .../tonkeeper/core/entities/AssetsEntity.kt | 2 +- .../core/entities/AssetsExtendedEntity.kt | 2 +- .../tonkeeper/core/entities/TransferEntity.kt | 2 +- .../tonkeeper/core/history/HistoryHelper.kt | 96 +++++++---- .../list/holder/HistoryActionHolder.kt | 20 ++- .../core/history/list/item/HistoryItem.kt | 18 +- .../fragment/camera/CameraFragment.kt | 2 +- .../tonconnect/auth/TCAuthFragment.kt | 20 ++- .../tonapps/tonkeeper/helper/DateFormat.kt | 60 ------- .../tonapps/tonkeeper/helper/DateHelper.kt | 65 +++----- .../com/tonapps/tonkeeper/koin/KoinModule.kt | 14 +- .../ui/component/coin/CoinInputView.kt | 4 +- .../ui/screen/action/ActionViewModel.kt | 6 +- .../ui/screen/backup/main/BackupViewModel.kt | 6 +- .../ui/screen/backup/main/list/Item.kt | 4 +- .../ui/screen/browser/dapp/DAppScreen.kt | 13 +- .../collectibles/CollectiblesViewModel.kt | 9 +- .../screen/country/CountryPickerViewModel.kt | 10 +- .../ui/screen/events/EventsViewModel.kt | 2 +- .../tonkeeper/ui/screen/init/InitArgs.kt | 3 + .../tonkeeper/ui/screen/init/InitRoute.kt | 2 +- .../tonkeeper/ui/screen/init/InitScreen.kt | 21 +-- .../tonkeeper/ui/screen/init/InitViewModel.kt | 157 ++++++++---------- .../tonkeeper/ui/screen/init/list/Adapter.kt | 2 +- .../tonkeeper/ui/screen/init/list/Holder.kt | 10 +- .../ui/screen/init/step/PasscodeScreen.kt | 2 +- .../ui/screen/init/step/PushScreen.kt | 42 ----- .../tonkeeper/ui/screen/nft/NftScreen.kt | 10 +- .../tonkeeper/ui/screen/nft/NftViewModel.kt | 12 +- .../enable/NotificationsEnableScreen.kt | 69 ++++++++ .../enable/NotificationsEnableViewModel.kt | 17 ++ .../list/NotificationsAdapter.kt | 4 - .../NotificationsManageScreen.kt} | 12 +- .../NotificationsManageViewModel.kt} | 13 +- .../{ => manage}/list/Adapter.kt | 10 +- .../notifications/{ => manage}/list/Item.kt | 8 +- .../manage/list/NotificationsAdapter.kt | 4 + .../{ => manage}/list/holder/AppHolder.kt | 4 +- .../list/holder/AppsHeaderHolder.kt | 4 +- .../manage/list/holder/Holder.kt | 11 ++ .../{ => manage}/list/holder/SpaceHolder.kt | 4 +- .../list/holder/WalletPushHolder.kt | 4 +- .../screen/purchase/main/PurchaseViewModel.kt | 5 - .../tonkeeper/ui/screen/root/RootActivity.kt | 15 +- .../tonkeeper/ui/screen/root/RootViewModel.kt | 27 ++- .../send/contacts/SendContactsScreen.kt | 55 ++++++ .../send/contacts/SendContactsViewModel.kt | 55 ++++++ .../ui/screen/send/contacts/list/Adapter.kt | 23 +++ .../ui/screen/send/contacts/list/Item.kt | 46 +++++ .../contacts/list/holder/ContactHolder.kt | 16 ++ .../contacts}/list/holder/Holder.kt | 4 +- .../send/contacts/list/holder/LatestHolder.kt | 22 +++ .../contacts/list/holder/MyWalletHolder.kt | 25 +++ .../send/contacts/list/holder/SpaceHolder.kt | 12 ++ .../ui/screen/send/{ => main}/SendArgs.kt | 3 +- .../ui/screen/send/main/SendContact.kt | 16 ++ .../ui/screen/send/{ => main}/SendEvent.kt | 5 +- .../screen/send/{ => main}/SendException.kt | 2 +- .../ui/screen/send/{ => main}/SendScreen.kt | 56 ++++--- .../screen/send/{ => main}/SendViewModel.kt | 66 +++++--- .../send/{ => main}/helper/SendNftHelper.kt | 5 +- .../send/{ => main}/state/SendAmountState.kt | 2 +- .../send/{ => main}/state/SendConfig.kt | 2 +- .../send/{ => main}/state/SendDestination.kt | 7 +- .../send/{ => main}/state/SendFeeState.kt | 2 +- .../send/{ => main}/state/SendTransaction.kt | 2 +- .../ui/screen/settings/main/SettingsScreen.kt | 4 +- .../screen/staking/stake/StakingViewModel.kt | 3 +- .../token/viewer/list/holder/ActionsHolder.kt | 2 +- .../screen/transaction/TransactionScreen.kt | 29 ++-- .../tonkeeper/ui/screen/wallet/main/State.kt | 4 +- .../ui/screen/wallet/main/WalletViewModel.kt | 18 +- .../ui/screen/wallet/main/list/Item.kt | 2 - .../screen/wallet/main/list/WalletAdapter.kt | 1 + .../wallet/main/list/holder/ActionsHolder.kt | 3 +- .../wallet/main/list/holder/BalanceHolder.kt | 13 +- .../wallet/manage/TokensManageScreen.kt | 11 +- .../ui/screen/wallet/manage/list/Adapter.kt | 5 +- .../wallet/manage/list/holder/TokenHolder.kt | 17 +- .../wallet/picker/list/holder/WalletHolder.kt | 4 +- .../main/res/layout/dialog_transaction.xml | 19 ++- .../main/res/layout/fragment_main_list.xml | 3 +- .../app/src/main/res/layout/fragment_nft.xml | 2 + ....xml => fragment_notifications_enable.xml} | 19 ++- ....xml => fragment_notifications_manage.xml} | 0 .../main/res/layout/fragment_send_create.xml | 11 ++ .../main/res/layout/fragment_send_review.xml | 2 +- .../app/src/main/res/layout/view_contact.xml | 32 ++++ .../wallet/localization/Localization.kt | 18 +- .../src/main/res/values-bg/strings.xml | 5 + .../src/main/res/values-es/strings.xml | 5 + .../src/main/res/values-id/strings.xml | 5 + .../src/main/res/values-ru/strings.xml | 7 +- .../src/main/res/values-tr/strings.xml | 5 + .../src/main/res/values-uk/strings.xml | 5 + .../src/main/res/values-uz/strings.xml | 5 + .../src/main/res/values-zh/strings.xml | 5 + .../src/main/res/values/strings.xml | 6 +- .../main/java/com/tonapps/extensions/Main.kt | 5 +- .../java/com/tonapps/extensions/Strings.kt | 18 ++ .../src/main/java/com/tonapps/icu/Coins.kt | 10 ++ .../java/com/tonapps/icu/CurrencyFormatter.kt | 23 ++- .../java/com/tonapps/network/OkHttpClient.kt | 14 +- .../io/tonapi/models/AuctionBidAction.kt | 2 +- .../io/tonapi/models/NftPurchaseAction.kt | 2 +- .../main/kotlin/io/tonapi/models/Refund.kt | 2 +- .../main/java/uikit/base/BaseListFragment.kt | 7 +- .../java/uikit/widget/SimpleRecyclerView.kt | 18 ++ .../main/res/drawable/ic_address_book_28.xml | 10 ++ 131 files changed, 1417 insertions(+), 760 deletions(-) create mode 100644 apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNSpamTransactions.kt create mode 100644 apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SpamTransactionState.kt delete mode 100644 apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/NftPrefsEntity.kt delete mode 100644 apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/ImportLegacyFolder.kt delete mode 100644 apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/NftPrefsFolder.kt delete mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateFormat.kt delete mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PushScreen.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableScreen.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableViewModel.kt delete mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/NotificationsAdapter.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{NotificationsScreen.kt => manage/NotificationsManageScreen.kt} (63%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{NotificationsViewModel.kt => manage/NotificationsManageViewModel.kt} (86%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/Adapter.kt (61%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/Item.kt (82%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/NotificationsAdapter.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/holder/AppHolder.kt (89%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/holder/AppsHeaderHolder.kt (62%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/Holder.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/holder/SpaceHolder.kt (59%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/{ => manage}/list/holder/WalletPushHolder.kt (87%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsScreen.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsViewModel.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Adapter.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Item.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/ContactHolder.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/{notifications => send/contacts}/list/holder/Holder.kt (64%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/LatestHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/MyWalletHolder.kt create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/SpaceHolder.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/SendArgs.kt (94%) create mode 100644 apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendContact.kt rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/SendEvent.kt (72%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/SendException.kt (81%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/SendScreen.kt (94%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/SendViewModel.kt (90%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/helper/SendNftHelper.kt (95%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/state/SendAmountState.kt (84%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/state/SendConfig.kt (61%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/state/SendDestination.kt (87%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/state/SendFeeState.kt (72%) rename apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/{ => main}/state/SendTransaction.kt (92%) rename apps/wallet/instance/app/src/main/res/layout/{fragment_init_push.xml => fragment_notifications_enable.xml} (68%) rename apps/wallet/instance/app/src/main/res/layout/{fragment_notifications.xml => fragment_notifications_manage.xml} (100%) create mode 100644 apps/wallet/instance/app/src/main/res/layout/view_contact.xml create mode 100644 ui/uikit/icon/src/main/res/drawable/ic_address_book_28.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 6f647cf89..fca025e1b 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 @@ -69,6 +69,9 @@ class API( private val internalApi = InternalApi(context, defaultHttpClient) private val configRepository = ConfigRepository(context, scope, internalApi) + @Volatile + private var cachedCountry: String? = null + val config: ConfigEntity get() { while (configRepository.configEntity == null) { @@ -429,34 +432,35 @@ class API( } } - fun pushSubscribe( + suspend fun pushSubscribe( locale: Locale, firebaseToken: String, deviceId: String, accounts: List<String> ): Boolean { - return try { - val url = "${config.tonapiMainnetHost}/v1/internal/pushes/plain/subscribe" - val accountsArray = JSONArray() - for (account in accounts) { - val jsonAccount = JSONObject() - jsonAccount.put("address", account) - accountsArray.put(jsonAccount) - } + val url = "${config.tonapiMainnetHost}/v1/internal/pushes/plain/subscribe" + val accountsArray = JSONArray() + for (account in accounts) { + val jsonAccount = JSONObject() + jsonAccount.put("address", account) + accountsArray.put(jsonAccount) + } - val json = JSONObject() - json.put("locale", locale.toString()) - json.put("device", deviceId) - json.put("token", firebaseToken) - json.put("accounts", accountsArray) + val json = JSONObject() + json.put("locale", locale.toString()) + json.put("device", deviceId) + json.put("token", firebaseToken) + json.put("accounts", accountsArray) - return tonAPIHttpClient.postJSON(url, json.toString()).isSuccessful - } catch (e: Throwable) { - false - } + Log.d("TONKeeperLog", "json: $json") + + return withRetry { + val response = tonAPIHttpClient.postJSON(url, json.toString()) + response.isSuccessful + } ?: false } - fun pushTonconnectSubscribe( + suspend fun pushTonconnectSubscribe( token: String, appUrl: String, accountId: String, @@ -465,24 +469,25 @@ class API( commercial: Boolean = true, silent: Boolean = true ): Boolean { - return try { - val url = "${config.tonapiMainnetHost}/v1/internal/pushes/tonconnect" + val url = "${config.tonapiMainnetHost}/v1/internal/pushes/tonconnect" - val json = JSONObject() - json.put("app_url", appUrl) - json.put("account", accountId) - json.put("firebase_token", firebaseToken) - sessionId?.let { json.put("session_id", it) } - json.put("commercial", commercial) - json.put("silent", silent) - val data = json.toString().replace("\\/", "/") + val json = JSONObject() + json.put("app_url", appUrl) + json.put("account", accountId) + json.put("firebase_token", firebaseToken) + sessionId?.let { json.put("session_id", it) } + json.put("commercial", commercial) + json.put("silent", silent) + val data = json.toString().replace("\\/", "/").trim() + + Log.d("TONKeeperLog", "json: $data") + Log.d("TONKeeperLog", "token: $token") + return withRetry { tonAPIHttpClient.postJSON(url, data, ArrayMap<String, String>().apply { set("X-TonConnect-Auth", token) }).isSuccessful - } catch (e: Throwable) { - false - } + } ?: false } fun pushTonconnectUnsubscribe( @@ -566,7 +571,10 @@ class API( } suspend fun resolveCountry(): String? = withContext(Dispatchers.IO) { - internalApi.resolveCountry() + if (cachedCountry == null) { + cachedCountry = internalApi.resolveCountry() + } + cachedCountry } suspend fun reportNtfSpam( 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 394a5b16d..b14581d91 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.isDebug import com.tonapps.extensions.locale import com.tonapps.extensions.packageInfo +import com.tonapps.extensions.withRetry import com.tonapps.network.get import com.tonapps.wallet.api.entity.ConfigEntity import com.tonapps.wallet.api.entity.NotificationEntity @@ -79,9 +80,15 @@ internal class InternalApi( } } - fun resolveCountry(): String? { + suspend fun resolveCountry(): String? { return try { - JSONObject(okHttpClient.get("https://api.country.is/")).getString("country") + val data = withRetry { okHttpClient.get("https://api.country.is/") } ?: return null + val country = JSONObject(data).getString("country") + if (country.isNullOrBlank()) { + null + } else { + country + } } catch (e: Throwable) { null } diff --git a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt index 4d5d52960..5bc177c92 100644 --- a/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt +++ b/apps/wallet/data/events/src/main/java/com/tonapps/wallet/data/events/EventsRepository.kt @@ -1,6 +1,7 @@ package com.tonapps.wallet.data.events import android.content.Context +import android.util.Log import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.prefs import com.tonapps.wallet.api.API @@ -66,6 +67,11 @@ class EventsRepository( } } + suspend fun get( + accountId: String, + testnet: Boolean + ) = getLocal(accountId, testnet) ?: getRemote(accountId, testnet) + suspend fun getRemote( accountId: String, testnet: Boolean, diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeBiometric.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeBiometric.kt index a98f07781..93c7b6c07 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeBiometric.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeBiometric.kt @@ -16,7 +16,7 @@ object PasscodeBiometric { fun isAvailableOnDevice(context: Context): Boolean { val authStatus = BiometricManager.from(context).canAuthenticate(authenticators) - return authStatus == BiometricManager.BIOMETRIC_SUCCESS || authStatus == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED + return authStatus == BiometricManager.BIOMETRIC_SUCCESS // || authStatus == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED } suspend fun showPrompt( diff --git a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/GooglePushService.kt b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/GooglePushService.kt index e16b0d0f6..326fc110e 100644 --- a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/GooglePushService.kt +++ b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/GooglePushService.kt @@ -24,6 +24,7 @@ class GooglePushService: FirebaseMessagingService() { private val recentlyReceivedMessageIds = ArrayDeque<String>(10) private fun onPushReceived(extras: Bundle) { + Log.d("TONKeeperLog", "onPushReceived: $extras") val firebaseMessageId = extras.getString("google.message_id") ?: return if (alreadyReceivedMessage(firebaseMessageId)) { return diff --git a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/Module.kt b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/Module.kt index 25d15626e..41a3ec725 100644 --- a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/Module.kt +++ b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/Module.kt @@ -3,5 +3,5 @@ package com.tonapps.wallet.data.push import org.koin.dsl.module val pushModule = module { - single { PushManager(get(), get(), get(), get(), get(), get(), get()) } + single { PushManager(get(), get(), get(), get(), get(), get()) } } \ No newline at end of file diff --git a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/PushManager.kt b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/PushManager.kt index 9f8845988..339f8794a 100644 --- a/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/PushManager.kt +++ b/apps/wallet/data/push/src/main/java/com/tonapps/wallet/data/push/PushManager.kt @@ -3,6 +3,7 @@ package com.tonapps.wallet.data.push import android.app.Notification import android.content.Context import android.graphics.Bitmap +import android.util.Log import com.tonapps.blockchain.ton.extensions.toUserFriendly import com.tonapps.extensions.locale import com.tonapps.network.getBitmap @@ -20,6 +21,7 @@ import com.tonapps.wallet.data.tonconnect.entities.DAppManifestEntity import com.tonapps.wallet.data.tonconnect.entities.DConnectEntity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -35,7 +37,6 @@ class PushManager( private val scope: CoroutineScope, private val accountRepository: AccountRepository, private val settingsRepository: SettingsRepository, - private val eventsRepository: EventsRepository, private val tonConnectRepository: TonConnectRepository, private val api: API ) { @@ -54,9 +55,10 @@ class PushManager( init { combine( settingsRepository.firebaseTokenFlow, - settingsRepository.walletPush, - ::subscribe - ).flowOn(Dispatchers.IO).launchIn(scope) + settingsRepository.walletPush + ) { token, _ -> + subscribe(token) + }.flowOn(Dispatchers.IO).launchIn(scope) accountRepository.selectedWalletFlow.onEach { _dAppPushFlow.value = getRemoteDAppEvents(it) @@ -158,19 +160,31 @@ class PushManager( } private suspend fun subscribe( - firebaseToken: String, - walletPush: Map<String, Boolean> - ) { - val wallets = accountRepository.getWallets() - val accounts = wallets.filter { - !it.testnet && settingsRepository.getPushWallet(it.id) - }.map { - it.accountId.toUserFriendly(testnet = false) + firebaseToken: String + ) = withContext(Dispatchers.IO) { + val tcAppsDeferred = async { tonConnectRepository.subscribePush(firebaseToken) } + val walletDeferred = async { subscribeWalletPush(firebaseToken) } + + if (!tcAppsDeferred.await()) { + Log.e("TONKeeperLog", "Failed to subscribe to TC apps push") } - val enabledAccounts = walletPush.filterValues { it }.keys - api.pushSubscribe(context.locale, firebaseToken, settingsRepository.installId, accounts) + if (!walletDeferred.await()) { + Log.e("TONKeeperLog", "Failed to subscribe to wallet push") + } + } - tonConnectRepository.updatePushToken(firebaseToken) + private suspend fun subscribeWalletPush( + firebaseToken: String + ): Boolean { + val wallets = accountRepository.getWallets() + val accounts = wallets.filter { !it.testnet && settingsRepository.getPushWallet(it.id) }.map { + it.accountId.toUserFriendly(testnet = false) + } + Log.d("TONKeeperLog", "accounts: $accounts") + if (accounts.isEmpty()) { + return true + } + return api.pushSubscribe(context.locale, firebaseToken, settingsRepository.installId, accounts) } } \ No newline at end of file diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt index b6ab5505b..276bd6848 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/RNLegacy.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import androidx.fragment.app.FragmentActivity import com.tonapps.wallet.data.rn.data.RNDecryptedData +import com.tonapps.wallet.data.rn.data.RNSpamTransactions import com.tonapps.wallet.data.rn.data.RNTC import com.tonapps.wallet.data.rn.data.RNVaultState import com.tonapps.wallet.data.rn.data.RNWallet @@ -121,6 +122,37 @@ class RNLegacy( } } + fun getSpamTransactions(walletId: String): RNSpamTransactions { + val key = keySpamTransactions(walletId) + val json = sql.getJSONObject(key) ?: return RNSpamTransactions(walletId) + val spam = mutableListOf<String>() + val nonSpam = mutableListOf<String>() + for (transactionId in json.keys()) { + if (json.optBoolean(transactionId)) { + spam.add(transactionId) + } else { + nonSpam.add(transactionId) + } + } + return RNSpamTransactions(walletId, spam.toList(), nonSpam.toList()) + } + + fun setSpamTransactions(walletId: String, data: RNSpamTransactions) { + val json = JSONObject() + for (transactionId in data.spam) { + json.put(transactionId, true) + } + for (transactionId in data.nonSpam) { + json.put(transactionId, false) + } + val key = keySpamTransactions(walletId) + sql.setJSONObject(key, json) + } + + private fun keySpamTransactions(walletId: String): String { + return "${walletId}/local-scam" + } + fun getValue(key: String): String? { return sql.getValue(key) } @@ -129,7 +161,10 @@ class RNLegacy( return sql.getJSONObject(key) } - fun setJSONValue(key: String, value: JSONObject) { + fun setJSONValue(key: String, value: JSONObject, v: Int = -1) { + if (v >= 0) { + value.put("__version", v) + } sql.setJSONObject(key, value) } @@ -185,18 +220,32 @@ class RNLegacy( return Pair(setupDismissed, hasOpenedTelegramChannel) } + fun setSetupLastBackupAt(walletId: String, date: Long) { + val json = getSetupJSON(walletId) + json.put("lastBackupAt", date) + setSetupJSON(walletId, json) + } + fun setSetupDismissed(walletId: String) { - val key = "${walletId}/setup" - val json = getJSONValue(key) ?: JSONObject() + val json = getSetupJSON(walletId) json.put("setupDismissed", true) - setJSONValue(key, json) + setSetupJSON(walletId, json) } fun setHasOpenedTelegramChannel(walletId: String) { - val key = "${walletId}/setup" - val json = getJSONValue(key) ?: JSONObject() + val json = getSetupJSON(walletId) json.put("hasOpenedTelegramChannel", true) - setJSONValue(key, json) + setSetupJSON(walletId, json) + } + + private fun getSetupJSON(walletId: String): JSONObject { + val key = "${walletId}/setup" + return getJSONValue(key) ?: JSONObject() + } + + private fun setSetupJSON(walletId: String, json: JSONObject) { + val key = "${walletId}/setup" + setJSONValue(key, json, 1) } fun getNotificationsEnabled(walletId: String): Boolean { diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNSpamTransactions.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNSpamTransactions.kt new file mode 100644 index 000000000..5c915bbd5 --- /dev/null +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/data/RNSpamTransactions.kt @@ -0,0 +1,7 @@ +package com.tonapps.wallet.data.rn.data + +class RNSpamTransactions( + val walletId: String, + val spam: List<String> = emptyList(), + val nonSpam: List<String> = emptyList() +) \ No newline at end of file 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 50deb332f..92547b5e7 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 @@ -12,9 +12,7 @@ import com.tonapps.wallet.data.core.Theme import com.tonapps.wallet.data.core.WalletCurrency import com.tonapps.wallet.data.core.isAvailableBiometric import com.tonapps.wallet.data.rn.RNLegacy -import com.tonapps.wallet.data.settings.entities.NftPrefsEntity import com.tonapps.wallet.data.settings.entities.TokenPrefsEntity -import com.tonapps.wallet.data.settings.folder.NftPrefsFolder import com.tonapps.wallet.data.settings.folder.TokenPrefsFolder import com.tonapps.wallet.data.settings.folder.WalletPrefsFolder import com.tonapps.wallet.localization.Language @@ -26,6 +24,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -60,9 +59,6 @@ class SettingsRepository( private val _languageFlow = MutableEffectFlow<Language>() val languageFlow = _languageFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() - private val _telegramChannelFlow = MutableStateFlow(true) - val telegramChannelFlow = _telegramChannelFlow.stateIn(scope, SharingStarted.Eagerly, true) - private val _themeFlow = MutableEffectFlow<Theme>() val themeFlow = _themeFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() @@ -73,7 +69,7 @@ class SettingsRepository( val firebaseTokenFlow = _firebaseTokenFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() private val _countryFlow = MutableEffectFlow<String>() - val countryFlow = _countryFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() + val countryFlow = _countryFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull().map { fixCountryCode(it) } private val _biometricFlow = MutableStateFlow<Boolean?>(null) val biometricFlow = _biometricFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() @@ -84,13 +80,12 @@ class SettingsRepository( private val _searchEngineFlow = MutableEffectFlow<SearchEngine>() val searchEngineFlow = _searchEngineFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() - private val _walletPush = MutableStateFlow<Map<String, Boolean>?>(null) - val walletPush = _walletPush.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() + private val _walletPush = MutableEffectFlow<Unit>() + val walletPush = _walletPush.stateIn(scope, SharingStarted.Eagerly, Unit) private val prefs = context.getSharedPreferences(NAME, Context.MODE_PRIVATE) - private val tokenPrefsFolder = TokenPrefsFolder(context) - private val walletPrefsFolder = WalletPrefsFolder(context) - private val nftPrefsFolder = NftPrefsFolder(context) + private val tokenPrefsFolder = TokenPrefsFolder(context, scope) + private val walletPrefsFolder = WalletPrefsFolder(context, scope) private val migrationHelper = RNMigrationHelper(scope, context, rnLegacy) val walletPrefsChangedFlow: Flow<Unit> @@ -99,9 +94,6 @@ class SettingsRepository( val tokenPrefsChangedFlow: Flow<Unit> get() = tokenPrefsFolder.changedFlow - val nftPrefsChangedFlow: Flow<Unit> - get() = nftPrefsFolder.changedFlow - val installId: String get() = prefs.getString(INSTALL_ID_KEY, null) ?: run { val id = java.util.UUID.randomUUID().toString() @@ -186,7 +178,7 @@ class SettingsRepository( } } - var country: String = prefs.getString(COUNTRY_KEY, null) ?: context.locale.country + var country: String = fixCountryCode(prefs.getString(COUNTRY_KEY, null)) set(value) { if (value != field) { prefs.edit().putString(COUNTRY_KEY, value).apply() @@ -206,6 +198,10 @@ class SettingsRepository( } } + fun getSpamStateTransaction(walletId: String, id: String) = walletPrefsFolder.getSpamStateTransaction(walletId, id) + + fun isSpamTransaction(walletId: String, id: String) = getSpamStateTransaction(walletId, id) == SpamTransactionState.SPAM + fun isPurchaseOpenConfirm(walletId: String, id: String) = walletPrefsFolder.isPurchaseOpenConfirm(walletId, id) fun disablePurchaseOpenConfirm(walletId: String, id: String) = walletPrefsFolder.disablePurchaseOpenConfirm(walletId, id) @@ -214,12 +210,8 @@ class SettingsRepository( fun setPushWallet(walletId: String, value: Boolean) { walletPrefsFolder.setPushEnabled(walletId, value) - - val map = (_walletPush.value ?: mapOf()).toMutableMap() - map[walletId] = value - _walletPush.tryEmit(map) - rnLegacy.setNotificationsEnabled(walletId, value) + _walletPush.tryEmit(Unit) } fun isSetupHidden(walletId: String): Boolean = walletPrefsFolder.isSetupHidden(walletId) @@ -234,6 +226,12 @@ class SettingsRepository( rnLegacy.setHasOpenedTelegramChannel(walletId) } + fun isTelegramChannel(walletId: String) = walletPrefsFolder.isTelegramChannel(walletId) + + fun setLastBackupAt(walletId: String, date: Long) { + rnLegacy.setSetupLastBackupAt(walletId, date) + } + fun getLocale(): Locale { if (language.code == Language.DEFAULT) { return context.locale @@ -250,20 +248,12 @@ class SettingsRepository( rnLegacy.setTokenHidden(walletId, tokenAddress, hidden) } - suspend fun setNftHidden( - walletId: String, - nftAddress: String, - hidden: Boolean = true - ) = withContext(Dispatchers.IO) { - nftPrefsFolder.setHidden(walletId, nftAddress, hidden) - } - - suspend fun setNftTrust( + suspend fun setTokenState( walletId: String, - nftAddress: String, - trust: Boolean = true + tokenAddress: String, + state: TokenPrefsEntity.State ) = withContext(Dispatchers.IO) { - nftPrefsFolder.setTrust(walletId, nftAddress, trust) + tokenPrefsFolder.setState(walletId, tokenAddress, state) } fun setTokenPinned(walletId: String, tokenAddress: String, pinned: Boolean) { @@ -289,18 +279,11 @@ class SettingsRepository( suspend fun getTokenPrefs( walletId: String, tokenAddress: String, - blacklist: Boolean, + blacklist: Boolean = false, ): TokenPrefsEntity = withContext(Dispatchers.IO) { tokenPrefsFolder.get(walletId, tokenAddress, blacklist) } - suspend fun getNftPrefs( - walletId: String, - nftAddress: String - ): NftPrefsEntity = withContext(Dispatchers.IO) { - nftPrefsFolder.get(walletId, nftAddress) - } - init { languageFlow.onEach { AppCompatDelegate.setApplicationLocales(getLocales()) @@ -311,7 +294,6 @@ class SettingsRepository( prefs.clear() tokenPrefsFolder.clear() walletPrefsFolder.clear() - nftPrefsFolder.clear() val legacyValues = importFromLegacy() biometric = legacyValues.biometric @@ -337,7 +319,7 @@ class SettingsRepository( _searchEngineFlow.tryEmit(searchEngine) _biometricFlow.tryEmit(biometric) _lockscreenFlow.tryEmit(lockScreen) - _walletPush.tryEmit(mapOf()) + _walletPush.tryEmit(Unit) } } @@ -388,6 +370,15 @@ class SettingsRepository( if (setupDismissed) { walletPrefsFolder.setupHide(walletId) } + + val spamTransactions = rnLegacy.getSpamTransactions(walletId) + for (transactionId in spamTransactions.spam) { + walletPrefsFolder.setSpamStateTransaction(walletId, transactionId, SpamTransactionState.SPAM) + } + + for (transactionId in spamTransactions.nonSpam) { + walletPrefsFolder.setSpamStateTransaction(walletId, transactionId, SpamTransactionState.NOT_SPAM) + } } } @@ -400,4 +391,12 @@ class SettingsRepository( } } + private fun fixCountryCode(code: String?): String { + return if (code.isNullOrBlank()) { + getLocale().country + } else { + code + } + } + } \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SpamTransactionState.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SpamTransactionState.kt new file mode 100644 index 000000000..177c140e1 --- /dev/null +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/SpamTransactionState.kt @@ -0,0 +1,5 @@ +package com.tonapps.wallet.data.settings + +enum class SpamTransactionState(val state: Int) { + UNKNOWN(0), SPAM(1), NOT_SPAM(2) +} \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/NftPrefsEntity.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/NftPrefsEntity.kt deleted file mode 100644 index e9c040ea8..000000000 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/NftPrefsEntity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tonapps.wallet.data.settings.entities - -data class NftPrefsEntity( - val hidden: Boolean = false, - val trust: Boolean = false, -) \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/TokenPrefsEntity.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/TokenPrefsEntity.kt index ff89a4376..f77184fc2 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/TokenPrefsEntity.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/entities/TokenPrefsEntity.kt @@ -2,7 +2,28 @@ package com.tonapps.wallet.data.settings.entities data class TokenPrefsEntity( val pinned: Boolean = false, - val hidden: Boolean = false, - val index: Int = -1, - val contains: Boolean -) \ No newline at end of file + val state: State = State.NONE, + private val hidden: Boolean = false, + val index: Int = -1 +) { + + companion object { + + fun state(value: Int) = when (value) { + 1 -> State.TRUST + 2 -> State.SPAM + else -> State.NONE + } + } + + enum class State(val state: Int) { + NONE(0), TRUST(1), SPAM(2) + } + + val isTrust: Boolean + get() = state == State.TRUST + + val isHidden: Boolean + get() = state == State.SPAM || hidden + +} \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/BaseSettingsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/BaseSettingsFolder.kt index 39ba363c3..fc71e2712 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/BaseSettingsFolder.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/BaseSettingsFolder.kt @@ -5,17 +5,22 @@ import android.content.SharedPreferences import android.util.Log import com.tonapps.extensions.MutableEffectFlow import com.tonapps.extensions.getByteArray +import com.tonapps.extensions.state +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.shareIn internal abstract class BaseSettingsFolder( - private val context: Context, - private val name: String + context: Context, + scope: CoroutineScope, + name: String ) { private val prefs = context.getSharedPreferences(name, Context.MODE_PRIVATE) private val _changedFlow = MutableEffectFlow<Unit>() - val changedFlow = _changedFlow.asSharedFlow() + val changedFlow = _changedFlow.shareIn(scope, SharingStarted.Lazily, 1) init { notifyChanged() diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/ImportLegacyFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/ImportLegacyFolder.kt deleted file mode 100644 index 3ca0d213c..000000000 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/ImportLegacyFolder.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tonapps.wallet.data.settings.folder - -import android.content.Context - -internal class ImportLegacyFolder(context: Context): BaseSettingsFolder(context, "import_legacy_folder") { - - private companion object { - private const val PASSCODE_KEY = "passcode" - private const val SETTINGS_KEY = "settings" - } - - var passcode: Boolean - get() = getBoolean(PASSCODE_KEY, false) - set(value) = putBoolean(PASSCODE_KEY, value) - - - var settings: Boolean - get() = getBoolean(SETTINGS_KEY, false) - set(value) = putBoolean(SETTINGS_KEY, value) -} \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/NftPrefsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/NftPrefsFolder.kt deleted file mode 100644 index 2a49312ae..000000000 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/NftPrefsFolder.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tonapps.wallet.data.settings.folder - -import android.content.Context -import android.util.Log -import com.tonapps.wallet.data.settings.entities.NftPrefsEntity - -internal class NftPrefsFolder(context: Context): BaseSettingsFolder(context, "nft_prefs") { - - private companion object { - private const val HIDDEN_PREFIX = "hidden_" - private const val TRUST_PREFIX = "trust_" - } - - fun get(walletId: String, nftAddress: String): NftPrefsEntity { - return NftPrefsEntity( - hidden = getBoolean(keyHidden(walletId, nftAddress), false), - trust = getBoolean(keyTrust(walletId, nftAddress), false) - ) - } - - fun setHidden(walletId: String, nftAddress: String, hidden: Boolean) { - putBoolean(keyHidden(walletId, nftAddress), hidden) - } - - fun setTrust(walletId: String, nftAddress: String, trust: Boolean) { - putBoolean(keyTrust(walletId, nftAddress), trust) - } - - private fun keyHidden(walletId: String, nftAddress: String): String { - return key(HIDDEN_PREFIX, walletId, nftAddress) - } - - private fun keyTrust(walletId: String, nftAddress: String): String { - return key(TRUST_PREFIX, walletId, nftAddress) - } - - private fun key( - prefix: String, - walletId: String, - nftAddress: String - ) = "$prefix$walletId:$nftAddress" -} \ No newline at end of file diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt index 0d05c7624..dc6d5564e 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/TokenPrefsFolder.kt @@ -2,31 +2,50 @@ package com.tonapps.wallet.data.settings.folder import android.content.Context import com.tonapps.wallet.data.settings.entities.TokenPrefsEntity +import kotlinx.coroutines.CoroutineScope -internal class TokenPrefsFolder(context: Context): BaseSettingsFolder(context, "token_prefs") { +internal class TokenPrefsFolder(context: Context, scope: CoroutineScope): BaseSettingsFolder(context, scope, "token_prefs") { private companion object { private const val PINNED_PREFIX = "pinned_" - private const val HIDDEN_PREFIX = "hidden_" + private const val STATE_PREFIX = "state_" private const val SORT_PREFIX = "sort_" + private const val HIDDEN_PREFIX = "hidden_" } fun get(walletId: String, tokenAddress: String, blacklist: Boolean): TokenPrefsEntity { - var hidden = getBoolean(keyHidden(walletId, tokenAddress)) - val pinned = if (hidden) false else getBoolean(keyPinned(walletId, tokenAddress), tokenAddress.equals("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", ignoreCase = true) || tokenAddress.equals("ton", ignoreCase = true)) - val index = if (pinned) getInt(keySort(walletId, tokenAddress)) else -1 - if (blacklist && !hidden && !contains(keyHidden(walletId, tokenAddress))) { - hidden = true - } - val contains = contains(keySort(walletId, tokenAddress)) || contains(keyHidden(walletId, tokenAddress)) || contains(keyHidden(walletId, tokenAddress)) + val state = getState(walletId, tokenAddress) + val hidden = getHidden(walletId, tokenAddress) + val pinned = getPinned(walletId, tokenAddress) + val index = getIndex(walletId, tokenAddress) return TokenPrefsEntity( pinned = pinned, + state = state, hidden = hidden, index = index, - contains = contains, ) } + fun getState(walletId: String, tokenAddress: String): TokenPrefsEntity.State { + val value = getInt(keyState(walletId, tokenAddress)) + return TokenPrefsEntity.state(value) + } + + fun getHidden(walletId: String, tokenAddress: String): Boolean { + return getBoolean(keyHidden(walletId, tokenAddress)) + } + + fun getPinned(walletId: String, tokenAddress: String): Boolean { + return getBoolean(keyPinned(walletId, tokenAddress)) + } + + fun getIndex(walletId: String, tokenAddress: String): Int { + return getInt(keySort(walletId, tokenAddress)) + } + + // val pinned = if (hidden) false else getBoolean(keyPinned(walletId, tokenAddress), tokenAddress.equals("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", ignoreCase = true) || tokenAddress.equals("ton", ignoreCase = true)) + // + fun setPinned(walletId: String, tokenAddress: String, pinned: Boolean) { putBoolean(keyPinned(walletId, tokenAddress), pinned) } @@ -35,6 +54,10 @@ internal class TokenPrefsFolder(context: Context): BaseSettingsFolder(context, " putBoolean(keyHidden(walletId, tokenAddress), hidden) } + fun setState(walletId: String, tokenAddress: String, state: TokenPrefsEntity.State) { + putInt(keyState(walletId, tokenAddress), state.state) + } + fun setSort(walletId: String, tokensAddress: List<String>) { edit { for ((index, tokenAddress) in tokensAddress.withIndex()) { @@ -43,14 +66,18 @@ internal class TokenPrefsFolder(context: Context): BaseSettingsFolder(context, " } } - private fun keyPinned(walletId: String, tokenAddress: String): String { - return key(PINNED_PREFIX, walletId, tokenAddress) + private fun keyState(walletId: String, tokenAddress: String): String { + return key(STATE_PREFIX, walletId, tokenAddress) } private fun keyHidden(walletId: String, tokenAddress: String): String { return key(HIDDEN_PREFIX, walletId, tokenAddress) } + private fun keyPinned(walletId: String, tokenAddress: String): String { + return key(PINNED_PREFIX, walletId, tokenAddress) + } + private fun keySort(walletId: String, tokenAddress: String): String { return key(SORT_PREFIX, walletId, tokenAddress) } diff --git a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt index 69dd85dd3..a25968979 100644 --- a/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt +++ b/apps/wallet/data/settings/src/main/java/com/tonapps/wallet/data/settings/folder/WalletPrefsFolder.kt @@ -4,9 +4,11 @@ import android.content.Context import android.os.SystemClock import android.util.Log import com.tonapps.wallet.data.settings.SettingsRepository +import com.tonapps.wallet.data.settings.SpamTransactionState import com.tonapps.wallet.data.settings.entities.WalletPrefsEntity +import kotlinx.coroutines.CoroutineScope -internal class WalletPrefsFolder(context: Context): BaseSettingsFolder(context, "wallet_prefs") { +internal class WalletPrefsFolder(context: Context, scope: CoroutineScope): BaseSettingsFolder(context, scope, "wallet_prefs") { private companion object { private const val SORT_PREFIX = "sort_" @@ -15,10 +17,29 @@ internal class WalletPrefsFolder(context: Context): BaseSettingsFolder(context, private const val SETUP_HIDDEN_PREFIX = "setup_hidden_" private const val LAST_UPDATED_PREFIX = "last_updated_" private const val TELEGRAM_CHANNEL_PREFIX = "telegram_channel_" + private const val SPAM_STATE_TRANSACTION_PREFIX = "spam_state_transaction_" + } + + fun getSpamStateTransaction( + walletId: String, + id: String + ): SpamTransactionState { + val key = keySpamStateTransaction(walletId, id) + val value = getInt(key, 0) + return SpamTransactionState.entries.firstOrNull { it.state == value } ?: SpamTransactionState.UNKNOWN + } + + fun setSpamStateTransaction( + walletId: String, + id: String, + state: SpamTransactionState + ) { + val key = keySpamStateTransaction(walletId, id) + putInt(key, state.state) } fun setLastUpdated(walletId: String) { - putLong(key(LAST_UPDATED_PREFIX, walletId), SystemClock.uptimeMillis(), false) + putLong(key(LAST_UPDATED_PREFIX, walletId), System.currentTimeMillis() / 1000, false) } fun getLastUpdated(walletId: String): Long { @@ -73,6 +94,11 @@ internal class WalletPrefsFolder(context: Context): BaseSettingsFolder(context, } } + private fun keySpamStateTransaction(walletId: String, id: String): String { + val key = key(SPAM_STATE_TRANSACTION_PREFIX, walletId) + return "$key$id" + } + private fun keyPurchaseOpenConfirm(walletId: String, id: String): String { val key = key(PURCHASE_PREFIX, walletId) return "$key$id" diff --git a/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/TonConnectRepository.kt b/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/TonConnectRepository.kt index 5bce89e7c..510fbb8ff 100644 --- a/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/TonConnectRepository.kt +++ b/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/TonConnectRepository.kt @@ -36,7 +36,9 @@ import com.tonapps.wallet.data.tonconnect.entities.reply.DAppSuccessEntity import com.tonapps.wallet.data.tonconnect.source.LocalDataSource import com.tonapps.wallet.data.tonconnect.source.RemoteDataSource import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow @@ -319,10 +321,10 @@ class TonConnectRepository( wallet: WalletEntity, connect: DConnectEntity, firebaseToken: String - ) { - val proofToken = accountRepository.requestTonProofToken(wallet) ?: return + ): Boolean { + val proofToken = accountRepository.requestTonProofToken(wallet) ?: return false val url = connect.url - api.pushTonconnectSubscribe( + return api.pushTonconnectSubscribe( token = proofToken, appUrl = url, accountId = wallet.address, @@ -336,16 +338,20 @@ class TonConnectRepository( private suspend fun subscribePush( connect: DConnectEntity, firebaseToken: String - ) { - val wallet = accountRepository.getWalletById(connect.walletId) ?: return - subscribePush(wallet, connect, firebaseToken) + ): Boolean { + val wallet = accountRepository.getWalletById(connect.walletId) ?: return false + return subscribePush(wallet, connect, firebaseToken) } - suspend fun updatePushToken(firebaseToken: String) = withContext(Dispatchers.IO) { + suspend fun subscribePush( + firebaseToken: String + ): Boolean = withContext(Dispatchers.IO) { val connections = localDataSource.getConnections() + val deferredList = mutableListOf<Deferred<Boolean>>() for (connect in connections) { - subscribePush(connect, firebaseToken) + deferredList.add(async { subscribePush(connect, firebaseToken) }) } + deferredList.map { it.await() }.any { it } } suspend fun connectLedger( diff --git a/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/entities/DAppRequestEntity.kt b/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/entities/DAppRequestEntity.kt index 17733e29d..b60f08183 100644 --- a/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/entities/DAppRequestEntity.kt +++ b/apps/wallet/data/tonconnect/src/main/java/com/tonapps/wallet/data/tonconnect/entities/DAppRequestEntity.kt @@ -3,6 +3,8 @@ package com.tonapps.wallet.data.tonconnect.entities import android.net.Uri import android.os.Parcelable +import android.util.Log +import androidx.core.net.toUri import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.json.JSONObject @@ -12,16 +14,32 @@ data class DAppRequestEntity( val v: Int = 2, val id: String, val r: String, - val ret: String? = null, + val ret: String = "back", + val source: Uri?, ) : Parcelable { @IgnoredOnParcel val payload = DAppPayloadEntity(JSONObject(r)) - constructor(uri: Uri) : this( + constructor(source: Uri?, uri: Uri) : this( + source = source, v = uri.getQueryParameter("v")?.toInt() ?: throw IllegalArgumentException("v is required"), id = uri.getQueryParameter("id") ?: throw IllegalArgumentException("id is required"), r = uri.getQueryParameter("r") ?: throw IllegalArgumentException("r is required"), - ret = uri.getQueryParameter("ret") + ret = uri.getQueryParameter("ret") ?: "back" ) + + val backUri: Uri? + get() { + if (ret == "back") { + return source + } else if (ret == "none") { + return null + } + return try { + ret.toUri() + } catch (e: Throwable) { + null + } + } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/AndroidManifest.xml b/apps/wallet/instance/app/src/main/AndroidManifest.xml index f69143300..0e1f66827 100644 --- a/apps/wallet/instance/app/src/main/AndroidManifest.xml +++ b/apps/wallet/instance/app/src/main/AndroidManifest.xml @@ -71,6 +71,7 @@ <data android:scheme="ton"/> <data android:scheme="tonkeeper"/> <data android:scheme="tc"/> + <data android:scheme="tonsite"/> </intent-filter> <intent-filter android:autoVerify="true" android:priority="1"> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/api/Extensions.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/api/Extensions.kt index ccdf311ff..bd0cc28e8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/api/Extensions.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/api/Extensions.kt @@ -5,7 +5,10 @@ import com.squareup.moshi.adapter import com.tonapps.icu.Coins import com.tonapps.blockchain.ton.extensions.toUserFriendly import com.tonapps.extensions.ifPunycodeToUnicode +import com.tonapps.extensions.max12 import com.tonapps.extensions.short12 +import com.tonapps.extensions.short6 +import com.tonapps.extensions.short8 import com.tonapps.tonkeeperx.R import io.tonapi.infrastructure.Serializer import io.tonapi.models.AccountAddress @@ -136,7 +139,11 @@ val JettonSwapAction.ton: Long fun AccountAddress.getNameOrAddress(testnet: Boolean): String { if (!name.isNullOrBlank()) { - return name!!.ifPunycodeToUnicode().short12 + val accountName = name!!.ifPunycodeToUnicode() + if (accountName.endsWith(".ton")) { + return accountName.short6 + } + return accountName.max12 } return address.toUserFriendly( wallet = isWallet, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt index 2921634a6..983f048d1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsEntity.kt @@ -19,7 +19,7 @@ sealed class AssetsEntity( val pref = if (asset is Token) { settingsRepository.getTokenPrefs(wallet.id, asset.token.address, asset.token.blacklist) } else { - TokenPrefsEntity(contains = false, index = -1) + TokenPrefsEntity() } AssetsExtendedEntity(asset, pref) }.filter { !it.hidden }.sortedWith(AssetsExtendedEntity.comparator).map { it.raw } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt index 32b0fc4cd..1c9b75536 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/AssetsExtendedEntity.kt @@ -62,7 +62,7 @@ data class AssetsExtendedEntity( get() = prefs.pinned val hidden: Boolean - get() = prefs.hidden + get() = prefs.isHidden val index: Int get() = prefs.index diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt index ede33b4fe..dddfdafb1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/entities/TransferEntity.kt @@ -59,7 +59,7 @@ data class TransferEntity( val sendMode: Int get() { - return if (max && isTon) TonSendMode.CARRY_ALL_REMAINING_BALANCE.value else (TonSendMode.PAY_GAS_SEPARATELY.value + TonSendMode.IGNORE_ERRORS.value) + return if (max && isTon) (TonSendMode.CARRY_ALL_REMAINING_BALANCE.value + TonSendMode.IGNORE_ERRORS.value) else (TonSendMode.PAY_GAS_SEPARATELY.value + TonSendMode.IGNORE_ERRORS.value) } private val coins: org.ton.block.Coins diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt index 054f7ee59..c450becdc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.core.history import android.content.Context +import android.util.Log import androidx.collection.arrayMapOf import com.tonapps.icu.Coins import com.tonapps.blockchain.ton.extensions.toUserFriendly @@ -117,7 +118,7 @@ class HistoryHelper( for (event in events) { val groupKey = getGroupKey(event.timestampForSort) val date = event.timestampForSort - val section = output[groupKey] ?: ActionDateSection(date, DateHelper.formatTransactionsGroupDate(context, date), mutableListOf()) + val section = output[groupKey] ?: ActionDateSection(date, DateHelper.formatTransactionsGroupDate(context, date, settingsRepository.getLocale()), mutableListOf()) section.events.add(event) output[groupKey] = section } @@ -247,11 +248,10 @@ class HistoryHelper( hiddenBalances: Boolean = false ): List<HistoryItem> = withContext(Dispatchers.IO) { val items = mutableListOf<HistoryItem>() + val nonSpamEvents = events.filter { !settingsRepository.isSpamTransaction(wallet.id, it.eventId) } - for ((index, event) in events.withIndex()) { - + for (event in nonSpamEvents) { val pending = event.inProgress - val prevEvent = events.getOrNull(index - 1) val actions = event.actions val fee = Coins.of(event.fee) @@ -262,8 +262,16 @@ class HistoryHelper( val chunkItems = mutableListOf<HistoryItem>() for ((actionIndex, action) in actions.withIndex()) { + val timestamp = if (removeDate) 0 else event.timestamp - val item = action(actionIndex, event.eventId, wallet, action, timestamp) + val item = action( + index = actionIndex, + txId = event.eventId, + wallet = wallet, + action = action, + timestamp = timestamp, + isScam = event.isScam + ) chunkItems.add(item.copy( pending = pending, position = ListCell.getPosition(actions.size, actionIndex), @@ -287,12 +295,13 @@ class HistoryHelper( txId: String, wallet: WalletEntity, action: Action, - timestamp: Long + timestamp: Long, + isScam: Boolean, ): HistoryItem.Event { val simplePreview = action.simplePreview - val date = DateHelper.formatTransactionTime(timestamp) - val dateDetails = DateHelper.formatTransactionDetailsTime(timestamp) + val date = DateHelper.formatTransactionTime(timestamp, settingsRepository.getLocale()) + val dateDetails = DateHelper.formatTransactionDetailsTime(timestamp, settingsRepository.getLocale()) if (action.jettonSwap != null) { val jettonSwap = action.jettonSwap!! @@ -309,12 +318,12 @@ class HistoryHelper( if (!isOut) { tokenCode = TokenEntity.TON.symbol - value = CurrencyFormatter.format(tokenCode, tonFromJetton) - value2 = CurrencyFormatter.format(symbol, amount) + value = CurrencyFormatter.format(tokenCode, tonFromJetton, 2) + value2 = CurrencyFormatter.format(symbol, amount, 2) } else { tokenCode = symbol - value = CurrencyFormatter.format(symbol, amount) - value2 = CurrencyFormatter.format(TokenEntity.TON.symbol, tonFromJetton) + value = CurrencyFormatter.format(symbol, amount, 2) + value2 = CurrencyFormatter.format(TokenEntity.TON.symbol, tonFromJetton, 2) } val rates = ratesRepository.getRates(currency, token) @@ -337,7 +346,8 @@ class HistoryHelper( isOut = isOut, currency = CurrencyFormatter.formatFiat(currency.code, inCurrency), failed = action.status == Action.Status.failed, - unverifiedToken = jettonPreview.verification != JettonVerificationType.whitelist + unverifiedToken = jettonPreview.verification != JettonVerificationType.whitelist, + isScam = isScam, ) } else if (action.jettonTransfer != null) { val jettonTransfer = action.jettonTransfer!! @@ -346,7 +356,7 @@ class HistoryHelper( val isOut = !wallet.isMyAddress(jettonTransfer.recipient?.address ?: "") val amount = Coins.ofNano(jettonTransfer.amount, jettonTransfer.jetton.decimals) - var value = CurrencyFormatter.format(symbol, amount, jettonTransfer.jetton.decimals) + var value = CurrencyFormatter.format(symbol, amount, 2) val itemAction: ActionType val accountAddress: AccountAddress? @@ -390,10 +400,11 @@ class HistoryHelper( testnet = wallet.testnet ), addressName = accountAddress?.name, - currency = CurrencyFormatter.format(currency.code, inCurrency), + currency = CurrencyFormatter.formatFiat(currency.code, inCurrency), failed = action.status == Action.Status.failed, unverifiedToken = jettonTransfer.jetton.verification != JettonVerificationType.whitelist, - senderAddress = jettonTransfer.sender?.address + senderAddress = jettonTransfer.sender?.address, + isScam = isScam, ) } else if (action.tonTransfer != null) { val tonTransfer = action.tonTransfer!! @@ -404,7 +415,7 @@ class HistoryHelper( val accountAddress: AccountAddress val amount = Coins.of(tonTransfer.amount) - var value = CurrencyFormatter.format("TON", amount, TokenEntity.TON.decimals) + var value = CurrencyFormatter.format("TON", amount, 2) if (isOut) { itemAction = ActionType.Send @@ -448,13 +459,14 @@ class HistoryHelper( currency = CurrencyFormatter.formatFiat(currency.code, inCurrency), failed = action.status == Action.Status.failed, senderAddress = tonTransfer.sender.address, + isScam = isScam, ) } else if (action.smartContractExec != null) { val smartContractExec = action.smartContractExec!! val executor = smartContractExec.executor val amount = Coins.of(smartContractExec.tonAttached) - val value = CurrencyFormatter.format("TON", amount) + val value = CurrencyFormatter.format("TON", amount, 2) return HistoryItem.Event( index = index, @@ -470,10 +482,12 @@ class HistoryHelper( dateDetails = dateDetails, isOut = true, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.nftItemTransfer != null) { val nftItemTransfer = action.nftItemTransfer!! val isOut = !wallet.isMyAddress(nftItemTransfer.recipient?.address ?: "-") + val sender = nftItemTransfer.sender ?: action.simplePreview.accounts.firstOrNull() val itemAction: ActionType val iconURL: String? @@ -482,11 +496,11 @@ class HistoryHelper( if (isOut) { itemAction = ActionType.NftSend iconURL = nftItemTransfer.recipient?.iconURL - subtitle = nftItemTransfer.recipient?.getNameOrAddress(wallet.testnet) ?: "" + subtitle = sender?.getNameOrAddress(wallet.testnet) ?: "" } else { itemAction = ActionType.NftReceived iconURL = nftItemTransfer.sender?.iconURL - subtitle = nftItemTransfer.sender?.getNameOrAddress(wallet.testnet) ?: "" + subtitle = sender?.getNameOrAddress(wallet.testnet) ?: "" } val nftItem = collectiblesRepository.getNft( @@ -517,7 +531,8 @@ class HistoryHelper( dateDetails = dateDetails, isOut = isOut, failed = action.status == Action.Status.failed, - senderAddress = nftItemTransfer.sender?.address, + senderAddress = sender?.address, + isScam = isScam, ) } else if (action.contractDeploy != null) { return HistoryItem.Event( @@ -534,12 +549,13 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.depositStake != null) { val depositStake = action.depositStake!! val amount = Coins.of(depositStake.amount) - val value = CurrencyFormatter.format("TON", amount) + val value = CurrencyFormatter.format("TON", amount, 2) return HistoryItem.Event( index = index, @@ -556,13 +572,14 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.jettonMint != null) { val jettonMint = action.jettonMint!! val amount = jettonMint.parsedAmount - val value = CurrencyFormatter.format(jettonMint.jetton.symbol, amount) + val value = CurrencyFormatter.format(jettonMint.jetton.symbol, amount, 2) return HistoryItem.Event( index = index, @@ -578,13 +595,14 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, - unverifiedToken = jettonMint.jetton.verification != JettonVerificationType.whitelist + unverifiedToken = jettonMint.jetton.verification != JettonVerificationType.whitelist, + isScam = isScam, ) } else if (action.withdrawStakeRequest != null) { val withdrawStakeRequest = action.withdrawStakeRequest!! val amount = Coins.of(withdrawStakeRequest.amount ?: 0L) - val value = CurrencyFormatter.format("TON", amount) + val value = CurrencyFormatter.format("TON", amount, 2) return HistoryItem.Event( index = index, @@ -601,6 +619,7 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.domainRenew != null) { val domainRenew = action.domainRenew!! @@ -618,6 +637,7 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.auctionBid != null) { val auctionBid = action.auctionBid!! @@ -626,7 +646,7 @@ class HistoryHelper( val amount = Coins.ofNano(auctionBid.amount.value) val tokenCode = auctionBid.amount.tokenName - val value = CurrencyFormatter.format(auctionBid.amount.tokenName, amount) + val value = CurrencyFormatter.format(auctionBid.amount.tokenName, amount, 2) return HistoryItem.Event( index = index, @@ -641,14 +661,15 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.type == Action.Type.unknown) { - return createUnknown(index, txId, action, date, timestamp, simplePreview, dateDetails) + return createUnknown(index, txId, action, date, timestamp, simplePreview, dateDetails, isScam) } else if (action.withdrawStake != null) { val withdrawStake = action.withdrawStake!! val amount = Coins.of(withdrawStake.amount) - val value = CurrencyFormatter.format("TON", amount) + val value = CurrencyFormatter.format("TON", amount, 2) return HistoryItem.Event( index = index, @@ -665,12 +686,13 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.nftPurchase != null) { val nftPurchase = action.nftPurchase!! val amount = Coins.of(nftPurchase.amount.value.toLong()) - val value = CurrencyFormatter.format(nftPurchase.amount.tokenName, amount) + val value = CurrencyFormatter.format(nftPurchase.amount.tokenName, amount, 2) val nftItem = collectiblesRepository.getNft( accountId = wallet.accountId, @@ -692,12 +714,13 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.jettonBurn != null) { val jettonBurn = action.jettonBurn!! val amount = jettonBurn.parsedAmount - val value = CurrencyFormatter.format(jettonBurn.jetton.symbol, amount) + val value = CurrencyFormatter.format(jettonBurn.jetton.symbol, amount, 2) return HistoryItem.Event( index = index, @@ -713,7 +736,8 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, - unverifiedToken = jettonBurn.jetton.verification != JettonVerificationType.whitelist + unverifiedToken = jettonBurn.jetton.verification != JettonVerificationType.whitelist, + isScam = isScam, ) } else if (action.unSubscribe != null) { val unsubscribe = action.unSubscribe!! @@ -732,12 +756,13 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else if (action.subscribe != null) { val subscribe = action.subscribe!! val amount = Coins.of(subscribe.amount) - val value = CurrencyFormatter.format("TON", amount) + val value = CurrencyFormatter.format("TON", amount, 2) return HistoryItem.Event( index = index, @@ -753,9 +778,10 @@ class HistoryHelper( dateDetails = dateDetails, isOut = false, failed = action.status == Action.Status.failed, + isScam = isScam, ) } else { - return createUnknown(index, txId, action, date, timestamp, simplePreview, dateDetails) + return createUnknown(index, txId, action, date, timestamp, simplePreview, dateDetails, isScam) } } @@ -767,6 +793,7 @@ class HistoryHelper( timestamp: Long, simplePreview: ActionSimplePreview, dateDetails: String, + isScam: Boolean, ) = HistoryItem.Event( index = index, txId = txId, @@ -779,6 +806,7 @@ class HistoryHelper( date = date, dateDetails = dateDetails, isOut = false, - failed = action.status == Action.Status.failed + failed = action.status == Action.Status.failed, + isScam = isScam, ) } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/holder/HistoryActionHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/holder/HistoryActionHolder.kt index 56e7f7dae..00978f6c1 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/holder/HistoryActionHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/holder/HistoryActionHolder.kt @@ -23,6 +23,7 @@ import com.tonapps.uikit.color.accentGreenColor import com.tonapps.uikit.color.iconSecondaryColor import com.tonapps.uikit.color.stateList import com.tonapps.uikit.color.textPrimaryColor +import com.tonapps.uikit.color.textSecondaryColor import com.tonapps.uikit.color.textTertiaryColor import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.wallet.data.core.HIDDEN_BALANCE @@ -84,8 +85,16 @@ class HistoryActionHolder( } itemView.background = item.position.drawable(context) - titleView.setText(item.action.nameRes) + if (item.isScam) { + titleView.setText(Localization.spam) + subtitleView.setTextColor(amountColorTertiary) + } else { + titleView.setText(item.action.nameRes) + subtitleView.setTextColor(context.textSecondaryColor) + } + subtitleView.text = item.subtitle + dateView.text = item.date if (item.failed) { @@ -102,7 +111,7 @@ class HistoryActionHolder( } bindPending(item.pending) - if (item.comment == null) { + if (item.comment == null || item.isScam) { commentView.visibility = View.GONE } else { bindComment(item.comment, item.txId, item.senderAddress ?: "") @@ -126,7 +135,7 @@ class HistoryActionHolder( } private fun bindAmount(item: HistoryItem.Event) { - if (item.action == ActionType.WithdrawStakeRequest) { + if (item.isScam || item.action == ActionType.WithdrawStakeRequest) { amountView.setTextColor(amountColorTertiary) } else { amountView.setTextColor(getAmountColor(item.value)) @@ -137,13 +146,14 @@ class HistoryActionHolder( amountView.text = item.value.withCustomSymbol(context) } - if (item.value2.isEmpty()) { amount2View.visibility = View.GONE } else { amount2View.visibility = View.VISIBLE if (item.hiddenBalance) { amount2View.text = HIDDEN_BALANCE + } else if (item.isScam) { + amount2View.text = item.value2.toString() } else { amount2View.text = item.value2.withCustomSymbol(context) } @@ -183,7 +193,7 @@ class HistoryActionHolder( } private fun bindNft(item: HistoryItem.Event) { - if (!item.hasNft) { + if (!item.hasNft || item.isScam) { nftView.visibility = View.GONE return } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/item/HistoryItem.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/item/HistoryItem.kt index 67d2cba03..8181fea34 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/item/HistoryItem.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/list/item/HistoryItem.kt @@ -3,7 +3,6 @@ package com.tonapps.tonkeeper.core.history.list.item import android.net.Uri import android.os.Parcel import android.os.Parcelable -import android.util.Log import com.tonapps.extensions.readBooleanCompat import com.tonapps.extensions.readCharSequenceCompat import com.tonapps.extensions.readEnum @@ -12,7 +11,6 @@ import com.tonapps.extensions.writeBooleanCompat import com.tonapps.extensions.writeCharSequenceCompat import com.tonapps.extensions.writeEnum import com.tonapps.tonkeeper.core.history.ActionType -import com.tonapps.tonkeeper.helper.DateFormat import com.tonapps.uikit.list.BaseListItem import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.collectibles.entities.NftEntity @@ -98,14 +96,13 @@ sealed class HistoryItem( val date: Long, ): HistoryItem(TYPE_HEADER) { - constructor(timestamp: Long) : this( - title = DateFormat.monthWithDate(timestamp), - date = timestamp + constructor(parcel: Parcel) : this( + parcel.readString()!!, + parcel.readLong() ) - constructor(parcel: Parcel) : this(parcel.readLong()) - override fun marshall(dest: Parcel, flags: Int) { + dest.writeString(title) dest.writeLong(date) } @@ -185,7 +182,8 @@ sealed class HistoryItem( val lt: Long = 0L, val failed: Boolean, val hiddenBalance: Boolean = false, - val unverifiedToken: Boolean = false + val unverifiedToken: Boolean = false, + val isScam: Boolean ): HistoryItem(TYPE_ACTION) { @Parcelize @@ -258,7 +256,8 @@ sealed class HistoryItem( lt = parcel.readLong(), failed = parcel.readBooleanCompat(), hiddenBalance = parcel.readBooleanCompat(), - unverifiedToken = parcel.readBooleanCompat() + unverifiedToken = parcel.readBooleanCompat(), + isScam = parcel.readBooleanCompat(), ) override fun marshall(dest: Parcel, flags: Int) { @@ -289,6 +288,7 @@ sealed class HistoryItem( dest.writeBooleanCompat(failed) dest.writeBooleanCompat(hiddenBalance) dest.writeBooleanCompat(unverifiedToken) + dest.writeBooleanCompat(isScam) } companion object CREATOR : Parcelable.Creator<Event> { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/camera/CameraFragment.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/camera/CameraFragment.kt index 5f1b8b1aa..feb54d2ce 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/camera/CameraFragment.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/camera/CameraFragment.kt @@ -126,7 +126,7 @@ class CameraFragment: BaseFragment(R.layout.fragment_camera), BaseFragment.Botto } val url = getUrlFromBarcode(barcode) ?: return readyUrl = true - if (rootViewModel.processDeepLink(Uri.parse(url), true)) { + if (rootViewModel.processDeepLink(Uri.parse(url), true, null)) { finish() } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/tonconnect/auth/TCAuthFragment.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/tonconnect/auth/TCAuthFragment.kt index 727c16ded..eb2e688c7 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/tonconnect/auth/TCAuthFragment.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/fragment/tonconnect/auth/TCAuthFragment.kt @@ -1,5 +1,6 @@ package com.tonapps.tonkeeper.fragment.tonconnect.auth +import android.net.Uri import android.os.Bundle import android.os.Looper import android.text.SpannableString @@ -12,6 +13,7 @@ import androidx.lifecycle.lifecycleScope import com.facebook.common.util.UriUtil import com.facebook.drawee.view.SimpleDraweeView import com.tonapps.blockchain.ton.extensions.toUserFriendly +import com.tonapps.extensions.getParcelableCompat import com.tonapps.wallet.localization.Localization import com.tonapps.tonkeeperx.R import com.tonapps.tonkeeper.core.tonconnect.models.TCData @@ -63,8 +65,9 @@ class TCAuthFragment: BaseFragment(R.layout.dialog_ton_connect), BaseFragment.Mo private val callbackKey: String? by lazy { arguments?.getString(CALLBACK_KEY) } private val fromBrowser: Boolean by lazy { arguments?.getBoolean(FROM_BROWSER_KEY) ?: false } + private val request: DAppRequestEntity by lazy { arguments?.getParcelableCompat(REQUEST_KEY)!! } - private val viewModel: TCAuthViewModel by viewModel { parametersOf(arguments?.getParcelable(REQUEST_KEY)!!) } + private val viewModel: TCAuthViewModel by viewModel { parametersOf(request) } private lateinit var closeView: View private lateinit var loaderView: LoaderView @@ -162,7 +165,7 @@ class TCAuthFragment: BaseFragment(R.layout.dialog_ton_connect), BaseFragment.Mo private fun setSuccess(result: DAppEventSuccessEntity) { connectProcessView.state = ProcessTaskView.State.SUCCESS - finalDelay() + finalDelay(request.backUri) callbackKey?.let { navigation?.setFragmentResult(it, Bundle().apply { putString(REPLY_ARG, result.toJSON().toString()) @@ -172,7 +175,7 @@ class TCAuthFragment: BaseFragment(R.layout.dialog_ton_connect), BaseFragment.Mo private fun setFailure() { connectProcessView.state = ProcessTaskView.State.FAILED - finalDelay() + finalDelay(request.backUri) } private fun cancelCallback() { @@ -181,9 +184,18 @@ class TCAuthFragment: BaseFragment(R.layout.dialog_ton_connect), BaseFragment.Mo } } - private fun finalDelay() { + private fun finalDelay(uri: Uri?) { + if (!fromBrowser && uri != null) { + redirectTo(uri) + } postDelayed(1000) { finish() } } + + private fun redirectTo(uri: Uri) { + try { + navigation?.openURL(uri.toString(), true) + } catch (ignored: Throwable) { } + } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateFormat.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateFormat.kt deleted file mode 100644 index c61c7df39..000000000 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateFormat.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tonapps.tonkeeper.helper - -import java.text.SimpleDateFormat -import java.time.Instant -import java.time.YearMonth -import java.time.ZoneId -import java.util.Locale - -// TODO refactor to use kotlinx-datetime -object DateFormat { - - private val monthWithDateFormat = SimpleDateFormat("d MMMM", Locale.getDefault()) - - fun monthWithDate( - date: Long - ): String { - return monthWithDateFormat.format(date * 1000) - } - - fun isToday(timestamp: Long): Boolean { - /*val localDate = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()).toLocalDate() - val today = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate() - - return localDate == today*/ - return false - } - - fun isYesterday(timestamp: Long): Boolean { - /*val localDate = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()).toLocalDate() - val yesterday = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate().minusDays(1) - - return localDate == yesterday*/ - return false - } - - fun isSameDay(timestamp1: Long, timestamp2: Long): Boolean { - /*val localDate1 = Instant.ofEpochSecond(timestamp1).atZone(ZoneId.systemDefault()).toLocalDate() - val localDate2 = Instant.ofEpochSecond(timestamp2).atZone(ZoneId.systemDefault()).toLocalDate() - - return localDate1 == localDate2*/ - return false - } - - fun isSameMonth(timestamp1: Long, timestamp2: Long): Boolean { - /*val yearMonth1 = YearMonth.from(Instant.ofEpochSecond(timestamp1).atZone(ZoneId.systemDefault()).toLocalDate()) - val yearMonth2 = YearMonth.from(Instant.ofEpochSecond(timestamp2).atZone(ZoneId.systemDefault()).toLocalDate()) - - return yearMonth1 == yearMonth2*/ - return false - } - - fun isSameYear(timestamp1: Long, timestamp2: Long): Boolean { - /*val localDate1 = Instant.ofEpochSecond(timestamp1).atZone(ZoneId.systemDefault()).toLocalDate() - val localDate2 = Instant.ofEpochSecond(timestamp2).atZone(ZoneId.systemDefault()).toLocalDate() - - return localDate1.year == localDate2.year*/ - return false - } - -} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateHelper.kt index a0138014a..fec38be7a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/helper/DateHelper.kt @@ -21,18 +21,17 @@ import java.util.Locale object DateHelper { - fun formatTransactionDetailsTime(date: Long): String { + fun formatTransactionDetailsTime(date: Long, locale: Locale): String { val instant = Instant.fromEpochMilliseconds(date * 1000) - return formatTransactionDetailsTime(instant) + return formatTransactionDetailsTime(instant, locale) } - fun formatTransactionDetailsTime(date: Instant): String { - val locale = Locale.getDefault() - val shortMonth = formatDate(date, "MMM").replace(".", "") + "," + fun formatTransactionDetailsTime(date: Instant, locale: Locale): String { + val shortMonth = formatDate(date, "MMM", locale).replace(".", "") + "," val month = if (locale.language == "en") shortMonth.capitalized else shortMonth - val time = formatDate(date, "HH:mm") - val day = formatDate(date, "d") - val year = formatDate(date, "yyyy") + val time = formatDate(date, "HH:mm", locale) + val day = formatDate(date, "d", locale) + val year = formatDate(date, "yyyy", locale) return if (isThisYear(date)) { "$day $month $time" } else { @@ -40,27 +39,21 @@ object DateHelper { } } - fun timestampToDateString(timestamp: Long): String { + fun timestampToDateString(timestamp: Long, locale: Locale): String { val date = Instant.fromEpochSeconds(timestamp) - return formatDate(date, "yyyy-MM-dd") + return formatDate(date, "yyyy-MM-dd", locale) } - fun dateToTimestamp(dateString: String): Long { - val date = LocalDate.parse(dateString) - return date.atStartOfDayIn(TimeZone.UTC).epochSeconds - } - - fun formatTransactionTime(date: Long): String { + fun formatTransactionTime(date: Long, locale: Locale): String { val instant = Instant.fromEpochMilliseconds(date * 1000) - return formatTransactionTime(instant) + return formatTransactionTime(instant, locale) } - fun formatTransactionTime(date: Instant): String { - val locale = Locale.getDefault() - val shortMonth = formatDate(date, "MMM").replace(".", "") + "," + fun formatTransactionTime(date: Instant, locale: Locale): String { + val shortMonth = formatDate(date, "MMM", locale).replace(".", "") + "," val month = if (locale.language == "en") shortMonth.capitalized else shortMonth - val time = formatDate(date, "HH:mm") - val day = formatDate(date, "d") + val time = formatDate(date, "HH:mm", locale) + val day = formatDate(date, "d", locale) return if (isThisMonth(date)) { time } else { @@ -68,14 +61,14 @@ object DateHelper { } } - fun formatTransactionsGroupDate(context: Context, timestamp: Long): String { + fun formatTransactionsGroupDate(context: Context, timestamp: Long, locale: Locale): String { val date = Instant.fromEpochMilliseconds(timestamp * 1000) return when { isToday(date) -> context.getString(Localization.today) isYesterday(date) -> context.getString(Localization.yesterday) - isThisMonth(date) -> formatDate(date, "d MMMM") - isThisYear(date) -> formatDate(date, "MMMM").capitalized - else -> formatDate(date, "MMMM yyyy").capitalized + isThisMonth(date) -> formatDate(date, "d MMMM", locale) + isThisYear(date) -> formatDate(date, "MMMM", locale).capitalized + else -> formatDate(date, "MMMM yyyy", locale).capitalized } } @@ -99,31 +92,25 @@ object DateHelper { return now.minus(date, DateTimeUnit.MONTH, TimeZone.currentSystemDefault()) < 1 } - private fun createFormatter(pattern: String): DateTimeFormatter { + private fun createFormatter(pattern: String, locale: Locale): DateTimeFormatter { return DateTimeFormatterBuilder() .appendPattern(pattern) - .toFormatter(Locale.getDefault()) + .toFormatter(locale) } - fun formatDate(instant: Instant, formatString: String): String { - val formatter = createFormatter(formatString) + fun formatDate(instant: Instant, formatString: String, locale: Locale): String { + val formatter = createFormatter(formatString, locale) val zonedDateTime = instant.toJavaInstant().atZone(java.time.ZoneId.systemDefault()) return formatter.format(zonedDateTime) } - fun formattedDate(unixTimestamp: Long): String { + fun formattedDate(unixTimestamp: Long, locale: Locale): String { if (0 >= unixTimestamp) { return "" } + val formatString = "MMM d, HH:mm" val instant = Instant.fromEpochMilliseconds(unixTimestamp * 1000) - val dateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) - - val month = dateTime.month.name.take(3) - val day = dateTime.dayOfMonth - val hour = dateTime.hour.toString().padStart(2, '0') - val minute = dateTime.minute.toString().padStart(2, '0') - - return "$month $day, $hour:$minute" + return formatDate(instant, formatString, locale).capitalized } } \ 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 739d33506..9bbd35b24 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 @@ -5,7 +5,6 @@ import com.tonapps.tonkeeper.core.history.HistoryHelper import com.tonapps.tonkeeper.ui.screen.main.MainViewModel import com.tonapps.tonkeeper.ui.screen.root.RootViewModel import com.tonapps.tonkeeper.fragment.tonconnect.auth.TCAuthViewModel -import com.tonapps.wallet.data.push.PushManager import com.tonapps.tonkeeper.sign.SignManager import com.tonapps.tonkeeper.ui.screen.action.ActionViewModel import com.tonapps.tonkeeper.ui.screen.backup.main.BackupViewModel @@ -26,8 +25,10 @@ import com.tonapps.tonkeeper.ui.screen.settings.language.LanguageViewModel import com.tonapps.tonkeeper.ui.screen.name.base.NameViewModel import com.tonapps.tonkeeper.ui.screen.name.edit.EditNameViewModel import com.tonapps.tonkeeper.ui.screen.nft.NftViewModel -import com.tonapps.tonkeeper.ui.screen.notifications.NotificationsViewModel -import com.tonapps.tonkeeper.ui.screen.send.SendViewModel +import com.tonapps.tonkeeper.ui.screen.notifications.enable.NotificationsEnableViewModel +import com.tonapps.tonkeeper.ui.screen.notifications.manage.NotificationsManageViewModel +import com.tonapps.tonkeeper.ui.screen.send.contacts.SendContactsViewModel +import com.tonapps.tonkeeper.ui.screen.send.main.SendViewModel import com.tonapps.tonkeeper.ui.screen.wallet.picker.PickerViewModel import com.tonapps.tonkeeper.ui.screen.wallet.picker.list.WalletPickerAdapter import com.tonapps.tonkeeper.ui.screen.settings.main.SettingsViewModel @@ -55,7 +56,6 @@ val koinModel = module { single(createdAtStart = true) { CoroutineScope(Dispatchers.IO + SupervisorJob()) } single { SettingsRepository(get(), get(), get()) } single { NetworkMonitor(get(), get()) } - single(createdAtStart = true) { PushManager(get(), get(), get(), get(), get(), get(), get()) } single { SignManager(get(), get(), get(), get(), get()) } single { HistoryHelper(get(), get(), get(), get(), get(), get(), get()) } @@ -84,9 +84,9 @@ val koinModel = module { viewModel { BrowserSearchViewModel(get(), get(), get(), get()) } viewModel { parameters -> DAppViewModel(url = parameters.get(), get(), get()) } viewModel { ChangePasscodeViewModel(get(), get()) } - viewModel { NotificationsViewModel(get(), get(), get()) } + viewModel { NotificationsManageViewModel(get(), get(), get()) } viewModel { parameters -> TokenViewModel(get(), tokenAddress = parameters.get(), get(), get(), get(), get(), get(), get()) } - viewModel { BackupViewModel(get(), get(), get()) } + viewModel { BackupViewModel(get(), get(), get(), get()) } viewModel { BackupCheckViewModel(get(), get()) } viewModel { TokensManageViewModel(get(), get(), get()) } viewModel { parameters -> SendViewModel(get(), nftAddress = parameters.get(), get(), get(), get(), get(), get(), get(), get()) } @@ -99,4 +99,6 @@ val koinModel = module { viewModel { parameters -> NftViewModel(nft = parameters.get(), get(), get(), get()) } viewModel { parameters -> StakeViewerViewModel(address = parameters.get(), get(), get(), get(), get()) } viewModel { parameters -> UnStakeViewModel(address = parameters.get(), get(), get(), get(), get(), get(), get()) } + viewModel { SendContactsViewModel(get(), get()) } + viewModel { NotificationsEnableViewModel(get(), get()) } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt index ee827a54a..23717d114 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/coin/CoinInputView.kt @@ -9,6 +9,7 @@ import android.view.View import androidx.core.content.res.ResourcesCompat import androidx.core.widget.doAfterTextChanged import com.tonapps.icu.Coins +import com.tonapps.icu.CurrencyFormatter import com.tonapps.tonkeeper.ui.component.TokenPickerView import com.tonapps.tonkeeper.ui.component.coin.drawable.SuffixDrawable import com.tonapps.tonkeeper.ui.component.coin.format.CoinFormattingConfig @@ -120,7 +121,8 @@ class CoinInputView @JvmOverloads constructor( if (BigDecimal.ZERO == value) { clear() } else { - editText.setText(value.stripTrailingZeros().toPlainString().removeSuffix(".0")) + val text = value.stripTrailingZeros().toPlainString().removeSuffix(".0") + editText.setText(text.replace(".", CurrencyFormatter.monetaryDecimalSeparator)) } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/action/ActionViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/action/ActionViewModel.kt index d07d5ec3f..98ed92e97 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/action/ActionViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/action/ActionViewModel.kt @@ -6,25 +6,21 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tonapps.blockchain.ton.extensions.base64 import com.tonapps.ledger.ton.Transaction -import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.extensions.signLedgerTransaction -import com.tonapps.tonkeeper.ui.screen.send.SendException +import com.tonapps.tonkeeper.ui.screen.send.main.SendException import com.tonapps.wallet.api.API import com.tonapps.wallet.data.account.entities.WalletEntity import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.passcode.PasscodeManager import com.tonapps.wallet.localization.Localization -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch -import org.ton.cell.Cell class ActionViewModel( private val args: ActionArgs, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt index 9ba743d3a..24899c838 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/BackupViewModel.kt @@ -8,6 +8,7 @@ import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.backup.BackupRepository import com.tonapps.wallet.data.passcode.PasscodeManager +import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.localization.Localization import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -18,7 +19,8 @@ import kotlinx.coroutines.flow.take class BackupViewModel( private val accountRepository: AccountRepository, private val backupRepository: BackupRepository, - private val passcodeManager: PasscodeManager + private val passcodeManager: PasscodeManager, + private val settingsRepository: SettingsRepository, ): ViewModel() { val uiItemsFlow = combine(backupRepository.stream, accountRepository.selectedWalletFlow) { backups, wallet -> @@ -31,7 +33,7 @@ class BackupViewModel( for ((index, backup) in it.withIndex()) { val position = ListCell.getPosition(backupsCount, index) - items.add(Item.Backup(position, backup)) + items.add(Item.Backup(position, backup, settingsRepository.getLocale())) } if (backupsCount > 0) { items.add(Item.Space) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/list/Item.kt index 00e5ae2d2..73fd7656b 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/backup/main/list/Item.kt @@ -4,6 +4,7 @@ import com.tonapps.tonkeeper.helper.DateHelper import com.tonapps.uikit.list.BaseListItem import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.backup.entities.BackupEntity +import java.util.Locale sealed class Item(type: Int): BaseListItem(type) { @@ -26,10 +27,11 @@ sealed class Item(type: Int): BaseListItem(type) { data class Backup( val position: ListCell.Position, val entity: BackupEntity, + val locale: Locale, ): Item(TYPE_BACKUP) { val date: String by lazy { - DateHelper.timestampToDateString(entity.date / 1000) + DateHelper.timestampToDateString(entity.date / 1000, locale) } } 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 57a5b225b..79b454893 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 @@ -14,6 +14,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.widget.AppCompatTextView +import androidx.core.net.toUri import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams @@ -54,6 +55,7 @@ import uikit.navigation.Navigation.Companion.navigation import uikit.widget.webview.bridge.BridgeWebView import java.util.UUID import kotlin.coroutines.resume +import kotlin.math.abs class DAppScreen: BaseFragment(R.layout.fragment_dapp) { @@ -73,12 +75,13 @@ class DAppScreen: BaseFragment(R.layout.fragment_dapp) { private val webViewCallback = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + val refererUri = request.requestHeaders["Referer"]?.toUri() val url = request.url.normalizeTONSites() if (url.scheme != "https") { navigation?.openURL(url.toString(), true) return true } - return rootViewModel.processDeepLink(url, false) + return rootViewModel.processDeepLink(url, false, refererUri) } override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { @@ -138,7 +141,7 @@ class DAppScreen: BaseFragment(R.layout.fragment_dapp) { webView.jsBridge = DAppBridge( send = { rootViewModel.tonconnectBridgeEvent(requireContext(), args.url, it) }, - connect = { _, request -> tonConnectAuth(request) }, + connect = { _, request -> tonConnectAuth(webView.url?.toUri(), request) }, restoreConnection = { dAppViewModel.restoreConnection(args.url) }, disconnect = { dAppViewModel.disconnect() } ) @@ -226,6 +229,7 @@ class DAppScreen: BaseFragment(R.layout.fragment_dapp) { } private suspend fun tonConnectAuth( + sourceUri: Uri?, request: DAppPayloadEntity ): String? = suspendCancellableCoroutine { continuation -> val id = UUID.randomUUID().toString() @@ -236,13 +240,14 @@ class DAppScreen: BaseFragment(R.layout.fragment_dapp) { continuation.resume(null) } } - openAuth(id, request) + openAuth(sourceUri, id, request) } - private fun openAuth(id: String, request: DAppPayloadEntity) { + private fun openAuth(sourceUri: Uri?, id: String, request: DAppPayloadEntity) { val entity = DAppRequestEntity( id = id, r = request.toJSON().toString(), + source = sourceUri, ) navigation?.add(TCAuthFragment.newInstance(entity, id, true)) } 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/CollectiblesViewModel.kt index 2e92fcaff..91ed541cf 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/CollectiblesViewModel.kt @@ -1,5 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.collectibles +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tonapps.extensions.MutableEffectFlow @@ -40,7 +41,7 @@ class CollectiblesViewModel( accountRepository.selectedWalletFlow, networkMonitor.isOnlineFlow, settingsRepository.hiddenBalancesFlow, - settingsRepository.nftPrefsChangedFlow + settingsRepository.tokenPrefsChangedFlow, ) { wallet, isOnline, hiddenBalances, _ -> loadItems(wallet, isOnline, hiddenBalances) }.launchIn(viewModelScope) @@ -85,11 +86,11 @@ class CollectiblesViewModel( ): List<Item> { val items = mutableListOf<Item>() for (nft in list) { - val nftPref = settingsRepository.getNftPrefs(wallet.id, nft.address) - if (nftPref.hidden) { + val tokenPref = settingsRepository.getTokenPrefs(wallet.id, nft.address) + if (tokenPref.isHidden) { continue } - if (!nft.isTrusted && nftPref.trust) { + if (!nft.isTrusted && tokenPref.isTrust) { items.add(Item.Nft(nft.copy(isTrusted = true), hiddenBalances)) } else { items.add(Item.Nft(nft, hiddenBalances)) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/country/CountryPickerViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/country/CountryPickerViewModel.kt index 67a1cbd9e..ef4b92fd0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/country/CountryPickerViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/country/CountryPickerViewModel.kt @@ -138,8 +138,14 @@ class CountryPickerViewModel( api.resolveCountry()?.let { list.add(it) } - list.add(settingsRepository.country) - list.add(context.locale.country) + val country = settingsRepository.country + if (country.isNotBlank()) { + list.add(country) + } + val langCountry = settingsRepository.getLocale().country + if (langCountry.isNotBlank()) { + list.add(langCountry) + } return list.distinct() } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/EventsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/EventsViewModel.kt index ebd19adf1..e122a9810 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/EventsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/events/EventsViewModel.kt @@ -148,7 +148,7 @@ class EventsViewModel( iconUri = Uri.parse(manifest.iconUrl), title = manifest.name, body = event.message, - date = DateHelper.timestampToDateString(event.dateUnix), + date = DateHelper.timestampToDateString(event.dateUnix, settingsRepository.getLocale()), timestamp = event.dateUnix, deepLink = event.link, host = manifest.host diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt index 01397c786..6bbb339f6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitArgs.kt @@ -32,6 +32,9 @@ data class InitArgs( private const val ARG_ACCOUNTS = "accounts" } + val labelName: String? + get() = name ?: ledgerConnectData?.model?.productName + constructor(bundle: Bundle) : this( type = bundle.getEnum(ARG_TYPE, Type.New), name = bundle.getString(ARG_NAME), diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitRoute.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitRoute.kt index 57993dda3..d327aac7e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitRoute.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitRoute.kt @@ -7,5 +7,5 @@ sealed class InitRoute { data object WatchAccount: InitRoute() data object LabelAccount: InitRoute() data object SelectAccount: InitRoute() - data object Push: InitRoute() + // data object Push: InitRoute() } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitScreen.kt index 5398bfe8e..f76383c3a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitScreen.kt @@ -1,17 +1,16 @@ package com.tonapps.tonkeeper.ui.screen.init import android.os.Bundle -import android.util.Log import android.view.View import androidx.core.view.doOnLayout import com.tonapps.ledger.ton.LedgerConnectData import com.tonapps.tonkeeper.ui.screen.init.list.AccountItem import com.tonapps.tonkeeper.ui.screen.init.step.LabelScreen import com.tonapps.tonkeeper.ui.screen.init.step.PasscodeScreen -import com.tonapps.tonkeeper.ui.screen.init.step.PushScreen import com.tonapps.tonkeeper.ui.screen.init.step.SelectScreen import com.tonapps.tonkeeper.ui.screen.init.step.WatchScreen import com.tonapps.tonkeeper.ui.screen.init.step.WordsScreen +import com.tonapps.tonkeeper.ui.screen.notifications.enable.NotificationsEnableScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.backgroundPageColor import org.koin.androidx.viewmodel.ext.android.viewModel @@ -21,6 +20,7 @@ import uikit.base.BaseFragment import uikit.extensions.collectFlow import uikit.extensions.runAnimation import uikit.extensions.withAlpha +import uikit.navigation.Navigation.Companion.navigation import uikit.widget.HeaderView class InitScreen: BaseFragment(R.layout.fragment_init), BaseFragment.SwipeBack { @@ -29,7 +29,7 @@ class InitScreen: BaseFragment(R.layout.fragment_init), BaseFragment.SwipeBack { InitArgs(requireArguments()) } - private val initViewModel: InitViewModel by viewModel { parametersOf(args.type) } + private val initViewModel: InitViewModel by viewModel { parametersOf(args) } private lateinit var headerView: HeaderView private lateinit var loaderContainerView: View @@ -37,15 +37,9 @@ class InitScreen: BaseFragment(R.layout.fragment_init), BaseFragment.SwipeBack { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - args.name?.let { + args.labelName?.let { initViewModel.setLabelName(it) } - args.publicKeyEd25519?.let { - initViewModel.setPublicKey(it) - } - args.ledgerConnectData?.let { - initViewModel.setLedgerConnectData(it) - } args.accounts?.let { initViewModel.setAccounts(it) } @@ -69,10 +63,12 @@ class InitScreen: BaseFragment(R.layout.fragment_init), BaseFragment.SwipeBack { } private fun onEvent(event: InitEvent) { - Log.d("InitViewModelLog", "onEvent: $event") when (event) { is InitEvent.Back -> popBackStack() - is InitEvent.Finish -> finish() + is InitEvent.Finish -> { + navigation?.add(NotificationsEnableScreen.newInstance()) + finish() + } is InitEvent.Loading -> setLoading(event.loading) } } @@ -85,7 +81,6 @@ class InitScreen: BaseFragment(R.layout.fragment_init), BaseFragment.SwipeBack { InitRoute.WatchAccount -> WatchScreen.newInstance() InitRoute.LabelAccount -> LabelScreen.newInstance() InitRoute.SelectAccount -> SelectScreen.newInstance() - InitRoute.Push -> PushScreen.newInstance() } val transaction = childFragmentManager.beginTransaction() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt index cfd26aa45..84db3d113 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/InitViewModel.kt @@ -1,12 +1,8 @@ package com.tonapps.tonkeeper.ui.screen.init -import android.Manifest import android.app.Application import android.content.Context -import android.content.pm.PackageManager -import android.os.Build import android.util.Log -import androidx.core.content.ContextCompat import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope @@ -57,13 +53,13 @@ import org.ton.api.pk.PrivateKeyEd25519 import org.ton.api.pub.PublicKeyEd25519 import org.ton.block.AddrStd import org.ton.mnemonic.Mnemonic -import uikit.extensions.context import uikit.navigation.Navigation +import kotlin.properties.Delegates @OptIn(FlowPreview::class) class InitViewModel( private val scope: CoroutineScope, - private val type: InitArgs.Type, + args: InitArgs, application: Application, private val passcodeManager: PasscodeManager, private val accountRepository: AccountRepository, @@ -74,8 +70,8 @@ class InitViewModel( savedStateHandle: SavedStateHandle ): AndroidViewModel(application) { - private val passcodeAfterSeed = false // type == InitArgs.Type.Import || type == InitArgs.Type.Testnet private val savedState = InitModelState(savedStateHandle) + private val type = args.type private val testnet: Boolean = type == InitArgs.Type.Testnet private val tonNetwork: TonNetwork @@ -95,7 +91,7 @@ class InitViewModel( .debounce(1000) .filter { it.isNotBlank() } .map { - val account = api.resolveAddressOrName(it, testnet) + val account = api.resolveAddressOrName(it.lowercase(), testnet) if (account == null || !account.active) { setWatchAccount(null) return@map null @@ -107,24 +103,22 @@ class InitViewModel( private val _accountsFlow = MutableEffectFlow<List<AccountItem>?>() val accountsFlow = _accountsFlow.asSharedFlow().filterNotNull() - private val hasPushPermission: Boolean - get() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return ContextCompat.checkSelfPermission( - context, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } - return true - } + private var hasPinCode by Delegates.notNull<Boolean>() init { - viewModelScope.launch { - if (!passcodeManager.hasPinCode()) { - routeTo(InitRoute.CreatePasscode) - } else { - startWalletFlow() - } + savedState.publicKey = args.publicKeyEd25519 + savedState.ledgerConnectData = args.ledgerConnectData + + when (type) { + InitArgs.Type.Watch -> routeTo(InitRoute.WatchAccount) + InitArgs.Type.New -> routeTo(InitRoute.LabelAccount) + InitArgs.Type.Import, InitArgs.Type.Testnet -> routeTo(InitRoute.ImportWords) + InitArgs.Type.Signer, InitArgs.Type.SignerQR -> withLoading { resolveWallets(savedState.publicKey!!) } + InitArgs.Type.Ledger -> routeTo(InitRoute.SelectAccount) + } + + viewModelScope.launch(Dispatchers.IO) { + hasPinCode = passcodeManager.hasPinCode() } } @@ -132,32 +126,31 @@ class InitViewModel( _routeFlow.tryEmit(route) } - fun toggleAccountSelection(address: String) { + fun toggleAccountSelection(address: String): Boolean { val items = getAccounts().toMutableList() - val oldItem = items.find { it.address.toRawAddress() == address } ?: return + val index = items.indexOfFirst { it.address.toRawAddress() == address } + if (index == -1) { + return false + } + val oldItem = items[index] val newItem = oldItem.copy(selected = !oldItem.selected) - items[items.indexOf(oldItem)] = newItem - setAccounts(items) + items[index] = newItem + setAccounts(items.toList()) + return true } - private fun startWalletFlow() { - if (type == InitArgs.Type.Watch) { - routeTo(InitRoute.WatchAccount) - } else if (type == InitArgs.Type.New) { - routeTo(InitRoute.LabelAccount) - } else if (type == InitArgs.Type.Import || type == InitArgs.Type.Testnet) { - routeTo(InitRoute.ImportWords) - } else if (type == InitArgs.Type.Signer || type == InitArgs.Type.SignerQR) { - _eventFlow.tryEmit(InitEvent.Loading(true)) - scope.launch(Dispatchers.IO) { - resolveWallets(savedState.publicKey!!) - _eventFlow.tryEmit(InitEvent.Loading(false)) - } - } else if (type == InitArgs.Type.Ledger) { - routeTo(InitRoute.SelectAccount) + private fun withLoading(block: suspend () -> Unit) { + setLoading(true) + scope.launch(Dispatchers.IO) { + block() + setLoading(false) } } + private fun setLoading(loading: Boolean) { + _eventFlow.tryEmit(InitEvent.Loading(loading)) + } + fun setUiTopOffset(offset: Int) { _uiTopOffset.value = offset } @@ -176,13 +169,13 @@ class InitViewModel( routeTo(InitRoute.ReEnterPasscode) } - fun reEnterPasscode(passcode: String) { + fun reEnterPasscode(context: Context, passcode: String) { val valid = savedState.passcode == passcode if (!valid) { routePopBackStack() - return + } else { + execute(context) } - startWalletFlow() } suspend fun setMnemonic(words: List<String>) { @@ -190,15 +183,6 @@ class InitViewModel( resolveWallets(words) } - fun setPublicKey(publicKey: PublicKeyEd25519?) { - savedState.publicKey = publicKey - } - - fun setLedgerConnectData(connectData: LedgerConnectData) { - savedState.ledgerConnectData = connectData - setLabelName(connectData.model.productName) - } - private suspend fun resolveWallets(mnemonic: List<String>) = withContext(Dispatchers.IO) { val seed = Mnemonic.toSeed(mnemonic) val privateKey = PrivateKeyEd25519(seed) @@ -207,33 +191,29 @@ class InitViewModel( } private suspend fun resolveWallets(publicKey: PublicKeyEd25519) = withContext(Dispatchers.IO) { - try { - val accounts = api.resolvePublicKey(publicKey, testnet).filter { - it.isWallet && it.walletVersion != WalletVersion.UNKNOWN && it.active - }.sortedByDescending { it.walletVersion.index }.toMutableList() - - if (accounts.count { it.walletVersion == WalletVersion.V5R1 } == 0) { - val contract = WalletV5R1Contract(publicKey, tonNetwork) - accounts.add(0, AccountDetailsEntity(contract, testnet)) - } + val accounts = api.resolvePublicKey(publicKey, testnet).filter { + it.isWallet && it.walletVersion != WalletVersion.UNKNOWN && it.active + }.sortedByDescending { it.walletVersion.index }.toMutableList() - val list = accounts.mapIndexed { index, account -> - getAccountItem(account, ListCell.getPosition(accounts.size, index)) - } + if (accounts.count { it.walletVersion == WalletVersion.V5R1 } == 0) { + val contract = WalletV5R1Contract(publicKey, tonNetwork) + accounts.add(0, AccountDetailsEntity(contract, testnet)) + } - val items = mutableListOf<AccountItem>() - for (account in list) { - items.add(account) - } - setAccounts(items) + val list = accounts.mapIndexed { index, account -> + getAccountItem(account, ListCell.getPosition(accounts.size, index)) + } - if (items.size > 1) { - routeTo(InitRoute.SelectAccount) - } else { - routeTo(InitRoute.LabelAccount) - } - } catch (e: Throwable) { - Log.e("InitViewModelLog", "error", e) + val items = mutableListOf<AccountItem>() + for (account in list) { + items.add(account) + } + setAccounts(items) + + if (items.size > 1) { + routeTo(InitRoute.SelectAccount) + } else { + routeTo(InitRoute.LabelAccount) } } @@ -313,24 +293,16 @@ class InitViewModel( )) } - fun setPush(context: Context) { - nextStep(context, InitRoute.Push) - } - fun nextStep(context: Context, from: InitRoute) { if (from == InitRoute.WatchAccount) { routeTo(InitRoute.LabelAccount) } else if (from == InitRoute.SelectAccount) { applyNameFromSelectedAccounts() routeTo(InitRoute.LabelAccount) - } else if (from == InitRoute.Push) { + } else if (hasPinCode) { execute(context) - } else if (!hasPushPermission) { - routeTo(InitRoute.Push) - } else if (passcodeAfterSeed && from == InitRoute.ImportWords) { - routeTo(InitRoute.CreatePasscode) } else { - execute(context) + routeTo(InitRoute.CreatePasscode) } } @@ -344,7 +316,7 @@ class InitViewModel( } private fun execute(context: Context) { - _eventFlow.tryEmit(InitEvent.Loading(true)) + setLoading(true) viewModelScope.launch(Dispatchers.IO) { try { @@ -372,11 +344,12 @@ class InitViewModel( settingsRepository.setPushWallet(wallet.id, true) } - accountRepository.setSelectedWallet(wallets.first().id) + val selectedWalletId = wallets.minByOrNull { it.version }!!.id + accountRepository.setSelectedWallet(selectedWalletId) _eventFlow.tryEmit(InitEvent.Finish) } catch (e: Throwable) { - _eventFlow.tryEmit(InitEvent.Loading(false)) + setLoading(false) Navigation.from(context)?.toast(e.message ?: "Error") } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Adapter.kt index d0b309164..6828aac8d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Adapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Adapter.kt @@ -6,7 +6,7 @@ import com.tonapps.uikit.list.BaseListAdapter import com.tonapps.uikit.list.BaseListHolder import com.tonapps.uikit.list.BaseListItem -class Adapter(private val onClick: (AccountItem) -> Unit): BaseListAdapter() { +class Adapter(private val onClick: (AccountItem) -> Boolean): BaseListAdapter() { override fun createHolder(parent: ViewGroup, viewType: Int): BaseListHolder<out BaseListItem> { return Holder(parent, onClick) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Holder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Holder.kt index 39350eaa4..6ca7c755e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Holder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/list/Holder.kt @@ -13,12 +13,13 @@ import com.tonapps.uikit.color.textTertiaryColor import com.tonapps.uikit.list.BaseListHolder import com.tonapps.wallet.localization.Localization import uikit.extensions.drawable +import uikit.extensions.reject import uikit.extensions.setColor import uikit.widget.CheckBoxView class Holder( parent: ViewGroup, - private val onClick: (AccountItem) -> Unit + private val onClick: (AccountItem) -> Boolean ): BaseListHolder<AccountItem>(parent, R.layout.view_select_wallet) { private val addressView = findViewById<AppCompatTextView>(R.id.address) @@ -27,7 +28,12 @@ class Holder( override fun onBind(item: AccountItem) { itemView.background = item.position.drawable(context) - itemView.setOnClickListener { onClick(item) } + itemView.setOnClickListener { + if (!onClick(item)) { + itemView.reject() + } + } + itemView.isEnabled = !item.ledgerAdded addressView.text = item.address.shortAddress selectedView.checked = item.selected diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PasscodeScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PasscodeScreen.kt index 21f4edad8..ac53b74a5 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PasscodeScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PasscodeScreen.kt @@ -43,7 +43,7 @@ class PasscodeScreen: BaseFragment(R.layout.fragment_init_passcode) { private fun setPasscode(code: String) { if (reEnter) { - initViewModel.reEnterPasscode(code) + initViewModel.reEnterPasscode(requireContext(), code) } else { initViewModel.setPasscode(code) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PushScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PushScreen.kt deleted file mode 100644 index af968e752..000000000 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/init/step/PushScreen.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tonapps.tonkeeper.ui.screen.init.step - -import android.Manifest -import android.os.Build -import android.os.Bundle -import android.view.View -import android.widget.Button -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.content.ContextCompat -import com.tonapps.tonkeeper.ui.screen.init.InitViewModel -import com.tonapps.tonkeeperx.R -import org.koin.androidx.viewmodel.ext.android.viewModel -import uikit.base.BaseFragment -import uikit.extensions.pinToBottomInsets - -class PushScreen: BaseFragment(R.layout.fragment_init_push) { - - private val initViewModel: InitViewModel by viewModel(ownerProducer = { requireParentFragment() }) - - private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - initViewModel.setPush(requireContext()) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val button = view.findViewById<Button>(R.id.button) - button.setOnClickListener { requestPermission() } - button.pinToBottomInsets() - } - - private fun requestPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPermission(Manifest.permission.POST_NOTIFICATIONS)) { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) - } else { - initViewModel.setPush(requireContext()) - } - } - - companion object { - fun newInstance() = PushScreen() - } -} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt index c3c0145b8..aaccfb1ed 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftScreen.kt @@ -3,7 +3,6 @@ package com.tonapps.tonkeeper.ui.screen.nft import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle -import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.Button @@ -15,22 +14,15 @@ import com.tonapps.tonkeeper.extensions.copyWithToast import com.tonapps.tonkeeper.koin.remoteConfig import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppArgs import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppScreen -import com.tonapps.tonkeeper.ui.screen.send.SendScreen -import com.tonapps.tonkeeper.ui.screen.token.unverified.TokenUnverifiedScreen -import com.tonapps.tonkeeper.ui.screen.web.WebScreen +import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentBlueColor import com.tonapps.uikit.color.accentOrangeColor import com.tonapps.uikit.color.accentRedColor -import com.tonapps.uikit.color.buttonGreenBackgroundColor -import com.tonapps.uikit.color.stateList import com.tonapps.uikit.icon.UIKitIcon import com.tonapps.uikit.list.ListCell -import com.tonapps.wallet.api.API import com.tonapps.wallet.data.collectibles.entities.NftEntity -import com.tonapps.wallet.data.settings.SettingsRepository import com.tonapps.wallet.localization.Localization -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import uikit.base.BaseFragment diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt index 3fa69b03e..c4a73f749 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/nft/NftViewModel.kt @@ -6,6 +6,7 @@ import com.tonapps.wallet.api.API import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.data.settings.SettingsRepository +import com.tonapps.wallet.data.settings.entities.TokenPrefsEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -28,7 +29,7 @@ class NftViewModel( val trustFlow = _trustFlow.asStateFlow().filterNotNull() private val prefFlow = accountRepository.selectedWalletFlow.take(1).map { wallet -> - settingsRepository.getNftPrefs(wallet.id, nft.address) + settingsRepository.getTokenPrefs(wallet.id, nft.address) } init { @@ -36,17 +37,14 @@ class NftViewModel( _trustFlow.value = true } else { collectFlow(prefFlow) { pref -> - _trustFlow.value = pref.trust + _trustFlow.value = pref.isTrust } } } fun reportSpam(spam: Boolean) = accountRepository.selectedWalletFlow.take(1).onEach { wallet -> - if (spam) { - settingsRepository.setNftHidden(wallet.id, nft.address) - } else { - settingsRepository.setNftTrust(wallet.id, nft.address) - } + val state = if (spam) TokenPrefsEntity.State.SPAM else TokenPrefsEntity.State.TRUST + settingsRepository.setTokenState(wallet.id, nft.address, state) api.reportNtfSpam(nft.address, spam) } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableScreen.kt new file mode 100644 index 000000000..81a912d18 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableScreen.kt @@ -0,0 +1,69 @@ +package com.tonapps.tonkeeper.ui.screen.notifications.enable + +import android.Manifest +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams +import com.tonapps.tonkeeperx.R +import org.koin.androidx.viewmodel.ext.android.viewModel +import uikit.base.BaseFragment +import uikit.extensions.collectFlow +import uikit.extensions.getDimensionPixelSize + +class NotificationsEnableScreen: BaseFragment(R.layout.fragment_notifications_enable) { + + private val notificationsEnableViewModel: NotificationsEnableViewModel by viewModel() + + private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + enablePush() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val laterView = view.findViewById<View>(R.id.later) + laterView.setOnClickListener { finish() } + + val button = view.findViewById<Button>(R.id.button) + button.setOnClickListener { requestPermission() } + + val offsetMedium = requireContext().getDimensionPixelSize(uikit.R.dimen.offsetMedium) + + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars() + WindowInsetsCompat.Type.navigationBars()) + laterView.updateLayoutParams<ViewGroup.MarginLayoutParams> { + topMargin = systemBarInsets.top + offsetMedium + } + + button.updateLayoutParams<ViewGroup.MarginLayoutParams> { + bottomMargin = systemBarInsets.bottom + offsetMedium + } + insets + } + } + + private fun requestPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPermission(Manifest.permission.POST_NOTIFICATIONS)) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + enablePush() + } + } + + private fun enablePush() { + collectFlow(notificationsEnableViewModel.enablePush()) { + finish() + } + } + + companion object { + fun newInstance() = NotificationsEnableScreen() + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableViewModel.kt new file mode 100644 index 000000000..53977632f --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/enable/NotificationsEnableViewModel.kt @@ -0,0 +1,17 @@ +package com.tonapps.tonkeeper.ui.screen.notifications.enable + +import androidx.lifecycle.ViewModel +import com.tonapps.wallet.data.account.AccountRepository +import com.tonapps.wallet.data.settings.SettingsRepository +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.take + +class NotificationsEnableViewModel( + private val accountRepository: AccountRepository, + private val settingsRepository: SettingsRepository +): ViewModel() { + + fun enablePush() = accountRepository.selectedWalletFlow.take(1).map { wallet -> + settingsRepository.setPushWallet(wallet.id, true) + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/NotificationsAdapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/NotificationsAdapter.kt deleted file mode 100644 index 524c516a1..000000000 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/NotificationsAdapter.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list - -class NotificationsAdapter { -} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageScreen.kt similarity index 63% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsScreen.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageScreen.kt index 66a2073a2..e22764793 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageScreen.kt @@ -1,8 +1,8 @@ -package com.tonapps.tonkeeper.ui.screen.notifications +package com.tonapps.tonkeeper.ui.screen.notifications.manage import android.os.Bundle import android.view.View -import com.tonapps.tonkeeper.ui.screen.notifications.list.Adapter +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Adapter import com.tonapps.tonkeeperx.R import org.koin.androidx.viewmodel.ext.android.viewModel import uikit.base.BaseFragment @@ -10,14 +10,14 @@ import uikit.extensions.collectFlow import uikit.widget.HeaderView import uikit.widget.SimpleRecyclerView -class NotificationsScreen: BaseFragment(R.layout.fragment_notifications), BaseFragment.SwipeBack { +class NotificationsManageScreen: BaseFragment(R.layout.fragment_notifications_manage), BaseFragment.SwipeBack { - private val notificationsViewModel: NotificationsViewModel by viewModel() + private val notificationsManageViewModel: NotificationsManageViewModel by viewModel() private val adapter = Adapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - collectFlow(notificationsViewModel.uiItemsFlow, adapter::submitList) + collectFlow(notificationsManageViewModel.uiItemsFlow, adapter::submitList) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -30,6 +30,6 @@ class NotificationsScreen: BaseFragment(R.layout.fragment_notifications), BaseFr } companion object { - fun newInstance() = NotificationsScreen() + fun newInstance() = NotificationsManageScreen() } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageViewModel.kt similarity index 86% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsViewModel.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageViewModel.kt index 9c2285ba4..2803ab236 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/NotificationsViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/NotificationsManageViewModel.kt @@ -1,8 +1,8 @@ -package com.tonapps.tonkeeper.ui.screen.notifications +package com.tonapps.tonkeeper.ui.screen.notifications.manage import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.account.AccountRepository import com.tonapps.wallet.data.settings.SettingsRepository @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.take -class NotificationsViewModel( +class NotificationsManageViewModel( private val accountRepository: AccountRepository, private val tonConnectRepository: TonConnectRepository, private val settingsRepository: SettingsRepository, @@ -32,12 +32,13 @@ class NotificationsViewModel( ) { wallet, apps -> val myApps = apps.filter { it.walletId == wallet.id } val uiItems = mutableListOf<Item>() - uiItems.add(Item.Wallet( + uiItems.add( + Item.Wallet( pushEnabled = settingsRepository.getPushWallet(wallet.id), walletId = wallet.id )) uiItems.add(Item.Space) - /*if (myApps.isNotEmpty()) { + if (myApps.isNotEmpty()) { uiItems.add(Item.AppsHeader) uiItems.add(Item.Space) for ((index, app) in myApps.withIndex()) { @@ -45,7 +46,7 @@ class NotificationsViewModel( uiItems.add(Item.App(app, position)) } uiItems.add(Item.Space) - }*/ + } _uiItemsFlow.value = uiItems }.flowOn(Dispatchers.IO).launchIn(viewModelScope) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Adapter.kt similarity index 61% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Adapter.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Adapter.kt index 6209574e1..4a80b3cdb 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Adapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Adapter.kt @@ -1,10 +1,10 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list import android.view.ViewGroup -import com.tonapps.tonkeeper.ui.screen.notifications.list.holder.AppHolder -import com.tonapps.tonkeeper.ui.screen.notifications.list.holder.AppsHeaderHolder -import com.tonapps.tonkeeper.ui.screen.notifications.list.holder.SpaceHolder -import com.tonapps.tonkeeper.ui.screen.notifications.list.holder.WalletPushHolder +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder.AppHolder +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder.AppsHeaderHolder +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder.SpaceHolder +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder.WalletPushHolder import com.tonapps.uikit.list.BaseListAdapter import com.tonapps.uikit.list.BaseListHolder import com.tonapps.uikit.list.BaseListItem diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Item.kt similarity index 82% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Item.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Item.kt index 7c87bfad4..0fcc4e2df 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/Item.kt @@ -1,8 +1,7 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list import com.tonapps.uikit.list.BaseListItem import com.tonapps.uikit.list.ListCell -import com.tonapps.wallet.data.tonconnect.entities.DAppManifestEntity import com.tonapps.wallet.data.tonconnect.entities.DConnectEntity sealed class Item(type: Int): BaseListItem(type) { @@ -35,12 +34,11 @@ sealed class Item(type: Int): BaseListItem(type) { constructor( app: DConnectEntity, - manifest: DAppManifestEntity, position: ListCell.Position ) : this( id = app.uniqueId, - name = manifest.name, - icon = manifest.iconUrl, + name = app.manifest.name, + icon = app.manifest.iconUrl, pushEnabled = app.enablePush, position = position, walletId = app.walletId, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/NotificationsAdapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/NotificationsAdapter.kt new file mode 100644 index 000000000..db30bb847 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/NotificationsAdapter.kt @@ -0,0 +1,4 @@ +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list + +class NotificationsAdapter { +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppHolder.kt similarity index 89% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppHolder.kt index e36803bb8..783683705 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppHolder.kt @@ -1,9 +1,9 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list.holder +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder import android.view.ViewGroup import androidx.appcompat.widget.AppCompatTextView import com.tonapps.tonkeeper.koin.tonConnectRepository -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item import com.tonapps.tonkeeperx.R import com.tonapps.wallet.data.tonconnect.TonConnectRepository import uikit.extensions.drawable diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppsHeaderHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppsHeaderHolder.kt similarity index 62% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppsHeaderHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppsHeaderHolder.kt index 3813c29de..cb0c215fc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/AppsHeaderHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/AppsHeaderHolder.kt @@ -1,7 +1,7 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list.holder +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder import android.view.ViewGroup -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item import com.tonapps.tonkeeperx.R class AppsHeaderHolder(parent: ViewGroup): Holder<Item.AppsHeader>(parent, R.layout.view_notifications_apps_header) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/Holder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/Holder.kt new file mode 100644 index 000000000..bdc9fc146 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/Holder.kt @@ -0,0 +1,11 @@ +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder + +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item +import com.tonapps.uikit.list.BaseListHolder + +abstract class Holder<I: Item>( + parent: ViewGroup, + @LayoutRes resId: Int, +): BaseListHolder<I>(parent, resId) \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/SpaceHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/SpaceHolder.kt similarity index 59% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/SpaceHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/SpaceHolder.kt index c469ece80..cffc1783a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/SpaceHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/SpaceHolder.kt @@ -1,7 +1,7 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list.holder +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder import android.view.ViewGroup -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item import com.tonapps.tonkeeperx.R class SpaceHolder(parent: ViewGroup): Holder<Item.Space>(parent, R.layout.view_wallet_space) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/WalletPushHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/WalletPushHolder.kt similarity index 87% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/WalletPushHolder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/WalletPushHolder.kt index d3af9a33c..938cccbaf 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/WalletPushHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/manage/list/holder/WalletPushHolder.kt @@ -1,8 +1,8 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list.holder +package com.tonapps.tonkeeper.ui.screen.notifications.manage.list.holder import android.view.ViewGroup import com.tonapps.tonkeeper.koin.settingsRepository -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.notifications.manage.list.Item import com.tonapps.tonkeeperx.R import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.settings.SettingsRepository diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/main/PurchaseViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/main/PurchaseViewModel.kt index 7715e5580..87d1bb532 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/main/PurchaseViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/purchase/main/PurchaseViewModel.kt @@ -1,8 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.purchase.main import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tonapps.extensions.MutableEffectFlow import com.tonapps.tonkeeper.ui.screen.purchase.main.list.Item import com.tonapps.uikit.list.ListCell import com.tonapps.wallet.data.account.AccountRepository @@ -12,13 +10,10 @@ import com.tonapps.wallet.data.purchase.entity.PurchaseMethodEntity import com.tonapps.wallet.data.settings.SettingsRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take class PurchaseViewModel( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt index b25954ef4..df1745471 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootActivity.kt @@ -17,9 +17,10 @@ import com.tonapps.tonkeeper.ui.screen.backup.main.BackupScreen import com.tonapps.tonkeeper.ui.screen.init.InitArgs import com.tonapps.tonkeeper.ui.screen.init.InitScreen import com.tonapps.tonkeeper.ui.screen.main.MainScreen +import com.tonapps.tonkeeper.ui.screen.notifications.enable.NotificationsEnableScreen import com.tonapps.tonkeeper.ui.screen.purchase.main.PurchaseScreen import com.tonapps.tonkeeper.ui.screen.purchase.web.PurchaseWebScreen -import com.tonapps.tonkeeper.ui.screen.send.SendScreen +import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeper.ui.screen.staking.stake.StakingScreen import com.tonapps.tonkeeper.ui.screen.staking.viewer.StakeViewerScreen import com.tonapps.tonkeeper.ui.screen.start.StartScreen @@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.onEach import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import uikit.dialog.alert.AlertDialog +import uikit.extensions.activity import uikit.extensions.collectFlow import uikit.extensions.findFragment import uikit.navigation.Navigation.Companion.navigation @@ -177,7 +179,8 @@ class RootActivity: NavigationActivity() { ) { val fragment = supportFragmentManager.findFragment<SendScreen>() if (fragment == null) { - add(SendScreen.newInstance( + add( + SendScreen.newInstance( targetAddress = targetAddress, tokenAddress = tokenAddress, amountNano = amountNano, @@ -229,7 +232,7 @@ class RootActivity: NavigationActivity() { private fun handleIntent(intent: Intent) { val uri = intent.data ?: return - processDeepLink(uri, false) + processDeepLink(uri) } override fun openURL(url: String, external: Boolean) { @@ -249,11 +252,11 @@ class RootActivity: NavigationActivity() { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) } else { - processDeepLink(uri, false) + processDeepLink(uri) } } - private fun processDeepLink(uri: Uri, fromQR: Boolean) { - rootViewModel.processDeepLink(uri, fromQR) + private fun processDeepLink(uri: Uri) { + rootViewModel.processDeepLink(uri, false, getReferrer()) } } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt index a5acb023c..e36fe58e9 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/root/RootViewModel.kt @@ -161,6 +161,12 @@ class RootViewModel( AnalyticsHelper.setConfig(context, config) AnalyticsHelper.trackEvent("launch_app") } + + settingsRepository.countryFlow.take(1).filter { it.isBlank() }.map { + api.resolveCountry() + }.filterNotNull().onEach { + settingsRepository.country = it + }.flowOn(Dispatchers.IO).launchIn(viewModelScope) } private suspend fun initShortcuts( @@ -296,15 +302,15 @@ class RootViewModel( } }.take(1) - fun processDeepLink(uri: Uri, fromQR: Boolean): Boolean { + fun processDeepLink(uri: Uri, fromQR: Boolean, refSource: Uri?): Boolean { if (DeepLink.isSupportedUri(uri)) { - resolveDeepLink(uri, fromQR) + resolveDeepLink(uri, fromQR, refSource) return true } return false } - private fun resolveDeepLink(uri: Uri, fromQR: Boolean) { + private fun resolveDeepLink(uri: Uri, fromQR: Boolean, refSource: Uri?) { if (uri.host == "signer") { collectFlow(hasWalletFlow.take(1)) { delay(1000) @@ -314,7 +320,7 @@ class RootViewModel( } accountRepository.selectedWalletFlow.take(1).onEach { wallet -> delay(1000) - resolveOther(uri, wallet) + resolveOther(refSource, uri, wallet) }.launchIn(viewModelScope) } @@ -327,13 +333,17 @@ class RootViewModel( } } - private fun resolveOther(_uri: Uri, wallet: WalletEntity) { + private fun resolveOther( + refSource: Uri?, + _uri: Uri, + wallet: WalletEntity + ) { val url = _uri.toString().replace("ton://", "https://app.tonkeeper.com/").replace("tonkeeper://", "https://app.tonkeeper.com/") val uri = Uri.parse(url) val path = uri.path if (DeepLink.isTonConnectUri(uri)) { - resolveTonConnect(uri, wallet) + resolveTonConnect(uri, wallet, refSource) } else if (MainScreen.isSupportedDeepLink(url) || MainScreen.isSupportedDeepLink(_uri.toString())) { _eventFlow.tryEmit(RootEvent.OpenTab(_uri.toString())) } else if (path?.startsWith("/staking") == true) { @@ -398,14 +408,15 @@ class RootViewModel( private fun resolveTonConnect( uri: Uri, - wallet: WalletEntity + wallet: WalletEntity, + source: Uri? ) { try { if (!wallet.hasPrivateKey && !wallet.isLedger) { toast(Localization.not_supported) return } - val request = DAppRequestEntity(uri) + val request = DAppRequestEntity(source, uri) _eventFlow.tryEmit(RootEvent.TonConnect(request)) } catch (e: Throwable) { toast(Localization.invalid_link) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsScreen.kt new file mode 100644 index 000000000..a69a36c11 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsScreen.kt @@ -0,0 +1,55 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts + +import android.os.Bundle +import android.view.View +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Adapter +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import com.tonapps.tonkeeper.ui.screen.send.main.SendContact +import com.tonapps.wallet.localization.Localization +import org.koin.androidx.viewmodel.ext.android.viewModel +import uikit.base.BaseFragment +import uikit.base.BaseListFragment +import uikit.extensions.collectFlow +import uikit.navigation.Navigation.Companion.navigation + +class SendContactsScreen: BaseListFragment(), BaseFragment.BottomSheet { + + private val requestKey: String by lazy { arguments?.getString(ARG_REQUEST_KEY)!! } + + private val adapter = Adapter { selectContact(it) } + + private val sendContactsViewModel: SendContactsViewModel by viewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setTitle(getString(Localization.contacts)) + setAdapter(adapter) + collectFlow(sendContactsViewModel.uiItemsFlow, adapter::submitList) + } + + private fun selectContact(item: Item) { + val contact = when (item) { + is Item.LatestContact -> SendContact(SendContact.CONTACT_TYPE, item.userFriendlyAddress) + is Item.MyWallet -> SendContact(SendContact.MY_WALLET_TYPE, item.userFriendlyAddress) + else -> return + } + + val bundle = Bundle() + bundle.putParcelable("contact", contact) + navigation?.setFragmentResult(requestKey, bundle) + finish() + } + + companion object { + + private const val ARG_REQUEST_KEY = "request_key" + + fun newInstance(requestKey: String): SendContactsScreen { + val screen = SendContactsScreen() + screen.arguments = Bundle().apply { + putString(ARG_REQUEST_KEY, requestKey) + } + return screen + } + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsViewModel.kt new file mode 100644 index 000000000..dc0380ee2 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/SendContactsViewModel.kt @@ -0,0 +1,55 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.tonapps.blockchain.ton.extensions.toRawAddress +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import com.tonapps.uikit.list.ListCell +import com.tonapps.wallet.data.account.AccountRepository +import com.tonapps.wallet.data.account.entities.WalletEntity +import com.tonapps.wallet.data.events.EventsRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +class SendContactsViewModel( + private val accountRepository: AccountRepository, + private val eventsRepository: EventsRepository, +): ViewModel() { + + val uiItemsFlow = accountRepository.selectedWalletFlow.map { currentWallet -> + val myWallets = getMyWallets(currentWallet) + val latestContacts = getLatestContacts(currentWallet) + myWallets + Item.Space + latestContacts + }.flowOn(Dispatchers.IO) + + private suspend fun getMyWallets(currentWallet: WalletEntity): List<Item.MyWallet> { + val wallets = accountRepository.getWallets().filter { + it.address != currentWallet.address + } + + return wallets.mapIndexed { index, wallet -> + val position = ListCell.getPosition(wallets.size, index) + Item.MyWallet(position, wallet) + } + } + + private suspend fun getLatestContacts(currentWallet: WalletEntity): List<Item.LatestContact> { + val accountEvents = eventsRepository.get(currentWallet.accountId, currentWallet.testnet) ?: return emptyList() + val actions = accountEvents.events.map { it.actions }.flatten() + val accounts = actions.map { it.simplePreview } + .map { it.accounts } + .flatten() + .distinctBy { it.address } + + val latestAccounts = accounts.filter { + it.isWallet && it.address.toRawAddress() != currentWallet.accountId + }.take(6) + + return latestAccounts.mapIndexed { index, account -> + val position = ListCell.getPosition(latestAccounts.size, index) + Item.LatestContact(position, account, currentWallet.testnet) + } + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Adapter.kt new file mode 100644 index 000000000..a0342ea66 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Adapter.kt @@ -0,0 +1,23 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list + +import android.view.ViewGroup +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder.LatestHolder +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder.MyWalletHolder +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder.SpaceHolder +import com.tonapps.uikit.list.BaseListAdapter +import com.tonapps.uikit.list.BaseListHolder +import com.tonapps.uikit.list.BaseListItem + +class Adapter( + private val onClick: (Item) -> Unit +): BaseListAdapter() { + + override fun createHolder(parent: ViewGroup, viewType: Int): BaseListHolder<out BaseListItem> { + return when(viewType) { + Item.TYPE_MY_WALLET -> MyWalletHolder(parent, onClick) + Item.TYPE_SPACE -> SpaceHolder(parent) + Item.TYPE_LATEST_CONTACT -> LatestHolder(parent, onClick) + 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/send/contacts/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Item.kt new file mode 100644 index 000000000..a6761ceae --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/Item.kt @@ -0,0 +1,46 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list + +import com.tonapps.blockchain.ton.extensions.toUserFriendly +import com.tonapps.extensions.short8 +import com.tonapps.uikit.list.BaseListItem +import com.tonapps.uikit.list.ListCell +import com.tonapps.wallet.data.account.entities.WalletEntity +import io.tonapi.models.AccountAddress + +sealed class Item(type: Int): BaseListItem(type) { + + companion object { + const val TYPE_MY_WALLET = 1 + const val TYPE_SPACE = 2 + const val TYPE_LATEST_CONTACT = 3 + } + + data object Space: Item(TYPE_SPACE) + + data class MyWallet( + val position: ListCell.Position, + val wallet: WalletEntity + ): Item(TYPE_MY_WALLET) { + + val emoji: CharSequence + get() = wallet.label.emoji + + val name: String + get() = wallet.label.name + + val userFriendlyAddress: String = wallet.address.toUserFriendly(testnet = wallet.testnet) + } + + data class LatestContact( + val position: ListCell.Position, + val account: AccountAddress, + val testnet: Boolean + ): Item(TYPE_LATEST_CONTACT) { + + val userFriendlyAddress: String = account.address.toUserFriendly(testnet = testnet) + + val name: String + get() = account.name ?: userFriendlyAddress.short8 + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/ContactHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/ContactHolder.kt new file mode 100644 index 000000000..46c674238 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/ContactHolder.kt @@ -0,0 +1,16 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder + +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView +import com.tonapps.emoji.ui.EmojiView +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import com.tonapps.tonkeeperx.R + +abstract class ContactHolder<I: Item>(parent: ViewGroup): Holder<I>(parent, R.layout.view_contact) { + + val emojiView = itemView.findViewById<EmojiView>(R.id.emoji) + val nameView = itemView.findViewById<AppCompatTextView>(R.id.name) + val iconView = itemView.findViewById<AppCompatImageView>(R.id.icon) + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/Holder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/Holder.kt similarity index 64% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/Holder.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/Holder.kt index 5d92376bb..08e07e593 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/notifications/list/holder/Holder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/Holder.kt @@ -1,8 +1,8 @@ -package com.tonapps.tonkeeper.ui.screen.notifications.list.holder +package com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder import android.view.ViewGroup import androidx.annotation.LayoutRes -import com.tonapps.tonkeeper.ui.screen.notifications.list.Item +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item import com.tonapps.uikit.list.BaseListHolder abstract class Holder<I: Item>( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/LatestHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/LatestHolder.kt new file mode 100644 index 000000000..d280966c2 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/LatestHolder.kt @@ -0,0 +1,22 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder + +import android.view.ViewGroup +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import uikit.extensions.drawable + +class LatestHolder( + parent: ViewGroup, + private val onClick: (Item) -> Unit +): ContactHolder<Item.LatestContact>(parent) { + + init { + emojiView.setEmoji("\uD83D\uDD57") + } + + override fun onBind(item: Item.LatestContact) { + itemView.setOnClickListener { onClick(item) } + itemView.background = item.position.drawable(context) + + nameView.text = item.name + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/MyWalletHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/MyWalletHolder.kt new file mode 100644 index 000000000..a83967061 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/MyWalletHolder.kt @@ -0,0 +1,25 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder + +import android.view.ViewGroup +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import com.tonapps.uikit.icon.UIKitIcon +import uikit.extensions.drawable + +class MyWalletHolder( + parent: ViewGroup, + private val onClick: (Item) -> Unit +): ContactHolder<Item.MyWallet>(parent) { + + init { + iconView.setImageResource(UIKitIcon.ic_chevron_right_12) + } + + override fun onBind(item: Item.MyWallet) { + itemView.setOnClickListener { onClick(item) } + itemView.background = item.position.drawable(context) + + emojiView.setEmoji(item.emoji) + nameView.text = item.name + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/SpaceHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/SpaceHolder.kt new file mode 100644 index 000000000..6eb4039df --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/contacts/list/holder/SpaceHolder.kt @@ -0,0 +1,12 @@ +package com.tonapps.tonkeeper.ui.screen.send.contacts.list.holder + +import android.view.ViewGroup +import com.tonapps.tonkeeper.ui.screen.send.contacts.list.Item +import com.tonapps.tonkeeperx.R + +class SpaceHolder(parent: ViewGroup): Holder<Item.Space>(parent, R.layout.view_wallet_space) { + override fun onBind(item: Item.Space) { + + } + +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendArgs.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt similarity index 94% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendArgs.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt index c3d21f10c..21173d55a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendArgs.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendArgs.kt @@ -1,8 +1,7 @@ -package com.tonapps.tonkeeper.ui.screen.send +package com.tonapps.tonkeeper.ui.screen.send.main import android.os.Bundle import com.tonapps.blockchain.ton.extensions.toRawAddress -import com.tonapps.wallet.api.entity.TokenEntity import uikit.base.BaseArgs data class SendArgs( diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendContact.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendContact.kt new file mode 100644 index 000000000..7375c4219 --- /dev/null +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendContact.kt @@ -0,0 +1,16 @@ +package com.tonapps.tonkeeper.ui.screen.send.main + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class SendContact( + val type: Int, + val address: String, +): Parcelable { + + companion object { + const val MY_WALLET_TYPE = 1 + const val CONTACT_TYPE = 2 + } +} \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendEvent.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt similarity index 72% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendEvent.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt index f49122659..1d790355c 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendEvent.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendEvent.kt @@ -1,9 +1,6 @@ -package com.tonapps.tonkeeper.ui.screen.send +package com.tonapps.tonkeeper.ui.screen.send.main import com.tonapps.icu.Coins -import com.tonapps.ledger.ton.Transaction -import org.ton.api.pub.PublicKeyEd25519 -import org.ton.cell.Cell sealed class SendEvent { data object Failed: SendEvent() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendException.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendException.kt similarity index 81% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendException.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendException.kt index 397e53f93..0aeb67e24 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendException.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendException.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send +package com.tonapps.tonkeeper.ui.screen.send.main sealed class SendException: Exception() { class UnableSendTransaction: SendException() diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt similarity index 94% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendScreen.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt index 66095b309..f2860cd59 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendScreen.kt @@ -1,25 +1,15 @@ -@file:OptIn(FlowPreview::class) - -package com.tonapps.tonkeeper.ui.screen.send +package com.tonapps.tonkeeper.ui.screen.send.main import android.content.Context import android.graphics.drawable.Drawable -import android.net.Uri import android.os.Bundle import android.text.SpannableString -import android.text.SpannableStringBuilder -import android.text.TextPaint import android.text.method.LinkMovementMethod -import android.text.style.ClickableSpan -import android.text.style.ForegroundColorSpan -import android.util.Log import android.view.View import android.widget.Button import androidx.appcompat.widget.AppCompatTextView import androidx.core.net.toUri -import androidx.lifecycle.lifecycleScope -import com.tonapps.blockchain.ton.extensions.toUserFriendly -import com.tonapps.icu.Coins +import com.tonapps.extensions.getParcelableCompat import com.tonapps.icu.CurrencyFormatter.withCustomSymbol import com.tonapps.ledger.ton.Transaction import com.tonapps.tonkeeper.api.shortAddress @@ -30,10 +20,10 @@ import com.tonapps.tonkeeper.extensions.getTitle import com.tonapps.tonkeeper.fragment.camera.CameraFragment import com.tonapps.tonkeeper.ui.component.coin.CoinInputView import com.tonapps.tonkeeper.ui.screen.ledger.sign.LedgerSignScreen -import com.tonapps.tonkeeper.ui.screen.send.state.SendAmountState -import com.tonapps.tonkeeper.ui.screen.send.state.SendDestination -import com.tonapps.tonkeeper.ui.screen.send.state.SendFeeState -import com.tonapps.tonkeeper.ui.screen.send.state.SendTransaction +import com.tonapps.tonkeeper.ui.screen.send.contacts.SendContactsScreen +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendAmountState +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendDestination +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendTransaction import com.tonapps.tonkeeper.ui.screen.signer.qr.SignerQRScreen import com.tonapps.tonkeeper.view.TransactionDetailView import com.tonapps.tonkeeperx.R @@ -47,15 +37,8 @@ import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.Wallet import com.tonapps.wallet.data.collectibles.entities.NftEntity import com.tonapps.wallet.localization.Localization -import io.tonapi.models.Account import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.take import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.ton.bitstring.BitString @@ -80,6 +63,8 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS private val args: SendArgs by lazy { SendArgs(requireArguments()) } private val signerQRRequestKey: String by lazy { "send_${UUID.randomUUID()}" } + private val contractsRequestKey: String by lazy { "contacts_${UUID.randomUUID()}" } + private val sendViewModel: SendViewModel by viewModel { parametersOf(args.nftAddress) } private val signerResultContract = SingerResultContract() @@ -106,7 +91,6 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS private lateinit var commentInput: InputView private lateinit var button: Button private lateinit var taskContainerView: View - private lateinit var pasteView: View private lateinit var addressActionsView: View private lateinit var confirmButton: Button private lateinit var processTaskView: ProcessTaskView @@ -134,6 +118,11 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS sendViewModel.sendSignedMessage(BitString(sign)) } } + + navigation?.setFragmentResultListener(contractsRequestKey) { bundle -> + val contact = bundle.getParcelableCompat<SendContact>("contact") ?: return@setFragmentResultListener + addressInput.text = contact.address + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -142,7 +131,7 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS val createHeaderView = view.findViewById<HeaderView>(R.id.create_header) createHeaderView.closeView.background = null createHeaderView.doOnActionClick = { finish() } - createHeaderView.doOnCloseClick = { navigation?.add(CameraFragment.newInstance()) } + createHeaderView.doOnCloseClick = { openCamera() } val reviewHeaderView = view.findViewById<HeaderView>(R.id.review_header) reviewHeaderView.doOnCloseClick = { showCreate() } @@ -158,8 +147,11 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS addressActionsView.visibility = if (text.isBlank()) View.VISIBLE else View.GONE } - pasteView = view.findViewById(R.id.paste) - pasteView.setOnClickListener { addressInput.text = requireContext().clipboardText() } + view.findViewById<View>(R.id.paste).setOnClickListener { + addressInput.text = requireContext().clipboardText() + } + + view.findViewById<View>(R.id.address_book).setOnClickListener { openAddressBook() } amountView = view.findViewById(R.id.amount) amountView.doOnValueChanged = sendViewModel::userInputAmount @@ -276,6 +268,16 @@ class SendScreen: BaseFragment(R.layout.fragment_send_new), BaseFragment.BottomS initializeArgs(args.targetAddress, args.amountNano, args.text, args.tokenAddress) } + private fun openAddressBook() { + navigation?.add(SendContactsScreen.newInstance(contractsRequestKey)) + getCurrentFocus()?.hideKeyboard() + } + + private fun openCamera() { + navigation?.add(CameraFragment.newInstance()) + getCurrentFocus()?.hideKeyboard() + } + fun initializeArgs( targetAddress: String?, amountNano: Long, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt similarity index 90% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendViewModel.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt index 442580162..e3b5f1feb 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/SendViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send +package com.tonapps.tonkeeper.ui.screen.send.main import android.app.Application import android.content.Context @@ -14,10 +14,10 @@ import com.tonapps.tonkeeper.api.totalFees import com.tonapps.tonkeeper.core.AnalyticsHelper import com.tonapps.tonkeeper.core.entities.SendMetadataEntity import com.tonapps.tonkeeper.core.entities.TransferEntity -import com.tonapps.tonkeeper.ui.screen.send.helper.SendNftHelper -import com.tonapps.tonkeeper.ui.screen.send.state.SendAmountState -import com.tonapps.tonkeeper.ui.screen.send.state.SendDestination -import com.tonapps.tonkeeper.ui.screen.send.state.SendTransaction +import com.tonapps.tonkeeper.ui.screen.send.main.helper.SendNftHelper +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendAmountState +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendDestination +import com.tonapps.tonkeeper.ui.screen.send.main.state.SendTransaction import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.entities.WalletEntity @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.cache import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce @@ -109,29 +108,28 @@ class SendViewModel( .distinctUntilChanged() .debounce { if (it.isEmpty()) 0 else 600 } - val destinationFlow = combine( + private val destinationFlow = combine( accountRepository.selectedWalletFlow, userInputAddressFlow, ) { wallet, address -> if (address.isEmpty()) { return@combine SendDestination.Empty } - getDestinationAccount(address, wallet.testnet) }.flowOn(Dispatchers.IO).state(viewModelScope) - val tokensFlow = accountRepository.selectedWalletFlow.map { wallet -> + private val tokensFlow = accountRepository.selectedWalletFlow.map { wallet -> tokenRepository.get(currency, wallet.accountId, wallet.testnet) }.flowOn(Dispatchers.IO).stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) - val selectedTokenFlow = combine( + private val selectedTokenFlow = combine( tokensFlow, userInputFlow.map { it.token }.distinctUntilChanged() ) { tokens, selectedToken -> tokens.find { it.address == selectedToken.address } ?: AccountTokenEntity.EMPTY }.distinctUntilChanged().flowOn(Dispatchers.IO).stateIn(viewModelScope, SharingStarted.Eagerly, AccountTokenEntity.EMPTY) - val ratesTokenFlow = selectedTokenFlow.map { token -> + private val ratesTokenFlow = selectedTokenFlow.map { token -> ratesRepository.getRates(currency, token.address) }.state(viewModelScope) @@ -146,11 +144,14 @@ class SendViewModel( val uiRequiredMemoFlow = destinationFlow.map { it as? SendDestination.Account }.map { it?.memoRequired == true } + val uiExistingTargetFlow = destinationFlow.map { it as? SendDestination.Account }.map { it?.existing == true } + val uiEncryptedCommentAvailableFlow = combine( uiRequiredMemoFlow, - walletTypeFlow - ) { requiredMemo, walletType -> - !requiredMemo && (walletType == Wallet.Type.Default || walletType == Wallet.Type.Testnet || walletType == Wallet.Type.Lockup) + walletTypeFlow, + uiExistingTargetFlow, + ) { requiredMemo, walletType, existingTarget -> + existingTarget && !requiredMemo && (walletType == Wallet.Type.Default || walletType == Wallet.Type.Testnet || walletType == Wallet.Type.Lockup) }.stateIn(viewModelScope, SharingStarted.Eagerly, true) val uiInputEncryptedComment = combine( @@ -160,13 +161,13 @@ class SendViewModel( encryptedComment && available }.stateIn(viewModelScope, SharingStarted.Eagerly, false) - val uiInputComment = userInputFlow.map { it.comment }.distinctUntilChanged() + private val uiInputComment = userInputFlow.map { it.comment }.distinctUntilChanged() - val uiInputAmountCurrency = userInputFlow.map { it.amountCurrency } + private val uiInputAmountCurrency = userInputFlow.map { it.amountCurrency } .distinctUntilChanged() .stateIn(viewModelScope, SharingStarted.Eagerly, true) - val inputAmountFlow = userInputFlow.map { it.amount }.distinctUntilChanged() + private val inputAmountFlow = userInputFlow.map { it.amount }.distinctUntilChanged() private val _uiEventFlow = MutableEffectFlow<SendEvent>() val uiEventFlow = _uiEventFlow.asSharedFlow() @@ -198,12 +199,12 @@ class SendViewModel( rates.convertFromFiat(token.address, token.fiat - amount) } - val remainingFormat = CurrencyFormatter.format(token.symbol, remainingToken, token.decimals, RoundingMode.UP, false) + val remainingFormat = CurrencyFormatter.format(token.symbol, remainingToken, 2, RoundingMode.DOWN, false) SendAmountState( remainingFormat = context.getString(Localization.remaining_balance, remainingFormat), converted = converted.stripTrailingZeros(), - convertedFormat = CurrencyFormatter.format(convertedCode, converted, decimals, RoundingMode.UP, false), + convertedFormat = CurrencyFormatter.format(convertedCode, converted, 2, RoundingMode.DOWN, false), insufficientBalance = if (remaining.isZero) false else remaining.isNegative, currencyCode = if (amountCurrency) currencyCode else "", amountCurrency = amountCurrency, @@ -220,8 +221,13 @@ class SendViewModel( false } else if (recipient.memoRequired && comment.isNullOrEmpty()) { false + } else if (isNft || amount.isPositive) { + true + } else if (balance.insufficientBalance) { + false } else { - (isNft || (!balance.insufficientBalance && (amount.isPositive || amount.isZero))) + false + // (isNft || (!balance.insufficientBalance && (amount.isPositive || amount.isZero))) } } @@ -231,10 +237,16 @@ class SendViewModel( ratesTokenFlow, uiInputAmountCurrency, ) { token, amount, rates, amountCurrency -> - if (amountCurrency) { - rates.convertFromFiat(token.address, amount) - } else { + if (!amountCurrency) { amount + } else { + val converted = rates.convertFromFiat(token.address, amount) + val diff = token.balance.value.diff(converted) + if (99.7f >= diff || 100.3f >= diff) { + token.balance.value + } else { + converted + } } } @@ -397,7 +409,11 @@ class SendViewModel( SendEvent.Fee( value = coins, format = CurrencyFormatter.format(code, coins, TokenEntity.TON.decimals), - convertedFormat = CurrencyFormatter.format(currency.code, converted, currency.decimals), + convertedFormat = CurrencyFormatter.format( + currency.code, + converted, + currency.decimals + ), ) } catch (e: Throwable) { null @@ -438,7 +454,7 @@ class SendViewModel( } fun userInputComment(comment: String?) { - _userInputFlow.update { it.copy(comment = comment) } + _userInputFlow.update { it.copy(comment = comment?.trim()) } } fun swap() { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/helper/SendNftHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/SendNftHelper.kt similarity index 95% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/helper/SendNftHelper.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/SendNftHelper.kt index 4012b81e8..5ad862d8f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/helper/SendNftHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/helper/SendNftHelper.kt @@ -1,7 +1,5 @@ -package com.tonapps.tonkeeper.ui.screen.send.helper +package com.tonapps.tonkeeper.ui.screen.send.main.helper -import android.util.Log -import com.tonapps.blockchain.ton.BASE_FORWARD_AMOUNT import com.tonapps.blockchain.ton.ONE_TON import com.tonapps.blockchain.ton.TonTransferHelper import com.tonapps.blockchain.ton.contract.BaseWalletContract @@ -17,7 +15,6 @@ import org.ton.cell.Cell import org.ton.contract.wallet.WalletTransfer import org.ton.contract.wallet.WalletTransferBuilder import java.math.BigInteger -import kotlin.math.abs object SendNftHelper { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendAmountState.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendAmountState.kt similarity index 84% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendAmountState.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendAmountState.kt index db1539b54..fae722a59 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendAmountState.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendAmountState.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send.state +package com.tonapps.tonkeeper.ui.screen.send.main.state import com.tonapps.icu.Coins diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendConfig.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendConfig.kt similarity index 61% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendConfig.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendConfig.kt index 0df51f9cb..0737796cd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendConfig.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendConfig.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send.state +package com.tonapps.tonkeeper.ui.screen.send.main.state data class SendConfig( val amountFiat: Boolean = false, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendDestination.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendDestination.kt similarity index 87% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendDestination.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendDestination.kt index 78baa0957..38ab4a8bc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendDestination.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendDestination.kt @@ -1,7 +1,6 @@ -package com.tonapps.tonkeeper.ui.screen.send.state +package com.tonapps.tonkeeper.ui.screen.send.main.state import com.tonapps.blockchain.ton.extensions.isValidTonAddress -import io.tonapi.models.Account import io.tonapi.models.AccountStatus import org.ton.api.pub.PublicKeyEd25519 import org.ton.block.AddrStd @@ -18,6 +17,7 @@ sealed class SendDestination { val name: String?, val isScam: Boolean, val isBounce: Boolean, + val existing: Boolean ): SendDestination() { companion object { @@ -46,7 +46,8 @@ sealed class SendDestination { isWallet = account.isWallet, name = account.name, isScam = account.isScam ?: false, - isBounce = isBounce(query, account) + isBounce = isBounce(query, account), + existing = (account.status == AccountStatus.active || account.status == AccountStatus.frozen) ) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendFeeState.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFeeState.kt similarity index 72% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendFeeState.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFeeState.kt index f72fb7bbe..ca4f9242e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendFeeState.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendFeeState.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send.state +package com.tonapps.tonkeeper.ui.screen.send.main.state import com.tonapps.icu.Coins diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendTransaction.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendTransaction.kt similarity index 92% rename from apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendTransaction.kt rename to apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendTransaction.kt index 0af76346c..7aa5e9390 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/state/SendTransaction.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/state/SendTransaction.kt @@ -1,4 +1,4 @@ -package com.tonapps.tonkeeper.ui.screen.send.state +package com.tonapps.tonkeeper.ui.screen.send.main.state import com.tonapps.icu.Coins import com.tonapps.wallet.api.entity.BalanceEntity diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt index 4a6254212..9ee3246cd 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/settings/main/SettingsScreen.kt @@ -15,7 +15,7 @@ import com.tonapps.tonkeeper.ui.screen.root.RootActivity import com.tonapps.tonkeeper.ui.screen.settings.currency.CurrencyScreen import com.tonapps.tonkeeper.ui.screen.settings.language.LanguageScreen import com.tonapps.tonkeeper.ui.screen.name.edit.EditNameScreen -import com.tonapps.tonkeeper.ui.screen.notifications.NotificationsScreen +import com.tonapps.tonkeeper.ui.screen.notifications.manage.NotificationsManageScreen import com.tonapps.tonkeeper.ui.screen.settings.legal.LegalScreen import com.tonapps.tonkeeper.ui.screen.settings.main.list.Adapter import com.tonapps.tonkeeper.ui.screen.settings.main.list.Item @@ -70,7 +70,7 @@ class SettingsScreen: BaseListFragment(), BaseFragment.SwipeBack { is Item.SearchEngine -> searchPicker(item) is Item.DeleteWatchAccount -> deleteWatchAccount() is Item.Rate -> openRate() - is Item.Notifications -> navigation?.add(NotificationsScreen.newInstance()) + is Item.Notifications -> navigation?.add(NotificationsManageScreen.newInstance()) is Item.FAQ -> navigation?.openURL(item.url, true) else -> return } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/stake/StakingViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/stake/StakingViewModel.kt index d610c5c43..a81bd8d7f 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/stake/StakingViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/staking/stake/StakingViewModel.kt @@ -1,7 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.staking.stake import android.content.Context -import android.util.Log import androidx.lifecycle.ViewModel import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519 import com.tonapps.extensions.MutableEffectFlow @@ -12,7 +11,7 @@ import com.tonapps.tonkeeper.api.totalFees import com.tonapps.tonkeeper.core.entities.SendMetadataEntity import com.tonapps.tonkeeper.core.entities.TransferEntity import com.tonapps.tonkeeper.extensions.signLedgerTransaction -import com.tonapps.tonkeeper.ui.screen.send.SendException +import com.tonapps.tonkeeper.ui.screen.send.main.SendException import com.tonapps.wallet.api.API import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.AccountRepository diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt index 58effa3e2..e7d380e3e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/token/viewer/list/holder/ActionsHolder.kt @@ -3,7 +3,7 @@ package com.tonapps.tonkeeper.ui.screen.token.viewer.list.holder import android.view.View import android.view.ViewGroup import com.tonapps.tonkeeper.ui.screen.qr.QRScreen -import com.tonapps.tonkeeper.ui.screen.send.SendScreen +import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeper.ui.screen.swap.SwapScreen import com.tonapps.tonkeeper.ui.screen.token.viewer.list.Item import com.tonapps.tonkeeperx.R diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/transaction/TransactionScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/transaction/TransactionScreen.kt index c70f7500f..65665dab5 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/transaction/TransactionScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/transaction/TransactionScreen.kt @@ -59,6 +59,7 @@ class TransactionScreen: BaseFragment(R.layout.dialog_transaction), BaseFragment private val historyHelper: HistoryHelper by inject() private lateinit var iconView: FrescoView + private lateinit var spamView: View private lateinit var amountView: AppCompatTextView private lateinit var currencyView: AppCompatTextView private lateinit var dateView: AppCompatTextView @@ -84,30 +85,32 @@ class TransactionScreen: BaseFragment(R.layout.dialog_transaction), BaseFragment finish() } - iconView = view.findViewById(R.id.icon)!! - amountView = view.findViewById(R.id.amount)!! - currencyView = view.findViewById(R.id.currency)!! - dateView = view.findViewById(R.id.date)!! - unverifiedView = view.findViewById(R.id.unverified)!! + iconView = view.findViewById(R.id.icon) + amountView = view.findViewById(R.id.amount) + spamView = view.findViewById(R.id.spam) + spamView.visibility = if (action.isScam) View.VISIBLE else View.GONE + currencyView = view.findViewById(R.id.currency) + dateView = view.findViewById(R.id.date) + unverifiedView = view.findViewById(R.id.unverified) unverifiedView.setOnClickListener { navigation?.add(TokenUnverifiedScreen.newInstance()) } - dataView = view.findViewById(R.id.data)!! + dataView = view.findViewById(R.id.data) - feeView = view.findViewById(R.id.fee)!! + feeView = view.findViewById(R.id.fee) feeView.title = getString(Localization.fee) - commentView = view.findViewById(R.id.comment)!! + commentView = view.findViewById(R.id.comment) commentView.title = getString(Localization.comment) - accountNameView = view.findViewById(R.id.account_name)!! - accountAddressView = view.findViewById(R.id.account_address)!! + accountNameView = view.findViewById(R.id.account_name) + accountAddressView = view.findViewById(R.id.account_address) - txView = view.findViewById(R.id.tx)!! + txView = view.findViewById(R.id.tx) txView.title = getString(Localization.transaction) - explorerButton = view.findViewById(R.id.open_explorer)!! + explorerButton = view.findViewById(R.id.open_explorer) unverifiedView.visibility = if (action.unverifiedToken) { View.VISIBLE @@ -125,7 +128,7 @@ class TransactionScreen: BaseFragment(R.layout.dialog_transaction), BaseFragment applyIcon(action.coinIconUrl) - if (action.comment != null) { + if (action.comment != null && !action.isScam) { applyComment(action.comment!!) } else { commentView.visibility = View.GONE diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/State.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/State.kt index 0cff2decb..ab26e9a8d 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/State.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/State.kt @@ -1,6 +1,5 @@ package com.tonapps.tonkeeper.ui.screen.wallet.main -import android.content.Context import com.tonapps.icu.Coins import com.tonapps.icu.Coins.Companion.DEFAULT_DECIMALS import com.tonapps.icu.Coins.Companion.sumOf @@ -277,7 +276,6 @@ sealed class State { data class Settings( val hiddenBalance: Boolean, val config: ConfigEntity, - val status: Item.Status, - val telegramChannel: Boolean, + val status: Item.Status ): State() } \ No newline at end of file diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/WalletViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/WalletViewModel.kt index 9fb103a4f..b318cba1e 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/WalletViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/WalletViewModel.kt @@ -74,13 +74,17 @@ class WalletViewModel( private val _stateMainFlow = MutableStateFlow<State.Main?>(null) private val stateMainFlow = _stateMainFlow.asStateFlow().filterNotNull() + private val updateWalletSettings = combine( + settingsRepository.tokenPrefsChangedFlow, + settingsRepository.walletPrefsChangedFlow + ) { _, _ -> } + private val _stateSettingsFlow = combine( settingsRepository.hiddenBalancesFlow, api.configFlow, statusFlow, - settingsRepository.telegramChannelFlow, - ) { hiddenBalance, config, status, telegramChannel -> - State.Settings(hiddenBalance, config, status, telegramChannel) + ) { hiddenBalance, config, status -> + State.Settings(hiddenBalance, config, status) }.distinctUntilChanged() private val _uiItemsFlow = MutableStateFlow<List<Item>?>(null) @@ -122,7 +126,7 @@ class WalletViewModel( settingsRepository.currencyFlow, backupRepository.stream, networkMonitor.isOnlineFlow, - settingsRepository.walletPrefsChangedFlow, + updateWalletSettings, ) { wallet, currency, backups, isOnline, _ -> if (isOnline) { setStatus(Status.Updating) @@ -157,7 +161,7 @@ class WalletViewModel( alertNotificationsFlow, pushManager.dAppPushFlow, _stateSettingsFlow, - settingsRepository.walletPrefsChangedFlow, + updateWalletSettings, ) { state, alerts, dAppNotifications, settings, _ -> val status = settings.status /* if (settings.status == Status.NoInternet) { settings.status @@ -176,7 +180,7 @@ class WalletViewModel( pushEnabled = context.hasPushPermission() && settingsRepository.getPushWallet(state.wallet.id), biometryEnabled = settingsRepository.biometric, hasBackup = state.hasBackup, - showTelegramChannel = settings.telegramChannel + showTelegramChannel = settingsRepository.isTelegramChannel(state.wallet.id) ) } @@ -190,7 +194,7 @@ class WalletViewModel( alerts = alerts, dAppNotifications = State.DAppNotifications(dAppEvents, apps), setup = uiSetup, - lastUpdatedFormat = DateHelper.formattedDate(lastUpdated) + lastUpdatedFormat = DateHelper.formattedDate(lastUpdated, settingsRepository.getLocale()) ) _uiItemsFlow.value = uiItems setCached(state.wallet, uiItems) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/Item.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/Item.kt index 4da85e7f9..8b9bc74cc 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/Item.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/Item.kt @@ -1,11 +1,9 @@ package com.tonapps.tonkeeper.ui.screen.wallet.main.list -import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Parcel import android.os.Parcelable -import android.util.Log import com.facebook.common.util.UriUtil import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.icu.Coins diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/WalletAdapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/WalletAdapter.kt index ff0675017..92b9380c0 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/WalletAdapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/WalletAdapter.kt @@ -1,5 +1,6 @@ package com.tonapps.tonkeeper.ui.screen.wallet.main.list +import android.util.Log import android.view.ViewGroup import com.tonapps.tonkeeper.ui.screen.wallet.main.list.holder.ActionsHolder import com.tonapps.tonkeeper.ui.screen.wallet.main.list.holder.AlertHolder diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/ActionsHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/ActionsHolder.kt index 2cd5fbe50..cf13f1397 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/ActionsHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/ActionsHolder.kt @@ -5,14 +5,13 @@ import android.view.ViewGroup import com.tonapps.tonkeeper.extensions.openCamera import com.tonapps.tonkeeper.ui.screen.purchase.main.PurchaseScreen import com.tonapps.tonkeeper.ui.screen.qr.QRScreen -import com.tonapps.tonkeeper.ui.screen.send.SendScreen +import com.tonapps.tonkeeper.ui.screen.send.main.SendScreen import com.tonapps.tonkeeper.ui.screen.staking.stake.StakingScreen import com.tonapps.tonkeeper.ui.screen.swap.SwapScreen import com.tonapps.tonkeeper.ui.screen.wallet.main.list.Item import com.tonapps.tonkeeperx.R import com.tonapps.wallet.api.entity.TokenEntity import com.tonapps.wallet.data.account.Wallet -import uikit.navigation.Navigation class ActionsHolder(parent: ViewGroup): Holder<Item.Actions>(parent, R.layout.view_wallet_actions) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/BalanceHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/BalanceHolder.kt index 82a777253..6b32d90b8 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/BalanceHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/main/list/holder/BalanceHolder.kt @@ -125,10 +125,10 @@ class BalanceHolder( walletAddressView.text = address.shortAddress walletAddressView.setTextColor(context.textSecondaryColor) walletAddressView.setOnClickListener { - if (walletType == Wallet.Type.Default) { - context.copyWithToast(address) - } else { + if (walletType == Wallet.Type.Testnet || walletType == Wallet.Type.Watch) { context.copyWithToast(address, getTypeColor(walletType)) + } else { + context.copyWithToast(address) } } } @@ -140,11 +140,7 @@ class BalanceHolder( walletTypeView.visibility = View.VISIBLE walletTypeView.setTextColor(color) walletTypeView.backgroundTintList = color.withAlpha(.16f).stateList - if (version == WalletVersion.V5BETA) { - walletTypeView.setText(Localization.w5beta) - } else { - walletTypeView.setText(Localization.w5) - } + walletTypeView.setText(if (version == WalletVersion.V5BETA) Localization.w5beta else Localization.w5) return } @@ -169,6 +165,7 @@ class BalanceHolder( private fun getTypeColor(type: Wallet.Type): Int { return when (type) { Wallet.Type.Ledger -> context.accentGreenColor + Wallet.Type.Signer, Wallet.Type.SignerQR -> context.accentPurpleColor else -> context.accentOrangeColor } } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/TokensManageScreen.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/TokensManageScreen.kt index 485d8a81d..47a0b7f05 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/TokensManageScreen.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/TokensManageScreen.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.tonapps.tonkeeper.ui.screen.wallet.manage.list.Adapter import com.tonapps.tonkeeper.ui.screen.wallet.manage.list.Item import com.tonapps.tonkeeper.ui.screen.wallet.manage.list.holder.Holder +import com.tonapps.tonkeeper.ui.screen.wallet.manage.list.holder.TokenHolder import com.tonapps.wallet.localization.Localization import org.koin.androidx.viewmodel.ext.android.viewModel import uikit.HapticHelper @@ -20,7 +21,7 @@ class TokensManageScreen: BaseListFragment(), BaseFragment.BottomSheet { private val tokensManageViewModel: TokensManageViewModel by viewModel() private val adapter: Adapter by lazy { - Adapter(tokensManageViewModel::onPinChange, tokensManageViewModel::onHiddenChange) + Adapter(tokensManageViewModel::onPinChange, tokensManageViewModel::onHiddenChange, ::onDrag) } override fun onCreate(savedInstanceState: Bundle?) { @@ -28,15 +29,19 @@ class TokensManageScreen: BaseListFragment(), BaseFragment.BottomSheet { collectFlow(tokensManageViewModel.uiItemsFlow, adapter::submitList) } + private fun onDrag(holder: TokenHolder) { + getTouchHelper()?.startDrag(holder) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setTitle(getString(Localization.manage)) + setTitle(getString(Localization.home_screen)) setAdapter(adapter) val horizontalOffset = requireContext().getDimensionPixelSize(uikit.R.dimen.cornerMedium) setListPadding(horizontalOffset, 0, horizontalOffset, 0) setTouchHelperCallback(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) { - override fun isLongPressDragEnabled() = true + override fun isLongPressDragEnabled() = false override fun onMove( recyclerView: RecyclerView, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/Adapter.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/Adapter.kt index ccb717655..1c990b180 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/Adapter.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/Adapter.kt @@ -15,7 +15,8 @@ import com.tonapps.uikit.list.DiffCallback class Adapter( private val doOnPinChange: (tokenAddress: String, pin: Boolean) -> Unit, - private val doOnHiddeChange: (tokenAddress: String, hidden: Boolean) -> Unit + private val doOnHiddeChange: (tokenAddress: String, hidden: Boolean) -> Unit, + private val doOnDrag: (holder: TokenHolder) -> Unit, ): RecyclerView.Adapter<Holder<*>>() { private var list = listOf<Item>() @@ -41,7 +42,7 @@ class Adapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder<*> { return when(viewType) { Item.TYPE_TITLE -> TitleHolder(parent) - Item.TYPE_TOKEN -> TokenHolder(parent, doOnPinChange, doOnHiddeChange) + Item.TYPE_TOKEN -> TokenHolder(parent, doOnPinChange, doOnHiddeChange, doOnDrag) Item.TYPE_SPACE -> SpaceHolder(parent) else -> throw IllegalArgumentException("Unknown view type: $viewType") } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/holder/TokenHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/holder/TokenHolder.kt index 265b94c28..9b4ee9891 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/holder/TokenHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/manage/list/holder/TokenHolder.kt @@ -1,24 +1,28 @@ package com.tonapps.tonkeeper.ui.screen.wallet.manage.list.holder +import android.annotation.SuppressLint +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.MotionEventCompat import com.tonapps.icu.CurrencyFormatter.withCustomSymbol import com.tonapps.tonkeeper.ui.screen.wallet.manage.list.Item import com.tonapps.tonkeeperx.R import com.tonapps.uikit.color.accentBlueColor -import com.tonapps.uikit.color.iconPrimaryColor import com.tonapps.uikit.color.iconSecondaryColor import com.tonapps.uikit.color.stateList import com.tonapps.uikit.icon.UIKitIcon import uikit.extensions.drawable import uikit.widget.FrescoView +@SuppressLint("ClickableViewAccessibility") class TokenHolder( parent: ViewGroup, private val doOnPinChange: (tokenAddress: String, pin: Boolean) -> Unit, - private val doOnHiddeChange: (tokenAddress: String, hidden: Boolean) -> Unit + private val doOnHiddeChange: (tokenAddress: String, hidden: Boolean) -> Unit, + private val doOnDrag: (holder: TokenHolder) -> Unit ): Holder<Item.Token>(parent, R.layout.view_wallet_manage_token) { private val iconView = findViewById<FrescoView>(R.id.icon) @@ -28,6 +32,15 @@ class TokenHolder( private val hiddenView = findViewById<AppCompatImageView>(R.id.hidden) private val reorderView = findViewById<AppCompatImageView>(R.id.reorder) + init { + reorderView.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + doOnDrag(this@TokenHolder) + } + false + } + } + override fun onBind(item: Item.Token) { itemView.background = item.position.drawable(context) iconView.setImageURI(item.iconUri, this) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/picker/list/holder/WalletHolder.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/picker/list/holder/WalletHolder.kt index 4df8cfc53..e12bcc1df 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/picker/list/holder/WalletHolder.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/wallet/picker/list/holder/WalletHolder.kt @@ -7,6 +7,8 @@ import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import com.tonapps.blockchain.ton.contract.WalletVersion import com.tonapps.emoji.ui.EmojiView +import com.tonapps.extensions.max12 +import com.tonapps.extensions.max24 import com.tonapps.icu.CurrencyFormatter import com.tonapps.icu.CurrencyFormatter.withCustomSymbol import com.tonapps.tonkeeper.koin.accountRepository @@ -51,7 +53,7 @@ class WalletHolder( colorView.backgroundTintList = ColorStateList.valueOf(item.color) emojiView.setEmoji(item.emoji) - nameView.text = item.name + nameView.text = item.name.max24 if (item.hiddenBalance) { balanceView.text = HIDDEN_BALANCE } else { diff --git a/apps/wallet/instance/app/src/main/res/layout/dialog_transaction.xml b/apps/wallet/instance/app/src/main/res/layout/dialog_transaction.xml index e680e58c9..aa8b07676 100644 --- a/apps/wallet/instance/app/src/main/res/layout/dialog_transaction.xml +++ b/apps/wallet/instance/app/src/main/res/layout/dialog_transaction.xml @@ -51,14 +51,31 @@ android:id="@+id/icon" android:layout_width="96dp" android:layout_height="96dp" + android:layout_marginBottom="22dp" app:roundAsCircle="true" android:layout_gravity="center"/> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/spam" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginBottom="6dp" + android:text="@string/spam" + android:textAllCaps="true" + android:gravity="center" + android:singleLine="true" + android:paddingHorizontal="8dp" + android:paddingVertical="4dp" + android:background="@drawable/bg_wallet_type" + android:backgroundTint="?attr/accentOrangeColor" + android:textAppearance="@style/TextAppearance.Label2" + android:textColor="?attr/textPrimaryColor"/> + <androidx.appcompat.widget.AppCompatTextView android:id="@+id/amount" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="22dp" android:gravity="center" android:singleLine="true" android:textAppearance="@style/TextAppearance.H2" diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_main_list.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_main_list.xml index cd26d88e2..64cfc7bd6 100644 --- a/apps/wallet/instance/app/src/main/res/layout/fragment_main_list.xml +++ b/apps/wallet/instance/app/src/main/res/layout/fragment_main_list.xml @@ -15,10 +15,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" + android:layout_margin="@dimen/offsetMedium" android:visibility="gone" android:title="@string/empty_collectibles_title" android:description="@string/empty_collectibles_subtitle" - android:negativeButtonText="@string/receive"/> + android:positiveButtonText="@string/receive"/> <uikit.widget.HeaderView android:id="@+id/header" diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_nft.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_nft.xml index 63217f5d8..4dbd84e33 100644 --- a/apps/wallet/instance/app/src/main/res/layout/fragment_nft.xml +++ b/apps/wallet/instance/app/src/main/res/layout/fragment_nft.xml @@ -36,6 +36,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" + android:singleLine="true" android:textAppearance="@style/TextAppearance.Label1" android:text="@string/report_spam"/> @@ -49,6 +50,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" + android:singleLine="true" android:textAppearance="@style/TextAppearance.Label1" android:text="@string/not_spam"/> </uikit.widget.RowLayout> diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_init_push.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_notifications_enable.xml similarity index 68% rename from apps/wallet/instance/app/src/main/res/layout/fragment_init_push.xml rename to apps/wallet/instance/app/src/main/res/layout/fragment_notifications_enable.xml index 5bf38d5f3..8291bbe3c 100644 --- a/apps/wallet/instance/app/src/main/res/layout/fragment_init_push.xml +++ b/apps/wallet/instance/app/src/main/res/layout/fragment_notifications_enable.xml @@ -3,7 +3,22 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <androidx.appcompat.widget.LinearLayoutCompat + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/later" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:layout_margin="@dimen/offsetMedium" + android:text="@string/later" + android:textAppearance="@style/TextAppearance.Label2" + android:textColor="?attr/buttonSecondaryForegroundColor" + android:background="@drawable/bg_button_secondary" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="6dp" + android:paddingBottom="6dp"/> + + <uikit.widget.ColumnLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" @@ -23,7 +38,7 @@ android:title="@string/enable_notification_title" android:description="@string/enable_notification_subtitle"/> - </androidx.appcompat.widget.LinearLayoutCompat> + </uikit.widget.ColumnLayout> <Button style="@style/Widget.App.Button" diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_notifications.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_notifications_manage.xml similarity index 100% rename from apps/wallet/instance/app/src/main/res/layout/fragment_notifications.xml rename to apps/wallet/instance/app/src/main/res/layout/fragment_notifications_manage.xml diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_send_create.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_send_create.xml index 8c71858b9..8c20a5a3d 100644 --- a/apps/wallet/instance/app/src/main/res/layout/fragment_send_create.xml +++ b/apps/wallet/instance/app/src/main/res/layout/fragment_send_create.xml @@ -51,6 +51,17 @@ android:textColor="?attr/buttonTertiaryForegroundColor" android:text="@string/paste"/> + <View + android:layout_width="@dimen/offsetMedium" + android:layout_height="wrap_content"/> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/address_book" + android:layout_width="28dp" + android:layout_height="28dp" + android:tint="?attr/accentBlueColor" + android:src="@drawable/ic_address_book_28"/> + </uikit.widget.RowLayout> </FrameLayout> diff --git a/apps/wallet/instance/app/src/main/res/layout/fragment_send_review.xml b/apps/wallet/instance/app/src/main/res/layout/fragment_send_review.xml index a075bd8fb..f9c1f0186 100644 --- a/apps/wallet/instance/app/src/main/res/layout/fragment_send_review.xml +++ b/apps/wallet/instance/app/src/main/res/layout/fragment_send_review.xml @@ -119,7 +119,7 @@ android:layout_height="wrap_content" android:visibility="gone" app:successLabel="@string/done" - app:errorLabel="@string/error"/> + app:errorLabel="@string/sending_error"/> </FrameLayout> diff --git a/apps/wallet/instance/app/src/main/res/layout/view_contact.xml b/apps/wallet/instance/app/src/main/res/layout/view_contact.xml new file mode 100644 index 000000000..8f7e7e66e --- /dev/null +++ b/apps/wallet/instance/app/src/main/res/layout/view_contact.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<uikit.widget.RowLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/itemHeight" + android:paddingHorizontal="@dimen/offsetMedium"> + + <com.tonapps.emoji.ui.EmojiView + android:id="@+id/emoji" + android:layout_width="28dp" + android:layout_height="28dp" + android:layout_gravity="center"/> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="center" + android:singleLine="true" + android:paddingHorizontal="@dimen/offsetMedium" + android:textAppearance="@style/TextAppearance.Body1" + android:textColor="?attr/textPrimaryColor"/> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/icon" + android:layout_width="28dp" + android:layout_height="28dp" + android:layout_gravity="center" + android:scaleType="centerInside" + android:tint="?attr/iconTertiaryColor"/> + +</uikit.widget.RowLayout> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/java/com/tonapps/wallet/localization/Localization.kt b/apps/wallet/localization/src/main/java/com/tonapps/wallet/localization/Localization.kt index 9802430c5..96d7e2c84 100644 --- a/apps/wallet/localization/src/main/java/com/tonapps/wallet/localization/Localization.kt +++ b/apps/wallet/localization/src/main/java/com/tonapps/wallet/localization/Localization.kt @@ -4,13 +4,13 @@ typealias Localization = R.string val SupportedLanguages = listOf( Language(), - Language("en"), - Language("ru"), - Language("uk"), - Language("uz"), - Language("id"), - Language("es"), - Language("tr"), - Language("zh"), - Language("bg") + Language("en-US"), + Language("ru-RU"), + Language("uk-UA"), + Language("uz-UZ"), + Language("id-ID"), + Language("es-ES"), + Language("tr-TR"), + Language("zh-CN"), + Language("bg-BG") ) diff --git a/apps/wallet/localization/src/main/res/values-bg/strings.xml b/apps/wallet/localization/src/main/res/values-bg/strings.xml index fbc4047d4..aeb82fabe 100644 --- a/apps/wallet/localization/src/main/res/values-bg/strings.xml +++ b/apps/wallet/localization/src/main/res/values-bg/strings.xml @@ -10,9 +10,11 @@ <string name="setup_finish_backup">Резервирайте възстановителната фраза на портфейла</string> <string name="setup_finish_telegram">Присъединете се към канала на Tonkeeper</string> + <string name="contacts">Контакти</string> <string name="step_1">Етап 1</string> <string name="step_2">Етап 2</string> <string name="step_3">Етап 3</string> + <string name="home_screen">Начален екран</string> <string name="signer_step_1">Сканирайте QR кода с Signer</string> <string name="signer_step_2">Потвърдете транзакцията си в Signer</string> <string name="signer_step_3">Сканирайте QR кода на подписаната транзакция от Signer</string> @@ -42,6 +44,7 @@ <string name="pinned">Прикрепено</string> <string name="all_assets">Всички активи</string> <string name="sorted_by_price">Сортирано по цена</string> + <string name="spam">Спам</string> <string name="staking_max_apy" translatable="false">MAX APY</string> <string name="staking_minimum_deposit">Минимален депозит %1$s.</string> <string name="staking_options">Опции</string> @@ -360,6 +363,7 @@ <string name="banner_top_button">Инсталирайте стабилната версия</string> <string name="signer_description">Отворете Signer » Изберете необходимия ключ » Сканирайте QR код</string> <string name="signer_open">Отворете Signer на това устройство</string> + <string name="later">По-късно</string> <string name="signer" translatable="false">Signer</string> <string name="choose_wallet_title">Изберете портфейл</string> <string name="choose_wallet_subtitle">Изберете портфейли, които искате да добавите.</string> @@ -393,4 +397,5 @@ <string name="bluetooth_permissions_alert_message">Моля, включете разрешенията за Bluetooth в настройките, за да използвате тази функция</string> <string name="bluetooth_permissions_alert_open_settings">Отворете Настройки</string> <string name="biometric_enabled">Биометричните са активирани</string> + <string name="sending_error">Грешка при изпращане, опитайте отново по-късно</string> </resources> diff --git a/apps/wallet/localization/src/main/res/values-es/strings.xml b/apps/wallet/localization/src/main/res/values-es/strings.xml index f6a354eb4..46a9a0014 100644 --- a/apps/wallet/localization/src/main/res/values-es/strings.xml +++ b/apps/wallet/localization/src/main/res/values-es/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">Usa la biometría para aprobar transacciones</string> <string name="setup_finish_backup">Haz una copia de seguridad de la frase de recuperación del monedero</string> <string name="setup_finish_telegram">Únete al canal de Tonkeeper</string> + <string name="contacts">Contactos</string> <string name="step_1">Paso 1</string> <string name="step_2">Paso 2</string> <string name="step_3">Paso 3</string> + <string name="home_screen">Pantalla de inicio</string> <string name="signer_step_1">Escanea el código QR con Signer</string> <string name="signer_step_2">Confirme su transacción en Signer</string> <string name="signer_step_3">Escanee el código QR de la transacción firmada desde el Firmante</string> @@ -348,4 +350,7 @@ <string name="intro_title">Bienvenido a <annotation colorRes="textAccent">Tonkeeper</annotation></string> <string name="required_comment">Comentario requerido</string> <string name="biometric_enabled">Biométrico habilitado</string> + <string name="sending_error">Error de envío, inténtalo de nuevo más tarde</string> + <string name="spam">Spam</string> + <string name="later">Más tarde</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-id/strings.xml b/apps/wallet/localization/src/main/res/values-id/strings.xml index c3a91539f..905dc2eb8 100644 --- a/apps/wallet/localization/src/main/res/values-id/strings.xml +++ b/apps/wallet/localization/src/main/res/values-id/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">Gunakan biometrik untuk menyetujui transaksi</string> <string name="setup_finish_backup">Cadangkan frasa pemulihan dompet</string> <string name="setup_finish_telegram">Bergabung dengan saluran Tonkeeper</string> + <string name="contacts">Kontak</string> <string name="step_1">Langkah 1</string> <string name="step_2">Langkah 2</string> <string name="step_3">Langkah 3</string> + <string name="home_screen">Layar beranda</string> <string name="signer_step_1">Pindai kode QR dengan Signer</string> <string name="signer_step_2">Konfirmasikan transaksi Anda di Signer</string> <string name="signer_step_3">Pindai kode QR transaksi yang ditandatangani dari Signer</string> @@ -348,4 +350,7 @@ <string name="intro_title">Selamat datang di <annotation colorRes="textAccent">Tonkeeper</annotation></string> <string name="required_comment">Komentar yang diperlukan</string> <string name="biometric_enabled">Biometrik diaktifkan</string> + <string name="sending_error">Kesalahan pengiriman, coba lagi nanti</string> + <string name="spam">Spam</string> + <string name="later">Nanti</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-ru/strings.xml b/apps/wallet/localization/src/main/res/values-ru/strings.xml index 202daa8de..5dedeee30 100644 --- a/apps/wallet/localization/src/main/res/values-ru/strings.xml +++ b/apps/wallet/localization/src/main/res/values-ru/strings.xml @@ -3,7 +3,7 @@ <string name="incorrect_phrase">Неправильная фраза</string> <string name="w5_wallet">Кошелек W5</string> <string name="nft_unverified">Непроверенный NFT</string> - <string name="report_spam">Пожаловаться на спам</string> + <string name="report_spam">Спам</string> <string name="not_spam">Не спам</string> <string name="unverified">Непроверенно</string> <string name="setup_finish_title">Завершить настройку</string> @@ -13,6 +13,7 @@ <string name="setup_finish_telegram">Присоединяйтесь к каналу Тонкипер</string> <string name="staked">Стейкинг</string> <string name="unstake_amount">Сумма снятия ставки</string> + <string name="sending_error">Ошибка отправки. Попробуйте позже</string> <string name="unverified_token_description">Токен выглядит подозрительно по одной или нескольким причинам.</string> <string name="unverified_token_reason_1">Низкая ликвидность. У токена может быть какая-то стоимость, но она крайне мала.</string> @@ -310,9 +311,11 @@ <string name="last_updated">Обновлён в %1$s</string> <string name="max">Максимум</string> + <string name="contacts">Контакты</string> <string name="step_1">Шаг 1</string> <string name="step_2">Шаг 2</string> <string name="step_3">Шаг 3</string> + <string name="home_screen">Главный экран</string> <string name="signer_step_1">Сканируйте QR-код с помощью Signer</string> <string name="signer_step_2">Подтвердите транзакцию в Signer</string> <string name="signer_step_3">Отсканируйте QR-код подписанной транзакции из Signer</string> @@ -392,4 +395,6 @@ <string name="bluetooth_permissions_alert_open_settings">Открыть настройки</string> <string name="required_comment">Обязательный комментарий</string> <string name="biometric_enabled">Биометрия включена</string> + <string name="spam">Спам</string> + <string name="later">Позже</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-tr/strings.xml b/apps/wallet/localization/src/main/res/values-tr/strings.xml index c3bfa4a3c..a360f56ac 100644 --- a/apps/wallet/localization/src/main/res/values-tr/strings.xml +++ b/apps/wallet/localization/src/main/res/values-tr/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">İşlemleri onaylamak için biyometriyi kullanın</string> <string name="setup_finish_backup">Cüzdan kurtarma ifadesini yedekleyin</string> <string name="setup_finish_telegram">Tonkeeper kanalına katıl</string> + <string name="contacts">Kişiler</string> <string name="step_1">Adım 1</string> <string name="step_2">Adım 2</string> <string name="step_3">Adım 3</string> + <string name="home_screen">Ana ekran</string> <string name="signer_step_1">Signer ile QR kodunu tarayın</string> <string name="signer_step_2">İşleminizi Signer\'da onaylayın</string> <string name="signer_step_3">İmzalayan\'dan imzalı işlem QR kodunu tarayın</string> @@ -348,4 +350,7 @@ <string name="intro_title"><annotation colorRes="textAccent">Tonkeeper</annotation>\'a hoş geldiniz</string> <string name="required_comment">Gerekli yorum</string> <string name="biometric_enabled">Biyometrik etkin</string> + <string name="sending_error">Gönderim hatası, daha sonra tekrar deneyin</string> + <string name="spam">Spam</string> + <string name="later">Daha sonra</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-uk/strings.xml b/apps/wallet/localization/src/main/res/values-uk/strings.xml index db3b7781a..d23e91ec8 100644 --- a/apps/wallet/localization/src/main/res/values-uk/strings.xml +++ b/apps/wallet/localization/src/main/res/values-uk/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">Використовуйте біометрію для підтвердження транзакцій</string> <string name="setup_finish_backup">Зробіть резервну копію фрази для відновлення гаманця</string> <string name="setup_finish_telegram">Приєднуйтесь до каналу Tonkeeper</string> + <string name="contacts">Контакти</string> <string name="step_1">Крок 1</string> <string name="step_2">Крок 2</string> <string name="step_3">Крок 3</string> + <string name="home_screen">Головний екран</string> <string name="signer_step_1">Відскануйте QR-код за допомогою Signer</string> <string name="signer_step_2">Підтвердьте свою транзакцію в Signer</string> <string name="signer_step_3">Відскануйте QR-код підписаної транзакції від Signer</string> @@ -348,4 +350,7 @@ <string name="intro_title">Вітаємо в <annotation colorRes="textAccent">Tonkeeper</annotation></string> <string name="required_comment">Обов\'язковий коментар</string> <string name="biometric_enabled">Біометрія включена</string> + <string name="sending_error">Помилка відправки, спробуйте пізніше</string> + <string name="spam">Спам</string> + <string name="later">Пізніше</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-uz/strings.xml b/apps/wallet/localization/src/main/res/values-uz/strings.xml index 3ffd1f62b..1cd20de16 100644 --- a/apps/wallet/localization/src/main/res/values-uz/strings.xml +++ b/apps/wallet/localization/src/main/res/values-uz/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">Tranzaksiyalarni tasdiqlash uchun biometrikani qo\'llang</string> <string name="setup_finish_backup">Hamyonni tiklash iborasini zaxiralash</string> <string name="setup_finish_telegram">Tonkeeper kanaliga qo\'shiling</string> + <string name="contacts">Kontaktlar</string> <string name="step_1">1-qadam</string> <string name="step_2">2-qadam</string> <string name="step_3">3-qadam</string> + <string name="home_screen">Bosh ekran</string> <string name="signer_step_1">Signer bilan QR kodni skanerlang</string> <string name="signer_step_2">Signer orqali tranzaksiyani tasdiqlang</string> <string name="signer_step_3">Signer dan imzolangan tranzaksiya QR kodini skanerlang</string> @@ -348,4 +350,7 @@ <string name="intro_title"><annotation colorRes="textAccent">Tonkeeper</annotation>ga xush kelibsiz</string> <string name="required_comment">Sharh kerak</string> <string name="biometric_enabled">Biometrik yoqilgan</string> + <string name="sending_error">Yuborishda xatolik, keyinroq qayta urinib ko‘ring</string> + <string name="spam">Spam</string> + <string name="later">Keyinchalik</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values-zh/strings.xml b/apps/wallet/localization/src/main/res/values-zh/strings.xml index a6eee2832..0e0d09434 100644 --- a/apps/wallet/localization/src/main/res/values-zh/strings.xml +++ b/apps/wallet/localization/src/main/res/values-zh/strings.xml @@ -9,9 +9,11 @@ <string name="setup_finish_biometry">使用生物识别技术批准交易</string> <string name="setup_finish_backup">备份钱包恢复短语</string> <string name="setup_finish_telegram">加入Tonkeeper频道</string> + <string name="contacts">联系方式</string> <string name="step_1">步骤 1</string> <string name="step_2">第 2 步</string> <string name="step_3">步骤 3</string> + <string name="home_screen">主屏幕</string> <string name="signer_step_1">使用签名器扫描二维码</string> <string name="signer_step_2">在 Signer 中确认您的交易</string> <string name="signer_step_3">从签名者扫描已签名交易的二维码</string> @@ -348,4 +350,7 @@ <string name="intro_title">欢迎来到 <annotation colorRes="textAccent">Tonkeeper</annotation></string> <string name="required_comment">必填注释</string> <string name="biometric_enabled">已启用生物识别</string> + <string name="sending_error">发送错误,请稍后重试</string> + <string name="spam">垃圾邮件</string> + <string name="later">之后</string> </resources> \ No newline at end of file diff --git a/apps/wallet/localization/src/main/res/values/strings.xml b/apps/wallet/localization/src/main/res/values/strings.xml index 3d419aed1..ec5a71306 100644 --- a/apps/wallet/localization/src/main/res/values/strings.xml +++ b/apps/wallet/localization/src/main/res/values/strings.xml @@ -10,16 +10,18 @@ <string name="nft_unverified">Unverified NFT</string> <string name="report_spam">Report spam</string> <string name="not_spam">Not spam</string> + <string name="spam">Spam</string> <string name="unverified">Unverified</string> <string name="setup_finish_title">Finish setting up</string> <string name="setup_finish_push">Enable transaction notifications</string> <string name="setup_finish_biometry">Use biometry to approve transactions</string> <string name="setup_finish_backup">Back up the wallet recovery phrase</string> <string name="setup_finish_telegram">Join Tonkeeper channel</string> - + <string name="contacts">Contacts</string> <string name="step_1">Step 1</string> <string name="step_2">Step 2</string> <string name="step_3">Step 3</string> + <string name="home_screen">Home screen</string> <string name="signer_step_1">Scan the QR code with Signer</string> <string name="signer_step_2">Confirm your transaction in Signer</string> @@ -316,8 +318,10 @@ <string name="currency_gel_name">Georgian Lari</string> <string name="currency_bdt_name">Bangladeshi Taka</string> + <string name="later">Later</string> <string name="done">Done</string> <string name="error">Error</string> + <string name="sending_error">Sending error, try again later</string> <string name="unnamed_collection">Unnamed collection</string> <string name="view_details">View details</string> diff --git a/lib/extensions/src/main/java/com/tonapps/extensions/Main.kt b/lib/extensions/src/main/java/com/tonapps/extensions/Main.kt index 10cb877de..20763acd0 100644 --- a/lib/extensions/src/main/java/com/tonapps/extensions/Main.kt +++ b/lib/extensions/src/main/java/com/tonapps/extensions/Main.kt @@ -1,5 +1,6 @@ package com.tonapps.extensions +import android.util.Log import kotlinx.coroutines.delay suspend fun <R> withRetry( @@ -10,7 +11,9 @@ suspend fun <R> withRetry( for (i in 0 until times) { try { return block() - } catch (ignored: Throwable) {} + } catch (e: Throwable) { + Log.e("TONKeeperLog", "error request", e) + } delay(delay) } return null diff --git a/lib/extensions/src/main/java/com/tonapps/extensions/Strings.kt b/lib/extensions/src/main/java/com/tonapps/extensions/Strings.kt index 7b689aa4c..106bfc892 100644 --- a/lib/extensions/src/main/java/com/tonapps/extensions/Strings.kt +++ b/lib/extensions/src/main/java/com/tonapps/extensions/Strings.kt @@ -15,12 +15,30 @@ val String.short8: String return substring(0, 8) + "…" + substring(length - 8, length) } +val String.short6: String + get() { + if (length < 12) return this + return substring(0, 6) + "…" + substring(length - 6, length) + } + val String.short4: String get() { if (length < 8) return this return substring(0, 4) + "…" + substring(length - 4, length) } +val String.max12: String + get() { + if (length < 12) return this + return substring(0, 12) + "…" + } + +val String.max24: String + get() { + if (length < 24) return this + return substring(0, 24) + "…" + } + fun String.ifPunycodeToUnicode(): String { return if (startsWith(Punycode.PREFIX_STRING)) { Punycode.decodeSafe(this) diff --git a/lib/icu/src/main/java/com/tonapps/icu/Coins.kt b/lib/icu/src/main/java/com/tonapps/icu/Coins.kt index 97a31eb05..126ad5992 100644 --- a/lib/icu/src/main/java/com/tonapps/icu/Coins.kt +++ b/lib/icu/src/main/java/com/tonapps/icu/Coins.kt @@ -190,6 +190,16 @@ data class Coins( fun toDouble(): Double = value.toDouble() + fun diff(coins: Coins): Float { + if (coins.isZero || isZero) { + return 0f + } + val percentage = coins.value.divide(value, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP) + return percentage.toFloat() + } + override fun describeContents(): Int { return 0 } diff --git a/lib/icu/src/main/java/com/tonapps/icu/CurrencyFormatter.kt b/lib/icu/src/main/java/com/tonapps/icu/CurrencyFormatter.kt index eec287e7d..d178e7643 100644 --- a/lib/icu/src/main/java/com/tonapps/icu/CurrencyFormatter.kt +++ b/lib/icu/src/main/java/com/tonapps/icu/CurrencyFormatter.kt @@ -99,17 +99,13 @@ object CurrencyFormatter { fun format( currency: String = "", value: BigDecimal, - scale: Int = 0, + customScale: Int = 0, roundingMode: RoundingMode = RoundingMode.DOWN, replaceSymbol: Boolean = true, ): CharSequence { - var bigDecimal = value.stripTrailingZeros() - if (scale > 0) { - bigDecimal = bigDecimal.setScale(scale, roundingMode) - } else if (bigDecimal.scale() > 0) { - bigDecimal = bigDecimal.setScale(getScale(value.abs()), roundingMode) - } - bigDecimal = bigDecimal.stripTrailingZeros() + val targetScale = getScale(value.abs()) + val scale = if (targetScale > customScale) targetScale else customScale + val bigDecimal = value.stripTrailingZeros().setScale(scale, roundingMode).stripTrailingZeros() val decimals = bigDecimal.scale() val amount = getFormat(decimals).format(bigDecimal) return format(currency, amount, replaceSymbol) @@ -118,21 +114,21 @@ object CurrencyFormatter { fun format( currency: String = "", value: Coins, - scale: Int = 0, + customScale: Int = 0, roundingMode: RoundingMode = RoundingMode.DOWN, replaceSymbol: Boolean = true, ): CharSequence { - return format(currency, value.value, scale, roundingMode, replaceSymbol) + return format(currency, value.value, customScale, roundingMode, replaceSymbol) } fun formatFiat( currency: String, value: Coins, - scale: Int = 2, + customScale: Int = 2, roundingMode: RoundingMode = RoundingMode.DOWN, replaceSymbol: Boolean = true, ): CharSequence { - return format(currency, value, scale, roundingMode, replaceSymbol) + return format(currency, value, customScale, roundingMode, replaceSymbol) } private fun getScale(value: BigDecimal): Int { @@ -190,6 +186,9 @@ object CurrencyFormatter { } fun CharSequence.withCustomSymbol(context: Context): CharSequence { + if (true) { // Not now... maybe in future + return this + } val startIndex = indexOf(TON_SYMBOL) val endIndex = startIndex + TON_SYMBOL.length if (startIndex == -1) { diff --git a/lib/network/src/main/java/com/tonapps/network/OkHttpClient.kt b/lib/network/src/main/java/com/tonapps/network/OkHttpClient.kt index 93cf0b5c4..bb9ef0ef3 100644 --- a/lib/network/src/main/java/com/tonapps/network/OkHttpClient.kt +++ b/lib/network/src/main/java/com/tonapps/network/OkHttpClient.kt @@ -55,7 +55,7 @@ fun OkHttpClient.post( headers?.forEach { (key, value) -> builder.addHeader(key, value) } - return newCall(builder.build()).execute() + return execute(builder.build()) } fun OkHttpClient.get( @@ -66,7 +66,17 @@ fun OkHttpClient.get( headers?.forEach { (key, value) -> builder.addHeader(key, value) } - return newCall(builder.build()).execute().body?.string() ?: throw Exception("Empty response") + return execute(builder.build()).body?.string() ?: throw Exception("Empty response") +} + +private fun OkHttpClient.execute(request: Request): Response { + val response = newCall(request).execute() + if (!response.isSuccessful) { + val data = response.body?.string() ?: "" + + Log.e("TONKeeperLog", "Request failed: response=$response\ndata=$data") + } + return response } fun OkHttpClient.getBitmap(url: String): Bitmap { diff --git a/tonapi/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt b/tonapi/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt index 5e4e1b2e0..0ffcd9344 100644 --- a/tonapi/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt +++ b/tonapi/src/main/kotlin/io/tonapi/models/AuctionBidAction.kt @@ -36,7 +36,7 @@ import com.squareup.moshi.JsonClass data class AuctionBidAction ( @Json(name = "auction_type") - val auctionType: AuctionBidAction.AuctionType, + val auctionType: String, @Json(name = "amount") val amount: Price, diff --git a/tonapi/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt b/tonapi/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt index 76b47f5fa..dd6d277b2 100644 --- a/tonapi/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt +++ b/tonapi/src/main/kotlin/io/tonapi/models/NftPurchaseAction.kt @@ -36,7 +36,7 @@ import com.squareup.moshi.JsonClass data class NftPurchaseAction ( @Json(name = "auction_type") - val auctionType: NftPurchaseAction.AuctionType, + val auctionType: String, @Json(name = "amount") val amount: Price, diff --git a/tonapi/src/main/kotlin/io/tonapi/models/Refund.kt b/tonapi/src/main/kotlin/io/tonapi/models/Refund.kt index 10f28f29b..82ac510f6 100644 --- a/tonapi/src/main/kotlin/io/tonapi/models/Refund.kt +++ b/tonapi/src/main/kotlin/io/tonapi/models/Refund.kt @@ -30,7 +30,7 @@ import com.squareup.moshi.JsonClass data class Refund ( @Json(name = "type") - val type: Refund.Type, + val type: String, @Json(name = "origin") val origin: kotlin.String diff --git a/ui/uikit/core/src/main/java/uikit/base/BaseListFragment.kt b/ui/uikit/core/src/main/java/uikit/base/BaseListFragment.kt index b62c6e880..05860227a 100644 --- a/ui/uikit/core/src/main/java/uikit/base/BaseListFragment.kt +++ b/ui/uikit/core/src/main/java/uikit/base/BaseListFragment.kt @@ -13,12 +13,13 @@ import uikit.extensions.collectFlow import uikit.extensions.getDimensionPixelSize import uikit.extensions.topScrolled import uikit.widget.HeaderView +import uikit.widget.SimpleRecyclerView open class BaseListFragment: BaseFragment(R.layout.fragment_list) { private lateinit var headerContainer: FrameLayout private lateinit var headerView: HeaderView - private lateinit var listView: RecyclerView + private lateinit var listView: SimpleRecyclerView override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -64,9 +65,11 @@ open class BaseListFragment: BaseFragment(R.layout.fragment_list) { } fun setTouchHelperCallback(callback: ItemTouchHelper.SimpleCallback) { - ItemTouchHelper(callback).attachToRecyclerView(listView) + listView.setTouchHelper(ItemTouchHelper(callback)) } + fun getTouchHelper() = listView.getTouchHelper() + fun setListPadding(left: Int, top: Int, right: Int, bottom: Int) { listView.setPadding(left, top, right, bottom) } diff --git a/ui/uikit/core/src/main/java/uikit/widget/SimpleRecyclerView.kt b/ui/uikit/core/src/main/java/uikit/widget/SimpleRecyclerView.kt index 22cc61ecf..2f819be52 100644 --- a/ui/uikit/core/src/main/java/uikit/widget/SimpleRecyclerView.kt +++ b/ui/uikit/core/src/main/java/uikit/widget/SimpleRecyclerView.kt @@ -2,6 +2,7 @@ package uikit.widget import android.content.Context import android.util.AttributeSet +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.tonapps.uikit.list.LinearLayoutManager import uikit.R @@ -14,6 +15,7 @@ open class SimpleRecyclerView @JvmOverloads constructor( ) : RecyclerView(context, attrs, defStyle) { private var maxHeight: Int = 0 + private var itemTouchHelper: ItemTouchHelper? = null init { layoutManager = LinearLayoutManager(context, VERTICAL, false) @@ -22,6 +24,22 @@ open class SimpleRecyclerView @JvmOverloads constructor( } } + fun setTouchHelper(touchHelper: ItemTouchHelper?) { + if (touchHelper == itemTouchHelper) { + return + } + clearTouchHelper() + itemTouchHelper = touchHelper + itemTouchHelper?.attachToRecyclerView(this) + } + + fun getTouchHelper() = itemTouchHelper + + private fun clearTouchHelper() { + itemTouchHelper?.attachToRecyclerView(null) + itemTouchHelper = null + } + override fun onMeasure(widthSpec: Int, heightSpec: Int) { super.onMeasure(widthSpec, heightSpec) if (maxHeight in 1..<measuredHeight) { diff --git a/ui/uikit/icon/src/main/res/drawable/ic_address_book_28.xml b/ui/uikit/icon/src/main/res/drawable/ic_address_book_28.xml new file mode 100644 index 000000000..796c9909a --- /dev/null +++ b/ui/uikit/icon/src/main/res/drawable/ic_address_book_28.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="28dp" + android:height="28dp" + android:viewportWidth="28" + android:viewportHeight="28"> + <path + android:pathData="M2,10L3,10V13H2C1.448,13 1,13.448 1,14C1,14.552 1.448,15 2,15H3V18H2C1.448,18 1,18.448 1,19C1,19.552 1.448,20 2,20H3.011C3.041,21.539 3.149,22.492 3.545,23.27C4.024,24.211 4.789,24.976 5.73,25.455C6.8,26 8.2,26 11,26H17C19.8,26 21.2,26 22.27,25.455C23.211,24.976 23.976,24.211 24.455,23.27C25,22.2 25,20.8 25,18V10C25,7.2 25,5.8 24.455,4.73C23.976,3.789 23.211,3.024 22.27,2.545C21.2,2 19.8,2 17,2H11C8.2,2 6.8,2 5.73,2.545C4.789,3.024 4.024,3.789 3.545,4.73C3.149,5.508 3.041,6.461 3.011,8H2C1.448,8 1,8.448 1,9C1,9.552 1.448,10 2,10ZM5.002,20C5.01,21.165 5.057,21.831 5.327,22.362C5.615,22.927 6.074,23.385 6.638,23.673C6.994,23.855 7.412,23.935 8,23.971C8.016,20.671 10.696,18 14,18C17.304,18 19.984,20.671 20,23.971C20.588,23.935 21.006,23.855 21.362,23.673C21.927,23.385 22.385,22.927 22.673,22.362C23,21.72 23,20.88 23,19.2V8.8C23,7.12 23,6.28 22.673,5.638C22.385,5.074 21.927,4.615 21.362,4.327C20.72,4 19.88,4 18.2,4H9.8C8.12,4 7.28,4 6.638,4.327C6.074,4.615 5.615,5.074 5.327,5.638C5.057,6.169 5.01,6.835 5.002,8H6C6.552,8 7,8.448 7,9C7,9.552 6.552,10 6,10H5V13H6C6.552,13 7,13.448 7,14C7,14.552 6.552,15 6,15H5V18H6C6.552,18 7,18.448 7,19C7,19.552 6.552,20 6,20H5.002ZM14,20C16.209,20 18,21.791 18,24H10C10,21.791 11.791,20 14,20ZM16,11.5C16,12.605 15.105,13.5 14,13.5C12.895,13.5 12,12.605 12,11.5C12,10.395 12.895,9.5 14,9.5C15.105,9.5 16,10.395 16,11.5ZM18,11.5C18,13.709 16.209,15.5 14,15.5C11.791,15.5 10,13.709 10,11.5C10,9.291 11.791,7.5 14,7.5C16.209,7.5 18,9.291 18,11.5Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> +</vector>