Skip to content

Commit

Permalink
TW-344: Handle hide/show StickyTimestamp for web
Browse files Browse the repository at this point in the history
  • Loading branch information
nqhhdev authored and hoangdat committed Feb 15, 2024
1 parent b3bae60 commit 19784ba
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 101 deletions.
57 changes: 36 additions & 21 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:fluffychat/pages/chat/chat_view_style.dart';
import 'package:fluffychat/presentation/mixins/handle_clipboard_action_mixin.dart';
import 'package:fluffychat/presentation/mixins/paste_image_mixin.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:fluffychat/utils/extension/global_key_extension.dart';
import 'package:universal_html/html.dart' as html;

import 'package:adaptive_dialog/adaptive_dialog.dart';
Expand Down Expand Up @@ -102,6 +103,9 @@ class ChatController extends State<Chat>

static const Duration _delayHideStickyTimestampHeader = Duration(seconds: 2);

final GlobalKey stickyTimestampKey =
GlobalKey(debugLabel: 'stickyTimestampKey');

final responsive = getIt.get<ResponsiveUtils>();

final getPinnedMessageInteractor = getIt.get<ChatGetPinnedEventsInteractor>();
Expand Down Expand Up @@ -1560,36 +1564,47 @@ class ChatController extends State<Chat>
}

void handleDisplayStickyTimestamp(DateTime dateTime) {
Logs().d('Chat::handleDisplayStickyTimestamp() Event Time - $dateTime');
if (_currentChatScrollState.isEndScroll) return;
if (!scrollController.hasClients) return;
_currentDateTimeEvent = dateTime;
if (scrollController.offset > 0) {
stickyTimestampNotifier.value ??= dateTime;
if (stickyTimestampNotifier.value?.day != dateTime.day) {
Logs().d(
'Chat::handleDisplayStickyTimestamp() StickyTimestampNotifier - ${stickyTimestampNotifier.value}',
);
Logs().d(
'Chat::handleDisplayStickyTimestamp() CurrentDateTimeEvent - $_currentDateTimeEvent',
);
stickyTimestampNotifier.value = dateTime;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_currentChatScrollState.isEndScroll) return;
_currentDateTimeEvent = dateTime;
if (scrollController.offset > 0) {
stickyTimestampNotifier.value ??= dateTime;
if (stickyTimestampNotifier.value?.day != dateTime.day) {
Logs().d(
'Chat::handleDisplayStickyTimestamp() StickyTimestampNotifier - ${stickyTimestampNotifier.value}',
);
Logs().d(
'Chat::handleDisplayStickyTimestamp() CurrentDateTimeEvent - $_currentDateTimeEvent',
);
stickyTimestampNotifier.value = dateTime;
}
}
}
});
}

bool isInViewPortCondition(
double deltaTop,
double deltaBottom,
double viewPortDimension,
) {
final stickyTimestampHeight =
stickyTimestampKey.globalPaintBoundsRect?.height ?? 0;
return deltaTop < viewPortDimension - stickyTimestampHeight &&
deltaBottom > viewPortDimension - stickyTimestampHeight;
}

void _handleHideStickyTimestamp() {
Logs().d('Chat::_handleHideStickyTimestamp() - Hide sticky timestamp');
if (stickyTimestampNotifier.value != null) {
stickyTimestampNotifier.value = null;
}
stickyTimestampNotifier.value = null;
}

void handleScrollEndNotification() async {
Logs().d('Chat::handleScrollEndNotification() - End of scroll');
_currentChatScrollState = ChatScrollState.endScroll;
await Future.delayed(_delayHideStickyTimestampHeader);
_handleHideStickyTimestamp();
if (PlatformInfos.isMobile) {
_currentChatScrollState = ChatScrollState.endScroll;
await Future.delayed(_delayHideStickyTimestampHeader);
_handleHideStickyTimestamp();
}
}

void handleScrollStartNotification() {
Expand Down
136 changes: 71 additions & 65 deletions lib/pages/chat/chat_event_list.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'dart:ui';

import 'package:fluffychat/pages/chat/group_chat_empty_view.dart';
import 'package:fluffychat/pages/chat_draft/draft_chat_empty_widget.dart';
import 'package:fluffychat/presentation/model/search/presentation_search.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';

import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:inview_notifier_list/inview_notifier_list.dart';
import 'package:linagora_design_flutter/linagora_design_flutter.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
Expand All @@ -13,7 +16,6 @@ import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/message/message.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:visibility_detector/visibility_detector.dart';

class ChatEventList extends StatelessWidget {
final ChatController controller;
Expand Down Expand Up @@ -70,23 +72,28 @@ class ChatEventList extends StatelessWidget {
}
return false;
},
child: SelectionTextContainer(
chatController: controller,
focusNode: controller.selectionFocusNode,
child: ListView.custom(
padding: EdgeInsets.only(
top: 16,
bottom: 8.0,
left: horizontalPadding,
right: horizontalPadding,
),
reverse: true,
controller: controller.scrollController,
keyboardDismissBehavior: PlatformInfos.isMobile
? ScrollViewKeyboardDismissBehavior.manual
: ScrollViewKeyboardDismissBehavior.onDrag,
childrenDelegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
PointerDeviceKind.trackpad,
},
),
child: SelectionTextContainer(
chatController: controller,
focusNode: controller.selectionFocusNode,
child: InViewNotifierList(
padding: EdgeInsets.only(
top: 16,
bottom: 8.0,
left: horizontalPadding,
right: horizontalPadding,
),
reverse: true,
controller: controller.scrollController,
itemCount: events.length + 2,
builder: (context, index) {
// Footer to display typing indicator and read receipts:
if (index == 0) {
if (controller.timeline!.isRequestingFuture) {
Expand Down Expand Up @@ -133,60 +140,59 @@ class ChatEventList extends StatelessWidget {
final nextEvent = index < controller.timeline!.events.length
? controller.timeline!.events[currentEventIndex + 1]
: null;
return VisibilityDetector(
key: ValueKey(event.eventId),
onVisibilityChanged: (visibilityInfo) {
if (visibilityInfo.visibleFraction > 0.4) {
return InViewNotifierWidget(
id: event.eventId,
builder: (context, bool isInView, _) {
if (isInView) {
controller.handleDisplayStickyTimestamp(
event.originServerTs,
);
}
return AutoScrollTag(
key: ValueKey(event.eventId),
index: index,
controller: controller.scrollController,
highlightColor: LinagoraRefColors.material().primary[99],
child: event.isVisibleInGui
? Message(
event,
onSwipe: (direction) =>
controller.replyAction(replyTo: event),
onAvatarTap: (Event event) =>
controller.onContactTap(
contactPresentationSearch: event
.senderFromMemoryOrFallback
.toContactPresentationSearch(),
context: context,
path: 'rooms',
),
onSelect: controller.onSelectMessage,
selectMode: controller.selectMode,
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
timeline: controller.timeline!,
previousEvent: previousEvent,
nextEvent: nextEvent,
onHover: (isHover, event) =>
controller.onHover(isHover, index, event),
isHoverNotifier: controller.focusHover,
listHorizontalActionMenu:
controller.listHorizontalActionMenuBuilder(),
onMenuAction: controller.handleHorizontalActionMenu,
hideKeyboardChatScreen:
controller.hideKeyboardChatScreen,
markedUnreadLocation:
controller.unreadReceivedMessageLocation,
hideTimeStamp: isInView,
)
: const SizedBox(),
);
},
child: AutoScrollTag(
key: ValueKey(event.eventId),
index: index,
controller: controller.scrollController,
highlightColor: LinagoraRefColors.material().primary[99],
child: event.isVisibleInGui
? Message(
event,
onSwipe: (direction) =>
controller.replyAction(replyTo: event),
onAvatarTap: (Event event) => controller.onContactTap(
contactPresentationSearch: event
.senderFromMemoryOrFallback
.toContactPresentationSearch(),
context: context,
path: 'rooms',
),
onSelect: controller.onSelectMessage,
selectMode: controller.selectMode,
scrollToEventId: (String eventId) =>
controller.scrollToEventId(eventId),
longPressSelect: controller.selectedEvents.isEmpty,
selected: controller.selectedEvents
.any((e) => e.eventId == event.eventId),
timeline: controller.timeline!,
previousEvent: previousEvent,
nextEvent: nextEvent,
onHover: (isHover, event) =>
controller.onHover(isHover, index, event),
isHoverNotifier: controller.focusHover,
listHorizontalActionMenu:
controller.listHorizontalActionMenuBuilder(),
onMenuAction: controller.handleHorizontalActionMenu,
hideKeyboardChatScreen:
controller.hideKeyboardChatScreen,
markedUnreadLocation:
controller.unreadReceivedMessageLocation,
)
: Container(),
),
);
},
childCount: events.length + 2,
findChildIndexCallback: (key) =>
controller.findChildIndexCallback(key, thisEventsKeyMap),
isInViewPortCondition: controller.isInViewPortCondition,
),
),
),
Expand Down
25 changes: 13 additions & 12 deletions lib/pages/chat/chat_view_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,19 @@ class ChatViewBody extends StatelessWidget with MessageContentMixin {
thickness: ChatViewBodyStyle.dividerSize,
color: Theme.of(context).dividerColor,
),
ValueListenableBuilder(
valueListenable: controller.stickyTimestampNotifier,
builder: (context, stickyTimestamp, child) {
if (stickyTimestamp == null) {
return child!;
}
return StickyTimestampWidget(
isStickyHeader: true,
content: stickyTimestamp.relativeTime(context),
);
},
child: const SizedBox.shrink(),
SizedBox(
key: controller.stickyTimestampKey,
child: ValueListenableBuilder(
valueListenable: controller.stickyTimestampNotifier,
builder: (context, stickyTimestamp, child) {
return StickyTimestampWidget(
isStickyHeader: stickyTimestamp != null,
content: stickyTimestamp != null
? stickyTimestamp.relativeTime(context)
: '',
);
},
),
),
],
),
Expand Down
6 changes: 5 additions & 1 deletion lib/pages/chat/events/message/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Message extends StatelessWidget {
final VoidCallback? hideKeyboardChatScreen;
final ContextMenuBuilder? menuChildren;
final FocusNode? focusNode;
final bool? hideTimeStamp;

const Message(
this.event, {
Expand All @@ -68,6 +69,7 @@ class Message extends StatelessWidget {
this.onMenuAction,
this.markedUnreadLocation,
this.focusNode,
this.hideTimeStamp = false,
}) : super(key: key);

/// Indicates wheither the user may use a mouse instead
Expand Down Expand Up @@ -147,7 +149,9 @@ class Message extends StatelessWidget {
children: [
if (displayTime)
StickyTimestampWidget(
content: event.originServerTs.relativeTime(context),
content: hideTimeStamp == false
? event.originServerTs.relativeTime(context)
: '',
),
if (markedUnreadLocation != null &&
markedUnreadLocation == event.eventId) ...[
Expand Down
14 changes: 14 additions & 0 deletions lib/utils/extension/global_key_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';

extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBoundsRect {
final renderObject = currentContext?.findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}
10 changes: 9 additions & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.18.1"
inview_notifier_list:
dependency: "direct main"
description:
name: inview_notifier_list
sha256: "1ca80ee39aa585e84a4b9dc1fe7211c5f64614ce3064b0007d9396073e852e14"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
io:
dependency: transitive
description:
Expand Down Expand Up @@ -2894,7 +2902,7 @@ packages:
source: git
version: "1.0.0"
visibility_detector:
dependency: "direct main"
dependency: transitive
description:
name: visibility_detector
sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d"
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ dependencies:
after_layout: ^1.2.0
photo_manager_image_provider: ^2.1.0
flutter_slidable: ^3.0.1
visibility_detector: 0.3.3
inview_notifier_list: 3.0.0

dev_dependencies:
build_runner: ^2.3.3
Expand Down

0 comments on commit 19784ba

Please sign in to comment.