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 ): 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().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(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 - ) { - 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() + val nonSpam = mutableListOf() + 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 = emptyList(), + val nonSpam: List = 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() 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() 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() - 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(null) val biometricFlow = _biometricFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() @@ -84,13 +80,12 @@ class SettingsRepository( private val _searchEngineFlow = MutableEffectFlow() val searchEngineFlow = _searchEngineFlow.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() - private val _walletPush = MutableStateFlow?>(null) - val walletPush = _walletPush.stateIn(scope, SharingStarted.Eagerly, null).filterNotNull() + private val _walletPush = MutableEffectFlow() + 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 @@ -99,9 +94,6 @@ class SettingsRepository( val tokenPrefsChangedFlow: Flow get() = tokenPrefsFolder.changedFlow - val nftPrefsChangedFlow: Flow - 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() - 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) { 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>() 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 @@ + 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 = withContext(Dispatchers.IO) { val items = mutableListOf() + 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() 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 { 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 { val items = mutableListOf() 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?>() 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() 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) { @@ -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) = 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() - 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() + 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 { 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(parent, R.layout.view_select_wallet) { private val addressView = findViewById(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