diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index e4f93a0b8a..894683f3f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -393,6 +393,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools Icon( painterResource(MR.images.ic_arrow_forward), contentDescription = null, + modifier = Modifier.mirrorIfRtl(), tint = CurrentColors.value.colors.secondary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt index 5f7aec3dd6..4b56c0c090 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt @@ -25,6 +25,7 @@ import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import chat.simplex.common.views.chat.group.* import chat.simplex.common.views.chat.item.sendCommandMsg import chat.simplex.common.views.helpers.commandMenuAnimSpec +import chat.simplex.common.views.helpers.mirrorIfRtl import chat.simplex.res.* import dev.icerock.moko.resources.compose.painterResource import kotlinx.coroutines.launch @@ -126,6 +127,7 @@ fun CommandsMenuView( Icon( painterResource(MR.images.ic_arrow_back_ios_new), contentDescription = null, + modifier = Modifier.mirrorIfRtl(), tint = MaterialTheme.colors.secondary ) Spacer(Modifier.width(DEFAULT_PADDING_HALF)) @@ -205,6 +207,7 @@ fun CommandsMenuView( Icon( painterResource(MR.images.ic_chevron_right), contentDescription = null, + modifier = Modifier.mirrorIfRtl(), tint = MaterialTheme.colors.secondary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt index e318f406de..837a23e2b6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.platform.appPreferences import chat.simplex.common.ui.theme.isInDarkTheme +import chat.simplex.common.views.helpers.mirrorIfRtl import chat.simplex.res.* import kotlinx.datetime.Clock @@ -92,7 +93,7 @@ private fun CIMetaText( } if (showViaProxy && meta.sentViaProxy == true) { Spacer(Modifier.width(4.dp)) - Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp).mirrorIfRtl(), tint = MaterialTheme.colors.secondary) } if (showStatus) { Spacer(Modifier.width(4.dp)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 822aef9aa2..a70e121654 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -1247,7 +1247,7 @@ fun Modifier.clipChatItem(chatItem: ChatItem? = null, tailVisible: Boolean = fal } } -private fun chatItemShape(roundness: Float, density: Density, tailVisible: Boolean, sent: Boolean = false): GenericShape = GenericShape { size, _ -> +private fun chatItemShape(roundness: Float, density: Density, tailVisible: Boolean, sent: Boolean = false): GenericShape = GenericShape { size, layoutDirection -> val (msgTailWidth, msgBubbleMaxRadius) = with(density) { Pair(msgTailWidthDp.toPx(), msgBubbleMaxRadius.toPx()) } val width = size.width val height = size.height @@ -1299,7 +1299,10 @@ private fun chatItemShape(roundness: Float, density: Density, tailVisible: Boole quadraticBezierTo(bubbleInitialX, 0f, bubbleInitialX + rx, 0f) // Top-left corner } - if (sent) { + // The default path draws the tail at the bottom-left corner. We mirror the path so the tail + // ends up on the side closer to the message author's profile in chat: right for sent in LTR, + // left for received in LTR — and the opposite under RTL where chat sides are mirrored. + if (sent != (layoutDirection == LayoutDirection.Rtl)) { val matrix = Matrix() matrix.scale(-1f, 1f) this.transform(matrix) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index fd422b649a..663d422605 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -645,9 +645,11 @@ fun CenteredRowLayout( val second = measureable[1].measure(constraints.copy(minWidth = 0, minHeight = 0, maxWidth = (constraints.maxWidth - first.measuredWidth - third.measuredWidth).coerceAtLeast(0))) // Limit width for every other element to width of important element and height for a sum of all elements. layout(constraints.maxWidth, constraints.maxHeight) { - first.place(0, ((constraints.maxHeight - first.measuredHeight) / 2).coerceAtLeast(0)) - second.place((constraints.maxWidth - second.measuredWidth) / 2, ((constraints.maxHeight - second.measuredHeight) / 2).coerceAtLeast(0)) - third.place(constraints.maxWidth - third.measuredWidth, ((constraints.maxHeight - third.measuredHeight) / 2).coerceAtLeast(0)) + // placeRelative mirrors x under RTL so the leading slot (first) and trailing slot (third) + // swap visual sides automatically when the locale flips. + first.placeRelative(0, ((constraints.maxHeight - first.measuredHeight) / 2).coerceAtLeast(0)) + second.placeRelative((constraints.maxWidth - second.measuredWidth) / 2, ((constraints.maxHeight - second.measuredHeight) / 2).coerceAtLeast(0)) + third.placeRelative(constraints.maxWidth - third.measuredWidth, ((constraints.maxHeight - third.measuredHeight) / 2).coerceAtLeast(0)) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 1c2ef93a09..7a10ab0037 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -128,7 +128,7 @@ fun CallAppBar( fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, height: Dp = 24.dp) { IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) { Icon( - painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), Modifier.height(height), tint = tintColor + painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), Modifier.height(height).mirrorIfRtl(), tint = tintColor ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt index 8ad877d879..777f890b37 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Modifiers.kt @@ -5,12 +5,19 @@ import androidx.compose.foundation.layout.offset import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import kotlin.math.roundToInt +// Horizontally flips an asymmetric directional drawable (back/forward arrows, chevrons, signal +// waves) when the layout direction is RTL. Apply to the Icon's modifier at each call site. +@Composable +fun Modifier.mirrorIfRtl(): Modifier = + if (LocalLayoutDirection.current == LayoutDirection.Rtl) this.scale(scaleX = -1f, scaleY = 1f) else this + fun Modifier.badgeLayout() = layout { measurable, constraints -> val placeable = measurable.measure(constraints) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt index 7f8b902b94..243d7b4dcc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt @@ -16,26 +16,26 @@ fun SubscriptionStatusIcon( ) { @Composable fun ZeroIcon() { - Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color.copy(alpha = 0.33f), modifier = modifier) + Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color.copy(alpha = 0.33f), modifier = modifier.mirrorIfRtl()) } when { variableValue <= 0f -> ZeroIcon() variableValue > 0f && variableValue <= 0.25f -> Box { ZeroIcon() - Icon(painterResource(MR.images.ic_radiowaves_up_forward_1_bar), null, tint = color, modifier = modifier) + Icon(painterResource(MR.images.ic_radiowaves_up_forward_1_bar), null, tint = color, modifier = modifier.mirrorIfRtl()) } variableValue > 0.25f && variableValue <= 0.5f -> Box { ZeroIcon() - Icon(painterResource(MR.images.ic_radiowaves_up_forward_2_bar), null, tint = color, modifier = modifier) + Icon(painterResource(MR.images.ic_radiowaves_up_forward_2_bar), null, tint = color, modifier = modifier.mirrorIfRtl()) } variableValue > 0.5f && variableValue <= 0.75f -> Box { ZeroIcon() - Icon(painterResource(MR.images.ic_radiowaves_up_forward_3_bar), null, tint = color, modifier = modifier) + Icon(painterResource(MR.images.ic_radiowaves_up_forward_3_bar), null, tint = color, modifier = modifier.mirrorIfRtl()) } - else -> Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color, modifier = modifier) + else -> Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color, modifier = modifier.mirrorIfRtl()) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 29819f21cb..d4cfeab2c7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -570,6 +570,7 @@ private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contact painter = painterResource(MR.images.ic_arrow_forward_ios), contentDescription = stringResource(MR.strings.new_chat_share_profile), tint = MaterialTheme.colors.secondary, + modifier = Modifier.mirrorIfRtl(), ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt index cf708c72d8..b690400e7f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt @@ -280,7 +280,7 @@ private fun BackButton(modifier: Modifier = Modifier, color: Color = MaterialThe painterResource(MR.images.ic_arrow_back_ios_new), contentDescription = stringResource(MR.strings.back), tint = color, - modifier = Modifier.height(24.dp) + modifier = Modifier.height(24.dp).mirrorIfRtl() ) Text(stringResource(MR.strings.back), color = color) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 81ea810acb..ae0ca52d3c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -141,7 +141,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool .clickable { currentVersion.value = prev } .padding(8.dp) ) { - Icon(painterResource(MR.images.ic_arrow_back_ios_new), "previous", tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_arrow_back_ios_new), "previous", Modifier.mirrorIfRtl(), tint = MaterialTheme.colors.primary) Text(versionDescriptions[prev].version, color = MaterialTheme.colors.primary) } } @@ -158,7 +158,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool .padding(8.dp) ) { Text(versionDescriptions[next].version, color = MaterialTheme.colors.primary) - Icon(painterResource(MR.images.ic_arrow_forward_ios), "next", tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_arrow_forward_ios), "next", Modifier.mirrorIfRtl(), tint = MaterialTheme.colors.primary) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt index c1d4a40dae..6d7ed79184 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.desktop.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.getBitmapFromByteArray +import chat.simplex.common.views.helpers.mirrorIfRtl import chat.simplex.res.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -35,7 +36,7 @@ actual fun FullScreenVideoView(player: VideoPlayer, modifier: Modifier, close: ( Box(Modifier.fillMaxSize().padding(bottom = 50.dp)) { SurfaceFromPlayer(player, modifier) IconButton(onClick = close, Modifier.padding(top = 5.dp)) { - Icon(painterResource(MR.images.ic_arrow_back_ios_new), null, Modifier.size(30.dp), tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_arrow_back_ios_new), null, Modifier.size(30.dp).mirrorIfRtl(), tint = MaterialTheme.colors.primary) } } Controls(player)