Merge pull request #673 from pangeachat/message-toolbar-improvements
Message toolbar improvements
This commit is contained in:
commit
1605fd95f3
10 changed files with 231 additions and 148 deletions
|
|
@ -22,6 +22,7 @@ abstract class AppConfig {
|
|||
static const double messageFontSize = 15.75;
|
||||
static const bool allowOtherHomeservers = true;
|
||||
static const bool enableRegistration = true;
|
||||
static const double toolbarMaxHeight = 315.0;
|
||||
// #Pangea
|
||||
// static const Color primaryColor = Color(0xFF5625BA);
|
||||
// static const Color primaryColorLight = Color(0xFFCCBDEA);
|
||||
|
|
|
|||
|
|
@ -1588,6 +1588,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
void showToolbar(
|
||||
PangeaMessageEvent pangeaMessageEvent, {
|
||||
MessageMode? mode,
|
||||
Event? nextEvent,
|
||||
Event? prevEvent,
|
||||
}) {
|
||||
// Close keyboard, if open
|
||||
if (inputFocus.hasFocus && PlatformInfos.isMobile) {
|
||||
|
|
@ -1610,6 +1612,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
event: pangeaMessageEvent.event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
textSelection: textSelection,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
);
|
||||
} catch (err) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/message.dart';
|
||||
|
|
@ -105,9 +106,19 @@ class ChatEventList extends StatelessWidget {
|
|||
// Request history button or progress indicator:
|
||||
if (i == events.length + 1) {
|
||||
if (controller.timeline!.isRequestingHistory) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
// #Pangea
|
||||
// return const Center(
|
||||
// child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
// );
|
||||
return const Column(
|
||||
children: [
|
||||
SizedBox(height: AppConfig.toolbarMaxHeight),
|
||||
Center(
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
),
|
||||
],
|
||||
);
|
||||
// Pangea#
|
||||
}
|
||||
if (controller.timeline!.canRequestHistory) {
|
||||
return Builder(
|
||||
|
|
@ -117,17 +128,31 @@ class ChatEventList extends StatelessWidget {
|
|||
.addPostFrameCallback((_) => controller.requestHistory);
|
||||
// WidgetsBinding.instance
|
||||
// .addPostFrameCallback(controller.requestHistory);
|
||||
// Pangea#
|
||||
return Center(
|
||||
child: IconButton(
|
||||
onPressed: controller.requestHistory,
|
||||
icon: const Icon(Icons.refresh_outlined),
|
||||
),
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: AppConfig.toolbarMaxHeight),
|
||||
Center(
|
||||
child: IconButton(
|
||||
onPressed: controller.requestHistory,
|
||||
icon: const Icon(Icons.refresh_outlined),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
// return Center(
|
||||
// child: IconButton(
|
||||
// onPressed: controller.requestHistory,
|
||||
// icon: const Icon(Icons.refresh_outlined),
|
||||
// ),
|
||||
// );
|
||||
// Pangea#
|
||||
},
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
// #Pangea
|
||||
// return const SizedBox.shrink();
|
||||
return const SizedBox(height: AppConfig.toolbarMaxHeight);
|
||||
// Pangea#
|
||||
}
|
||||
// #Pangea
|
||||
// i--;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ class HtmlMessage extends StatelessWidget {
|
|||
final bool isOverlay;
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
final ChatController controller;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
// Pangea#
|
||||
|
||||
const HtmlMessage({
|
||||
|
|
@ -36,6 +38,8 @@ class HtmlMessage extends StatelessWidget {
|
|||
required this.isOverlay,
|
||||
this.pangeaMessageEvent,
|
||||
required this.controller,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
// Pangea#
|
||||
});
|
||||
|
||||
|
|
@ -99,7 +103,11 @@ class HtmlMessage extends StatelessWidget {
|
|||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (pangeaMessageEvent != null && !isOverlay) {
|
||||
controller.showToolbar(pangeaMessageEvent!);
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent!,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
);
|
||||
}
|
||||
},
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -71,7 +71,11 @@ class Message extends StatelessWidget {
|
|||
// #Pangea
|
||||
void showToolbar(PangeaMessageEvent? pangeaMessageEvent) {
|
||||
if (pangeaMessageEvent != null && !isOverlay) {
|
||||
controller.showToolbar(pangeaMessageEvent);
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: previousEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
|
@ -445,12 +449,13 @@ class Message extends StatelessWidget {
|
|||
onInfoTab: onInfoTab,
|
||||
borderRadius: borderRadius,
|
||||
// #Pangea
|
||||
selected: selected,
|
||||
pangeaMessageEvent:
|
||||
pangeaMessageEvent,
|
||||
immersionMode: immersionMode,
|
||||
isOverlay: isOverlay,
|
||||
controller: controller,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: previousEvent,
|
||||
// Pangea#
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
|
|
@ -535,21 +540,18 @@ class Message extends StatelessWidget {
|
|||
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
|
||||
// #Pangea
|
||||
// if (showReceiptsRow || displayTime || selected || displayReadMarker) {
|
||||
if (showReceiptsRow ||
|
||||
displayTime ||
|
||||
selected ||
|
||||
displayReadMarker ||
|
||||
(pangeaMessageEvent?.showMessageButtons ?? false)) {
|
||||
if (!isOverlay &&
|
||||
(showReceiptsRow ||
|
||||
displayTime ||
|
||||
displayReadMarker ||
|
||||
(pangeaMessageEvent?.showMessageButtons ?? false))) {
|
||||
// Pangea#
|
||||
container = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// #Pangea
|
||||
// if (displayTime || selected)
|
||||
if ((displayTime || selected) && !isOverlay)
|
||||
// Pangea#
|
||||
if (displayTime || selected)
|
||||
Padding(
|
||||
padding: displayTime
|
||||
? const EdgeInsets.symmetric(vertical: 8.0)
|
||||
|
|
@ -581,8 +583,9 @@ class Message extends StatelessWidget {
|
|||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
// #Pangea
|
||||
child: !showReceiptsRow &&
|
||||
!(pangeaMessageEvent?.showMessageButtons ?? false)
|
||||
child: isOverlay ||
|
||||
(!showReceiptsRow &&
|
||||
!(pangeaMessageEvent?.showMessageButtons ?? false))
|
||||
// child: !showReceiptsRow
|
||||
// Pangea#
|
||||
? const SizedBox.shrink()
|
||||
|
|
@ -602,11 +605,10 @@ class Message extends StatelessWidget {
|
|||
MessageButtons(
|
||||
controller: controller,
|
||||
pangeaMessageEvent: pangeaMessageEvent!,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: previousEvent,
|
||||
),
|
||||
// #Pangea
|
||||
if (!isOverlay)
|
||||
// Pangea#
|
||||
MessageReactions(event, timeline),
|
||||
MessageReactions(event, timeline),
|
||||
],
|
||||
),
|
||||
// child: MessageReactions(event, timeline),
|
||||
|
|
@ -673,7 +675,15 @@ class Message extends StatelessWidget {
|
|||
left: 8.0,
|
||||
right: 8.0,
|
||||
top: nextEventSameSender ? 1.0 : 4.0,
|
||||
bottom: previousEventSameSender ? 1.0 : 4.0,
|
||||
bottom:
|
||||
// #Pangea
|
||||
isOverlay
|
||||
? 0
|
||||
:
|
||||
// Pangea#
|
||||
previousEventSameSender
|
||||
? 1.0
|
||||
: 4.0,
|
||||
),
|
||||
child: container,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ class MessageContent extends StatelessWidget {
|
|||
final void Function(Event)? onInfoTab;
|
||||
final BorderRadius borderRadius;
|
||||
// #Pangea
|
||||
final bool selected;
|
||||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
//question: are there any performance benefits to using booleans
|
||||
//here rather than passing the choreographer? pangea rich text, a widget
|
||||
|
|
@ -38,6 +37,8 @@ class MessageContent extends StatelessWidget {
|
|||
final bool immersionMode;
|
||||
final bool isOverlay;
|
||||
final ChatController controller;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
// Pangea#
|
||||
|
||||
const MessageContent(
|
||||
|
|
@ -46,11 +47,12 @@ class MessageContent extends StatelessWidget {
|
|||
super.key,
|
||||
required this.textColor,
|
||||
// #Pangea
|
||||
required this.selected,
|
||||
this.pangeaMessageEvent,
|
||||
required this.immersionMode,
|
||||
this.isOverlay = false,
|
||||
required this.controller,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
// Pangea#
|
||||
required this.borderRadius,
|
||||
});
|
||||
|
|
@ -209,6 +211,8 @@ class MessageContent extends StatelessWidget {
|
|||
isOverlay: isOverlay,
|
||||
controller: controller,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
|
|
@ -327,6 +331,8 @@ class MessageContent extends StatelessWidget {
|
|||
controller: controller,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
isOverlay: isOverlay,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
child:
|
||||
// Pangea#
|
||||
Linkify(
|
||||
|
|
|
|||
|
|
@ -2,21 +2,28 @@ 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:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class MessageButtons extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
|
||||
const MessageButtons({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.pangeaMessageEvent,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
});
|
||||
|
||||
void showActivity(BuildContext context) {
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent,
|
||||
mode: MessageMode.practiceActivity,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
|
|
@ -18,6 +17,8 @@ import 'package:matrix/matrix.dart';
|
|||
class MessageSelectionOverlay extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
final Event event;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final MessageMode? initialMode;
|
||||
final MessageTextSelection textSelection;
|
||||
|
|
@ -28,6 +29,8 @@ class MessageSelectionOverlay extends StatefulWidget {
|
|||
required this.pangeaMessageEvent,
|
||||
required this.textSelection,
|
||||
this.initialMode,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -35,83 +38,81 @@ class MessageSelectionOverlay extends StatefulWidget {
|
|||
MessageSelectionOverlayState createState() => MessageSelectionOverlayState();
|
||||
}
|
||||
|
||||
class MessageSelectionOverlayState extends State<MessageSelectionOverlay> {
|
||||
double overlayBottomOffset = -1;
|
||||
double adjustedOverlayBottomOffset = -1;
|
||||
Size? messageSize;
|
||||
Offset? messageOffset;
|
||||
|
||||
final StreamController _completeAnimationStream =
|
||||
StreamController.broadcast();
|
||||
class MessageSelectionOverlayState extends State<MessageSelectionOverlay>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
Animation<double>? _overlayPositionAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (messageSize == null || messageOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// position the overlay directly over the underlying message
|
||||
setOverlayBottomOffset();
|
||||
final headerBottomOffset = screenHeight - headerHeight;
|
||||
final footerBottomOffset = footerHeight;
|
||||
final currentBottomOffset =
|
||||
screenHeight - messageOffset!.dy - messageSize!.height;
|
||||
|
||||
// wait for the toolbar to animate to full height
|
||||
_completeAnimationStream.stream.first.then((_) {
|
||||
if (toolbarHeight == null ||
|
||||
messageSize == null ||
|
||||
messageOffset == null) {
|
||||
return;
|
||||
}
|
||||
final bool hasHeaderOverflow =
|
||||
messageOffset!.dy < AppConfig.toolbarMaxHeight;
|
||||
final bool hasFooterOverflow = footerHeight > currentBottomOffset;
|
||||
|
||||
// Once the toolbar has fully expanded, adjust
|
||||
// the overlay's position if there's an overflow
|
||||
final overlayTopOffset = messageOffset!.dy - toolbarHeight!;
|
||||
if (!hasHeaderOverflow && !hasFooterOverflow) return;
|
||||
|
||||
final bool hasHeaderOverflow = overlayTopOffset < headerHeight;
|
||||
final bool hasFooterOverflow = overlayBottomOffset < footerHeight;
|
||||
double scrollOffset = 0;
|
||||
double animationEndOffset = 0;
|
||||
|
||||
if (hasHeaderOverflow) {
|
||||
final overlayHeight = toolbarHeight! + messageSize!.height;
|
||||
adjustedOverlayBottomOffset = screenHeight -
|
||||
overlayHeight -
|
||||
footerHeight -
|
||||
MediaQuery.of(context).padding.bottom;
|
||||
} else if (hasFooterOverflow) {
|
||||
adjustedOverlayBottomOffset = footerHeight;
|
||||
}
|
||||
if (hasHeaderOverflow) {
|
||||
final midpoint = (headerBottomOffset + footerBottomOffset) / 2;
|
||||
animationEndOffset = midpoint - messageSize!.height;
|
||||
scrollOffset = animationEndOffset - currentBottomOffset;
|
||||
} else if (hasFooterOverflow) {
|
||||
scrollOffset = footerHeight - currentBottomOffset;
|
||||
animationEndOffset = currentBottomOffset + scrollOffset;
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
});
|
||||
_overlayPositionAnimation = Tween<double>(
|
||||
begin: currentBottomOffset,
|
||||
end: animationEndOffset,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
),
|
||||
);
|
||||
|
||||
widget.controller.scrollController.animateTo(
|
||||
widget.controller.scrollController.offset - scrollOffset,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
);
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_completeAnimationStream.close();
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void setOverlayBottomOffset() {
|
||||
// Try to get the offset and size of the original message bubble.
|
||||
// If it fails, return an empty SizedBox. For instance, this can fail if
|
||||
// you change the screen size while the overlay is open.
|
||||
try {
|
||||
final messageRenderBox = MatrixState.pAnyState.getRenderBox(
|
||||
RenderBox? get messageRenderBox => MatrixState.pAnyState.getRenderBox(
|
||||
widget.event.eventId,
|
||||
);
|
||||
if (messageRenderBox != null && messageRenderBox.hasSize) {
|
||||
messageSize = messageRenderBox.size;
|
||||
messageOffset = messageRenderBox.localToGlobal(Offset.zero);
|
||||
final messageTopOffset = messageOffset!.dy;
|
||||
overlayBottomOffset =
|
||||
screenHeight - messageTopOffset - messageSize!.height;
|
||||
}
|
||||
} catch (err) {
|
||||
overlayBottomOffset = adjustedOverlayBottomOffset = -1;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Size? get messageSize => messageRenderBox?.size;
|
||||
Offset? get messageOffset => messageRenderBox?.localToGlobal(Offset.zero);
|
||||
|
||||
// height of the reply/forward bar + the reaction picker + contextual padding
|
||||
double get footerHeight =>
|
||||
|
|
@ -123,23 +124,14 @@ class MessageSelectionOverlayState extends State<MessageSelectionOverlay> {
|
|||
|
||||
double get screenHeight => MediaQuery.of(context).size.height;
|
||||
|
||||
double? get toolbarHeight {
|
||||
try {
|
||||
final toolbarRenderBox = MatrixState.pAnyState.getRenderBox(
|
||||
'${widget.pangeaMessageEvent.eventId}-toolbar',
|
||||
);
|
||||
|
||||
return toolbarRenderBox?.size.height;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (overlayBottomOffset == -1) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final bool showDetails = (Matrix.of(context)
|
||||
.store
|
||||
.getBool(SettingKeys.displayChatDetailsColumn) ??
|
||||
false) &&
|
||||
FluffyThemes.isThreeColumnMode(context) &&
|
||||
widget.controller.room.membership == Membership.join;
|
||||
|
||||
final overlayMessage = ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
|
|
@ -166,7 +158,6 @@ class MessageSelectionOverlayState extends State<MessageSelectionOverlay> {
|
|||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
controller: widget.controller,
|
||||
textSelection: widget.textSelection,
|
||||
completeAnimationStream: _completeAnimationStream,
|
||||
initialMode: widget.initialMode,
|
||||
),
|
||||
),
|
||||
|
|
@ -184,57 +175,72 @@ class MessageSelectionOverlayState extends State<MessageSelectionOverlay> {
|
|||
timeline: widget.controller.timeline!,
|
||||
isOverlay: true,
|
||||
animateIn: false,
|
||||
nextEvent: widget.nextEvent,
|
||||
previousEvent: widget.prevEvent,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final bool showDetails = (Matrix.of(context)
|
||||
.store
|
||||
.getBool(SettingKeys.displayChatDetailsColumn) ??
|
||||
false) &&
|
||||
FluffyThemes.isThreeColumnMode(context) &&
|
||||
widget.controller.room.membership == Membership.join;
|
||||
final positionedOverlayMessage = _overlayPositionAnimation == null
|
||||
? Positioned(
|
||||
left: 0,
|
||||
right: showDetails ? FluffyThemes.columnWidth : 0,
|
||||
bottom: screenHeight - messageOffset!.dy - messageSize!.height,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: overlayMessage,
|
||||
),
|
||||
)
|
||||
: AnimatedBuilder(
|
||||
animation: _overlayPositionAnimation!,
|
||||
builder: (context, child) {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: showDetails ? FluffyThemes.columnWidth : 0,
|
||||
bottom: _overlayPositionAnimation!.value,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: overlayMessage,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedPositioned(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
left: 0,
|
||||
right: showDetails ? FluffyThemes.columnWidth : 0,
|
||||
bottom: adjustedOverlayBottomOffset == -1
|
||||
? overlayBottomOffset
|
||||
: adjustedOverlayBottomOffset,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: overlayMessage,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
OverlayFooter(controller: widget.controller),
|
||||
],
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: FluffyThemes.isColumnMode(context) ? 8.0 : 0.0,
|
||||
right: FluffyThemes.isColumnMode(context) ? 8.0 : 0.0,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
positionedOverlayMessage,
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
OverlayFooter(controller: widget.controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDetails)
|
||||
const SizedBox(
|
||||
width: FluffyThemes.columnWidth,
|
||||
),
|
||||
],
|
||||
if (showDetails)
|
||||
const SizedBox(
|
||||
width: FluffyThemes.columnWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
child: OverlayHeader(controller: widget.controller),
|
||||
),
|
||||
],
|
||||
Material(
|
||||
child: OverlayHeader(controller: widget.controller),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_ca
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class MessageToolbar extends StatefulWidget {
|
||||
final MessageTextSelection textSelection;
|
||||
|
|
@ -22,14 +23,11 @@ class MessageToolbar extends StatefulWidget {
|
|||
final ChatController controller;
|
||||
final MessageMode? initialMode;
|
||||
|
||||
final StreamController completeAnimationStream;
|
||||
|
||||
const MessageToolbar({
|
||||
super.key,
|
||||
required this.textSelection,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.controller,
|
||||
required this.completeAnimationStream,
|
||||
this.initialMode,
|
||||
});
|
||||
|
||||
|
|
@ -267,7 +265,6 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: toolbarContent,
|
||||
onEnd: () => widget.completeAnimationStream.add(null),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -284,12 +281,16 @@ class ToolbarSelectionArea extends StatelessWidget {
|
|||
final PangeaMessageEvent? pangeaMessageEvent;
|
||||
final bool isOverlay;
|
||||
final Widget child;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
|
||||
const ToolbarSelectionArea({
|
||||
required this.controller,
|
||||
this.pangeaMessageEvent,
|
||||
this.isOverlay = false,
|
||||
required this.child,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -302,12 +303,20 @@ class ToolbarSelectionArea extends StatelessWidget {
|
|||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (pangeaMessageEvent != null && !isOverlay) {
|
||||
controller.showToolbar(pangeaMessageEvent!);
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent!,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (pangeaMessageEvent != null && !isOverlay) {
|
||||
controller.showToolbar(pangeaMessageEvent!);
|
||||
controller.showToolbar(
|
||||
pangeaMessageEvent!,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../models/pangea_match_model.dart';
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ class PangeaRichText extends StatefulWidget {
|
|||
final TextStyle? style;
|
||||
final bool isOverlay;
|
||||
final ChatController controller;
|
||||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
|
||||
const PangeaRichText({
|
||||
super.key,
|
||||
|
|
@ -28,6 +31,8 @@ class PangeaRichText extends StatefulWidget {
|
|||
required this.immersionMode,
|
||||
required this.isOverlay,
|
||||
required this.controller,
|
||||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
this.style,
|
||||
});
|
||||
|
||||
|
|
@ -139,6 +144,8 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
isOverlay: widget.isOverlay,
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
controller: widget.controller,
|
||||
nextEvent: widget.nextEvent,
|
||||
prevEvent: widget.prevEvent,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: textSpan,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue