Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUM-5780:Use SurfaceCompoitionGroupMapper to support container components in SR #2182

Merged
merged 2 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ internal class ComposeWireframeMapper(
) {

private val composeMappers = mapOf<String, CompositionGroupMapper>(
"Text" to TextCompositionGroupMapper(colorStringFormatter),
"Button" to ButtonCompositionGroupMapper(colorStringFormatter),
"BottomNavigation" to SurfaceCompositionGroupMapper(colorStringFormatter),
"BottomNavigationItem" to TabCompositionGroupMapper(colorStringFormatter),
"TopAppBar" to SurfaceCompositionGroupMapper(colorStringFormatter),
"TabRow" to SurfaceCompositionGroupMapper(colorStringFormatter),
"Tab" to TabCompositionGroupMapper(colorStringFormatter),
"TabRow" to TabRowCompositionGroupMapper(colorStringFormatter)
"Text" to TextCompositionGroupMapper(colorStringFormatter),
"Button" to ButtonCompositionGroupMapper(colorStringFormatter)
)

// region WireframeMapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import com.datadog.android.sessionreplay.compose.internal.data.UiContext
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.android.sessionreplay.utils.ColorStringFormatter

internal class TabRowCompositionGroupMapper(
colorStringFormatter: ColorStringFormatter
) : AbstractCompositionGroupMapper(colorStringFormatter) {
/**
* This class is responsible for the mapping of the Jetpack Compose components which have "backgroundColor"
* & "contentColor" in the parameter list. These components are mostly container components such as
* [BottomNavigation], [TopAppBar] and [TabRow], etc. They usually have a [Surface] implementation under the hood,
* and can take other composable inside.
*/
internal open class SurfaceCompositionGroupMapper(colorStringFormatter: ColorStringFormatter) :
AbstractCompositionGroupMapper(colorStringFormatter) {
override fun map(
stableGroupId: Long,
parameters: Sequence<ComposableParameter>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ import kotlin.math.roundToInt
)
@MockitoSettings(strictness = Strictness.LENIENT)
@ForgeConfiguration(SessionReplayComposeForgeConfigurator::class)
internal class TabRowCompositionGroupMapperTest : AbstractCompositionGroupMapperTest() {
internal class SurfaceCompositionGroupMapperTest : AbstractCompositionGroupMapperTest() {

private lateinit var tabRowCompositionGroupMapper: TabRowCompositionGroupMapper
private lateinit var surfaceCompositionGroupMapper: SurfaceCompositionGroupMapper

private lateinit var mockCompositionGroup: CompositionGroup

private lateinit var fakeBoxWithDensity: Box

@BeforeEach
fun `set up`(forge: Forge) {
tabRowCompositionGroupMapper = TabRowCompositionGroupMapper(colorStringFormatter = mockColorStringFormatter)
surfaceCompositionGroupMapper = SurfaceCompositionGroupMapper(colorStringFormatter = mockColorStringFormatter)
mockCompositionGroup = mockGroupWithCoordinates(forge)
fakeBoxWithDensity = Box(
left = forge.aLong(),
Expand All @@ -67,7 +67,7 @@ internal class TabRowCompositionGroupMapperTest : AbstractCompositionGroupMapper
).thenReturn(contentColorHexStr)

// When
val actual = tabRowCompositionGroupMapper.map(
val actual = surfaceCompositionGroupMapper.map(
stableGroupId = fakeGroupId,
parameters = listOf(
ComposableParameter(
Expand Down Expand Up @@ -98,7 +98,7 @@ internal class TabRowCompositionGroupMapperTest : AbstractCompositionGroupMapper
convertColorIntAlpha(backgroundColor).second
)
).thenReturn(backgroundColorHexStr)
val actual = tabRowCompositionGroupMapper.map(
val actual = surfaceCompositionGroupMapper.map(
stableGroupId = fakeGroupId,
parameters = listOf(
ComposableParameter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,23 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon
import androidx.compose.material.Scaffold
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Email
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
Expand All @@ -37,83 +48,135 @@ import kotlinx.coroutines.launch
*/
class JetpackComposeActivity : AppCompatActivity() {

@Suppress("LongMethod")
@OptIn(ExperimentalPagerApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppCompatTheme {
Column {
val pages = remember {
listOf(Page.Navigation, Page.Interactions)
}

val pagerState = rememberPagerState()
AppScaffold()
}
}
}

val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
val rumMonitor = GlobalRumMonitor.get()
val screen = pages[pagerState.currentPage].trackingName
if (event == Lifecycle.Event.ON_RESUME) {
rumMonitor.startView(screen, screen)
} else if (event == Lifecycle.Event.ON_PAUSE) {
rumMonitor.stopView(screen)
}
}
@Composable
private fun NavigationBar() {
val selectedIndex = remember { mutableIntStateOf(1) }
BottomNavigation {
BottomNavigationItem(
selected = selectedIndex.intValue == 1,
onClick = {
selectedIndex.intValue = 1
},
icon = {
Icon(imageVector = Icons.Filled.Edit, contentDescription = null)
},
label = {
Text("label 1")
}
)

lifecycleOwner.lifecycle.addObserver(observer)
BottomNavigationItem(
selected = selectedIndex.intValue == 2,
onClick = {
selectedIndex.intValue = 2
},
icon = {
Icon(imageVector = Icons.Filled.Email, contentDescription = null)
},
label = {
Text("label 2")
}
)
}
}

onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
@Composable
@OptIn(ExperimentalPagerApi::class)
private fun AppScaffold() {
Scaffold(
topBar = {
TopAppBar(
title =
{
Text("Jetpack compose top bar")
}
)
},
bottomBar = {
NavigationBar()
}
) {
AppContent(modifier = Modifier.padding(it))
}
}

LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }
// drop 1st, because it will be tracked by the lifecycle
.drop(1)
.collect { page ->
val screen = pages[page].trackingName
GlobalRumMonitor.get().startView(screen, screen)
}
@Composable
@OptIn(ExperimentalPagerApi::class)
@Suppress("LongMethod")
private fun AppContent(modifier: Modifier = Modifier) {
Column(modifier) {
val pages = remember {
listOf(Page.Navigation, Page.Interactions)
}
val pagerState = rememberPagerState()
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
val rumMonitor = GlobalRumMonitor.get()
val screen = pages[pagerState.currentPage].trackingName
if (event == Lifecycle.Event.ON_RESUME) {
rumMonitor.startView(screen, screen)
} else if (event == Lifecycle.Event.ON_PAUSE) {
rumMonitor.stopView(screen)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}

TabRow(
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier.pagerTabIndicatorOffset(
pagerState,
tabPositions
),
height = TabRowDefaults.IndicatorHeight * 2
)
}
) {
val coroutineScope = rememberCoroutineScope()
pages.forEachIndexed { index, page ->
Tab(
text = { Text(page.name) },
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
}
)
}
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }
// drop 1st, because it will be tracked by the lifecycle
.drop(1)
.collect { page ->
val screen = pages[page].trackingName
GlobalRumMonitor.get().startView(screen, screen)
}
}

HorizontalPager(
count = pages.size,
state = pagerState
) { page ->
when (page) {
0 -> NavigationSampleView()
else -> InteractionSampleView()
TabRow(
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier.pagerTabIndicatorOffset(
pagerState,
tabPositions
),
height = TabRowDefaults.IndicatorHeight * 2
)
}
) {
val coroutineScope = rememberCoroutineScope()
pages.forEachIndexed { index, page ->
Tab(
text = { Text(page.name) },
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
}
}
)
}
}
HorizontalPager(
count = pages.size,
state = pagerState
) { page ->
when (page) {
0 -> NavigationSampleView()
else -> InteractionSampleView()
}
}
}
Expand Down