Added selection features to toolbar overlay
This commit is contained in:
parent
668b9dd65f
commit
622384036b
9 changed files with 287 additions and 63 deletions
|
|
@ -165,7 +165,9 @@ class ChatEventList extends StatelessWidget {
|
|||
),
|
||||
highlightMarker:
|
||||
controller.scrollToEventIdMarker == event.eventId,
|
||||
onSelect: controller.onSelectMessage,
|
||||
// #Pangea
|
||||
// onSelect: controller.onSelectMessage,
|
||||
// Pangea#
|
||||
scrollToEventId: (String eventId) =>
|
||||
controller.scrollToEventId(eventId),
|
||||
longPressSelect: controller.selectedEvents.isNotEmpty,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:fluffychat/pages/chat/chat_emoji_picker.dart';
|
|||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_input_row.dart';
|
||||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
||||
|
|
@ -35,6 +34,21 @@ class ChatView extends StatelessWidget {
|
|||
|
||||
const ChatView(this.controller, {super.key});
|
||||
|
||||
// #Pangea
|
||||
List<Widget> _editedAppBarActions(BuildContext context) {
|
||||
if (!controller.selectMode) {
|
||||
return [
|
||||
ChatSettingsPopupMenu(
|
||||
controller.room,
|
||||
(!controller.room.isDirectChat && !controller.room.isArchived),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
List<Widget> _appBarActions(BuildContext context) {
|
||||
if (controller.selectMode) {
|
||||
return [
|
||||
|
|
@ -197,26 +211,33 @@ class ChatView extends StatelessWidget {
|
|||
? null
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
leading: controller.selectMode
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: controller.clearSelectedEvents,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: UnreadRoomsBadge(
|
||||
filter: (r) =>
|
||||
r.id != controller.roomId
|
||||
// #Pangea
|
||||
&&
|
||||
!r.isAnalyticsRoom,
|
||||
// Pangea#
|
||||
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
|
||||
child: const Center(child: BackButton()),
|
||||
),
|
||||
leading:
|
||||
// #Pangea
|
||||
// controller.selectMode
|
||||
// ? IconButton(
|
||||
// icon: const Icon(Icons.close),
|
||||
// onPressed: controller.clearSelectedEvents,
|
||||
// tooltip: L10n.of(context)!.close,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// )
|
||||
// :
|
||||
// Pangea#
|
||||
UnreadRoomsBadge(
|
||||
filter: (r) =>
|
||||
r.id != controller.roomId
|
||||
// #Pangea
|
||||
&&
|
||||
!r.isAnalyticsRoom,
|
||||
// Pangea#
|
||||
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
|
||||
child: const Center(child: BackButton()),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
title: ChatAppBarTitle(controller),
|
||||
actions: _appBarActions(context),
|
||||
// #Pangea
|
||||
// actions: _appBarActions(context),
|
||||
actions: _editedAppBarActions(context),
|
||||
// Pangea#
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(appbarBottomHeight),
|
||||
child: Column(
|
||||
|
|
@ -497,7 +518,6 @@ class ChatView extends StatelessWidget {
|
|||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
ChatEmojiPicker(controller),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:fluffychat/utils/string_color.dart';
|
|||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:swipe_to_action/swipe_to_action.dart';
|
||||
|
|
@ -26,7 +25,6 @@ class Message extends StatelessWidget {
|
|||
final Event? nextEvent;
|
||||
final Event? previousEvent;
|
||||
final bool displayReadMarker;
|
||||
final void Function(Event) onSelect;
|
||||
final void Function(Event) onAvatarTab;
|
||||
final void Function(Event) onInfoTab;
|
||||
final void Function(String) scrollToEventId;
|
||||
|
|
@ -38,6 +36,7 @@ class Message extends StatelessWidget {
|
|||
final bool animateIn;
|
||||
final void Function()? resetAnimateIn;
|
||||
// #Pangea
|
||||
// final void Function(Event) onSelect;
|
||||
final bool immersionMode;
|
||||
final bool definitions;
|
||||
final ChatController controller;
|
||||
|
|
@ -50,7 +49,9 @@ class Message extends StatelessWidget {
|
|||
this.previousEvent,
|
||||
this.displayReadMarker = false,
|
||||
this.longPressSelect = false,
|
||||
required this.onSelect,
|
||||
// #Pangea
|
||||
// required this.onSelect,
|
||||
// Pangea#
|
||||
required this.onInfoTab,
|
||||
required this.onAvatarTab,
|
||||
required this.scrollToEventId,
|
||||
|
|
@ -203,8 +204,10 @@ class Message extends StatelessWidget {
|
|||
left: 0,
|
||||
right: 0,
|
||||
child: InkWell(
|
||||
onTap: () => onSelect(event),
|
||||
onLongPress: () => onSelect(event),
|
||||
// #Pangea
|
||||
// onTap: () => onSelect(event),
|
||||
// onLongPress: () => onSelect(event),
|
||||
// Pangea#
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
child: Material(
|
||||
|
|
@ -228,17 +231,20 @@ class Message extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: rowMainAxisAlignment,
|
||||
children: [
|
||||
if (longPressSelect)
|
||||
SizedBox(
|
||||
height: 32,
|
||||
width: Avatar.defaultSize,
|
||||
child: Checkbox.adaptive(
|
||||
value: selected,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (_) => onSelect(event),
|
||||
),
|
||||
)
|
||||
else if (nextEventSameSender || ownMessage)
|
||||
// #Pangea
|
||||
// if (longPressSelect)
|
||||
// SizedBox(
|
||||
// height: 32,
|
||||
// width: Avatar.defaultSize,
|
||||
// child: Checkbox.adaptive(
|
||||
// value: selected,
|
||||
// shape: const CircleBorder(),
|
||||
// onChanged: (_) => onSelect(event),
|
||||
// ),
|
||||
// )
|
||||
// else
|
||||
// Pangea#
|
||||
if (nextEventSameSender || ownMessage)
|
||||
SizedBox(
|
||||
width: Avatar.defaultSize,
|
||||
child: Center(
|
||||
|
|
@ -319,13 +325,13 @@ class Message extends StatelessWidget {
|
|||
),
|
||||
onDoubleTap: () =>
|
||||
toolbarController?.showToolbar(context),
|
||||
// onLongPress: longPressSelect
|
||||
// ? null
|
||||
// : () {
|
||||
// HapticFeedback.heavyImpact();
|
||||
// onSelect(event);
|
||||
// },
|
||||
// Pangea#
|
||||
onLongPress: longPressSelect
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
onSelect(event);
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
opacity: animateIn
|
||||
? 0
|
||||
|
|
|
|||
|
|
@ -317,9 +317,11 @@ class MessageContent extends StatelessWidget {
|
|||
pangeaMessageEvent != null &&
|
||||
!(toolbarController!.highlighted) &&
|
||||
!selected) {
|
||||
toolbarController!.controller.onSelectMessage(
|
||||
pangeaMessageEvent!.event,
|
||||
);
|
||||
// #Pangea
|
||||
// toolbarController!.controller.onSelectMessage(
|
||||
// pangeaMessageEvent!.event,
|
||||
// );
|
||||
// Pangea#
|
||||
return;
|
||||
}
|
||||
toolbarController?.toolbar?.textSelection
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class OverlayUtil {
|
|||
Alignment? followerAnchor,
|
||||
bool closePrevOverlay = true,
|
||||
bool targetScreen = false,
|
||||
Function? onDismiss,
|
||||
}) {
|
||||
try {
|
||||
if (closePrevOverlay) {
|
||||
|
|
@ -44,6 +45,7 @@ class OverlayUtil {
|
|||
if (backDropToDismiss)
|
||||
TransparentBackdrop(
|
||||
backgroundColor: backgroundColor,
|
||||
onDismiss: onDismiss,
|
||||
),
|
||||
Positioned(
|
||||
width: width,
|
||||
|
|
@ -194,8 +196,10 @@ class OverlayUtil {
|
|||
|
||||
class TransparentBackdrop extends StatelessWidget {
|
||||
final Color? backgroundColor;
|
||||
final Function? onDismiss;
|
||||
const TransparentBackdrop({
|
||||
super.key,
|
||||
this.onDismiss,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
|
|
@ -211,6 +215,9 @@ class TransparentBackdrop extends StatelessWidget {
|
|||
focusColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onTap: () {
|
||||
if (onDismiss != null) {
|
||||
onDismiss!();
|
||||
}
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
},
|
||||
child: Container(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart'
|
|||
import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
|
||||
|
|
@ -72,7 +74,7 @@ class ToolbarDisplayController {
|
|||
immersionMode: immersionMode,
|
||||
ownMessage: pangeaMessageEvent.ownMessage,
|
||||
toolbarController: this,
|
||||
width: messageWidth,
|
||||
width: 300,
|
||||
nextEvent: nextEvent,
|
||||
previousEvent: previousEvent,
|
||||
);
|
||||
|
|
@ -84,18 +86,33 @@ class ToolbarDisplayController {
|
|||
Widget overlayEntry;
|
||||
if (toolbar == null) return;
|
||||
try {
|
||||
overlayEntry = Container(
|
||||
constraints:
|
||||
BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height * .72),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
toolbar!,
|
||||
const SizedBox(height: 6),
|
||||
overlayMessage,
|
||||
],
|
||||
),
|
||||
overlayEntry = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
OverlayHeader(controller: controller),
|
||||
const SizedBox(
|
||||
height: 7,
|
||||
),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * .72,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
toolbar!,
|
||||
const SizedBox(height: 9),
|
||||
overlayMessage,
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 7,
|
||||
),
|
||||
OverlayFooter(controller: controller),
|
||||
],
|
||||
);
|
||||
} catch (err) {
|
||||
debugger(when: kDebugMode);
|
||||
|
|
@ -113,9 +130,10 @@ class ToolbarDisplayController {
|
|||
closePrevOverlay:
|
||||
MatrixState.pangeaController.subscriptionController.isSubscribed,
|
||||
targetScreen: true,
|
||||
onDismiss: controller.clearSelectedEvents,
|
||||
);
|
||||
|
||||
// controller.onSelectMessage(pangeaMessageEvent.event);
|
||||
controller.onSelectMessage(pangeaMessageEvent.event);
|
||||
|
||||
if (MatrixState.pAnyState.entries.isNotEmpty) {
|
||||
overlayId = MatrixState.pAnyState.entries.last.hashCode.toString();
|
||||
|
|
|
|||
57
lib/pangea/widgets/chat/overlay_footer.dart
Normal file
57
lib/pangea/widgets/chat/overlay_footer.dart
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_input_row.dart';
|
||||
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
|
||||
class OverlayFooter extends StatelessWidget {
|
||||
ChatController controller;
|
||||
|
||||
OverlayFooter({
|
||||
required this.controller,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 18.0 : 10.0;
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ReactionsPicker(controller),
|
||||
ReplyDisplay(controller),
|
||||
ChatInputRow(controller),
|
||||
SizedBox(
|
||||
height: (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
112
lib/pangea/widgets/chat/overlay_header.dart
Normal file
112
lib/pangea/widgets/chat/overlay_header.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
|
||||
class OverlayHeader extends StatelessWidget {
|
||||
ChatController controller;
|
||||
|
||||
OverlayHeader({
|
||||
required this.controller,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Event selectedEvent = controller.selectedEvents.single;
|
||||
|
||||
return AppBar(
|
||||
actionsIconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: MatrixState.pAnyState.closeOverlay,
|
||||
tooltip: L10n.of(context)!.close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
titleSpacing: 0,
|
||||
title: ChatAppBarTitle(controller),
|
||||
actions: [
|
||||
if (controller.canEditSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: L10n.of(context)!.edit,
|
||||
onPressed: controller.editSelectedEventAction,
|
||||
),
|
||||
if (selectedEvent.messageType == MessageTypes.Text)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy_outlined),
|
||||
tooltip: L10n.of(context)!.copy,
|
||||
onPressed: controller.copyEventsAction,
|
||||
),
|
||||
if (controller.canSaveSelectedEvent)
|
||||
// Use builder context to correctly position the share dialog on iPad
|
||||
Builder(
|
||||
builder: (context) => IconButton(
|
||||
icon: Icon(Icons.adaptive.share),
|
||||
tooltip: L10n.of(context)!.share,
|
||||
onPressed: () => controller.saveSelectedEvent(context),
|
||||
),
|
||||
),
|
||||
if (controller.canPinSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.push_pin_outlined),
|
||||
onPressed: controller.pinEvent,
|
||||
tooltip: L10n.of(context)!.pinMessage,
|
||||
),
|
||||
if (controller.canRedactSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
tooltip: L10n.of(context)!.redactMessage,
|
||||
onPressed: controller.redactEventsAction,
|
||||
),
|
||||
PopupMenuButton<_EventContextAction>(
|
||||
onSelected: (action) {
|
||||
switch (action) {
|
||||
case _EventContextAction.info:
|
||||
controller.showEventInfo();
|
||||
controller.clearSelectedEvents();
|
||||
break;
|
||||
case _EventContextAction.report:
|
||||
controller.reportEventAction();
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: _EventContextAction.info,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context)!.messageInfo),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (selectedEvent.status.isSent)
|
||||
PopupMenuItem(
|
||||
value: _EventContextAction.report,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.shield_outlined,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context)!.reportMessage),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -143,9 +143,9 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
(e) => e.eventId == widget.pangeaMessageEvent.eventId,
|
||||
) ??
|
||||
false)) {
|
||||
widget.toolbarController?.controller.onSelectMessage(
|
||||
widget.pangeaMessageEvent.event,
|
||||
);
|
||||
// widget.toolbarController?.controller.onSelectMessage(
|
||||
// widget.pangeaMessageEvent.event,
|
||||
// );
|
||||
return;
|
||||
}
|
||||
widget.toolbarController?.toolbar?.textSelection
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue