diff --git a/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt b/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt new file mode 100644 index 0000000000..ca241efce8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.core.ui + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.core.ui.DesignFeatureToggles +import javax.inject.Inject + +class DefaultDesignFeatureToggles @Inject constructor( + featureTogglesManager: FeatureTogglesManager, +) : DesignFeatureToggles { + override val isRedesignEnabled: Boolean = featureTogglesManager.isFeatureEnabled("APP_REDESIGN_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt index de5f7ef9e3..b5b3c5ea29 100644 --- a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt +++ b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.SnackbarHostState import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.ui.DefaultUiMessageSender import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.DesignFeatureToggles import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.message.EventMessageHandler @@ -23,12 +24,14 @@ internal object UiDependenciesModule { fun provideUiDependencies( vibratorHapticManager: VibratorHapticManager, appThemeModeHolder: AppThemeModeHolder, + designFeatureToggles: DesignFeatureToggles, ): UiDependencies { return object : UiDependencies { override val vibratorHapticManager = vibratorHapticManager override val appThemeModeHolder = appThemeModeHolder override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState() override val eventMessageHandler: EventMessageHandler = EventMessageHandler() + override val designFeatureToggles: DesignFeatureToggles = designFeatureToggles } } diff --git a/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt new file mode 100644 index 0000000000..b02028c5bf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.di.core.ui + +import com.tangem.core.ui.DesignFeatureToggles +import com.tangem.tap.core.ui.DefaultDesignFeatureToggles +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface CoreUiBindsModule { + + @Binds + fun bindDesignFeatureToggles(impl: DefaultDesignFeatureToggles): DesignFeatureToggles +} \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d91dbb1dbe..6ec92245ac 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -16,7 +16,7 @@ diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 36923953bd..51984d020a 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -67,6 +67,10 @@ "name": "FEED_ENABLED", "version": "undefined" }, + { + "name": "APP_REDESIGN_ENABLED", + "version": "undefined" + }, { "name": "GASLESS_TRANSACTIONS_ENABLED", "version": "5.33.0" diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 33e81eb02f..db9bc30c60 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -61,6 +61,16 @@ dependencies { api(deps.jodatime) implementation(deps.timber) implementation(deps.markdown) + implementation(deps.haze) { + exclude(module = "activity-compose") + exclude(module = "activity") + exclude(module = "activity-ktx") + } + implementation(deps.haze.materials) { + exclude(module = "activity-compose") + exclude(module = "activity") + exclude(module = "activity-ktx") + } /** Tests */ testImplementation(deps.test.junit) diff --git a/core/ui/src/main/java/com/tangem/core/ui/DesignFeatureToggles.kt b/core/ui/src/main/java/com/tangem/core/ui/DesignFeatureToggles.kt new file mode 100644 index 0000000000..fc8cf40b5c --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/DesignFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.core.ui + +interface DesignFeatureToggles { + val isRedesignEnabled: Boolean +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt index 0db95df8d1..10c50f5742 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt @@ -16,4 +16,6 @@ interface UiDependencies { val globalSnackbarHostState: SnackbarHostState val eventMessageHandler: EventMessageHandler + + val designFeatureToggles: DesignFeatureToggles } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/haze/HazeExt.kt b/core/ui/src/main/java/com/tangem/core/ui/components/haze/HazeExt.kt new file mode 100644 index 0000000000..779a7a0b55 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/haze/HazeExt.kt @@ -0,0 +1,57 @@ +package com.tangem.core.ui.components.haze + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.LocalHazeState +import com.tangem.core.ui.res.LocalPowerSavingState +import com.tangem.core.ui.res.LocalRootBackgroundColor +import dev.chrisbanes.haze.* +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi + +@OptIn(ExperimentalHazeMaterialsApi::class) +@Composable +internal fun ProvideHaze(content: @Composable () -> Unit) { + val hazeState = rememberHazeState() + CompositionLocalProvider( + LocalHazeState provides hazeState, + LocalHazeStyle provides CupertinoMaterials.ultraThin(), + ) { + content() + } +} + +/** + * Applies a haze effect to the [Modifier] with consideration of global haze settings and power saving mode. + * + * @param configure A lambda to configure the [HazeEffectScope]. + * @return A [Modifier] with the configured haze effect applied. + */ +@Composable +fun Modifier.hazeEffectTangem( + style: HazeStyle = HazeStyle.Unspecified, + configure: HazeEffectScope.() -> Unit = {}, +): Modifier { + val powerSavingEnabled = LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsState() + val hazeState = LocalHazeState.current + val isGlobalBlurEnabled = hazeState.blurEnabled && !powerSavingEnabled.value + val rootBackground by LocalRootBackgroundColor.current + + return hazeEffect(hazeState, style) { + fallbackTint = HazeTint(rootBackground) + if (isGlobalBlurEnabled) { + configure() + } + blurEnabled = isGlobalBlurEnabled + } +} + +/** + * Applies a haze source to the [Modifier] using the current global haze state. + */ +@Composable +fun Modifier.hazeSourceTangem(zIndex: Float = 0f, key: Any? = null) = + this.hazeSource(LocalHazeState.current, zIndex, key) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/powersaving/PowerSavingState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/powersaving/PowerSavingState.kt new file mode 100644 index 0000000000..54a3a42e9b --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/powersaving/PowerSavingState.kt @@ -0,0 +1,66 @@ +package com.tangem.core.ui.components.powersaving + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.PowerManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class PowerSavingState( + appContext: Context, + lifecycle: androidx.lifecycle.Lifecycle, +) { + + private val powerManager = + appContext.getSystemService(Context.POWER_SERVICE) as? PowerManager ?: error("PowerManager not available") + + val isPowerSavingModeEnabled: StateFlow + field = MutableStateFlow(powerManager.isPowerSaveMode) + + init { + val intentFilter = IntentFilter().apply { + addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) + } + + val receiver = PowerSavingModeReceiver() + + ContextCompat.registerReceiver( + appContext, + receiver, + intentFilter, + ContextCompat.RECEIVER_NOT_EXPORTED, + ) + + lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + appContext.unregisterReceiver(receiver) + } + }) + } + + inner class PowerSavingModeReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) { + isPowerSavingModeEnabled.value = powerManager.isPowerSaveMode + } + } + } +} + +@Composable +internal fun rememberPowerSavingState(): PowerSavingState { + val appContext = LocalContext.current.applicationContext + val lifecycle = LocalLifecycleOwner.current.lifecycle + return remember(appContext, lifecycle) { + PowerSavingState(appContext = appContext, lifecycle = lifecycle) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index a83759bff8..829a8ff0e1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -18,6 +18,8 @@ import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.LocalSystemBarsIconsController import com.tangem.core.ui.components.SystemBarsIconsController import com.tangem.core.ui.components.TangemShimmer +import com.tangem.core.ui.components.powersaving.PowerSavingState +import com.tangem.core.ui.components.powersaving.rememberPowerSavingState import com.tangem.core.ui.components.text.BladeAnimation import com.tangem.core.ui.components.text.rememberBladeAnimation import com.tangem.core.ui.haptic.DefaultHapticManager @@ -28,6 +30,7 @@ import com.tangem.core.ui.windowsize.WindowSize import com.tangem.core.ui.windowsize.rememberWindowSize import com.tangem.domain.apptheme.model.AppThemeMode import com.valentinilk.shimmer.Shimmer +import dev.chrisbanes.haze.HazeState @OptIn(ExperimentalFoundationApi::class) @Composable @@ -54,7 +57,13 @@ fun TangemTheme( overrideSystemBarColors = overrideSystemBarColors, typography = typography, dimens = dimens, - content = content, + content = { + if (uiDependencies.designFeatureToggles.isRedesignEnabled) { + TangemThemeRedesign { content() } + } else { + content() + } + }, ) } @@ -114,6 +123,7 @@ fun TangemTheme( LocalWindowSize provides windowSize, LocalBladeAnimation provides rememberBladeAnimation(), LocalSystemBarsIconsController provides systemBarsIconsController, + LocalPowerSavingState provides rememberPowerSavingState(), ) { CompositionLocalProvider( LocalTangemShimmer provides TangemShimmer, @@ -403,6 +413,18 @@ val LocalBladeAnimation = staticCompositionLocalOf { error("No MainBottomSheetColor provided") } +val LocalHazeState = staticCompositionLocalOf { + error("No HazeState provided") +} + +val LocalRedesignEnabled = staticCompositionLocalOf { + false +} + +val LocalPowerSavingState = compositionLocalOf { + error("No PowerSavingState provided") +} + /** * Determines whether the dark theme should be used based on the given [AppThemeMode]. * diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt index 8862de2500..efa104abf2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt @@ -2,8 +2,12 @@ package com.tangem.core.ui.res +import androidx.compose.foundation.layout.Box import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.haze.ProvideHaze +import com.tangem.core.ui.components.haze.hazeSourceTangem /** * Provides additional theming for redesigned components. @@ -21,12 +25,17 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) { colorScheme = tangemColorScheme(colors = themeColors), ) { CompositionLocalProvider( + LocalRedesignEnabled provides true, LocalTangemColors provides themeColors, LocalTangemColors2 provides if (LocalIsInDarkTheme.current) darkThemeColors2() else lightThemeColors2(), LocalTangemTypography2 provides TangemTypography2(InterFamily), LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) }, ) { - content() + ProvideHaze { + Box(Modifier.hazeSourceTangem(zIndex = 1f)) { + content() + } + } } } } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/cardDetails/TangemPayCardDetailsBlockComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/cardDetails/TangemPayCardDetailsBlockComponent.kt index 2714c0a662..9fb41015c0 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/cardDetails/TangemPayCardDetailsBlockComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/cardDetails/TangemPayCardDetailsBlockComponent.kt @@ -12,7 +12,7 @@ internal interface TangemPayCardDetailsBlockComponent { val state: StateFlow @Composable - fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) + fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier) data class Params(val params: TangemPayDetailsContainerComponent.Params) } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6acac86f00..02b196315c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -98,6 +98,7 @@ markdown = "0.7.2" markdownComposeView = "0.5.4" usedesk = "4.4.0" sumsub = "1.38.0" +haze = "1.7.1" # endregion Other libraries # region Tools @@ -302,4 +303,6 @@ markdown-composeview = { module = "com.github.jeziellago:compose-markdown", vers usedesk-chat-sdk = { module = "com.github.Usedesk.Android_SDK:chat-sdk", version.ref = "usedesk" } usedesk-chat-gui = { module = "com.github.Usedesk.Android_SDK:chat-gui", version.ref = "usedesk" } sumsub-sdk = { module = "com.sumsub.sns:idensic-mobile-sdk", version.ref = "sumsub" } +haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" } +haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" } # endregion Other