Skip to content

Commit 3f157a7

Browse files
authored
feat: enhance threads, bubble UI and download commands (#131)
1 parent 780699b commit 3f157a7

File tree

13 files changed

+290
-186
lines changed

13 files changed

+290
-186
lines changed

lib/chat_room/common/view/chat_room_page.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class _ChatRoomPageState extends State<ChatRoomPage> {
7272
(TimelineManager m) => m.getUpdatingTimeline(widget.room.id),
7373
);
7474

75+
final threadeMode = watchPropertyValue((DraftManager m) => m.threadMode);
76+
7577
registerStreamHandler(
7678
select: (ChatManager m) => m.getLeftRoomStream(widget.room.id),
7779
handler: (context, leftRoomUpdate, cancel) {
@@ -133,12 +135,13 @@ class _ChatRoomPageState extends State<ChatRoomPage> {
133135
key: chatRoomScaffoldKey,
134136
endDrawer: ChatRoomInfoDrawer(room: widget.room),
135137
appBar: ChatRoomTitleBar(room: widget.room),
136-
bottomNavigationBar: widget.room.isArchived || widget.room.isSpace
137-
? null
138-
: ChatInput(
139-
key: ValueKey('${widget.room.id}input'),
140-
room: widget.room,
141-
),
138+
bottomNavigationBar: AnimatedSwitcher(
139+
duration: const Duration(milliseconds: 400),
140+
child:
141+
widget.room.isArchived || widget.room.isSpace || threadeMode
142+
? const SizedBox.shrink()
143+
: ChatInput(room: widget.room),
144+
),
142145
body: FutureBuilder<Timeline>(
143146
key: ValueKey(widget.room.id),
144147
future: _timelineFuture,

lib/chat_room/input/draft_manager.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:typed_data';
44
import 'package:collection/collection.dart';
55
import 'package:file_picker/file_picker.dart';
66
import 'package:file_selector/file_selector.dart';
7+
import 'package:flutter_it/flutter_it.dart';
78
import 'package:matrix/matrix.dart';
89
import 'package:mime/mime.dart';
910
import 'package:path/path.dart' as p;
@@ -21,11 +22,24 @@ class DraftManager extends SafeChangeNotifier {
2122
required Client client,
2223
required LocalImageService localImageService,
2324
}) : _client = client,
24-
_localImageService = localImageService;
25+
_localImageService = localImageService {
26+
sendCommand = Command.createAsync(
27+
(room) => send(room: room),
28+
initialValue: null,
29+
);
30+
}
2531

2632
final Client _client;
2733
final LocalImageService _localImageService;
2834

35+
bool _threadMode = false;
36+
bool get threadMode => _threadMode;
37+
void setThreadMode(bool value) {
38+
if (value == _threadMode) return;
39+
_threadMode = value;
40+
notifyListeners();
41+
}
42+
2943
Event? _replyEvent;
3044
Event? get replyEvent => _replyEvent;
3145
void setReplyEvent(Event? event, {bool notify = true}) {
@@ -74,7 +88,9 @@ class DraftManager extends SafeChangeNotifier {
7488
_threadRootEventId = null;
7589
}
7690

77-
Future<void> send({required Room room, String? text}) async {
91+
late final Command<Room, void> sendCommand;
92+
93+
Future<void> send({required Room room}) async {
7894
try {
7995
await room.setTyping(false);
8096
} on Exception catch (e, s) {

lib/chat_room/input/view/chat_input.dart

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'dart:math';
44
import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:flutter_it/flutter_it.dart';
7-
import 'package:future_loading_dialog/future_loading_dialog.dart';
87
import 'package:matrix/matrix.dart';
98
import 'package:yaru/yaru.dart';
109

@@ -48,8 +47,6 @@ class _ChatInputState extends State<ChatInput> {
4847
// Check if the "@"-Key has been pressed
4948
if (_sendController.text.contains('@')) {
5049
printMessageInDebugMode('@ key pressed');
51-
52-
return KeyEventResult.handled;
5350
}
5451

5552
final enterPressedWithoutShift =
@@ -63,7 +60,7 @@ class _ChatInputState extends State<ChatInput> {
6360
);
6461

6562
if (enterPressedWithoutShift) {
66-
send(context);
63+
send();
6764
return KeyEventResult.handled;
6865
} else if (event is KeyRepeatEvent) {
6966
// Disable holding enter
@@ -82,33 +79,14 @@ class _ChatInputState extends State<ChatInput> {
8279
super.dispose();
8380
}
8481

85-
Future<void> send(BuildContext context) async =>
86-
di<DraftManager>().filesDrafts.containsKey(widget.room.id) &&
87-
di<DraftManager>().filesDrafts[widget.room.id]!.isNotEmpty
88-
? showFutureLoadingDialog(
89-
context: context,
90-
title: context.l10n.sendingAttachment,
91-
future: () => di<DraftManager>()
92-
.send(room: widget.room)
93-
.timeout(const Duration(seconds: 30)),
94-
barrierDismissible: true,
95-
backLabel: context.l10n.close,
96-
onError: (error) {
97-
if (error is TimeoutException) {
98-
return context.l10n.oopsSomethingWentWrong;
99-
}
100-
return error.toString();
101-
},
102-
).then((res) {
103-
if (res.isValue) {
104-
_sendController.clear();
105-
_sendNode.requestFocus();
106-
}
107-
})
108-
: di<DraftManager>().send(room: widget.room).then((_) {
109-
_sendController.clear();
110-
_sendNode.requestFocus();
111-
});
82+
void send() {
83+
final isRunning = di<DraftManager>().sendCommand.isRunning;
84+
if (isRunning.value) return;
85+
di<DraftManager>().sendCommand.runAsync(widget.room).then((_) {
86+
_sendController.clear();
87+
_sendNode.requestFocus();
88+
});
89+
}
11290

11391
@override
11492
Widget build(BuildContext context) {
@@ -122,6 +100,8 @@ class _ChatInputState extends State<ChatInput> {
122100
(ChatManager m) => m.archiveActive,
123101
);
124102

103+
final sending = watchValue((DraftManager m) => m.sendCommand.isRunning);
104+
125105
final replyEvent = watchPropertyValue((DraftManager m) => m.replyEvent);
126106

127107
final editEvent = watchPropertyValue(
@@ -274,9 +254,12 @@ class _ChatInputState extends State<ChatInput> {
274254
padding: EdgeInsets.zero,
275255
icon: transform,
276256
onPressed:
277-
attaching || archiveActive || unAcceptedDirectChat
257+
sending ||
258+
attaching ||
259+
archiveActive ||
260+
unAcceptedDirectChat
278261
? null
279-
: () async => send(context),
262+
: () => send(),
280263
),
281264
],
282265
),

lib/chat_room/timeline/chat_room_pinned_events_dialog.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class _ChatRoomPinnedEventTileState extends State<ChatRoomPinnedEventTile> {
8181
event: s.data!,
8282
timeline: widget.timeline,
8383
onReplyOriginClick: (p0) async {},
84-
eventPosition: EventPosition.semanticSingle,
84+
eventPosition: EventPosition.single,
8585
)
8686
: const Text(''),
8787
);

lib/chat_room/timeline/chat_room_timeline_list.dart

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,27 @@ class _ChatRoomTimelineListState extends State<ChatRoomTimelineList> {
111111
mainAxisSize: MainAxisSize.min,
112112
children: [
113113
if (!event.hideInTimeline(
114-
showAvatarChanges: showAvatarChanges,
115-
showDisplayNameChanges: showDisplayNameChanges,
116-
) &&
117-
!event
118-
.getDisplayEvent(widget.timeline)
119-
.hideInTimeline(
120-
showAvatarChanges: showAvatarChanges,
121-
showDisplayNameChanges: showDisplayNameChanges,
122-
)) ...[
114+
showAvatarChanges: showAvatarChanges,
115+
showDisplayNameChanges: showDisplayNameChanges,
116+
)) ...[
123117
if (previous != null &&
118+
!previous.hideInTimeline(
119+
showAvatarChanges: showAvatarChanges,
120+
showDisplayNameChanges: showDisplayNameChanges,
121+
) &&
124122
event.originServerTs.toLocal().day !=
125123
previous.originServerTs.toLocal().day)
126-
Text(
127-
previous.originServerTs
128-
.toLocal()
129-
.formatAndLocalizeDay(context),
130-
textAlign: TextAlign.center,
131-
style: theme.textTheme.labelSmall,
124+
Padding(
125+
padding: const EdgeInsets.all(8.0),
126+
child: Text(
127+
previous.originServerTs
128+
.toLocal()
129+
.formatAndLocalizeDay(context),
130+
textAlign: TextAlign.center,
131+
style: theme.textTheme.labelSmall?.copyWith(
132+
color: theme.colorScheme.onSurfaceVariant,
133+
),
134+
),
132135
),
133136
RepaintBoundary(
134137
child: ChatEventTile(

lib/chat_room/timeline/chat_thread_dialog.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,19 @@ class _ChatThreadDialogState extends State<ChatThreadDialog> {
4242
.eventId,
4343
notify: false,
4444
);
45+
46+
WidgetsBinding.instance.addPostFrameCallback((_) {
47+
di<DraftManager>().setThreadMode(true);
48+
});
4549
}
4650

4751
@override
4852
void dispose() {
4953
super.dispose();
5054
di<DraftManager>().resetThreadIds();
55+
WidgetsBinding.instance.addPostFrameCallback((_) {
56+
di<DraftManager>().setThreadMode(false);
57+
});
5158
}
5259

5360
@override
@@ -89,7 +96,7 @@ class _ChatThreadDialogState extends State<ChatThreadDialog> {
8996
event: threadChild,
9097
timeline: widget.timeline,
9198
onReplyOriginClick: widget.onReplyOriginClick,
92-
eventPosition: EventPosition.semanticSingle,
99+
eventPosition: EventPosition.single,
93100
allowNormalReply: false,
94101
);
95102
},

lib/common/view/theme.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Color getTileColor(bool isUserEvent, ThemeData theme) {
4747

4848
return isUserEvent
4949
? userColor
50-
: getMonochromeBg(theme: theme, factor: 6, darkFactor: 15);
50+
: getMonochromeBg(theme: theme, factor: 6, darkFactor: 18);
5151
}
5252

5353
Color getTileIconColor(ThemeData theme) {

lib/events/chat_download_manager.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'dart:async';
22

3+
import 'package:flutter_it/flutter_it.dart';
34
import 'package:matrix/matrix.dart';
45
import 'package:safe_change_notifier/safe_change_notifier.dart';
56

7+
import '../extensions/event_x.dart';
68
import 'chat_download_service.dart';
79

810
class ChatDownloadManager extends SafeChangeNotifier {
@@ -16,6 +18,25 @@ class ChatDownloadManager extends SafeChangeNotifier {
1618
final ChatDownloadService _service;
1719
StreamSubscription<bool>? _propertiesChangedSub;
1820

21+
final _downloadCommands = <Event, Command<void, MatrixFile?>>{};
22+
Command<void, MatrixFile?> getDownloadCommand(Event event) =>
23+
_downloadCommands.putIfAbsent(
24+
event,
25+
() => Command.createAsyncWithProgress(
26+
(_, handle) => event.downloadAndDecryptAttachment(
27+
onDownloadProgress: (v) {
28+
// the amount of downloaded bytes is v
29+
// the total size of the file is event.fileSize
30+
final progress = event.fileSize != null && event.fileSize! > 0
31+
? v / event.fileSize!
32+
: null;
33+
handle.updateProgress(progress ?? 0);
34+
},
35+
),
36+
initialValue: null,
37+
),
38+
);
39+
1940
String? isEventDownloaded(Event event) => _service.isEventDownloaded(event);
2041
Future<void> safeFile({
2142
required Event event,

lib/events/view/chat_message_bubble.dart

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,23 @@ class ChatMessageBubble extends StatelessWidget {
3636
minWidth: ChatMessageBubble.minWidth,
3737
),
3838
child: Container(
39-
margin:
40-
const EdgeInsets.symmetric(
41-
horizontal: kSmallPadding,
42-
vertical: kTinyPadding,
43-
).copyWith(
44-
top: event.isImage
45-
? null
46-
: eventPosition == EventPosition.top
47-
? kMediumPadding
48-
: null,
49-
),
50-
padding: const EdgeInsets.only(
51-
top: kSmallPadding,
52-
bottom: kSmallPadding,
39+
margin: const EdgeInsets.symmetric(vertical: kTinyPadding).copyWith(
40+
top: event.isImage
41+
? null
42+
: eventPosition == EventPosition.top
43+
? kMediumPadding
44+
: null,
5345
),
46+
padding: switch (eventPosition) {
47+
EventPosition.single => const EdgeInsets.symmetric(
48+
vertical: kSmallPadding,
49+
),
50+
EventPosition.top => const EdgeInsets.only(top: kMediumPadding),
51+
EventPosition.middle => const EdgeInsets.symmetric(
52+
vertical: kTinyPadding,
53+
),
54+
EventPosition.bottom => const EdgeInsets.only(bottom: kMediumPadding),
55+
},
5456
child: ChatMessageBubbleContent(
5557
color: color,
5658
eventPosition: eventPosition,

0 commit comments

Comments
 (0)