Toolbar placed over selected message

This commit is contained in:
Kelrap 2024-08-07 17:10:44 -04:00
parent a911b9f852
commit bb263c71c2
3 changed files with 199 additions and 142 deletions

View file

@ -1,26 +1,99 @@
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
class MessageSelectionOverlay extends StatelessWidget {
final ChatController controller;
final ToolbarDisplayController toolbarController;
final Function closeToolbar;
final Widget toolbar;
final Widget overlayMessage;
final PangeaMessageEvent pangeaMessageEvent;
final bool ownMessage;
final bool immersionMode;
final String targetId;
const MessageSelectionOverlay({
required this.controller,
required this.closeToolbar,
required this.toolbar,
required this.overlayMessage,
required this.pangeaMessageEvent,
required this.immersionMode,
required this.ownMessage,
required this.targetId,
required this.toolbarController,
super.key,
});
@override
Widget build(BuildContext context) {
final LayerLinkAndKey layerLinkAndKey =
MatrixState.pAnyState.layerLinkAndKey(targetId);
final targetRenderBox =
layerLinkAndKey.key.currentContext?.findRenderObject();
double center = 290;
double left = 0;
bool showDown = false;
final double footerSize = PlatformInfos.isMobile
? PlatformInfos.isIOS
? 138
: 140
: 154;
final double headerSize = PlatformInfos.isMobile
? PlatformInfos.isIOS
? 122
: 86
: 80;
final double stackSize =
MediaQuery.of(context).size.height - footerSize - headerSize;
if (targetRenderBox != null) {
final Size transformTargetSize = (targetRenderBox as RenderBox).size;
final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero);
left = targetOffset.dx;
showDown = targetOffset.dy + transformTargetSize.height <=
headerSize + stackSize / 2;
center = targetOffset.dy -
headerSize +
(showDown ? transformTargetSize.height + 3 : (-3));
// If top of selected message extends below header
if (targetOffset.dy <= headerSize) {
center = transformTargetSize.height + 3;
showDown = true;
}
// If bottom of selected message extends below footer
else if (targetOffset.dy + transformTargetSize.height >=
headerSize + stackSize) {
center = stackSize - transformTargetSize.height - 3;
}
// If message is too long, or awkwardly positioned,
// center to avoid hitting edges of stack
if (transformTargetSize.height >= stackSize / 2 - 3 ||
(targetOffset.dy < headerSize + stackSize / 2 &&
targetOffset.dy + transformTargetSize.height >
headerSize + stackSize / 2)) {
center = stackSize / 2;
}
}
final Widget overlayMessage = OverlayMessage(
pangeaMessageEvent.event,
timeline: pangeaMessageEvent.timeline,
immersionMode: immersionMode,
ownMessage: pangeaMessageEvent.ownMessage,
toolbarController: toolbarController,
width: 290,
showDown: showDown,
);
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
@ -35,14 +108,28 @@ class MessageSelectionOverlay extends StatelessWidget {
height: 7,
),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
child: Stack(
children: [
toolbar,
const SizedBox(height: 9),
overlayMessage,
Positioned(
left: ownMessage ? null : left,
right: ownMessage
? PlatformInfos.isMobile
? 8
: 16
: null,
bottom: stackSize - center + 3,
child: showDown ? overlayMessage : toolbar,
),
Positioned(
left: ownMessage ? null : left,
right: ownMessage
? PlatformInfos.isMobile
? 8
: 16
: null,
top: center + 3,
child: showDown ? toolbar : overlayMessage,
),
],
),
),

View file

@ -5,7 +5,6 @@ import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/message_mode_enum.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart';
@ -14,10 +13,10 @@ import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart'
import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart';
import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -75,47 +74,6 @@ class ToolbarDisplayController {
}
focusNode.requestFocus();
final LayerLinkAndKey layerLinkAndKey =
MatrixState.pAnyState.layerLinkAndKey(targetId);
final targetRenderBox =
layerLinkAndKey.key.currentContext?.findRenderObject();
if (targetRenderBox != null) {
final Size transformTargetSize = (targetRenderBox as RenderBox).size;
messageWidth = transformTargetSize.width;
final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero);
final double minValue =
controller.scrollController.position.minScrollExtent;
final double maxValue =
controller.scrollController.position.maxScrollExtent;
final double middlePoint = controller.scrollController.offset -
targetOffset.dy +
MediaQuery.of(context).size.height / 2 +
37;
// Scroll so message is right under half point of screen
controller.scrollController.animateTo(
middlePoint < minValue
? minValue
: middlePoint > maxValue
? maxValue
: middlePoint,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
);
}
final Widget overlayMessage = OverlayMessage(
pangeaMessageEvent.event,
timeline: pangeaMessageEvent.timeline,
immersionMode: immersionMode,
ownMessage: pangeaMessageEvent.ownMessage,
toolbarController: this,
width: 300,
nextEvent: nextEvent,
previousEvent: previousEvent,
);
// I'm not sure why I put this here, but it causes the toolbar
// not to open immediately after clicking (user has to scroll or move their cursor)
// so I'm commenting it out for now
@ -127,8 +85,11 @@ class ToolbarDisplayController {
controller: controller,
closeToolbar: closeToolbar,
toolbar: toolbar!,
overlayMessage: overlayMessage,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode: immersionMode,
ownMessage: pangeaMessageEvent.ownMessage,
targetId: targetId,
toolbarController: this,
);
} catch (err) {
debugger(when: kDebugMode);
@ -372,6 +333,10 @@ class MessageToolbarState extends State<MessageToolbar> {
@override
Widget build(BuildContext context) {
final double maxHeight = (MediaQuery.of(context).size.height -
(PlatformInfos.isIOS ? 254 : 232)) /
2;
return Material(
type: MaterialType.transparency,
child: Container(
@ -386,18 +351,18 @@ class MessageToolbarState extends State<MessageToolbar> {
Radius.circular(25),
),
),
constraints: const BoxConstraints(
maxWidth: 300,
minWidth: 300,
maxHeight: 300,
constraints: BoxConstraints(
maxWidth: 290,
minWidth: 290,
maxHeight: maxHeight,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: const BoxConstraints(
minWidth: 300,
maxHeight: 228,
constraints: BoxConstraints(
minWidth: 290,
maxHeight: maxHeight - 72,
),
child: SingleChildScrollView(
child: AnimatedSize(

View file

@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
@ -11,8 +12,6 @@ import '../../../config/app_config.dart';
class OverlayMessage extends StatelessWidget {
final Event event;
final Event? nextEvent;
final Event? previousEvent;
final bool selected;
final Timeline timeline;
// final LanguageModel? selectedDisplayLang;
@ -21,16 +20,16 @@ class OverlayMessage extends StatelessWidget {
final bool ownMessage;
final ToolbarDisplayController toolbarController;
final double? width;
final bool showDown;
const OverlayMessage(
this.event, {
this.nextEvent,
this.previousEvent,
this.selected = false,
required this.timeline,
required this.immersionMode,
required this.ownMessage,
required this.toolbarController,
required this.showDown,
this.width,
super.key,
});
@ -49,9 +48,13 @@ class OverlayMessage extends StatelessWidget {
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface;
const hardCorner = Radius.circular(4);
const roundedCorner = Radius.circular(AppConfig.borderRadius);
const borderRadius = BorderRadius.all(
roundedCorner,
final borderRadius = BorderRadius.only(
topLeft: !showDown && !ownMessage ? hardCorner : roundedCorner,
topRight: !showDown && ownMessage ? hardCorner : roundedCorner,
bottomLeft: showDown && !ownMessage ? hardCorner : roundedCorner,
bottomRight: showDown && ownMessage ? hardCorner : roundedCorner,
);
final noBubble = {
@ -83,93 +86,95 @@ class OverlayMessage extends StatelessWidget {
: (color.blue * lightness).round(),
);
final double maxHeight = (MediaQuery.of(context).size.height -
(PlatformInfos.isIOS ? 254 : 232)) /
2;
final pangeaMessageEvent = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: ownMessage,
);
return Flexible(
// Make overlay message scrollable so long messages don't run offscreen
child: SingleChildScrollView(
child: Material(
color: noBubble ? Colors.transparent : color,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: borderRadius,
return SingleChildScrollView(
child: Material(
color: noBubble ? Colors.transparent : color,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: borderRadius,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
padding: noBubble || noPadding
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
constraints: BoxConstraints(
maxWidth: width ?? FluffyThemes.columnWidth * 1.25,
maxHeight: maxHeight,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: MessageContent(
event.getDisplayEvent(timeline),
textColor: textColor,
borderRadius: borderRadius,
selected: selected,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode: immersionMode,
toolbarController: toolbarController,
isOverlay: true,
),
),
),
padding: noBubble || noPadding
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
) ||
(pangeaMessageEvent.showUseType))
Padding(
padding: const EdgeInsets.only(
top: 4.0,
),
constraints: BoxConstraints(
maxWidth: width ?? FluffyThemes.columnWidth * 1.25,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: MessageContent(
event.getDisplayEvent(timeline),
textColor: textColor,
borderRadius: borderRadius,
selected: selected,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode: immersionMode,
toolbarController: toolbarController,
isOverlay: true,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (pangeaMessageEvent.showUseType) ...[
pangeaMessageEvent.msgUseType.iconView(
context,
textColor.withAlpha(164),
),
const SizedBox(width: 4),
],
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
)) ...[
Icon(
Icons.edit_outlined,
color: textColor.withAlpha(164),
size: 14,
),
Text(
' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,
),
),
],
],
),
),
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
) ||
(pangeaMessageEvent.showUseType))
Padding(
padding: const EdgeInsets.only(
top: 4.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (pangeaMessageEvent.showUseType) ...[
pangeaMessageEvent.msgUseType.iconView(
context,
textColor.withAlpha(164),
),
const SizedBox(width: 4),
],
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
)) ...[
Icon(
Icons.edit_outlined,
color: textColor.withAlpha(164),
size: 14,
),
Text(
' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,
),
),
],
],
),
),
],
),
],
),
),
),