Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-28 09:56:00 +03:00
parent 3d16de3f4f
commit d65b4039da
15 changed files with 26 additions and 945 deletions

View file

@ -1,60 +0,0 @@
package com.tangem.tap.common.extensions
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.tangem.utils.Provider
import com.tangem.wallet.R
import timber.log.Timber
fun FragmentManager.showFragmentAllowingStateLoss(name: String, fragmentProvider: Provider<Fragment>) {
if (backStackEntryCount > 0) {
val currentFragmentName = getBackStackEntryAt(backStackEntryCount - 1).name
if (name == currentFragmentName) {
Timber.i("Fragment $name is already at the top of the stack")
return
}
}
Timber.i("Showing $name route")
val isPoppedBack = popBackStackImmediate(name, 0)
if (!isPoppedBack) {
val fragment = fragmentProvider()
if (fragment is DialogFragment) {
fragment.showDialog(fragmentManager = this, name)
} else {
fragment.showFragment(fragmentManager = this, name)
}
Timber.i("Route $name is shown")
} else {
Timber.i("Route $name is found in backstack and shown")
}
}
private fun DialogFragment.showDialog(fragmentManager: FragmentManager, name: String) {
val transaction = fragmentManager.beginTransaction()
try {
transaction.addToBackStack(name)
show(transaction, name)
} catch (e: IllegalStateException) {
transaction.add(this, name)
transaction.addToBackStack(name)
transaction.commitAllowingStateLoss()
}
}
private fun Fragment.showFragment(fragmentManager: FragmentManager, name: String) {
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, this, name)
transaction.addToBackStack(name)
transaction.commitAllowingStateLoss()
}

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.extensions
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
@ -43,20 +45,16 @@ suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet) {
state.globalState.tapWalletManager.onWalletSelected(userWallet)
}
fun Store<*>.dispatchErrorNotification(error: TapError) {
dispatchOnMain(GlobalAction.ShowErrorNotification(error))
}
/**
* @param fatal used to indicate errors that should not normally occur
*/
fun Store<*>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) {
fun Store<AppState>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) {
val prefix = if (fatal) "FATAL ERROR: " else "DEBUG ERROR: "
dispatchDebugErrorNotification(TapError.CustomError("$prefix $message"))
}
fun Store<*>.dispatchDebugErrorNotification(error: TapError) {
dispatchOnMain(GlobalAction.DebugShowErrorNotification(error))
fun Store<AppState>.dispatchDebugErrorNotification(error: TapError) {
inject(DaggerGraphState::uiMessageSender).send(SnackbarMessage(stringReference(error.message ?: "debug error")))
}
fun Store<*>.dispatchDialogShow(dialog: StateDialog) {

View file

@ -31,7 +31,6 @@ data class AppState(
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(
logMiddleware,
notificationsMiddleware,
GlobalMiddleware.handler,
HomeMiddleware.handler,
DetailsMiddleware().detailsMiddleware,

View file

@ -1,141 +0,0 @@
package com.tangem.tap.common.redux
import android.content.Context
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.domain.ArgError
import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.assembleErrors
import com.tangem.tap.notificationsHandler
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import org.rekotlin.Action
import org.rekotlin.Middleware
import java.lang.ref.WeakReference
class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
private var baseLayout = basicCoordinatorLayout
private fun showNotification(message: String) {
baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.also { snackbar -> snackbar.show() }
}
}
private fun showDebugNotification(message: String) {
baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.also { snackbar ->
snackbar.setBackgroundTint(layout.getColor(R.color.warning_warning))
snackbar.show()
}
}
}
fun showNotification(message: Int, args: List<Any>? = null) {
baseLayout.get()?.let {
showNotification(getMessageString(it.context, message, args))
}
}
fun showToastNotification(message: Int, args: List<Any>? = null) {
baseLayout.get()?.let {
Toast.makeText(it.context, getMessageString(it.context, message, args), Toast.LENGTH_LONG).show()
}
}
fun showNotification(errorList: List<Pair<Int, List<Any>?>>, builder: (List<String>) -> String) {
val context = baseLayout.get()?.context ?: return
val message = builder(errorList.map { getMessageString(context, it.first, it.second) })
showNotification(message)
}
fun showDebugErrorNotification(message: Int, args: List<Any>? = null) {
baseLayout.get()?.let {
showDebugNotification(getMessageString(it.context, message, args))
}
}
}
fun getMessageString(context: Context, message: Int, args: List<Any>?): String {
return if (args.isNullOrEmpty()) {
context.getString(message)
} else {
context.getString(message, *args.toTypedArray())
}
}
val notificationsMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
handleNotificationAction(action)
next(action)
}
}
}
@Suppress("ComplexMethod")
private fun handleNotificationAction(action: Action) {
if (action is Debug && !BuildConfig.DEBUG) return
when (action) {
is NotificationAction -> {
notificationsHandler?.showNotification(action.messageResource)
}
is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
is ErrorAction -> {
when (action) {
is Debug -> {
val args = (action.error as? ArgError)?.args ?: listOf()
when (action) {
is DebugNotification -> {
notificationsHandler?.showNotification(action.error.messageResource, args)
}
is DebugToastNotification -> {
notificationsHandler?.showToastNotification(action.error.messageResource, args)
}
is DebugErrorAction -> {
notificationsHandler?.showDebugErrorNotification(action.error.messageResource, args)
}
}
}
else -> {
when (action.error) {
is MultiMessageError -> {
val multiError = action.error as MultiMessageError
notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
}
else -> {
val args = (action.error as? ArgError)?.args ?: listOf()
notificationsHandler?.showNotification(action.error.messageResource, args)
}
}
}
}
}
}
}
interface ToastNotificationAction : Action {
val messageResource: Int
}
interface NotificationAction : Action {
val messageResource: Int
}
interface ErrorAction : Action {
val error: TapError
}
// Processed only in the debug builds
interface Debug
interface DebugNotification : Debug, NotificationAction
interface DebugToastNotification : Debug, ToastNotificationAction
interface DebugErrorAction : Debug, ErrorAction

View file

@ -5,17 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.redux.DebugErrorAction
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.domain.TapError
import org.rekotlin.Action
sealed class GlobalAction : Action {
// notifications
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
// dialogs
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
object HideDialog : GlobalAction()