parent
1380233f2e
commit
2f17f5b6c6
6 changed files with 116 additions and 17 deletions
|
|
@ -29,6 +29,7 @@ import "package:nexus/widgets/renderers/message.dart";
|
|||
import "package:nexus/widgets/reaction_row.dart";
|
||||
import "package:nexus/widgets/renderers/membership.dart";
|
||||
import "package:nexus/widgets/renderers/generic_event.dart";
|
||||
import "package:nexus/widgets/swipe_to_reply.dart";
|
||||
import "package:nexus/widgets/timestamp.dart";
|
||||
|
||||
class const EventRenderer(
|
||||
|
|
@ -39,6 +40,9 @@ class const EventRenderer(
|
|||
final int? maxLines,
|
||||
final JumpToEvent? jumpToEvent,
|
||||
final IList<PopupMenuEntry> Function(Event event)? getEventOptions,
|
||||
|
||||
/// If set, events can be swiped, calling this
|
||||
final VoidCallback? onSwipe,
|
||||
super.key,
|
||||
}) extends HookConsumerWidget {
|
||||
@override
|
||||
|
|
@ -239,18 +243,21 @@ class const EventRenderer(
|
|||
child: GestureDetector(
|
||||
onSecondaryTapUp: contextMenuCallback,
|
||||
onLongPressStart: contextMenuCallback,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8)
|
||||
.copyWith(top: isGrouped ? 0 : 8),
|
||||
child: Wrap(
|
||||
verticalDirection: .up,
|
||||
spacing: 6,
|
||||
crossAxisAlignment: .center,
|
||||
children: [
|
||||
child,
|
||||
if (child is! MessageRenderer)
|
||||
Timestamp(event.timestamp),
|
||||
],
|
||||
child: SwipeToReply(
|
||||
onSwipe: onSwipe,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8)
|
||||
.copyWith(top: isGrouped ? 0 : 8),
|
||||
child: Wrap(
|
||||
verticalDirection: .up,
|
||||
spacing: 6,
|
||||
crossAxisAlignment: .center,
|
||||
children: [
|
||||
child,
|
||||
if (child is! MessageRenderer)
|
||||
Timestamp(event.timestamp),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class const ChatTimeline({
|
|||
required final IList<PopupMenuEntry> Function(Event) getEventOptions,
|
||||
required final int? highlightedEvent,
|
||||
required final double composerHeight,
|
||||
final void Function(int rowId)? onSwipe,
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
Widget eventRow(
|
||||
|
|
@ -26,6 +27,7 @@ class const ChatTimeline({
|
|||
roomId: roomId,
|
||||
jumpToEvent: jumpToEvent,
|
||||
getEventOptions: getEventOptions,
|
||||
onSwipe: onSwipe == null ? null : () => onSwipe!(eventRowId),
|
||||
),
|
||||
key: key,
|
||||
isHighlighted: highlightedEvent == eventRowId,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import "package:measure_size/measure_size.dart";
|
|||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/client_state.dart";
|
||||
import "package:nexus/controllers/member_list_opened.dart";
|
||||
import "package:nexus/controllers/power_level.dart";
|
||||
import "package:nexus/controllers/settings.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/room_chat.dart";
|
||||
import "package:nexus/helpers/hooks/chat_scroll.dart";
|
||||
|
|
@ -171,19 +173,30 @@ final class const RoomChat({
|
|||
return null;
|
||||
}, [jumpRowId]);
|
||||
|
||||
void onRelation(int rowId, RelationType type) {
|
||||
relatedEvent.value = rowId;
|
||||
relationType.value = type;
|
||||
composerNode.requestFocus();
|
||||
}
|
||||
|
||||
IList<PopupMenuEntry> getEventOptions(Event event) =>
|
||||
event.buildEventOptions(
|
||||
context: context,
|
||||
ref: ref,
|
||||
roomId: roomId,
|
||||
userId: userId,
|
||||
onRelation: (rowId, type) {
|
||||
relatedEvent.value = rowId;
|
||||
relationType.value = type;
|
||||
composerNode.requestFocus();
|
||||
},
|
||||
onRelation: onRelation,
|
||||
);
|
||||
|
||||
final swipeToReply = ref.watch(
|
||||
SettingsController.provider.select(
|
||||
(settings) => settings.value?.swipeToReply ?? false,
|
||||
),
|
||||
);
|
||||
final canReply = ref.watch(
|
||||
PowerLevelController.provider(.new(eventType: .message, roomId: roomId)),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
endDrawer: PinnedEventsDrawer(
|
||||
roomId,
|
||||
|
|
@ -224,6 +237,9 @@ final class const RoomChat({
|
|||
roomId: roomId,
|
||||
jumpToEvent: jumpToEvent,
|
||||
getEventOptions: getEventOptions,
|
||||
onSwipe: swipeToReply && canReply
|
||||
? (rowId) => onRelation(rowId, .reply)
|
||||
: null,
|
||||
highlightedEvent: highlightedEvent.value,
|
||||
composerHeight: composerSize.value,
|
||||
),
|
||||
|
|
|
|||
58
lib/widgets/swipe_to_reply.dart
Normal file
58
lib/widgets/swipe_to_reply.dart
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import "package:flutter/services.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
|
||||
class const SwipeToReply(
|
||||
final Widget child, {
|
||||
required final VoidCallback? onSwipe,
|
||||
super.key,
|
||||
}) extends HookWidget {
|
||||
static const maxDrag = 64.0;
|
||||
static const threshold = 0.75;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = useAnimationController(duration: Durations.short4);
|
||||
useListenable(controller);
|
||||
|
||||
if (onSwipe == null) return child;
|
||||
|
||||
final progress = controller.value;
|
||||
|
||||
return GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
final previous = controller.value;
|
||||
controller.value = previous + details.primaryDelta! / maxDrag;
|
||||
|
||||
if (previous < threshold && controller.value >= threshold) {
|
||||
HapticFeedback.lightImpact();
|
||||
}
|
||||
},
|
||||
onHorizontalDragEnd: (_) {
|
||||
if (controller.value >= threshold) onSwipe!();
|
||||
controller.animateBack(0, curve: Curves.easeOut);
|
||||
},
|
||||
onHorizontalDragCancel: () =>
|
||||
controller.animateBack(0, curve: Curves.easeOut),
|
||||
child: Stack(
|
||||
alignment: .centerLeft,
|
||||
children: [
|
||||
Positioned(
|
||||
left: 16,
|
||||
child: Opacity(
|
||||
opacity: (progress / threshold).clamp(0, 1),
|
||||
child: Transform.scale(
|
||||
scale: progress >= threshold ? 1 : 0.75,
|
||||
child: const Icon(Icons.reply),
|
||||
),
|
||||
),
|
||||
),
|
||||
Transform.translate(
|
||||
offset: .new(progress * maxDrag, 0),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue