Compare commits
1 commit
main
...
krille/mig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57db6c88cd |
4 changed files with 126 additions and 126 deletions
|
|
@ -16,7 +16,7 @@ import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:record/record.dart';
|
import 'package:record/record.dart';
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:universal_html/html.dart' as html;
|
import 'package:universal_html/html.dart' as html;
|
||||||
|
|
||||||
|
|
@ -29,6 +29,7 @@ import 'package:fluffychat/pages/chat/recording_dialog.dart';
|
||||||
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
||||||
import 'package:fluffychat/utils/error_reporter.dart';
|
import 'package:fluffychat/utils/error_reporter.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||||
|
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'package:fluffychat/widgets/app_lock.dart';
|
import 'package:fluffychat/widgets/app_lock.dart';
|
||||||
|
|
@ -104,7 +105,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
String get roomId => widget.room.id;
|
String get roomId => widget.room.id;
|
||||||
|
|
||||||
final AutoScrollController scrollController = AutoScrollController();
|
final ItemScrollController itemScrollController = ItemScrollController();
|
||||||
|
final ScrollOffsetController scrollOffsetController =
|
||||||
|
ScrollOffsetController();
|
||||||
|
final ItemPositionsListener itemPositionsListener =
|
||||||
|
ItemPositionsListener.create();
|
||||||
|
final ScrollOffsetListener scrollOffsetListener =
|
||||||
|
ScrollOffsetListener.create();
|
||||||
|
|
||||||
FocusNode inputFocus = FocusNode();
|
FocusNode inputFocus = FocusNode();
|
||||||
StreamSubscription<html.Event>? onFocusSub;
|
StreamSubscription<html.Event>? onFocusSub;
|
||||||
|
|
@ -230,21 +237,20 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
setReadMarker(eventId: mostRecentEventId);
|
setReadMarker(eventId: mostRecentEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateScrollController() {
|
void _updateScrollController(double offset) {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!scrollController.hasClients) return;
|
|
||||||
if (timeline?.allowNewEvent == false ||
|
if (timeline?.allowNewEvent == false ||
|
||||||
scrollController.position.pixels > 0 && _scrolledUp == false) {
|
offset > 2 && _scrolledUp == false) {
|
||||||
setState(() => _scrolledUp = true);
|
setState(() => _scrolledUp = true);
|
||||||
} else if (scrollController.position.pixels <= 0 && _scrolledUp == true) {
|
} else if (offset <= 2 && _scrolledUp == true) {
|
||||||
setState(() => _scrolledUp = false);
|
setState(() => _scrolledUp = false);
|
||||||
setReadMarker();
|
setReadMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollController.position.pixels == 0 ||
|
if (offset == 0 || offset == 64) {
|
||||||
scrollController.position.pixels == 64) {
|
|
||||||
requestFuture();
|
requestFuture();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +265,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
scrollController.addListener(_updateScrollController);
|
scrollOffsetListener.changes.listen(_updateScrollController);
|
||||||
inputFocus.addListener(_inputFocusListener);
|
inputFocus.addListener(_inputFocusListener);
|
||||||
|
|
||||||
_loadDraft();
|
_loadDraft();
|
||||||
|
|
@ -292,7 +298,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
if (timeline?.events.any((event) => event.eventId == fullyRead) ??
|
if (timeline?.events.any((event) => event.eventId == fullyRead) ??
|
||||||
false) {
|
false) {
|
||||||
Logs().v('Scroll up to visible event', fullyRead);
|
Logs().v('Scroll up to visible event', fullyRead);
|
||||||
setReadMarker();
|
scrollToEventId(fullyRead);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -901,7 +907,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
void scrollToEventId(String eventId) async {
|
void scrollToEventId(String eventId) async {
|
||||||
final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId);
|
final events =
|
||||||
|
timeline!.events.where((event) => event.isVisibleInGui).toList();
|
||||||
|
final eventIndex = events.indexWhere((e) => e.eventId == eventId);
|
||||||
if (eventIndex == -1) {
|
if (eventIndex == -1) {
|
||||||
setState(() {
|
setState(() {
|
||||||
timeline = null;
|
timeline = null;
|
||||||
|
|
@ -920,11 +928,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
setState(() {
|
setState(() {
|
||||||
scrollToEventIdMarker = eventId;
|
scrollToEventIdMarker = eventId;
|
||||||
});
|
});
|
||||||
await scrollController.scrollToIndex(
|
await itemScrollController.scrollTo(
|
||||||
eventIndex,
|
index: eventIndex + 1,
|
||||||
preferPosition: AutoScrollPosition.middle,
|
duration: FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
);
|
);
|
||||||
_updateScrollController();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void scrollDown() async {
|
void scrollDown() async {
|
||||||
|
|
@ -939,7 +947,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
});
|
});
|
||||||
await loadTimelineFuture;
|
await loadTimelineFuture;
|
||||||
}
|
}
|
||||||
scrollController.jumpTo(0);
|
itemScrollController.scrollTo(
|
||||||
|
index: 0,
|
||||||
|
duration: FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEmojiSelected(_, Emoji? emoji) {
|
void onEmojiSelected(_, Emoji? emoji) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/pages/chat/chat.dart';
|
import 'package:fluffychat/pages/chat/chat.dart';
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
|
||||||
import 'package:fluffychat/utils/account_config.dart';
|
import 'package:fluffychat/utils/account_config.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
|
||||||
|
|
||||||
class ChatEventList extends StatelessWidget {
|
class ChatEventList extends StatelessWidget {
|
||||||
final ChatController controller;
|
final ChatController controller;
|
||||||
|
|
@ -41,119 +40,108 @@ class ChatEventList extends StatelessWidget {
|
||||||
controller.room.client.applicationAccountConfig.wallpaperUrl != null;
|
controller.room.client.applicationAccountConfig.wallpaperUrl != null;
|
||||||
|
|
||||||
return SelectionArea(
|
return SelectionArea(
|
||||||
child: ListView.custom(
|
child: ScrollablePositionedList.builder(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: 16,
|
top: 16,
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: horizontalPadding,
|
left: horizontalPadding,
|
||||||
right: horizontalPadding,
|
right: horizontalPadding,
|
||||||
),
|
),
|
||||||
|
itemCount: events.length + 2,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
controller: controller.scrollController,
|
itemScrollController: controller.itemScrollController,
|
||||||
keyboardDismissBehavior: PlatformInfos.isIOS
|
scrollOffsetController: controller.scrollOffsetController,
|
||||||
? ScrollViewKeyboardDismissBehavior.onDrag
|
itemPositionsListener: controller.itemPositionsListener,
|
||||||
: ScrollViewKeyboardDismissBehavior.manual,
|
scrollOffsetListener: controller.scrollOffsetListener,
|
||||||
childrenDelegate: SliverChildBuilderDelegate(
|
itemBuilder: (BuildContext context, int i) {
|
||||||
(BuildContext context, int i) {
|
// Footer to display typing indicator and read receipts:
|
||||||
// Footer to display typing indicator and read receipts:
|
if (i == 0) {
|
||||||
if (i == 0) {
|
if (controller.timeline!.isRequestingFuture) {
|
||||||
if (controller.timeline!.isRequestingFuture) {
|
return const Center(
|
||||||
return const Center(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (controller.timeline!.canRequestFuture) {
|
|
||||||
return Center(
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: controller.requestFuture,
|
|
||||||
icon: const Icon(Icons.refresh_outlined),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
SeenByRow(controller),
|
|
||||||
TypingIndicators(controller),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (controller.timeline!.canRequestFuture) {
|
||||||
// Request history button or progress indicator:
|
return Center(
|
||||||
if (i == events.length + 1) {
|
child: IconButton(
|
||||||
if (controller.timeline!.isRequestingHistory) {
|
onPressed: controller.requestFuture,
|
||||||
return const Center(
|
icon: const Icon(Icons.refresh_outlined),
|
||||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (controller.timeline!.canRequestHistory) {
|
|
||||||
return Builder(
|
|
||||||
builder: (context) {
|
|
||||||
WidgetsBinding.instance
|
|
||||||
.addPostFrameCallback(controller.requestHistory);
|
|
||||||
return Center(
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: controller.requestHistory,
|
|
||||||
icon: const Icon(Icons.refresh_outlined),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
i--;
|
|
||||||
|
|
||||||
// The message at this index:
|
|
||||||
final event = events[i];
|
|
||||||
final animateIn = animateInEventIndex != null &&
|
|
||||||
controller.timeline!.events.length > animateInEventIndex &&
|
|
||||||
event == controller.timeline!.events[animateInEventIndex];
|
|
||||||
|
|
||||||
return AutoScrollTag(
|
|
||||||
key: ValueKey(event.eventId),
|
|
||||||
index: i,
|
|
||||||
controller: controller.scrollController,
|
|
||||||
child: Message(
|
|
||||||
event,
|
|
||||||
animateIn: animateIn,
|
|
||||||
resetAnimateIn: () {
|
|
||||||
controller.animateInEventIndex = null;
|
|
||||||
},
|
|
||||||
onSwipe: () => controller.replyAction(replyTo: event),
|
|
||||||
onInfoTab: controller.showEventInfo,
|
|
||||||
onAvatarTab: (Event event) => showAdaptiveBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (c) => UserBottomSheet(
|
|
||||||
user: event.senderFromMemoryOrFallback,
|
|
||||||
outerContext: context,
|
|
||||||
onMention: () => controller.sendController.text +=
|
|
||||||
'${event.senderFromMemoryOrFallback.mention} ',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
highlightMarker:
|
);
|
||||||
controller.scrollToEventIdMarker == event.eventId,
|
}
|
||||||
onSelect: controller.onSelectMessage,
|
return Column(
|
||||||
scrollToEventId: (String eventId) =>
|
mainAxisSize: MainAxisSize.min,
|
||||||
controller.scrollToEventId(eventId),
|
children: [
|
||||||
longPressSelect: controller.selectedEvents.isNotEmpty,
|
SeenByRow(controller),
|
||||||
selected: controller.selectedEvents
|
TypingIndicators(controller),
|
||||||
.any((e) => e.eventId == event.eventId),
|
],
|
||||||
timeline: controller.timeline!,
|
|
||||||
displayReadMarker:
|
|
||||||
controller.readMarkerEventId == event.eventId &&
|
|
||||||
controller.timeline?.allowNewEvent == false,
|
|
||||||
nextEvent: i + 1 < events.length ? events[i + 1] : null,
|
|
||||||
previousEvent: i > 0 ? events[i - 1] : null,
|
|
||||||
avatarPresenceBackgroundColor:
|
|
||||||
hasWallpaper ? Colors.transparent : null,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
childCount: events.length + 2,
|
|
||||||
findChildIndexCallback: (key) =>
|
// Request history button or progress indicator:
|
||||||
controller.findChildIndexCallback(key, thisEventsKeyMap),
|
if (i == events.length + 1) {
|
||||||
),
|
if (controller.timeline!.isRequestingHistory) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (controller.timeline!.canRequestHistory) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
WidgetsBinding.instance
|
||||||
|
.addPostFrameCallback(controller.requestHistory);
|
||||||
|
return Center(
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: controller.requestHistory,
|
||||||
|
icon: const Icon(Icons.refresh_outlined),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
|
||||||
|
// The message at this index:
|
||||||
|
final event = events[i];
|
||||||
|
final animateIn = animateInEventIndex != null &&
|
||||||
|
controller.timeline!.events.length > animateInEventIndex &&
|
||||||
|
event == controller.timeline!.events[animateInEventIndex];
|
||||||
|
|
||||||
|
return Message(
|
||||||
|
event,
|
||||||
|
animateIn: animateIn,
|
||||||
|
resetAnimateIn: () {
|
||||||
|
controller.animateInEventIndex = null;
|
||||||
|
},
|
||||||
|
onSwipe: () => controller.replyAction(replyTo: event),
|
||||||
|
onInfoTab: controller.showEventInfo,
|
||||||
|
onAvatarTab: (Event event) => showAdaptiveBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (c) => UserBottomSheet(
|
||||||
|
user: event.senderFromMemoryOrFallback,
|
||||||
|
outerContext: context,
|
||||||
|
onMention: () => controller.sendController.text +=
|
||||||
|
'${event.senderFromMemoryOrFallback.mention} ',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
highlightMarker: controller.scrollToEventIdMarker == event.eventId,
|
||||||
|
onSelect: controller.onSelectMessage,
|
||||||
|
scrollToEventId: (String eventId) =>
|
||||||
|
controller.scrollToEventId(eventId),
|
||||||
|
longPressSelect: controller.selectedEvents.isNotEmpty,
|
||||||
|
selected: controller.selectedEvents
|
||||||
|
.any((e) => e.eventId == event.eventId),
|
||||||
|
timeline: controller.timeline!,
|
||||||
|
displayReadMarker: controller.readMarkerEventId == event.eventId &&
|
||||||
|
controller.timeline?.allowNewEvent == false,
|
||||||
|
nextEvent: i + 1 < events.length ? events[i + 1] : null,
|
||||||
|
previousEvent: i > 0 ? events[i - 1] : null,
|
||||||
|
avatarPresenceBackgroundColor:
|
||||||
|
hasWallpaper ? Colors.transparent : null,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1694,14 +1694,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
scroll_to_index:
|
scrollable_positioned_list:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: scroll_to_index
|
name: scrollable_positioned_list
|
||||||
sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176
|
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "0.3.8"
|
||||||
sdp_transform:
|
sdp_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ dependencies:
|
||||||
qr_code_scanner: ^1.0.1
|
qr_code_scanner: ^1.0.1
|
||||||
receive_sharing_intent: 1.4.5 # Update needs more work
|
receive_sharing_intent: 1.4.5 # Update needs more work
|
||||||
record: ^5.1.2
|
record: ^5.1.2
|
||||||
scroll_to_index: ^3.0.1
|
scrollable_positioned_list: ^0.3.8
|
||||||
share_plus: ^9.0.0
|
share_plus: ^9.0.0
|
||||||
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401
|
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401
|
||||||
slugify: ^2.0.0
|
slugify: ^2.0.0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue