Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-11 15:29:17 +04:00
parent ba4ceb2e8b
commit 69aeaf5776
12 changed files with 96 additions and 23 deletions

View file

@ -0,0 +1,61 @@
package com.tangem.tap
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.utils.Provider
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class DecomposeFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val component by lazy(mode = LazyThreadSafetyMode.NONE) {
requireNotNull(componentBuilder?.build()) {
"Component builder is not set, call newInstance() for DecomposeFragment creation first."
}
}
@Composable
override fun ScreenContent(modifier: Modifier) {
component.Content(modifier)
EventMessageEffect(
messageHandler = uiDependencies.eventMessageHandler,
snackbarHostState = uiDependencies.globalSnackbarHostState,
)
}
private class ComponentBuilder<C : ComposableContentComponent, P : Any, F : ComponentFactory<C, P>>(
private val contextProvider: Provider<AppComponentContext>,
private val params: P,
private val componentFactory: F,
) {
fun build(): C = componentFactory.create(contextProvider(), params)
}
companion object {
private var componentBuilder: ComponentBuilder<*, *, *>? = null
fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<C, P>> newInstance(
contextProvider: Provider<AppComponentContext>,
params: P,
componentFactory: F,
): Fragment {
this@Companion.componentBuilder = ComponentBuilder(contextProvider, params, componentFactory)
return DecomposeFragment()
}
}
}

View file

@ -7,8 +7,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.core.ui.UiDependencies
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -16,7 +15,6 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import timber.log.Timber
@Module
@InstallIn(ActivityComponent::class)
@ -29,17 +27,11 @@ internal object RootAppComponentContextModule {
@ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder,
uiDependencies: UiDependencies,
): AppComponentContext {
// TODO: Implement message handler
val dummyMessageHandler = object : UiMessageHandler {
override fun handleMessage(message: UiMessage) {
Timber.w("Unable to handle message: $message")
}
}
return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(),
messageHandler = dummyMessageHandler,
messageHandler = uiDependencies.eventMessageHandler,
dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder,
)

View file

@ -1,7 +1,9 @@
package com.tangem.tap.di
import androidx.compose.material3.SnackbarHostState
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.theme.AppThemeModeHolder
import dagger.Module
import dagger.Provides
@ -19,6 +21,8 @@ internal object UiDependenciesModule {
return object : UiDependencies {
override val hapticManager = hapticManager
override val appThemeModeHolder = appThemeModeHolder
override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState()
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
}
}
}

View file

@ -5,7 +5,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@Stable
fun interface ComposableContentProvider {
fun interface ComposableContentComponent {
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check

View file

@ -1,11 +1,19 @@
package com.tangem.core.ui
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Stable
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.theme.AppThemeModeHolder
@Stable
interface UiDependencies {
val hapticManager: HapticManager
val appThemeModeHolder: AppThemeModeHolder
val globalSnackbarHostState: SnackbarHostState
val eventMessageHandler: EventMessageHandler
}

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.message
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.core.ui.event.StateEvent
@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
/**
* Message handler that is used to show or remove an [EventMessage] in the UI.
*/
@Stable
class EventMessageHandler(
private val events: MutableStateFlow<StateEvent<EventMessage>> = MutableStateFlow(consumedEvent()),
) : UiMessageHandler, StateFlow<StateEvent<EventMessage>> by events {

View file

@ -3,19 +3,18 @@ package com.tangem.core.ui.res
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material3.SnackbarHostState
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
@Composable
fun TangemTheme(
isDark: Boolean = false,
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
hapticManager: HapticManager = MockHapticManager,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
content: @Composable () -> Unit,
) {
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
@ -33,6 +32,7 @@ fun TangemTheme(
LocalTangemShapes provides shapes,
LocalIsInDarkTheme provides isDark,
LocalHapticManager provides hapticManager,
LocalSnackbarHostState provides snackbarHostState,
) {
ProvideTextStyle(
value = TangemTheme.typography.body1,
@ -204,4 +204,8 @@ val LocalIsInDarkTheme = staticCompositionLocalOf { false }
val LocalHapticManager = staticCompositionLocalOf<HapticManager> {
error("No HapticManager provided")
}
val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
error("No SnackbarHostState provided")
}

View file

@ -58,6 +58,7 @@ internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
TangemTheme(
isDark = shouldUseDarkTheme(appThemeMode),
hapticManager = uiDependencies.hapticManager,
snackbarHostState = uiDependencies.globalSnackbarHostState,
) {
ScreenContent(modifier = screenModifier)
}

View file

@ -2,10 +2,10 @@ package com.tangem.features.details.component
import androidx.compose.material3.SnackbarHostState
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.ComposableContentProvider
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
interface DetailsComponent : ComposableContentProvider {
interface DetailsComponent : ComposableContentComponent {
val snackbarHostState: SnackbarHostState

View file

@ -1,9 +1,9 @@
package com.tangem.features.details.component
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.ComposableContentProvider
import com.tangem.core.ui.ComposableContentComponent
interface UserWalletListComponent : ComposableContentProvider {
interface UserWalletListComponent : ComposableContentComponent {
interface Factory {
fun create(context: AppComponentContext): UserWalletListComponent

View file

@ -18,7 +18,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.ComposableContentProvider
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.res.TangemTheme
@ -36,7 +36,7 @@ private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
internal fun DetailsScreen(
state: DetailsUM,
snackbarHostState: SnackbarHostState,
userWalletListBlockContent: ComposableContentProvider,
userWalletListBlockContent: ComposableContentComponent,
modifier: Modifier = Modifier,
) {
val backgroundColor = TangemTheme.colors.background.secondary
@ -118,7 +118,7 @@ private fun TopBar(state: DetailsUM, scrollBehavior: TopAppBarScrollBehavior, mo
@Composable
private fun Content(
state: DetailsUM,
userWalletListBlockContent: ComposableContentProvider,
userWalletListBlockContent: ComposableContentComponent,
modifier: Modifier = Modifier,
) {
LazyColumn(
@ -152,7 +152,7 @@ private fun Content(
@Composable
private fun Block(
model: DetailsItemUM,
userWalletListBlockContent: ComposableContentProvider,
userWalletListBlockContent: ComposableContentComponent,
modifier: Modifier = Modifier,
) {
Column(

View file

@ -42,6 +42,7 @@ dependencies {
/** Compose libraries */
implementation(deps.compose.material)
implementation(deps.compose.material3)
implementation(deps.compose.animation)
implementation(deps.compose.foundation)
implementation(deps.compose.ui)