4726 word card in arabic goes way to the side (#4970)

* fix: initial work for word card positioning on RTL system

* fix: fix practice mode animation for RTL languages
This commit is contained in:
ggurdin 2025-12-30 09:29:29 -05:00 committed by GitHub
parent c507c7b54b
commit 9c4b8629e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 127 additions and 81 deletions

View file

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:provider/provider.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
@ -13,6 +14,8 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/languages/language_constants.dart';
import 'package:fluffychat/pangea/languages/locale_provider.dart';
import 'package:fluffychat/pangea/toolbar/layout/over_message_overlay.dart';
import 'package:fluffychat/pangea/toolbar/layout/practice_mode_transition_animation.dart';
import 'package:fluffychat/pangea/toolbar/layout/reading_assistance_mode_enum.dart';
@ -259,19 +262,53 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
);
}
bool get isRtl {
final locale = Provider.of<LocaleProvider>(context, listen: false)
.locale
?.languageCode;
return locale != null &&
LanguageConstants.rtlLanguageCodes.contains(locale);
}
double? get messageLeftOffset {
if (ownMessage) return null;
if (isRtl) {
return _originalMessageOffset.dx -
(showDetails ? FluffyThemes.columnWidth : 0);
}
if (ownMessage) return null;
return max(_originalMessageOffset.dx - columnWidth, 0);
}
double? get messageRightOffset {
if (mediaQuery == null || !ownMessage) return null;
if (isRtl) {
return mediaQuery!.size.width -
columnWidth -
_originalMessageOffset.dx -
originalMessageSize.width;
}
return mediaQuery!.size.width -
_originalMessageOffset.dx -
originalMessageSize.width -
(showDetails ? FluffyThemes.columnWidth : 0);
}
Alignment get messageAlignment {
return ownMessage ? Alignment.bottomRight : Alignment.bottomLeft;
}
CrossAxisAlignment get messageColumnAlignment {
if (isRtl) {
return ownMessage ? CrossAxisAlignment.start : CrossAxisAlignment.end;
}
return ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start;
}
double get _contentHeight {
final messageHeight =
_overlayMessageSize?.height ?? originalMessageSize.height;

View file

@ -17,77 +17,77 @@ class OverMessageOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Align(
alignment:
controller.ownMessage ? Alignment.bottomRight : Alignment.bottomLeft,
alignment: controller.messageAlignment,
child: Padding(
padding: EdgeInsets.only(
left: controller.messageLeftOffset ?? 0.0,
right: controller.messageRightOffset ?? 0.0,
),
child: SingleChildScrollView(
controller: controller.scrollController,
child: Column(
crossAxisAlignment: controller.ownMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (!controller.shouldScroll) ...[
WordCardSwitcher(controller: controller),
child: GestureDetector(
onTap: controller.widget.chatController.clearSelectedEvents,
child: SingleChildScrollView(
controller: controller.scrollController,
child: Column(
crossAxisAlignment: controller.messageColumnAlignment,
mainAxisSize: MainAxisSize.min,
children: [
if (!controller.shouldScroll) ...[
WordCardSwitcher(controller: controller),
const SizedBox(height: 4.0),
] else
AnimatedContainer(
duration: FluffyThemes.animationDuration,
height: controller.overheadContentHeight,
),
CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey(
'overlay_message_${controller.widget.event.eventId}',
)
.link,
child: ValueListenableBuilder(
valueListenable:
controller.widget.overlayController.selectedMode,
builder: (context, mode, __) {
return OverlayCenterContent(
event: controller.widget.event,
messageHeight: mode != SelectMode.emoji
? controller.originalMessageSize.height
: null,
messageWidth: controller.widget.overlayController
.selectModeController.isShowingExtraContent
? max(controller.originalMessageSize.width, 150)
: controller.originalMessageSize.width,
overlayController: controller.widget.overlayController,
chatController: controller.widget.chatController,
nextEvent: controller.widget.nextEvent,
prevEvent: controller.widget.prevEvent,
hasReactions: controller.hasReactions,
isTransitionAnimation: true,
readingAssistanceMode: controller.readingAssistanceMode,
overlayKey:
'overlay_message_${controller.widget.event.eventId}',
);
},
),
),
const SizedBox(height: 4.0),
] else
SelectModeButtons(
controller: controller.widget.chatController,
overlayController: controller.widget.overlayController,
launchPractice: () => controller.launchPractice(
ReadingAssistanceMode.practiceMode,
),
),
AnimatedContainer(
duration: FluffyThemes.animationDuration,
height: controller.overheadContentHeight,
height: max(0, controller.spaceBelowContent),
width: controller.mediaQuery!.size.width -
controller.columnWidth -
(controller.showDetails ? FluffyThemes.columnWidth : 0),
),
CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey(
'overlay_message_${controller.widget.event.eventId}',
)
.link,
child: ValueListenableBuilder(
valueListenable:
controller.widget.overlayController.selectedMode,
builder: (context, mode, __) {
return OverlayCenterContent(
event: controller.widget.event,
messageHeight: mode != SelectMode.emoji
? controller.originalMessageSize.height
: null,
messageWidth: controller.widget.overlayController
.selectModeController.isShowingExtraContent
? max(controller.originalMessageSize.width, 150)
: controller.originalMessageSize.width,
overlayController: controller.widget.overlayController,
chatController: controller.widget.chatController,
nextEvent: controller.widget.nextEvent,
prevEvent: controller.widget.prevEvent,
hasReactions: controller.hasReactions,
isTransitionAnimation: true,
readingAssistanceMode: controller.readingAssistanceMode,
overlayKey:
'overlay_message_${controller.widget.event.eventId}',
);
},
),
),
const SizedBox(height: 4.0),
SelectModeButtons(
controller: controller.widget.chatController,
overlayController: controller.widget.overlayController,
launchPractice: () => controller.launchPractice(
ReadingAssistanceMode.practiceMode,
),
),
AnimatedContainer(
duration: FluffyThemes.animationDuration,
height: max(0, controller.spaceBelowContent),
width: controller.mediaQuery!.size.width -
controller.columnWidth -
(controller.showDetails ? FluffyThemes.columnWidth : 0),
),
],
],
),
),
),
),

View file

@ -55,6 +55,9 @@ class PracticeModeTransitionAnimationState
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final isRtl = widget.controller.isRtl;
final columnWidth = isRtl ? 0 : widget.controller.columnWidth;
final startOffset = Offset(
widget.controller.ownMessage
? widget.controller.messageRightOffset!
@ -63,7 +66,7 @@ class PracticeModeTransitionAnimationState
);
final endOffset = Offset(
_centerMessageOffset!.dx - widget.controller.columnWidth,
_centerMessageOffset!.dx - columnWidth,
_centerMessageOffset!.dy,
);

View file

@ -174,27 +174,33 @@ class WordZoomWidget extends StatelessWidget {
],
);
return Material(
type: MaterialType.transparency,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 4.0,
return GestureDetector(
onTap: () {
// Absorb taps to prevent them from propagating
// to widgets below and closing the overlay.
},
child: Material(
type: MaterialType.transparency,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 4.0,
),
borderRadius: const BorderRadius.all(
Radius.circular(AppConfig.borderRadius),
),
),
borderRadius: const BorderRadius.all(
Radius.circular(AppConfig.borderRadius),
height: AppConfig.toolbarMaxHeight,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
content,
],
),
),
height: AppConfig.toolbarMaxHeight,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
content,
],
),
),
);
}