chore: fix some sizing issues on mobile, some updates to menu buttons (#3383)
This commit is contained in:
parent
aa3b3a3d1e
commit
a660ba32c1
5 changed files with 440 additions and 490 deletions
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
|
@ -12,21 +11,15 @@ import 'package:fluffychat/pages/chat/chat.dart';
|
|||
import 'package:fluffychat/pages/chat/input_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/error_reporter.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
|
||||
class PangeaChatInputRow extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
final MessageOverlayController? overlayController;
|
||||
|
||||
const PangeaChatInputRow({
|
||||
required this.controller,
|
||||
this.overlayController,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -74,305 +67,203 @@ class PangeaChatInputRowState extends State<PangeaChatInputRow> {
|
|||
: L10n.of(context).writeAMessage;
|
||||
}
|
||||
|
||||
void _deleteErrorEventsAction() async {
|
||||
try {
|
||||
if (widget.overlayController == null ||
|
||||
widget.overlayController!.event.status != EventStatus.error) {
|
||||
throw Exception(
|
||||
'Tried to delete failed to send events but one event is not failed to sent',
|
||||
);
|
||||
}
|
||||
await widget.overlayController!.event.cancelSend();
|
||||
_controller.clearSelectedEvents();
|
||||
} catch (e, s) {
|
||||
ErrorReporter(
|
||||
context,
|
||||
'Error while delete error events action',
|
||||
).onErrorCallback(e, s);
|
||||
}
|
||||
}
|
||||
|
||||
void _sendAgainAction() {
|
||||
if (widget.overlayController == null) {
|
||||
ErrorHandler.logError(
|
||||
e: "No selected events in send again action",
|
||||
s: StackTrace.current,
|
||||
data: {"roomId": _controller.room.id},
|
||||
);
|
||||
_controller.clearSelectedEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
final event = widget.overlayController!.event;
|
||||
if (event.status.isError) {
|
||||
event.sendAgain();
|
||||
}
|
||||
|
||||
final allEditEvents = event
|
||||
.aggregatedEvents(
|
||||
_controller.timeline!,
|
||||
RelationshipTypes.edit,
|
||||
)
|
||||
.where((e) => e.status.isError);
|
||||
for (final e in allEditEvents) {
|
||||
e.sendAgain();
|
||||
}
|
||||
|
||||
_controller.clearSelectedEvents();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
const height = 48.0;
|
||||
|
||||
if (widget.controller.selectMode) {
|
||||
return const SizedBox(height: height);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// if (!controller.selectMode) WritingAssistanceInputRow(controller),
|
||||
CompositedTransformTarget(
|
||||
link: _controller.choreographer.inputLayerLinkAndKey.link,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(
|
||||
widget.overlayController != null
|
||||
? AppConfig.chatInputRowOverlayPadding
|
||||
: 0.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.overlayController != null
|
||||
? Theme.of(context).cardColor
|
||||
: null,
|
||||
borderRadius: const BorderRadius.all(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8.0),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
key: widget.overlayController != null
|
||||
? null
|
||||
: _controller.choreographer.inputLayerLinkAndKey.key,
|
||||
key: _controller.choreographer.inputLayerLinkAndKey.key,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: widget.overlayController != null
|
||||
? <Widget>[
|
||||
if (widget.overlayController!.event.status ==
|
||||
EventStatus.error)
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
onPressed: _deleteErrorEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(Icons.delete),
|
||||
Text(L10n.of(context).delete),
|
||||
],
|
||||
),
|
||||
children: <Widget>[
|
||||
const SizedBox(width: 4),
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
height: height,
|
||||
width: _controller.sendController.text.isEmpty ? height : 0,
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
child: PopupMenuButton<String>(
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onSelected: _controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
),
|
||||
if (widget.overlayController!.event
|
||||
.getDisplayEvent(_controller.timeline!)
|
||||
.status
|
||||
.isSent)
|
||||
ReadingAssistanceInputBar(
|
||||
_controller,
|
||||
widget.overlayController!,
|
||||
),
|
||||
if (widget.overlayController!.event
|
||||
.getDisplayEvent(_controller.timeline!)
|
||||
.status
|
||||
.isError)
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
onPressed: _sendAgainAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).tryToSendAgain),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.send_outlined, size: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
: <Widget>[
|
||||
const SizedBox(width: 4),
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
height: height,
|
||||
width: _controller.sendController.text.isEmpty
|
||||
? height
|
||||
: 0,
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
child: PopupMenuButton<String>(
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onSelected: _controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.purple,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openVideoCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.brown,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (FluffyThemes.isColumnMode(context))
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType:
|
||||
SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
_controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(_controller.showEmojiPicker),
|
||||
),
|
||||
),
|
||||
onPressed: _controller.emojiPickerAction,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: InputBar(
|
||||
room: _controller.room,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
autofocus: !PlatformInfos.isMobile,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction: AppConfig.sendOnEnter == true &&
|
||||
PlatformInfos.isMobile
|
||||
? TextInputAction.send
|
||||
: null,
|
||||
onSubmitted: (String value) =>
|
||||
_controller.onInputBarSubmitted(value, context),
|
||||
onSubmitImage: _controller.sendImageFromClipBoard,
|
||||
focusNode: _controller.inputFocus,
|
||||
controller: _controller.sendController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
top: 3.0,
|
||||
),
|
||||
disabledBorder: InputBorder.none,
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
filled: false,
|
||||
),
|
||||
onChanged: _controller.onInputBarChanged,
|
||||
hintText: hintText(),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
StartIGCButton(
|
||||
controller: _controller,
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: PlatformInfos.platformCanRecord &&
|
||||
_controller.sendController.text.isEmpty &&
|
||||
!_controller.choreographer.itController.willOpen
|
||||
? FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
onPressed: _controller.voiceMessageAction,
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.mic_none_outlined),
|
||||
)
|
||||
: ChoreographerSendButton(controller: _controller),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.purple,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openVideoCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.brown,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (FluffyThemes.isColumnMode(context))
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
_controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(_controller.showEmojiPicker),
|
||||
),
|
||||
),
|
||||
onPressed: _controller.emojiPickerAction,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: InputBar(
|
||||
room: _controller.room,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
autofocus: !PlatformInfos.isMobile,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textInputAction: AppConfig.sendOnEnter == true &&
|
||||
PlatformInfos.isMobile
|
||||
? TextInputAction.send
|
||||
: null,
|
||||
onSubmitted: (String value) =>
|
||||
_controller.onInputBarSubmitted(value, context),
|
||||
onSubmitImage: _controller.sendImageFromClipBoard,
|
||||
focusNode: _controller.inputFocus,
|
||||
controller: _controller.sendController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
top: 3.0,
|
||||
),
|
||||
disabledBorder: InputBorder.none,
|
||||
hintMaxLines: 1,
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
filled: false,
|
||||
),
|
||||
onChanged: _controller.onInputBarChanged,
|
||||
hintText: hintText(),
|
||||
),
|
||||
),
|
||||
),
|
||||
StartIGCButton(
|
||||
controller: _controller,
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: PlatformInfos.platformCanRecord &&
|
||||
_controller.sendController.text.isEmpty &&
|
||||
!_controller.choreographer.itController.willOpen
|
||||
? FloatingActionButton.small(
|
||||
tooltip: L10n.of(context).voiceMessage,
|
||||
onPressed: _controller.voiceMessageAction,
|
||||
elevation: 0,
|
||||
heroTag: null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
backgroundColor: theme.bubbleColor,
|
||||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.mic_none_outlined),
|
||||
)
|
||||
: ChoreographerSendButton(controller: _controller),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ class OverlayFooter extends StatelessWidget {
|
|||
),
|
||||
child: PangeaChatInputRow(
|
||||
controller: controller,
|
||||
overlayController: overlayController,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -459,6 +459,11 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
transcription != null ||
|
||||
transcriptionError != null;
|
||||
|
||||
bool get showLanguageAssistance =>
|
||||
event.status.isSent &&
|
||||
event.type == EventTypes.Message &&
|
||||
event.messageType == MessageTypes.Text;
|
||||
|
||||
///////////////////////////////////
|
||||
/// Functions
|
||||
/////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -627,7 +627,14 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
|
||||
double? get _availableSpaceAboveContent {
|
||||
if (_contentHeight == null || _mediaQuery == null) return null;
|
||||
return max(0, (_mediaQuery!.size.height - _contentHeight!) / 2);
|
||||
return max(
|
||||
0,
|
||||
(_mediaQuery!.size.height -
|
||||
_mediaQuery!.padding.top -
|
||||
_mediaQuery!.padding.bottom -
|
||||
_contentHeight!) /
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
double? get _wordCardTopOffset {
|
||||
|
|
@ -660,123 +667,129 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
}
|
||||
|
||||
widget.overlayController.maxWidth = _toolbarMaxWidth;
|
||||
return Row(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
width: _mediaQuery!.size.width -
|
||||
_columnWidth -
|
||||
(_showDetails ? FluffyThemes.columnWidth : 0),
|
||||
child: Stack(
|
||||
alignment: _ownMessage
|
||||
? Alignment.centerRight
|
||||
: Alignment.centerLeft,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: widget.chatController.clearSelectedEvents,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.only(
|
||||
left: _messageLeftOffset ?? 0.0,
|
||||
right: _messageRightOffset ?? 0.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: _ownMessage
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_contentHeight != null &&
|
||||
_mediaQuery != null &&
|
||||
_availableSpaceAboveContent != null &&
|
||||
_availableSpaceAboveContent! <
|
||||
_overheadContentHeight)
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height:
|
||||
_contentHeight! + _overheadContentHeight >
|
||||
_mediaQuery!.size.height
|
||||
? _overheadContentHeight
|
||||
: (_overheadContentHeight -
|
||||
_availableSpaceAboveContent!) *
|
||||
2,
|
||||
return SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
width: _mediaQuery!.size.width -
|
||||
_columnWidth -
|
||||
(_showDetails ? FluffyThemes.columnWidth : 0),
|
||||
child: Stack(
|
||||
alignment: _ownMessage
|
||||
? Alignment.centerRight
|
||||
: Alignment.centerLeft,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: widget.chatController.clearSelectedEvents,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.only(
|
||||
left: _messageLeftOffset ?? 0.0,
|
||||
right: _messageRightOffset ?? 0.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: _ownMessage
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_contentHeight != null &&
|
||||
_mediaQuery != null &&
|
||||
_availableSpaceAboveContent != null &&
|
||||
_availableSpaceAboveContent! <
|
||||
_overheadContentHeight)
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height: _contentHeight! +
|
||||
_overheadContentHeight >
|
||||
_mediaQuery!.size.height
|
||||
? _overheadContentHeight
|
||||
: (_overheadContentHeight -
|
||||
_availableSpaceAboveContent!) *
|
||||
2,
|
||||
),
|
||||
CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState
|
||||
.layerLinkAndKey(
|
||||
'overlay_message_${widget.event.eventId}',
|
||||
)
|
||||
.link,
|
||||
child: OverlayCenterContent(
|
||||
event: widget.event,
|
||||
messageHeight: _originalMessageSize.height,
|
||||
messageWidth: widget
|
||||
.overlayController.showingExtraContent
|
||||
? max(_originalMessageSize.width, 150)
|
||||
: _originalMessageSize.width,
|
||||
overlayController: widget.overlayController,
|
||||
chatController: widget.chatController,
|
||||
nextEvent: widget.nextEvent,
|
||||
prevEvent: widget.prevEvent,
|
||||
hasReactions: _hasReactions,
|
||||
// sizeAnimation: _messageSizeAnimation,
|
||||
isTransitionAnimation: true,
|
||||
readingAssistanceMode: widget
|
||||
.overlayController.readingAssistanceMode,
|
||||
),
|
||||
),
|
||||
CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState
|
||||
.layerLinkAndKey(
|
||||
'overlay_message_${widget.event.eventId}',
|
||||
)
|
||||
.link,
|
||||
child: OverlayCenterContent(
|
||||
event: widget.event,
|
||||
messageHeight: _originalMessageSize.height,
|
||||
messageWidth:
|
||||
widget.overlayController.showingExtraContent
|
||||
? max(_originalMessageSize.width, 150)
|
||||
: _originalMessageSize.width,
|
||||
const SizedBox(height: 4.0),
|
||||
SelectModeButtons(
|
||||
controller: widget.chatController,
|
||||
overlayController: widget.overlayController,
|
||||
chatController: widget.chatController,
|
||||
nextEvent: widget.nextEvent,
|
||||
prevEvent: widget.prevEvent,
|
||||
hasReactions: _hasReactions,
|
||||
// sizeAnimation: _messageSizeAnimation,
|
||||
isTransitionAnimation: true,
|
||||
readingAssistanceMode: widget
|
||||
.overlayController.readingAssistanceMode,
|
||||
lauchPractice: () {},
|
||||
// lauchPractice: () {
|
||||
// _setReadingAssistanceMode(
|
||||
// ReadingAssistanceMode.practiceMode,
|
||||
// );
|
||||
// widget.overlayController
|
||||
// .updateSelectedSpan(null);
|
||||
// },
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
SelectModeButtons(
|
||||
controller: widget.chatController,
|
||||
overlayController: widget.overlayController,
|
||||
lauchPractice: () {},
|
||||
// lauchPractice: () {
|
||||
// _setReadingAssistanceMode(
|
||||
// ReadingAssistanceMode.practiceMode,
|
||||
// );
|
||||
// widget.overlayController
|
||||
// .updateSelectedSpan(null);
|
||||
// },
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedPositioned(
|
||||
top: _wordCardTopOffset,
|
||||
left: _wordCardLeftOffset,
|
||||
right: _messageRightOffset,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: AnimatedSize(
|
||||
AnimatedPositioned(
|
||||
top: _wordCardTopOffset,
|
||||
left: _wordCardLeftOffset,
|
||||
right: _messageRightOffset,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _wordCardTopOffset == null
|
||||
? const SizedBox()
|
||||
: widget.pangeaMessageEvent != null &&
|
||||
widget.overlayController.selectedToken !=
|
||||
null
|
||||
? ReadingAssistanceContent(
|
||||
pangeaMessageEvent:
|
||||
widget.pangeaMessageEvent!,
|
||||
overlayController: widget.overlayController,
|
||||
)
|
||||
: MessageReactionPicker(
|
||||
chatController: widget.chatController,
|
||||
),
|
||||
child: AnimatedSize(
|
||||
alignment: _ownMessage
|
||||
? Alignment.bottomRight
|
||||
: Alignment.bottomLeft,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _wordCardTopOffset == null
|
||||
? const SizedBox()
|
||||
: widget.pangeaMessageEvent != null &&
|
||||
widget.overlayController.selectedToken !=
|
||||
null
|
||||
? ReadingAssistanceContent(
|
||||
pangeaMessageEvent:
|
||||
widget.pangeaMessageEvent!,
|
||||
overlayController:
|
||||
widget.overlayController,
|
||||
)
|
||||
: MessageReactionPicker(
|
||||
chatController: widget.chatController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_showDetails)
|
||||
const SizedBox(
|
||||
width: FluffyThemes.columnWidth,
|
||||
],
|
||||
),
|
||||
],
|
||||
if (_showDetails)
|
||||
const SizedBox(
|
||||
width: FluffyThemes.columnWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -824,108 +837,111 @@ class MessageReactionPicker extends StatelessWidget {
|
|||
shadowColor: theme.colorScheme.surface.withAlpha(128),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...AppConfig.defaultReactions.map(
|
||||
(emoji) => IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Center(
|
||||
child: Opacity(
|
||||
opacity: sentReactions.contains(
|
||||
emoji,
|
||||
)
|
||||
? 0.33
|
||||
: 1,
|
||||
child: Text(
|
||||
emoji,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
child: SizedBox(
|
||||
height: 40.0,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...AppConfig.defaultReactions.map(
|
||||
(emoji) => IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Center(
|
||||
child: Opacity(
|
||||
opacity: sentReactions.contains(
|
||||
emoji,
|
||||
)
|
||||
? 0.33
|
||||
: 1,
|
||||
child: Text(
|
||||
emoji,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
onPressed: sentReactions.contains(emoji)
|
||||
? null
|
||||
: () => event.room.sendReaction(
|
||||
event.eventId,
|
||||
emoji,
|
||||
),
|
||||
),
|
||||
onPressed: sentReactions.contains(emoji)
|
||||
? null
|
||||
: () => event.room.sendReaction(
|
||||
event.eventId,
|
||||
emoji,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.add_reaction_outlined,
|
||||
),
|
||||
tooltip: L10n.of(context).customReaction,
|
||||
onPressed: () async {
|
||||
final emoji = await showAdaptiveBottomSheet<String>(
|
||||
context: context,
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
L10n.of(context).customReaction,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.add_reaction_outlined,
|
||||
),
|
||||
tooltip: L10n.of(context).customReaction,
|
||||
onPressed: () async {
|
||||
final emoji = await showAdaptiveBottomSheet<String>(
|
||||
context: context,
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
L10n.of(context).customReaction,
|
||||
),
|
||||
leading: CloseButton(
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).pop(
|
||||
null,
|
||||
leading: CloseButton(
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).pop(
|
||||
null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SizedBox(
|
||||
height: double.infinity,
|
||||
child: EmojiPicker(
|
||||
onEmojiSelected: (
|
||||
_,
|
||||
emoji,
|
||||
) =>
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop(
|
||||
emoji.emoji,
|
||||
),
|
||||
config: Config(
|
||||
emojiViewConfig: const EmojiViewConfig(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: SizedBox(
|
||||
height: double.infinity,
|
||||
child: EmojiPicker(
|
||||
onEmojiSelected: (
|
||||
_,
|
||||
emoji,
|
||||
) =>
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop(
|
||||
emoji.emoji,
|
||||
),
|
||||
bottomActionBarConfig: const BottomActionBarConfig(
|
||||
enabled: false,
|
||||
),
|
||||
categoryViewConfig: CategoryViewConfig(
|
||||
initCategory: Category.SMILEYS,
|
||||
backspaceColor: theme.colorScheme.primary,
|
||||
iconColor: theme.colorScheme.primary.withAlpha(
|
||||
128,
|
||||
config: Config(
|
||||
emojiViewConfig: const EmojiViewConfig(
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
bottomActionBarConfig: const BottomActionBarConfig(
|
||||
enabled: false,
|
||||
),
|
||||
categoryViewConfig: CategoryViewConfig(
|
||||
initCategory: Category.SMILEYS,
|
||||
backspaceColor: theme.colorScheme.primary,
|
||||
iconColor: theme.colorScheme.primary.withAlpha(
|
||||
128,
|
||||
),
|
||||
iconColorSelected: theme.colorScheme.primary,
|
||||
indicatorColor: theme.colorScheme.primary,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
skinToneConfig: SkinToneConfig(
|
||||
dialogBackgroundColor: Color.lerp(
|
||||
theme.colorScheme.surface,
|
||||
theme.colorScheme.primaryContainer,
|
||||
0.75,
|
||||
)!,
|
||||
indicatorColor: theme.colorScheme.onSurface,
|
||||
),
|
||||
iconColorSelected: theme.colorScheme.primary,
|
||||
indicatorColor: theme.colorScheme.primary,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
skinToneConfig: SkinToneConfig(
|
||||
dialogBackgroundColor: Color.lerp(
|
||||
theme.colorScheme.surface,
|
||||
theme.colorScheme.primaryContainer,
|
||||
0.75,
|
||||
)!,
|
||||
indicatorColor: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (emoji == null) return;
|
||||
if (sentReactions.contains(emoji)) return;
|
||||
await event.room.sendReaction(
|
||||
event.eventId,
|
||||
emoji,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
if (emoji == null) return;
|
||||
if (sentReactions.contains(emoji)) return;
|
||||
await event.room.sendReaction(
|
||||
event.eventId,
|
||||
emoji,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ enum MessageActions {
|
|||
download,
|
||||
pin,
|
||||
report,
|
||||
info;
|
||||
info,
|
||||
deleteOnError,
|
||||
sendAgain;
|
||||
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
|
|
@ -77,6 +79,10 @@ enum MessageActions {
|
|||
return Icons.shield_outlined;
|
||||
case MessageActions.info:
|
||||
return Icons.info_outlined;
|
||||
case MessageActions.deleteOnError:
|
||||
return Icons.delete;
|
||||
case MessageActions.sendAgain:
|
||||
return Icons.send_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +107,10 @@ enum MessageActions {
|
|||
return l10n.reportMessage;
|
||||
case MessageActions.info:
|
||||
return l10n.messageInfo;
|
||||
case MessageActions.deleteOnError:
|
||||
return l10n.delete;
|
||||
case MessageActions.sendAgain:
|
||||
return l10n.tryToSendAgain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -539,20 +549,34 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
|
||||
bool _messageActionEnabled(MessageActions action) {
|
||||
if (messageEvent == null) return false;
|
||||
if (widget.controller.selectedEvents.isEmpty) return false;
|
||||
final events = widget.controller.selectedEvents;
|
||||
|
||||
if (events.any((e) => !e.status.isSent)) {
|
||||
if (action == MessageActions.sendAgain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (events.every((e) => e.status.isError) &&
|
||||
action == MessageActions.deleteOnError) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MessageActions.reply:
|
||||
return widget.controller.selectedEvents.length == 1 &&
|
||||
return events.length == 1 &&
|
||||
widget.controller.room.canSendDefaultMessages;
|
||||
case MessageActions.edit:
|
||||
return widget.controller.canEditSelectedEvents &&
|
||||
!widget.controller.selectedEvents.first.isActivityMessage;
|
||||
!events.first.isActivityMessage;
|
||||
case MessageActions.delete:
|
||||
return widget.controller.canRedactSelectedEvents;
|
||||
case MessageActions.copy:
|
||||
return widget.controller.selectedEvents.length == 1 &&
|
||||
widget.controller.selectedEvents.single.messageType ==
|
||||
MessageTypes.Text;
|
||||
return events.length == 1 &&
|
||||
events.single.messageType == MessageTypes.Text;
|
||||
case MessageActions.download:
|
||||
return widget.controller.canSaveSelectedEvent;
|
||||
case MessageActions.pin:
|
||||
|
|
@ -560,7 +584,10 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
case MessageActions.forward:
|
||||
case MessageActions.report:
|
||||
case MessageActions.info:
|
||||
return widget.controller.selectedEvents.length == 1;
|
||||
return events.length == 1;
|
||||
case MessageActions.deleteOnError:
|
||||
case MessageActions.sendAgain:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -600,13 +627,23 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
widget.controller.showEventInfo();
|
||||
widget.controller.clearSelectedEvents();
|
||||
break;
|
||||
case MessageActions.deleteOnError:
|
||||
widget.controller.deleteErrorEventsAction();
|
||||
break;
|
||||
case MessageActions.sendAgain:
|
||||
widget.controller.sendAgainAction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final modes = messageEvent?.isAudioMessage == true ? audioModes : textModes;
|
||||
final modes = widget.overlayController.showLanguageAssistance
|
||||
? messageEvent?.isAudioMessage == true
|
||||
? audioModes
|
||||
: textModes
|
||||
: [];
|
||||
final actions = MessageActions.values.where(_messageActionEnabled);
|
||||
|
||||
return Material(
|
||||
|
|
@ -637,7 +674,9 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
),
|
||||
);
|
||||
} else if (index == modes.length) {
|
||||
return const Divider(height: 1.0);
|
||||
return modes.isNotEmpty
|
||||
? const Divider(height: 1.0)
|
||||
: const SizedBox();
|
||||
} else {
|
||||
final action = actions.elementAt(index - modes.length - 1);
|
||||
return SizedBox(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue