diff --git a/core/droidkaigiui/src/commonMain/kotlin/io/github/droidkaigi/confsched/droidkaigiui/component/AnimatedMediumTopAppBar.kt b/core/droidkaigiui/src/commonMain/kotlin/io/github/droidkaigi/confsched/droidkaigiui/component/AnimatedMediumTopAppBar.kt new file mode 100644 index 000000000..86635b829 --- /dev/null +++ b/core/droidkaigiui/src/commonMain/kotlin/io/github/droidkaigi/confsched/droidkaigiui/component/AnimatedMediumTopAppBar.kt @@ -0,0 +1,108 @@ +package io.github.droidkaigi.confsched.droidkaigiui.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.fadeIn +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons.AutoMirrored.Filled +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MediumTopAppBar +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AnimatedMediumTopAppBar( + title: String, + onBackClick: () -> Unit, + navIconContentDescription: String?, + modifier: Modifier = Modifier, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, + colors: TopAppBarColors = TopAppBarDefaults.largeTopAppBarColors().copy( + scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer, + ), + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + val density = LocalDensity.current.density + var navigationIconWidthDp by remember { mutableStateOf(0f) } + val isCenterTitle = remember(scrollBehavior?.state?.collapsedFraction) { + if (scrollBehavior == null) { + // Always left-align when scrollBehavior is not present + false + } else { + // Hide title position because it doesn't look smooth if it is displayed when collapsing + when (scrollBehavior.state.collapsedFraction) { + in 0.7f..1.0f -> true + in 0.0f..0.5f -> false + else -> null // Don't display while on the move. + } + } + } + + MediumTopAppBar( + title = { + AnimatedVisibility( + visible = isCenterTitle != null, + enter = fadeIn(), + // No animation required as it is erased with alpha + exit = ExitTransition.None, + ) { + Text( + text = title, + modifier = Modifier.then( + when (isCenterTitle) { + true -> { + Modifier + .padding(end = navigationIconWidthDp.dp) + .fillMaxWidth() + } + false -> Modifier + null -> Modifier.alpha(0f) + }, + ), + textAlign = TextAlign.Center, + ) + } + }, + modifier = modifier, + navigationIcon = { + IconButton( + modifier = Modifier + .onGloballyPositioned { + navigationIconWidthDp = it.size.width / density + }, + onClick = onBackClick, + ) { + Icon( + imageVector = Filled.ArrowBack, + contentDescription = navIconContentDescription, + ) + } + }, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior, + ) +} diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SponsorsScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SponsorsScreenRobot.kt index f6f27e7d7..4810b81a7 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SponsorsScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/SponsorsScreenRobot.kt @@ -67,6 +67,14 @@ class SponsorsScreenRobot @Inject constructor( ) } + fun scrollBottom() { + composeTestRule + .onNode(hasTestTag(SponsorsListLazyVerticalGridTestTag)) + .performScrollToNode( + hasTestTag(SponsorsListSponsorItemTestTagPrefix.plus(Sponsor.fakes().last().name)), + ) + } + fun checkDisplayPlatinumSponsors() { checkSponsorItemsDisplayedByRangeAndSponsorType( sponsorType = SponsorType.Platinum, @@ -92,7 +100,8 @@ class SponsorsScreenRobot @Inject constructor( sponsorType: SponsorType, fromTo: IntRange, ) { - val sponsorList = Sponsor.fakes().filter { it.plan.toSponsorType() == sponsorType }.subList(fromTo.first, fromTo.last) + val sponsorList = Sponsor.fakes().filter { it.plan.toSponsorType() == sponsorType } + .subList(fromTo.first, fromTo.last) sponsorList.forEach { sponsor -> composeTestRule .onNode(hasTestTag(SponsorsListSponsorItemTestTagPrefix.plus(sponsor.name))) diff --git a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt index de6a45ca0..c6902b369 100644 --- a/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt +++ b/feature/contributors/src/commonMain/kotlin/io/github/droidkaigi/confsched/contributors/ContributorsScreen.kt @@ -24,7 +24,7 @@ import io.github.droidkaigi.confsched.compose.rememberEventFlow import io.github.droidkaigi.confsched.contributors.component.ContributorsItem import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder -import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedLargeTopAppBar +import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedMediumTopAppBar import io.github.droidkaigi.confsched.model.Contributor import kotlinx.collections.immutable.PersistentList import org.jetbrains.compose.resources.stringResource @@ -100,7 +100,7 @@ fun ContributorsScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { if (!isTopAppBarHidden) { - AnimatedLargeTopAppBar( + AnimatedMediumTopAppBar( title = stringResource(ContributorsRes.string.contributor_title), onBackClick = onBackClick, scrollBehavior = scrollBehavior, diff --git a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt index 161a12a5c..985e9ba92 100644 --- a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt +++ b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt @@ -833,10 +833,9 @@ internal fun CardScreen( text = stringResource(ProfileCardRes.string.edit), modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.labelLarge, - color = Color.Black + color = Color.Black, ) } - } } } diff --git a/feature/settings/src/commonMain/kotlin/io/github/droidkaigi/confsched/settings/SettingsScreen.kt b/feature/settings/src/commonMain/kotlin/io/github/droidkaigi/confsched/settings/SettingsScreen.kt index c54f04d7a..e60ac4f8d 100644 --- a/feature/settings/src/commonMain/kotlin/io/github/droidkaigi/confsched/settings/SettingsScreen.kt +++ b/feature/settings/src/commonMain/kotlin/io/github/droidkaigi/confsched/settings/SettingsScreen.kt @@ -24,7 +24,7 @@ import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolderImpl -import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedLargeTopAppBar +import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedMediumTopAppBar import io.github.droidkaigi.confsched.droidkaigiui.plus import io.github.droidkaigi.confsched.model.FontFamily import io.github.droidkaigi.confsched.settings.section.accessibility @@ -96,7 +96,7 @@ fun SettingsScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { if (!isTopAppBarHidden) { - AnimatedLargeTopAppBar( + AnimatedMediumTopAppBar( title = stringResource(SettingsRes.string.settings_title), onBackClick = onBackClick, scrollBehavior = scrollBehavior, diff --git a/feature/sponsors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreenTest.kt b/feature/sponsors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreenTest.kt index 5ce968c4c..b585caa42 100644 --- a/feature/sponsors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreenTest.kt +++ b/feature/sponsors/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreenTest.kt @@ -65,9 +65,9 @@ class SponsorsScreenTest( } } - describe("when scroll to supporters header") { + describe("when scroll to scroll Bottom") { doIt { - scrollToSupportersSponsorsHeader() + scrollBottom() } itShould("display supporters sponsors") { captureScreenWithChecks { diff --git a/feature/sponsors/src/commonMain/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreen.kt b/feature/sponsors/src/commonMain/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreen.kt index e424d7411..16ddee39c 100644 --- a/feature/sponsors/src/commonMain/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreen.kt +++ b/feature/sponsors/src/commonMain/kotlin/io/github/droidkaigi/confsched/sponsors/SponsorsScreen.kt @@ -22,7 +22,7 @@ import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolderImpl -import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedLargeTopAppBar +import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedMediumTopAppBar import io.github.droidkaigi.confsched.model.Plan.GOLD import io.github.droidkaigi.confsched.model.Plan.PLATINUM import io.github.droidkaigi.confsched.model.Plan.SUPPORTER @@ -106,7 +106,7 @@ fun SponsorsScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { if (!isTopAppBarHidden) { - AnimatedLargeTopAppBar( + AnimatedMediumTopAppBar( title = stringResource(SponsorsRes.string.sponsor), onBackClick = onBackClick, scrollBehavior = scrollBehavior, diff --git a/feature/staff/src/commonMain/kotlin/io/github/droidkaigi/confsched/staff/StaffScreen.kt b/feature/staff/src/commonMain/kotlin/io/github/droidkaigi/confsched/staff/StaffScreen.kt index 4d2a02db1..912d69fcc 100644 --- a/feature/staff/src/commonMain/kotlin/io/github/droidkaigi/confsched/staff/StaffScreen.kt +++ b/feature/staff/src/commonMain/kotlin/io/github/droidkaigi/confsched/staff/StaffScreen.kt @@ -26,7 +26,7 @@ import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolderImpl -import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedLargeTopAppBar +import io.github.droidkaigi.confsched.droidkaigiui.component.AnimatedMediumTopAppBar import io.github.droidkaigi.confsched.model.Staff import io.github.droidkaigi.confsched.model.fakes import io.github.droidkaigi.confsched.staff.component.StaffItem @@ -103,7 +103,7 @@ fun StaffScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { if (!isTopAppBarHidden) { - AnimatedLargeTopAppBar( + AnimatedMediumTopAppBar( title = stringResource(StaffRes.string.staff_title), onBackClick = onBackClick, scrollBehavior = scrollBehavior,