Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-07 20:27:28 +02:00
parent 326352be30
commit 75e2b0b554
27 changed files with 194 additions and 2 deletions

View file

@ -0,0 +1,31 @@
package com.tangem.tap.common.haptic
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import com.tangem.core.ui.haptic.HapticManager
class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager {
override fun vibrateShort() {
vibrate(VIBRATION_SHORT_DURATION)
}
override fun vibrateLong() {
vibrate(VIBRATION_MEDIUM_DURATION)
}
private fun vibrate(durationMs: Long) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(durationMs)
}
}
companion object {
const val VIBRATION_SHORT_DURATION = 50L
const val VIBRATION_MEDIUM_DURATION = 100L
const val VIBRATION_LONG_DURATION = 200L
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.tap.di
import android.content.Context
import android.os.Build
import android.os.Vibrator
import android.os.VibratorManager
import com.tangem.tap.common.haptic.DefaultHapticManager
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.MockHapticManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class HapticModule {
@Provides
@Singleton
fun provideHapticManager(@ApplicationContext context: Context): HapticManager {
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
return if (vibrator.hasVibrator()) {
DefaultHapticManager(vibrator = vibrator)
} else {
MockHapticManager
}
}
}

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -25,6 +26,9 @@ internal class AddCustomTokenFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Composable
override fun ScreenContent(modifier: Modifier) {
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -18,6 +19,9 @@ internal class AppCurrencySelectorFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel: AppCurrencySelectorViewModel by viewModels()
@Composable

View file

@ -7,6 +7,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
@ -21,6 +22,9 @@ internal class AppSettingsFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var appCurrencyRepository: AppCurrencyRepository

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.viewModels
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.features.details.redux.DetailsAction
@ -18,6 +19,9 @@ internal class CardSettingsFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel: CardSettingsViewModel by viewModels()
@Composable

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.features.details.redux.DetailsState
@ -24,6 +25,9 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber<DetailsSta
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Composable
override fun ScreenContent(modifier: Modifier) {
AccessCodeRecoveryScreen(

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
@ -25,6 +26,9 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var walletsRepository: WalletsRepository

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.features.details.redux.DetailsState
@ -19,6 +20,9 @@ internal class ResetCardFragment : ComposeFragment(), StoreSubscriber<DetailsSta
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel = ResetCardViewModel(store)
private var screenState: MutableState<ResetCardScreenState> =

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.features.details.redux.DetailsState
@ -19,6 +20,9 @@ internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber<Details
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel = SecurityModeViewModel(store)
private var screenState: MutableState<SecurityModeScreenState> =

View file

@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.common.analytics.events.WalletConnect
@ -23,6 +24,9 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel: WalletConnectViewModel by viewModels()
private var screenState: MutableState<WalletConnectScreenState>? = null

View file

@ -13,6 +13,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.tokens.TokensAction
@ -32,6 +33,9 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -24,6 +25,9 @@ internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel: MainViewModel by activityViewModels()
@Composable

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -27,6 +28,9 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
override val expandedHeightFraction: Float = .98f
private val viewModel by viewModels<SaveWalletViewModel>()

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -25,6 +26,9 @@ internal class TokensListFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Composable
override fun ScreenContent(modifier: Modifier) {
val viewModel = hiltViewModel<TokensListViewModel>().apply {

View file

@ -16,6 +16,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -33,6 +34,9 @@ internal class WelcomeFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
override fun onStart() {
super.onStart()
Analytics.eraseContext()

View file

@ -0,0 +1,11 @@
package com.tangem.core.ui.haptic
import androidx.compose.runtime.Stable
@Stable
interface HapticManager {
fun vibrateShort()
fun vibrateLong()
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.ui.haptic
object MockHapticManager : HapticManager {
override fun vibrateShort() {
/** Intentionnaly do nothing */
}
override fun vibrateLong() {
/** Intentionnaly do nothing */
}
}

View file

@ -4,6 +4,8 @@ import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.runtime.*
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.MockHapticManager
// TODO: use isSystemInDarkTheme() for automatic color detection
internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false
@ -13,6 +15,7 @@ fun TangemTheme(
isDark: Boolean = false,
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
hapticManager: HapticManager = MockHapticManager,
content: @Composable () -> Unit,
) {
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
@ -29,6 +32,7 @@ fun TangemTheme(
LocalTangemDimens provides dimens,
LocalTangemShapes provides shapes,
LocalIsInDarkTheme provides isDark,
LocalHapticManager provides hapticManager,
) {
ProvideTextStyle(
value = TangemTheme.typography.body1,
@ -196,4 +200,8 @@ private val LocalTangemShapes = staticCompositionLocalOf<TangemShapes> {
error("No TangemShapes provided")
}
val LocalIsInDarkTheme = staticCompositionLocalOf { false }
val LocalIsInDarkTheme = staticCompositionLocalOf { false }
val LocalHapticManager = staticCompositionLocalOf<HapticManager> {
error("No HapticManager provided")
}

View file

@ -8,6 +8,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.apptheme.model.AppThemeMode
@ -26,6 +27,11 @@ internal interface ComposeScreen {
*/
val appThemeModeHolder: AppThemeModeHolder
/**
* Haptic manager.
*/
val hapticManager: HapticManager
/**
* The screen modifier.
*/
@ -55,7 +61,10 @@ internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
setContent {
val appThemeMode by appThemeModeHolder.appThemeMode
TangemTheme(isDark = shouldUseDarkTheme(appThemeMode)) {
TangemTheme(
isDark = shouldUseDarkTheme(appThemeMode),
hapticManager = hapticManager,
) {
ScreenContent(modifier = screenModifier)
}
}

View file

@ -20,6 +20,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.mlkit.vision.common.InputImage
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -41,6 +42,9 @@ internal class QrScanningFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var router: QrScanningRouter

View file

@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -22,6 +23,9 @@ class ReferralFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
private val viewModel by viewModels<ReferralViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -7,6 +7,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -28,6 +29,9 @@ internal class SendFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var router: SendRouter

View file

@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.navigation.ReduxNavController
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -27,6 +28,9 @@ class SwapFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var reduxNavController: ReduxNavController

View file

@ -7,6 +7,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeActivity
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -29,6 +30,9 @@ internal class TesterActivity : ComposeActivity() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
/** Router for inner feature navigation */
@Inject
lateinit var testerRouter: TesterRouter

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -21,6 +22,9 @@ internal class TokenDetailsFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
lateinit var tokenDetailsRouter: TokenDetailsRouter

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
@ -23,6 +24,9 @@ internal class WalletFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
override lateinit var hapticManager: HapticManager
@Inject
internal lateinit var manageTokensUi: ManageTokensUi