allow selection view for non-test/audio messages so they can be pinned, etc. (#1113)

pull/1544/head
ggurdin 12 months ago committed by GitHub
parent 401a26ef85
commit 25ab5e54bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1664,22 +1664,15 @@ class ChatController extends State<ChatPageWithRoom>
editEvent = null;
});
// #Pangea
// #Pangea
void showToolbar(
PangeaMessageEvent pangeaMessageEvent, {
Event event, {
PangeaMessageEvent? pangeaMessageEvent,
PangeaToken? selectedToken,
MessageMode? mode,
Event? nextEvent,
Event? prevEvent,
}) {
if (![
MessageTypes.Text,
MessageTypes.Audio,
].contains(pangeaMessageEvent.event.messageType) ||
pangeaMessageEvent.event.redacted) {
return;
}
// Close keyboard, if open
if (inputFocus.hasFocus && PlatformInfos.isMobile) {
inputFocus.unfocus();
@ -1698,7 +1691,7 @@ class ChatController extends State<ChatPageWithRoom>
try {
overlayEntry = MessageSelectionOverlay(
chatController: this,
event: pangeaMessageEvent.event,
event: event,
pangeaMessageEvent: pangeaMessageEvent,
initialSelectedToken: selectedToken,
nextEvent: nextEvent,
@ -1723,7 +1716,7 @@ class ChatController extends State<ChatPageWithRoom>
);
// select the message
onSelectMessage(pangeaMessageEvent.event);
onSelectMessage(event);
if (!kIsWeb) {
HapticFeedback.mediumImpact();
}

@ -24,6 +24,7 @@ class HtmlMessage extends StatelessWidget {
final bool isOverlay;
final PangeaMessageEvent? pangeaMessageEvent;
final ChatController controller;
final Event event;
final Event? nextEvent;
final Event? prevEvent;
// Pangea#
@ -35,6 +36,7 @@ class HtmlMessage extends StatelessWidget {
this.textColor = Colors.black,
// #Pangea
required this.isOverlay,
required this.event,
this.pangeaMessageEvent,
required this.controller,
this.nextEvent,
@ -95,9 +97,10 @@ class HtmlMessage extends StatelessWidget {
return SelectionArea(
child: GestureDetector(
onTap: () {
if (pangeaMessageEvent != null && !isOverlay) {
if (!isOverlay) {
controller.showToolbar(
pangeaMessageEvent!,
event,
pangeaMessageEvent: pangeaMessageEvent,
nextEvent: nextEvent,
prevEvent: prevEvent,
);

@ -74,7 +74,8 @@ class Message extends StatelessWidget {
// if overlayController is not null, the message is already in overlay mode
if (pangeaMessageEvent != null && overlayController == null) {
controller.showToolbar(
pangeaMessageEvent,
event,
pangeaMessageEvent: pangeaMessageEvent,
nextEvent: nextEvent,
prevEvent: previousEvent,
);
@ -611,6 +612,7 @@ class Message extends StatelessWidget {
children: [
if (pangeaMessageEvent?.showMessageButtons ?? false)
MessageButtons(
event: event,
controller: controller,
pangeaMessageEvent: pangeaMessageEvent!,
nextEvent: nextEvent,

@ -210,6 +210,7 @@ class MessageContent extends StatelessWidget {
textColor: textColor,
room: event.room,
// #Pangea
event: event,
isOverlay: overlayController != null,
controller: controller,
pangeaMessageEvent: pangeaMessageEvent,
@ -321,7 +322,8 @@ class MessageContent extends StatelessWidget {
style: messageTextStyle,
onClick: overlayController?.onClickOverlayMessageToken ??
(token) => controller.showToolbar(
pangeaMessageEvent!,
event,
pangeaMessageEvent: pangeaMessageEvent,
selectedToken: token,
),
isSelected: overlayController?.isTokenSelected,
@ -333,6 +335,7 @@ class MessageContent extends StatelessWidget {
return
// #Pangea
ToolbarSelectionArea(
event: event,
controller: controller,
pangeaMessageEvent: pangeaMessageEvent,
isOverlay: overlayController != null,

@ -6,6 +6,7 @@ import 'package:matrix/matrix.dart';
class MessageButtons extends StatelessWidget {
final ChatController controller;
final Event event;
final PangeaMessageEvent pangeaMessageEvent;
final Event? nextEvent;
final Event? prevEvent;
@ -13,6 +14,7 @@ class MessageButtons extends StatelessWidget {
const MessageButtons({
super.key,
required this.controller,
required this.event,
required this.pangeaMessageEvent,
this.nextEvent,
this.prevEvent,
@ -20,7 +22,8 @@ class MessageButtons extends StatelessWidget {
void showActivity(BuildContext context) {
controller.showToolbar(
pangeaMessageEvent,
event,
pangeaMessageEvent: pangeaMessageEvent,
mode: MessageMode.practiceActivity,
nextEvent: nextEvent,
prevEvent: prevEvent,

@ -32,13 +32,13 @@ class MessageSelectionOverlay extends StatefulWidget {
final Event _event;
final Event? _nextEvent;
final Event? _prevEvent;
final PangeaMessageEvent _pangeaMessageEvent;
final PangeaMessageEvent? _pangeaMessageEvent;
final PangeaToken? _initialSelectedToken;
const MessageSelectionOverlay({
required this.chatController,
required Event event,
required PangeaMessageEvent pangeaMessageEvent,
required PangeaMessageEvent? pangeaMessageEvent,
required PangeaToken? initialSelectedToken,
required Event? nextEvent,
required Event? prevEvent,
@ -65,12 +65,14 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
List<PangeaToken>? tokens;
bool initialized = false;
PangeaMessageEvent get pangeaMessageEvent => widget._pangeaMessageEvent;
PangeaMessageEvent? get pangeaMessageEvent => widget._pangeaMessageEvent;
final TtsController tts = TtsController();
bool _isPlayingAudio = false;
bool get showToolbarButtons => !widget._pangeaMessageEvent.isAudioMessage;
bool get showToolbarButtons =>
pangeaMessageEvent != null &&
pangeaMessageEvent!.event.messageType == MessageTypes.Text;
int get activitiesLeftToComplete => messageAnalyticsEntry?.numActivities ?? 0;
@ -127,7 +129,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
final timelineEvents = update.rooms?.join?[room.id]?.timeline?.events;
if (timelineEvents == null) return false;
final eventID = widget._pangeaMessageEvent.event.eventId;
final eventID = widget._event.eventId;
return timelineEvents.any(
(e) =>
e.type == EventTypes.Redaction ||
@ -141,20 +143,21 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
tts.setupTTS();
}
MessageAnalyticsEntry? get messageAnalyticsEntry => tokens != null
? MatrixState.pangeaController.getAnalytics.perMessage.get(
tokens!,
pangeaMessageEvent,
)
: null;
MessageAnalyticsEntry? get messageAnalyticsEntry =>
pangeaMessageEvent != null && tokens != null
? MatrixState.pangeaController.getAnalytics.perMessage.get(
tokens!,
pangeaMessageEvent!,
)
: null;
Future<void> _initializeTokensAndMode() async {
try {
final repEvent = pangeaMessageEvent.messageDisplayRepresentation;
final repEvent = pangeaMessageEvent?.messageDisplayRepresentation;
if (repEvent != null) {
tokens = await repEvent.tokensGlobal(
pangeaMessageEvent.senderId,
pangeaMessageEvent.originServerTs,
pangeaMessageEvent!.senderId,
pangeaMessageEvent!.originServerTs,
);
}
} catch (e, s) {
@ -172,7 +175,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
}
Future<void> _setInitialToolbarMode() async {
if (widget._pangeaMessageEvent.isAudioMessage) {
if (widget._pangeaMessageEvent?.isAudioMessage ?? false) {
toolbarMode = MessageMode.speechToText;
return setState(() {});
}
@ -259,11 +262,12 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
/// If there is no selectedSpan, then the whole message is the target
/// If there is a selectedSpan, then the target is the selected text
String get targetText {
if (_selectedSpan == null) {
return widget._pangeaMessageEvent.messageDisplayText;
if (_selectedSpan == null || pangeaMessageEvent == null) {
return widget._pangeaMessageEvent?.messageDisplayText ??
widget._event.body;
}
return widget._pangeaMessageEvent.messageDisplayText.substring(
return widget._pangeaMessageEvent!.messageDisplayText.substring(
_selectedSpan!.offset,
_selectedSpan!.offset + _selectedSpan!.length,
);
@ -297,6 +301,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
}
void setSelectedSpan(PracticeActivityModel activity) {
if (pangeaMessageEvent == null) return;
final RelevantSpanDisplayDetails? span =
activity.content.spanDisplayDetails;
@ -309,7 +315,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
_selectedSpan = PangeaTokenText(
offset: span.offset,
length: span.length,
content: widget._pangeaMessageEvent.messageDisplayText
content: widget._pangeaMessageEvent!.messageDisplayText
.substring(span.offset, span.offset + span.length),
);
} else {
@ -331,7 +337,7 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
PangeaTokenText? get selectedSpan => _selectedSpan;
bool get _hasReactions {
final reactionsEvents = widget._pangeaMessageEvent.event.aggregatedEvents(
final reactionsEvents = widget._event.aggregatedEvents(
widget.chatController.timeline!,
RelationshipTypes.reaction,
);
@ -547,20 +553,23 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: widget._pangeaMessageEvent.ownMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
crossAxisAlignment:
widget._event.senderId == widget._event.room.client.userID
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
MessageToolbar(
pangeaMessageEvent: widget._pangeaMessageEvent,
overLayController: this,
ttsController: tts,
),
if (pangeaMessageEvent != null)
MessageToolbar(
pangeaMessageEvent: pangeaMessageEvent!,
overLayController: this,
ttsController: tts,
),
const SizedBox(height: 8),
SizedBox(
height: _adjustedMessageHeight,
child: OverlayMessage(
pangeaMessageEvent,
widget._event,
pangeaMessageEvent: pangeaMessageEvent,
immersionMode:
widget.chatController.choreographer.immersionMode,
controller: widget.chatController,
@ -578,12 +587,13 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
child: SizedBox(
height: _reactionsHeight - 8,
child: MessageReactions(
widget._pangeaMessageEvent.event,
widget._event,
widget.chatController.timeline!,
),
),
),
ToolbarButtons(
event: widget._event,
overlayController: this,
width: 250,
),
@ -597,19 +607,21 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
: 0;
final double? leftPadding =
(widget._pangeaMessageEvent.ownMessage || _messageOffset == null)
(widget._event.senderId == widget._event.room.client.userID ||
_messageOffset == null)
? null
: _messageOffset!.dx - horizontalPadding - columnOffset;
final double? rightPadding = (widget._pangeaMessageEvent.ownMessage &&
_screenWidth != null &&
_messageOffset != null &&
_messageSize != null)
? _screenWidth! -
_messageOffset!.dx -
_messageSize!.width -
horizontalPadding
: null;
final double? rightPadding =
(widget._event.senderId == widget._event.room.client.userID &&
_screenWidth != null &&
_messageOffset != null &&
_messageSize != null)
? _screenWidth! -
_messageOffset!.dx -
_messageSize!.width -
horizontalPadding
: null;
final positionedOverlayMessage = (_overlayPositionAnimation == null)
? (_screenHeight == null ||

@ -20,6 +20,7 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix_api_lite/model/message_types.dart';
const double minCardHeight = 70;
@ -149,6 +150,12 @@ class MessageToolbar extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (![MessageTypes.Text, MessageTypes.Audio].contains(
pangeaMessageEvent.event.messageType,
)) {
return const SizedBox();
}
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,

@ -11,26 +11,29 @@ import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:matrix/matrix.dart';
class ToolbarButtons extends StatelessWidget {
final Event event;
final MessageOverlayController overlayController;
final double width;
const ToolbarButtons({
required this.event,
required this.overlayController,
required this.width,
super.key,
});
PangeaMessageEvent get pangeaMessageEvent =>
PangeaMessageEvent? get pangeaMessageEvent =>
overlayController.pangeaMessageEvent;
List<MessageMode> get modes => MessageMode.values
.where((mode) => mode.shouldShowAsToolbarButton(pangeaMessageEvent.event))
.where((mode) => mode.shouldShowAsToolbarButton(event))
.toList();
bool get messageInUserL2 =>
pangeaMessageEvent.messageDisplayLangCode ==
pangeaMessageEvent?.messageDisplayLangCode ==
MatrixState.pangeaController.languageController.userL2?.langCode;
static const double iconWidth = 36.0;
@ -42,7 +45,7 @@ class ToolbarButtons extends StatelessWidget {
overlayController.isPracticeComplete || !messageInUserL2;
final double barWidth = width - iconWidth;
if (!overlayController.showToolbarButtons) {
if (!overlayController.showToolbarButtons || pangeaMessageEvent == null) {
return const SizedBox();
}
@ -70,7 +73,7 @@ class ToolbarButtons extends StatelessWidget {
: min(
barWidth,
(barWidth / 3) *
pangeaMessageEvent.numberOfActivitiesCompleted,
pangeaMessageEvent!.numberOfActivitiesCompleted,
),
color: AppConfig.success,
margin: const EdgeInsets.symmetric(horizontal: iconWidth / 2),
@ -83,14 +86,14 @@ class ToolbarButtons extends StatelessWidget {
children: modes.mapIndexed((index, mode) {
final enabled = mode.isUnlocked(
index,
pangeaMessageEvent.numberOfActivitiesCompleted,
pangeaMessageEvent!.numberOfActivitiesCompleted,
totallyDone,
);
final color = mode.iconButtonColor(
context,
index,
overlayController.toolbarMode,
pangeaMessageEvent.numberOfActivitiesCompleted,
pangeaMessageEvent!.numberOfActivitiesCompleted,
totallyDone,
);
return Tooltip(

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class ToolbarSelectionArea extends StatelessWidget {
final Event event;
final ChatController controller;
final PangeaMessageEvent? pangeaMessageEvent;
final bool isOverlay;
@ -12,6 +13,7 @@ class ToolbarSelectionArea extends StatelessWidget {
final Event? prevEvent;
const ToolbarSelectionArea({
required this.event,
required this.controller,
this.pangeaMessageEvent,
this.isOverlay = false,
@ -27,7 +29,8 @@ class ToolbarSelectionArea extends StatelessWidget {
onTap: () {
if (pangeaMessageEvent != null && !isOverlay) {
controller.showToolbar(
pangeaMessageEvent!,
event,
pangeaMessageEvent: pangeaMessageEvent,
nextEvent: nextEvent,
prevEvent: prevEvent,
);
@ -36,7 +39,8 @@ class ToolbarSelectionArea extends StatelessWidget {
onLongPress: () {
if (pangeaMessageEvent != null && !isOverlay) {
controller.showToolbar(
pangeaMessageEvent!,
event,
pangeaMessageEvent: pangeaMessageEvent,
nextEvent: nextEvent,
prevEvent: prevEvent,
);

@ -10,7 +10,8 @@ import 'package:matrix/matrix.dart';
// @ggurdin be great to explain the need/function of a widget like this
class OverlayMessage extends StatelessWidget {
final PangeaMessageEvent pangeaMessageEvent;
final Event event;
final PangeaMessageEvent? pangeaMessageEvent;
final MessageOverlayController overlayController;
final ChatController controller;
final Event? nextEvent;
@ -21,13 +22,14 @@ class OverlayMessage extends StatelessWidget {
final double messageHeight;
const OverlayMessage(
this.pangeaMessageEvent, {
this.event, {
this.immersionMode = false,
required this.overlayController,
required this.controller,
required this.timeline,
required this.messageWidth,
required this.messageHeight,
this.pangeaMessageEvent,
this.nextEvent,
this.prevEvent,
super.key,
@ -36,14 +38,11 @@ class OverlayMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final bool ownMessage =
pangeaMessageEvent.event.senderId == Matrix.of(context).client.userID;
final bool ownMessage = event.senderId == Matrix.of(context).client.userID;
final displayTime =
pangeaMessageEvent.event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!pangeaMessageEvent.event.originServerTs
.sameEnvironment(nextEvent!.originServerTs);
final displayTime = event.type == EventTypes.RoomCreate ||
nextEvent == null ||
event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
final nextEventSameSender = nextEvent != null &&
{
@ -51,7 +50,7 @@ class OverlayMessage extends StatelessWidget {
EventTypes.Sticker,
EventTypes.Encrypted,
}.contains(nextEvent!.type) &&
nextEvent!.senderId == pangeaMessageEvent.event.senderId &&
nextEvent!.senderId == event.senderId &&
!displayTime;
final previousEventSameSender = prevEvent != null &&
@ -60,9 +59,8 @@ class OverlayMessage extends StatelessWidget {
EventTypes.Sticker,
EventTypes.Encrypted,
}.contains(prevEvent!.type) &&
prevEvent!.senderId == pangeaMessageEvent.event.senderId &&
prevEvent!.originServerTs
.sameEnvironment(pangeaMessageEvent.event.originServerTs);
prevEvent!.senderId == event.senderId &&
prevEvent!.originServerTs.sameEnvironment(event.originServerTs);
const hardCorner = Radius.circular(4);
const roundedCorner = Radius.circular(AppConfig.borderRadius);
@ -75,7 +73,7 @@ class OverlayMessage extends StatelessWidget {
ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
);
final displayEvent = pangeaMessageEvent.event.getDisplayEvent(timeline);
final displayEvent = event.getDisplayEvent(timeline);
// ignore: deprecated_member_use
var color = theme.colorScheme.surfaceVariant;
if (ownMessage) {
@ -88,12 +86,12 @@ class OverlayMessage extends StatelessWidget {
MessageTypes.Video,
MessageTypes.Image,
MessageTypes.Sticker,
}.contains(pangeaMessageEvent.event.messageType) &&
!pangeaMessageEvent.event.redacted;
}.contains(event.messageType) &&
!event.redacted;
final noPadding = {
MessageTypes.File,
MessageTypes.Audio,
}.contains(pangeaMessageEvent.event.messageType);
}.contains(event.messageType);
return Material(
color: color,
@ -116,7 +114,7 @@ class OverlayMessage extends StatelessWidget {
),
width: messageWidth,
child: MessageContent(
pangeaMessageEvent.event,
event,
textColor: ownMessage
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface,

@ -133,6 +133,7 @@ class PangeaRichTextState extends State<PangeaRichText> {
//TODO - take out of build function of every message
final Widget richText = ToolbarSelectionArea(
event: widget.pangeaMessageEvent.event,
isOverlay: widget.isOverlay,
pangeaMessageEvent: widget.pangeaMessageEvent,
controller: widget.controller,

Loading…
Cancel
Save