-
Notifications
You must be signed in to change notification settings - Fork 200
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
@@ -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 | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 🤔 |
||
} | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
Text( | ||
|
@@ -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) { | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 🤔 |
||
modifier = modifier | ||
.selectableGroup() | ||
.semantics { | ||
|
@@ -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, | ||
) | ||
} | ||
} | ||
} | ||
|
@@ -216,6 +279,7 @@ fun TimetableItemDetailHeadlinePreview() { | |
timetableItem = TimetableItem.Session.fake(), | ||
currentLang = Lang.JAPANESE, | ||
isLangSelectable = true, | ||
isAnimationFinished = true, | ||
onLanguageSelect = {}, | ||
) | ||
} | ||
|
@@ -233,6 +297,7 @@ fun TimetableItemDetailHeadlineWithEnglishPreview() { | |
timetableItem = TimetableItem.Session.fake(), | ||
currentLang = Lang.ENGLISH, | ||
isLangSelectable = true, | ||
isAnimationFinished = true, | ||
onLanguageSelect = {}, | ||
) | ||
} | ||
|
@@ -250,6 +315,7 @@ fun TimetableItemDetailHeadlineWithMixedPreview() { | |
timetableItem = TimetableItem.Session.fake(), | ||
currentLang = Lang.MIXED, | ||
isLangSelectable = true, | ||
isAnimationFinished = true, | ||
onLanguageSelect = {}, | ||
) | ||
} | ||
|
There was a problem hiding this comment.
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.