From 96a450e0e57780a6256012327f9710c747544c7e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 May 2023 16:28:00 +0300 Subject: [PATCH 1/5] Updated on 2026-08-14 --- .../tap/features/wallet/ui/WalletFragment.kt | 10 -- core/datasource/build.gradle.kts | 2 - core/datasource/src/main/AndroidManifest.xml | 3 +- .../AndroidNetworkConnectionManager.kt | 110 ++++++++++++++++++ .../RealInternetConnectionManager.kt | 29 ----- .../datasource/di/NetworkConnectionModule.kt | 14 +-- gradle/dependencies.toml | 3 - 7 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index be2387002a..4522299310 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -279,16 +279,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber - if (isOnline) { - (activity as? MainActivity)?.dismissSnackbar() - } else { - isNetworkConnectionError.value = true - binding.srlWallet.isRefreshing = false - (activity as? MainActivity)?.showSnackbar( - text = R.string.wallet_notification_no_internet, - buttonTitle = R.string.common_retry, - ) - } if (isOnline && isNetworkConnectionError.value) { refreshWalletData() } diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 3350c7930f..36c8fda7a2 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -22,7 +22,6 @@ dependencies { /** Coroutines */ implementation(deps.kotlin.coroutines) - implementation(deps.kotlin.coroutines.reactive) /** Logging */ implementation(deps.timber) @@ -35,7 +34,6 @@ dependencies { implementation(deps.okHttp.logging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) - implementation(deps.reactive.network) /** Time */ implementation(deps.jodatime) diff --git a/core/datasource/src/main/AndroidManifest.xml b/core/datasource/src/main/AndroidManifest.xml index a87322003a..b7e7790e14 100644 --- a/core/datasource/src/main/AndroidManifest.xml +++ b/core/datasource/src/main/AndroidManifest.xml @@ -3,5 +3,4 @@ xmlns:android="http://schemas.android.com/apk/res/android"> - - + \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt new file mode 100644 index 0000000000..6581fbb384 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt @@ -0,0 +1,110 @@ +package com.tangem.datasource.connection + +import android.app.Activity +import android.app.Application +import android.app.Application.ActivityLifecycleCallbacks +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.NetworkCapabilities +import android.os.Build +import android.os.Bundle +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton +import android.net.ConnectivityManager as AndroidConnectivityManager + +/** + * Network connection manager implementation using Android connectivity service + * + * @property context application context for registration lifecycle callbacks + * @property dispatchers coroutine dispatcher provider + * +[REDACTED_AUTHOR] + */ +@Singleton +internal class AndroidNetworkConnectionManager @Inject constructor( + @ApplicationContext private val context: Context, + private val dispatchers: CoroutineDispatcherProvider, +) : NetworkConnectionManager { + + private val _isOnline = MutableStateFlow(value = false) + private val callbacks = NetworkConnectionManagerCallbacks() + private val receiver = NetworkConnectionBroadcastReceiver() + + override val isOnline: Boolean get() = _isOnline.value + override val isOnlineFlow: StateFlow = _isOnline + + init { + (context as? Application)?.registerActivityLifecycleCallbacks(callbacks) + } + + private inner class NetworkConnectionManagerCallbacks : ActivityLifecycleCallbacks { + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + val intentFilter = IntentFilter().apply { + addAction(AndroidConnectivityManager.CONNECTIVITY_ACTION) + } + + activity.registerReceiver(receiver, intentFilter) + } + + override fun onActivityStarted(activity: Activity) = Unit + override fun onActivityResumed(activity: Activity) = Unit + override fun onActivityPaused(activity: Activity) = Unit + override fun onActivityStopped(activity: Activity) = Unit + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + + override fun onActivityDestroyed(activity: Activity) { + activity.unregisterReceiver(receiver) + (context as? Application)?.unregisterActivityLifecycleCallbacks(callbacks) + } + } + + private inner class NetworkConnectionBroadcastReceiver : BroadcastReceiver() { + + private val connectivityManager: android.net.ConnectivityManager? = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager + + override fun onReceive(c: Context?, intent: Intent?) { + val pendingResult = goAsync() + + CoroutineScope(SupervisorJob()).launch(dispatchers.io) { + try { + val status = isConnected() + + _isOnline.emit(value = status) + Timber.i("Connection status is ${if (status) "ONLINE" else "OFFLINE"}") + } finally { + pendingResult.finish() + } + } + } + + private fun isConnected(): Boolean { + connectivityManager ?: return false + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + capabilities != null && capabilities.hasNetworkTransport() + } else { + val networkInfo = connectivityManager.activeNetworkInfo + networkInfo != null && networkInfo.isConnectedOrConnecting + } + } + + private fun NetworkCapabilities.hasNetworkTransport(): Boolean { + return hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt deleted file mode 100644 index 669dc8ed20..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.datasource.connection - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler -import io.reactivex.BackpressureStrategy -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.reactive.asFlow - -internal class RealInternetConnectionManager : NetworkConnectionManager { - - private val scope = - CoroutineScope( - SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("RealInternetConnectionManager"), - ) - - override val isOnlineFlow: StateFlow = ReactiveNetwork - .observeInternetConnectivity() - .toFlowable(BackpressureStrategy.LATEST) - .asFlow() - .stateIn(scope, SharingStarted.Eagerly, initialValue = false) - - override val isOnline: Boolean - get() = isOnlineFlow.value -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt index b3eca89707..d112c7ccab 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt @@ -1,20 +1,20 @@ package com.tangem.datasource.di +import com.tangem.datasource.connection.AndroidNetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager -import com.tangem.datasource.connection.RealInternetConnectionManager +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal class NetworkConnectionModule { +internal interface NetworkConnectionModule { - @Provides + @Binds @Singleton - fun provideNetworkConnectionManager(): NetworkConnectionManager { - return RealInternetConnectionManager() - } + fun bindNetworkConnectionManager( + androidNetworkConnectionManager: AndroidNetworkConnectionManager, + ): NetworkConnectionManager } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 452d9dcf83..860b2162f4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -69,7 +69,6 @@ zxingQrCode = "3.5.1" mviCore = "1.3.1" kotlinSerialization = "1.4.1" arrow = "1.2.0-RC" -reactiveNetwork = "3.0.8" # endregion Other libraries # region Tangem @@ -175,7 +174,6 @@ appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer" armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" } coil = { module = "io.coil-kt:coil", version.ref = "coil" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" } -kotlin-coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref = "coroutine" } kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" } desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" } @@ -211,5 +209,4 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } -reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } # endregion Other From 91ba31fb84cd82574e82b2907b7f47167c3518a5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 May 2023 16:50:09 +0300 Subject: [PATCH 2/5] Updated on 2026-08-14 --- .../tap/features/wallet/ui/WalletFragment.kt | 10 ++ core/datasource/build.gradle.kts | 2 + core/datasource/src/main/AndroidManifest.xml | 3 +- .../AndroidNetworkConnectionManager.kt | 110 ------------------ .../RealInternetConnectionManager.kt | 29 +++++ .../datasource/di/NetworkConnectionModule.kt | 14 +-- gradle/dependencies.toml | 3 + 7 files changed, 53 insertions(+), 118 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 4522299310..be2387002a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -279,6 +279,16 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber + if (isOnline) { + (activity as? MainActivity)?.dismissSnackbar() + } else { + isNetworkConnectionError.value = true + binding.srlWallet.isRefreshing = false + (activity as? MainActivity)?.showSnackbar( + text = R.string.wallet_notification_no_internet, + buttonTitle = R.string.common_retry, + ) + } if (isOnline && isNetworkConnectionError.value) { refreshWalletData() } diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 36c8fda7a2..3350c7930f 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { /** Coroutines */ implementation(deps.kotlin.coroutines) + implementation(deps.kotlin.coroutines.reactive) /** Logging */ implementation(deps.timber) @@ -34,6 +35,7 @@ dependencies { implementation(deps.okHttp.logging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) + implementation(deps.reactive.network) /** Time */ implementation(deps.jodatime) diff --git a/core/datasource/src/main/AndroidManifest.xml b/core/datasource/src/main/AndroidManifest.xml index b7e7790e14..a87322003a 100644 --- a/core/datasource/src/main/AndroidManifest.xml +++ b/core/datasource/src/main/AndroidManifest.xml @@ -3,4 +3,5 @@ xmlns:android="http://schemas.android.com/apk/res/android"> - \ No newline at end of file + + diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt deleted file mode 100644 index 6581fbb384..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.datasource.connection - -import android.app.Activity -import android.app.Application -import android.app.Application.ActivityLifecycleCallbacks -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.NetworkCapabilities -import android.os.Build -import android.os.Bundle -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton -import android.net.ConnectivityManager as AndroidConnectivityManager - -/** - * Network connection manager implementation using Android connectivity service - * - * @property context application context for registration lifecycle callbacks - * @property dispatchers coroutine dispatcher provider - * -[REDACTED_AUTHOR] - */ -@Singleton -internal class AndroidNetworkConnectionManager @Inject constructor( - @ApplicationContext private val context: Context, - private val dispatchers: CoroutineDispatcherProvider, -) : NetworkConnectionManager { - - private val _isOnline = MutableStateFlow(value = false) - private val callbacks = NetworkConnectionManagerCallbacks() - private val receiver = NetworkConnectionBroadcastReceiver() - - override val isOnline: Boolean get() = _isOnline.value - override val isOnlineFlow: StateFlow = _isOnline - - init { - (context as? Application)?.registerActivityLifecycleCallbacks(callbacks) - } - - private inner class NetworkConnectionManagerCallbacks : ActivityLifecycleCallbacks { - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - val intentFilter = IntentFilter().apply { - addAction(AndroidConnectivityManager.CONNECTIVITY_ACTION) - } - - activity.registerReceiver(receiver, intentFilter) - } - - override fun onActivityStarted(activity: Activity) = Unit - override fun onActivityResumed(activity: Activity) = Unit - override fun onActivityPaused(activity: Activity) = Unit - override fun onActivityStopped(activity: Activity) = Unit - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit - - override fun onActivityDestroyed(activity: Activity) { - activity.unregisterReceiver(receiver) - (context as? Application)?.unregisterActivityLifecycleCallbacks(callbacks) - } - } - - private inner class NetworkConnectionBroadcastReceiver : BroadcastReceiver() { - - private val connectivityManager: android.net.ConnectivityManager? = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager - - override fun onReceive(c: Context?, intent: Intent?) { - val pendingResult = goAsync() - - CoroutineScope(SupervisorJob()).launch(dispatchers.io) { - try { - val status = isConnected() - - _isOnline.emit(value = status) - Timber.i("Connection status is ${if (status) "ONLINE" else "OFFLINE"}") - } finally { - pendingResult.finish() - } - } - } - - private fun isConnected(): Boolean { - connectivityManager ?: return false - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) - capabilities != null && capabilities.hasNetworkTransport() - } else { - val networkInfo = connectivityManager.activeNetworkInfo - networkInfo != null && networkInfo.isConnectedOrConnecting - } - } - - private fun NetworkCapabilities.hasNetworkTransport(): Boolean { - return hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || - hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || - hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) - } - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt new file mode 100644 index 0000000000..669dc8ed20 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt @@ -0,0 +1,29 @@ +package com.tangem.datasource.connection + +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler +import io.reactivex.BackpressureStrategy +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.reactive.asFlow + +internal class RealInternetConnectionManager : NetworkConnectionManager { + + private val scope = + CoroutineScope( + SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("RealInternetConnectionManager"), + ) + + override val isOnlineFlow: StateFlow = ReactiveNetwork + .observeInternetConnectivity() + .toFlowable(BackpressureStrategy.LATEST) + .asFlow() + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + + override val isOnline: Boolean + get() = isOnlineFlow.value +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt index d112c7ccab..b3eca89707 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt @@ -1,20 +1,20 @@ package com.tangem.datasource.di -import com.tangem.datasource.connection.AndroidNetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager -import dagger.Binds +import com.tangem.datasource.connection.RealInternetConnectionManager import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal interface NetworkConnectionModule { +internal class NetworkConnectionModule { - @Binds + @Provides @Singleton - fun bindNetworkConnectionManager( - androidNetworkConnectionManager: AndroidNetworkConnectionManager, - ): NetworkConnectionManager + fun provideNetworkConnectionManager(): NetworkConnectionManager { + return RealInternetConnectionManager() + } } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 860b2162f4..452d9dcf83 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -69,6 +69,7 @@ zxingQrCode = "3.5.1" mviCore = "1.3.1" kotlinSerialization = "1.4.1" arrow = "1.2.0-RC" +reactiveNetwork = "3.0.8" # endregion Other libraries # region Tangem @@ -174,6 +175,7 @@ appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer" armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" } coil = { module = "io.coil-kt:coil", version.ref = "coil" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" } +kotlin-coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref = "coroutine" } kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" } desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" } @@ -209,4 +211,5 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } +reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } # endregion Other From f0211b94728a3ef992f54362b4bd2efafdb428b2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 May 2023 16:52:00 +0300 Subject: [PATCH 3/5] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e632399d0..ade65672ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ android:supportsRtl="true" android:allowBackup="false" android:networkSecurityConfig="@xml/network_security_config" + android:usesCleartextTraffic="true" android:hardwareAccelerated="true" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:allowBackup"> From 2b3f6360e33319d06f895b331a0d06f2860c98b8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 May 2023 19:42:02 +0500 Subject: [PATCH 4/5] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 1 - app/src/main/res/xml/network_security_config.xml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ade65672ac..7e632399d0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,7 +35,6 @@ android:supportsRtl="true" android:allowBackup="false" android:networkSecurityConfig="@xml/network_security_config" - android:usesCleartextTraffic="true" android:hardwareAccelerated="true" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:allowBackup"> diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 454c57b5fd..22e9f8653e 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,5 +1,6 @@ + api.tangem.com @@ -7,6 +8,4 @@ - - From 7ddea4dbd83a28f0df3a2059a5707d2aefd3cbba Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 May 2023 15:01:58 +0300 Subject: [PATCH 5/5] Updated on 2026-08-14 --- .../tap/features/shop/ui/ShopFragment.kt | 58 +++- app/src/main/res/layout/fragment_shop.xml | 257 ++++++++---------- core/res/src/main/res/values-ru/strings.xml | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 2 - core/res/src/main/res/values/strings.xml | 4 +- 5 files changed, 169 insertions(+), 156 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index dccbe4d7b7..ad760cf53b 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -4,6 +4,9 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import android.view.View import android.view.View.OnFocusChangeListener import android.view.inputmethod.EditorInfo @@ -12,7 +15,9 @@ import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver +import com.tangem.tap.common.extensions.getColorCompat import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.data.ProductType @@ -23,14 +28,27 @@ import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentShopBinding import org.rekotlin.StoreSubscriber +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { + private val dateFormat = SimpleDateFormat("dd MMMM", Locale.getDefault()) private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind) + private val endPreorderTime = Calendar.getInstance() + private val isBuyAvailable: Boolean + private var cardTranslationY = 70f private lateinit var keyboardObserver: KeyboardObserver + init { + endPreorderTime.clear() + endPreorderTime.set(END_DATE_SOLD_OUT_YEAR, END_DATE_SOLD_OUT_MONTH, END_DATE_SOLD_OUT_DAY) + isBuyAvailable = System.currentTimeMillis() > endPreorderTime.timeInMillis + } + override fun subscribeToStore() { store.subscribe(this) { state -> state.skipRepeats { oldState, newState -> @@ -65,6 +83,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< setupCardsImages() setupProductSelection() setupPromoCodeEditText() + setupSoldOutDescription() binding.toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() @@ -127,6 +146,26 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } } + private fun setupSoldOutDescription() = with(binding) { + context?.let { cntx -> + if (!isBuyAvailable) { + val date = "${dateFormat.format(endPreorderTime.time)}!" + val soldOutTitle = cntx.getString(R.string.shop_sold_out_description_prefix) + val generalString = "$soldOutTitle $date" + val wordToSpan: Spannable = SpannableString(generalString) + wordToSpan.setSpan( + ForegroundColorSpan(cntx.getColorCompat(R.color.textBlack)), + generalString.length - date.length, + generalString.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) + tvSoldOutDesc.text = wordToSpan + } else { + tvSoldOutDesc.hide() + } + } + } + override fun newState(state: ShopState) { if (activity == null || view == null) return @@ -174,9 +213,16 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< } private fun handleButtonsState(state: ShopState) = with(binding) { - btnPayGooglePay.root.show(state.isGooglePayAvailable) - btnAlternativePayment.show(state.isGooglePayAvailable) - btnMainAction.show(!state.isGooglePayAvailable) + if (isBuyAvailable) { + btnPayGooglePay.root.show(state.isGooglePayAvailable) + btnAlternativePayment.show(state.isGooglePayAvailable) + btnMainAction.show(!state.isGooglePayAvailable) + } else { + btnPayGooglePay.root.hide() + btnAlternativePayment.hide() + btnMainAction.show() + btnMainAction.text = context?.getString(R.string.shop_pre_order_now) + } if (state.total != null) { btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } @@ -189,4 +235,10 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< store.dispatch(ShopAction.ResetState) super.handleOnBackPressed() } + + companion object { + private const val END_DATE_SOLD_OUT_YEAR = 2023 + private const val END_DATE_SOLD_OUT_MONTH = 5 // June, month start counting from zero! + private const val END_DATE_SOLD_OUT_DAY = 10 + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml index c3dadcdb4b..1e86e09354 100644 --- a/app/src/main/res/layout/fragment_shop.xml +++ b/app/src/main/res/layout/fragment_shop.xml @@ -132,7 +132,7 @@ android:paddingStart="3dp" android:paddingTop="3dp" android:paddingEnd="3dp" - app:layout_constraintBottom_toTopOf="@id/v_shipping_discount_background" + app:layout_constraintBottom_toTopOf="@id/ll_total" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:selectionRequired="true" @@ -156,162 +156,125 @@ app:chipMinTouchTargetSize="0dp" /> - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + app:layout_constraintBottom_toTopOf="@id/ll_buttons"> - + - + - + - + + + + + + + + + + + + + + + + + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Неверный адрес Купить сейчас - Бесплатно У меня есть промо-код… Tangem Wallet Другие способы оплаты - Доставка + Сделать предзаказ + Все распродано, но не огорчайтесь! Мы уже производим новые партии Tangem Wallet. Чтобы получить ваш кошелек скорее, оформите предзаказ сейчас. Отправка заказов начнется Итого Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 648337e245..edfc9bc0cc 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -330,11 +330,9 @@ 交易已成功簽署並發送至區塊鏈節點。錢包餘額稍後更新 無效地址 現在購買 - 免費 輸入折扣碼 Tangem Wallet 其他付款方式 - 運送 總計 Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用 安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 0bf985e22a..37b2deb719 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -338,11 +338,11 @@ Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Invalid address Buy now - Free I have a promo code… Tangem Wallet Other payment methods - Shipping + Pre-order now + Sold out, but don\'t worry! We are working very hard to get more for you! Make sure to place a pre-order here. Deliveries start on Total Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. Store your crypto assets secure while keeping private keys contained in your card