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

🔧 We have made modifications so that the language switching switch can be pressed even on small devices with low resolution. #956

Closed
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 @@ -23,6 +23,7 @@ import io.github.droidkaigi.confsched.model.TimetableItemId
import io.github.droidkaigi.confsched.model.TimetableSessionType.NORMAL
import io.github.droidkaigi.confsched.model.localSessionsRepository
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailEvent.Bookmark
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailEvent.EndTransitionAnimation
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailEvent.FavoriteListNavigated
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailEvent.SelectDescriptionLanguage
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenUiState.Loaded
Expand All @@ -34,6 +35,7 @@ sealed interface TimetableItemDetailEvent {
data class Bookmark(val timetableItem: TimetableItem) : TimetableItemDetailEvent
data class SelectDescriptionLanguage(val language: Lang) : TimetableItemDetailEvent
data object FavoriteListNavigated : TimetableItemDetailEvent
data object EndTransitionAnimation : TimetableItemDetailEvent
}

@Composable
Expand All @@ -52,6 +54,7 @@ fun timetableItemDetailPresenter(
)
var selectedDescriptionLanguage by rememberRetained { mutableStateOf<Lang?>(null) }
var shouldGoToFavoriteList by remember { mutableStateOf(false) }
var isEndTransitionAnimation by remember { mutableStateOf(false) }
val bookmarkedSuccessfullyString = stringResource(SessionsRes.string.bookmarked_successfully)
val viewBookmarkListString = stringResource(SessionsRes.string.view_bookmark_list)

Expand Down Expand Up @@ -82,6 +85,10 @@ fun timetableItemDetailPresenter(
is FavoriteListNavigated -> {
shouldGoToFavoriteList = false
}

is EndTransitionAnimation -> {
isEndTransitionAnimation = true
}
}
}
SafeLaunchedEffect(timetableItemStateWithBookmark?.first) {
Expand All @@ -101,6 +108,7 @@ fun timetableItemDetailPresenter(
timetableItemDetailSectionUiState = TimetableItemDetailSectionUiState(timetableItem),
isBookmarked = bookmarked,
isLangSelectable = timetableItem.sessionType == NORMAL,
isEndTransitionAnimation = isEndTransitionAnimation,
currentLang = selectedDescriptionLanguage,
roomThemeKey = timetableItem.room.getThemeKey(),
timetableItemId = timetableItemId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
Expand Down Expand Up @@ -53,6 +54,8 @@ import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailHead
import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailSummaryCard
import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailTopAppBar
import io.github.droidkaigi.confsched.sessions.navigation.TimetableItemDetailDestination
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import org.jetbrains.compose.ui.tooling.preview.Preview

const val timetableItemDetailScreenRouteItemIdParameterName = "timetableItemId"
Expand Down Expand Up @@ -124,6 +127,9 @@ fun TimetableItemDetailScreen(
onSelectedLanguage = {
eventFlow.tryEmit(TimetableItemDetailEvent.SelectDescriptionLanguage(it))
},
onEndTransitionAnimation = {
eventFlow.tryEmit(TimetableItemDetailEvent.EndTransitionAnimation)
},
snackbarHostState = snackbarHostState,
)
}
Expand All @@ -139,6 +145,7 @@ sealed interface TimetableItemDetailScreenUiState {
val timetableItemDetailSectionUiState: TimetableItemDetailSectionUiState,
val isBookmarked: Boolean,
val isLangSelectable: Boolean,
val isEndTransitionAnimation: Boolean,
val currentLang: Lang?,
val roomThemeKey: String,
val shouldGoToFavoriteList: Boolean,
Expand All @@ -164,11 +171,24 @@ private fun TimetableItemDetailScreen(
onCalendarRegistrationClick: (TimetableItem) -> Unit,
onShareClick: (TimetableItem) -> Unit,
onSelectedLanguage: (Lang) -> Unit,
onEndTransitionAnimation: () -> Unit,
snackbarHostState: SnackbarHostState,
) {
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedScope = LocalAnimatedVisibilityScope.current

if (
sharedTransitionScope != null &&
animatedScope != null
) {
LaunchedEffect(sharedTransitionScope, animatedScope) {
snapshotFlow { animatedScope.transition.isRunning }
.filter { it.not() }
.distinctUntilChanged()
.collect { onEndTransitionAnimation() }
}
}
Comment on lines +180 to +190
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the display of the language switching switch is not modified so that it is displayed after the animation finishes, in some cases the switch on the Row side is displayed after it is displayed on the Column side for a moment.
So, it was necessary to add a process to detect when the animation had finished.


val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Scaffold(
modifier = Modifier
Expand Down Expand Up @@ -230,6 +250,7 @@ private fun TimetableItemDetailScreen(
currentLang = uiState.currentLang,
timetableItem = uiState.timetableItem,
isLangSelectable = uiState.isLangSelectable,
isAnimationFinished = uiState.isEndTransitionAnimation,
onLanguageSelect = onSelectedLanguage,
)
}
Expand Down Expand Up @@ -292,6 +313,7 @@ fun TimetableItemDetailScreenPreview() {
),
isBookmarked = isBookMarked,
isLangSelectable = true,
isEndTransitionAnimation = true,
currentLang = Lang.JAPANESE,
roomThemeKey = "iguana",
timetableItemId = fakeSession.id,
Expand All @@ -306,6 +328,7 @@ fun TimetableItemDetailScreenPreview() {
onCalendarRegistrationClick = {},
onShareClick = {},
onSelectedLanguage = {},
onEndTransitionAnimation = {},
snackbarHostState = SnackbarHostState(),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.droidkaigi.confsched.sessions.component

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand All @@ -25,9 +27,16 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.CollectionInfo
import androidx.compose.ui.semantics.CollectionItemInfo
Expand Down Expand Up @@ -60,36 +69,73 @@ fun TimetableItemDetailHeadline(
currentLang: Lang?,
timetableItem: TimetableItem,
isLangSelectable: Boolean,
isAnimationFinished: Boolean,
onLanguageSelect: (Lang) -> Unit,
modifier: Modifier = Modifier,
) {
val currentLang = currentLang ?: timetableItem.language.toLang()
var rowWidth by remember { mutableStateOf(0) }
var currentLangTagWidth by remember { mutableStateOf(0) }
val languageWidths = remember { mutableStateListOf<Int>() }
val remainingWidth by remember(rowWidth, currentLangTagWidth, languageWidths) {
derivedStateOf { rowWidth - (currentLangTagWidth + languageWidths.sum()) }
}
val hasSpaceForLanguageSwitcher by remember(remainingWidth) {
derivedStateOf { remainingWidth >= 390 }
}

Column(
modifier = modifier
// FIXME: Implement and use a theme color instead of fixed colors like RoomColors.primary and RoomColors.primaryDim
.background(LocalRoomTheme.current.dimColor)
.padding(horizontal = 8.dp)
.fillMaxWidth(),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
TimetableItemTag(
tagText = timetableItem.room.name.currentLangTitle,
tagColor = LocalRoomTheme.current.primaryColor,
)
timetableItem.language.labels.forEach { label ->
Spacer(modifier = Modifier.padding(4.dp))
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.onSizeChanged { size ->
rowWidth = size.width
},
) {
TimetableItemTag(
tagText = label,
tagColor = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.onSizeChanged { size ->
currentLangTagWidth = size.width
},
tagText = timetableItem.room.name.currentLangTitle,
tagColor = LocalRoomTheme.current.primaryColor,
)
}
Spacer(modifier = Modifier.weight(1f))
if (isLangSelectable) {
timetableItem.language.labels.forEachIndexed { index, label ->
Spacer(modifier = Modifier.padding(4.dp))
TimetableItemTag(
modifier = Modifier
.onSizeChanged { size ->
if (index < languageWidths.size) {
languageWidths[index] = size.width
} else {
languageWidths.add(size.width)
}
},
tagText = label,
tagColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Spacer(modifier = Modifier.weight(1f))
LanguageSwitcher(
currentLang = currentLang,
onLanguageSelect = onLanguageSelect,
isVisible = isAnimationFinished && isLangSelectable && hasSpaceForLanguageSwitcher,
)
}
LanguageSwitcher(
currentLang = currentLang,
onLanguageSelect = onLanguageSelect,
isVisible = isAnimationFinished && isLangSelectable && hasSpaceForLanguageSwitcher.not(),
)
Comment on lines 128 to +138
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the LanguageSwitcher lined up like this is not a very good process. 🤔
If there is a better implementation, I will follow that. 🫡

}
Spacer(modifier = Modifier.height(16.dp))
Text(
Expand Down Expand Up @@ -133,6 +179,7 @@ fun TimetableItemDetailHeadline(
private fun LanguageSwitcher(
currentLang: Lang,
onLanguageSelect: (Lang) -> Unit,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
val normalizedCurrentLang = if (currentLang == Lang.MIXED) {
Expand All @@ -145,7 +192,11 @@ private fun LanguageSwitcher(
stringResource(SessionsRes.string.english) to Lang.ENGLISH,
)
val switcherContentDescription = stringResource(SessionsRes.string.select_language)
Row(

AnimatedVisibility(
visible = isVisible,
enter = fadeIn(),
exit = ExitTransition.None,
Comment on lines +196 to +199
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no animation, the language switch suddenly appears after the page display animation is finished, so it doesn't look very good. 🤔
So I've added a fade-in animation.

modifier = modifier
.selectableGroup()
.semantics {
Expand All @@ -155,52 +206,64 @@ private fun LanguageSwitcher(
columnCount = 1,
)
},
verticalAlignment = Alignment.CenterVertically,
) {
val lastIndex = availableLangs.size - 1
availableLangs.entries.forEachIndexed { index, (label, lang) ->
val isSelected = normalizedCurrentLang == lang
TextButton(
onClick = { onLanguageSelect(lang) },
modifier = Modifier
.semantics {
role = Role.Tab
selected = isSelected
collectionItemInfo = CollectionItemInfo(
rowIndex = index,
rowSpan = 1,
columnIndex = 0,
columnSpan = 1,
Row(
modifier = Modifier
.selectableGroup()
.semantics {
contentDescription = switcherContentDescription
collectionInfo = CollectionInfo(
rowCount = availableLangs.size,
columnCount = 1,
)
},
verticalAlignment = Alignment.CenterVertically,
) {
val lastIndex = availableLangs.size - 1
availableLangs.entries.forEachIndexed { index, (label, lang) ->
val isSelected = normalizedCurrentLang == lang
TextButton(
onClick = { onLanguageSelect(lang) },
modifier = Modifier
.semantics {
role = Role.Tab
selected = isSelected
collectionItemInfo = CollectionItemInfo(
rowIndex = index,
rowSpan = 1,
columnIndex = 0,
columnSpan = 1,
)
},
contentPadding = PaddingValues(12.dp),
) {
val contentColor = if (isSelected) {
LocalRoomTheme.current.primaryColor
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
AnimatedVisibility(isSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier
.padding(end = 4.dp)
.size(12.dp),
tint = contentColor,
)
},
contentPadding = PaddingValues(12.dp),
) {
val contentColor = if (isSelected) {
LocalRoomTheme.current.primaryColor
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
Text(
text = label,
color = contentColor,
style = MaterialTheme.typography.labelMedium,
)
}
AnimatedVisibility(isSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier
.padding(end = 4.dp)
.size(12.dp),
tint = contentColor,
if (index < lastIndex) {
VerticalDivider(
modifier = Modifier.height(11.dp),
color = MaterialTheme.colorScheme.outlineVariant,
)
}
Text(
text = label,
color = contentColor,
style = MaterialTheme.typography.labelMedium,
)
}
if (index < lastIndex) {
VerticalDivider(
modifier = Modifier.height(11.dp),
color = MaterialTheme.colorScheme.outlineVariant,
)
}
}
}
Expand All @@ -216,6 +279,7 @@ fun TimetableItemDetailHeadlinePreview() {
timetableItem = TimetableItem.Session.fake(),
currentLang = Lang.JAPANESE,
isLangSelectable = true,
isAnimationFinished = true,
onLanguageSelect = {},
)
}
Expand All @@ -233,6 +297,7 @@ fun TimetableItemDetailHeadlineWithEnglishPreview() {
timetableItem = TimetableItem.Session.fake(),
currentLang = Lang.ENGLISH,
isLangSelectable = true,
isAnimationFinished = true,
onLanguageSelect = {},
)
}
Expand All @@ -250,6 +315,7 @@ fun TimetableItemDetailHeadlineWithMixedPreview() {
timetableItem = TimetableItem.Session.fake(),
currentLang = Lang.MIXED,
isLangSelectable = true,
isAnimationFinished = true,
onLanguageSelect = {},
)
}
Expand Down
Loading