Merge branch 'main' into merge

This commit is contained in:
ggurdin 2024-09-11 12:29:04 -04:00
commit f3d32fa7ef
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
10 changed files with 231 additions and 148 deletions

View file

@ -22,6 +22,7 @@ abstract class AppConfig {
static const double messageFontSize = 16.0;
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);

View file

@ -1599,6 +1599,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) {
@ -1621,6 +1623,8 @@ class ChatController extends State<ChatPageWithRoom>
event: pangeaMessageEvent.event,
pangeaMessageEvent: pangeaMessageEvent,
textSelection: textSelection,
nextEvent: nextEvent,
prevEvent: prevEvent,
);
} catch (err) {
debugger(when: kDebugMode);

View file

@ -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--;

View file

@ -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#

View file

@ -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#
@ -441,12 +445,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(
@ -531,21 +536,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)
@ -575,8 +577,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()
@ -596,11 +599,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),
@ -666,7 +668,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,
),

View file

@ -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(

View file

@ -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,
);
}

View file

@ -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),
),
],
),
);
}
}

View file

@ -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,

View file

@ -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,