Skip to content

Commit 1a6164d

Browse files
authored
feat: pinned messaged and display redacted last events (#18)
1 parent 7b09cc7 commit 1a6164d

File tree

10 files changed

+186
-34
lines changed

10 files changed

+186
-34
lines changed

lib/chat/chat_room/common/view/chat_room_master_tile_subtitle.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class ChatRoomMasterTileSubTitle extends StatelessWidget with WatchItMixin {
2626

2727
if (typingUsers.isEmpty) {
2828
return _LastEvent(
29-
key: ValueKey('${lastEvent?.eventId}lastevent'),
29+
key: ValueKey(
30+
'${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
31+
),
3032
lastEvent: room.lastEvent,
3133
fallbackText: room.membership == Membership.invite ? room.name : null,
3234
);
@@ -58,25 +60,31 @@ class _LastEvent extends StatefulWidget with WatchItStatefulWidgetMixin {
5860
}
5961

6062
class _LastEventState extends State<_LastEvent> {
61-
late final Future<String> _future;
63+
late Future<String> _future;
6264

6365
static final Map<String, String> _cache = {};
6466

6567
@override
6668
void initState() {
6769
super.initState();
6870
_future = widget.lastEvent != null &&
71+
!widget.lastEvent!.redacted &&
6972
_cache.containsKey(widget.lastEvent!.eventId)
7073
? Future.value(_cache[widget.lastEvent!.eventId]!)
71-
: widget.lastEvent
72-
?.calcLocalizedBody(const MatrixDefaultLocalizations()) ??
74+
: widget.lastEvent?.calcLocalizedBody(
75+
const MatrixDefaultLocalizations(),
76+
hideReply: true,
77+
hideEdit: false,
78+
plaintextBody: true,
79+
) ??
7380
Future.value(widget.lastEvent?.body ?? '');
7481
}
7582

7683
@override
7784
Widget build(BuildContext context) {
7885
if (widget.lastEvent != null &&
79-
_cache.containsKey(widget.lastEvent!.eventId)) {
86+
_cache.containsKey(widget.lastEvent!.eventId) &&
87+
widget.lastEvent!.redacted == false) {
8088
return Text(
8189
_cache[widget.lastEvent!.eventId]!,
8290
maxLines: 1,

lib/chat/chat_room/common/view/chat_room_page.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import 'chat_room_timeline_list.dart';
1818
final GlobalKey<ScaffoldState> chatRoomScaffoldKey = GlobalKey();
1919

2020
class ChatRoomPage extends StatefulWidget with WatchItStatefulWidgetMixin {
21-
final Room room;
2221
const ChatRoomPage({required this.room, super.key});
2322

23+
final Room room;
24+
2425
@override
2526
State<ChatRoomPage> createState() => _ChatRoomPageState();
2627
}
@@ -108,7 +109,7 @@ class _ChatRoomPageState extends State<ChatRoomPage> {
108109
),
109110
if (updating)
110111
Positioned(
111-
top: 3 * kBigPadding,
112+
top: 4 * kBigPadding,
112113
child: RepaintBoundary(
113114
child: CircleAvatar(
114115
backgroundColor: colorScheme.surface,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:matrix/matrix.dart';
3+
import 'package:watch_it/watch_it.dart';
4+
5+
import '../../../common/chat_model.dart';
6+
import '../../../events/view/chat_event_tile.dart';
7+
8+
class ChatRoomPinnedEventsDialog extends StatelessWidget with WatchItMixin {
9+
const ChatRoomPinnedEventsDialog({
10+
super.key,
11+
required this.timeline,
12+
});
13+
14+
final Timeline timeline;
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
final pinnedEvents = watchStream(
19+
(ChatModel m) => m
20+
.getJoinedRoomUpdate(timeline.room.id)
21+
.map((_) => timeline.room.pinnedEventIds),
22+
initialValue: timeline.room.pinnedEventIds,
23+
).data ??
24+
[];
25+
return AlertDialog(
26+
content: SizedBox(
27+
height: 400,
28+
width: 400,
29+
child: ListView.builder(
30+
itemCount: pinnedEvents.length,
31+
itemBuilder: (context, index) => ChatRoomPinnedEventTile(
32+
eventId: pinnedEvents.elementAt(index),
33+
timeline: timeline,
34+
),
35+
),
36+
),
37+
);
38+
}
39+
}
40+
41+
class ChatRoomPinnedEventTile extends StatefulWidget {
42+
const ChatRoomPinnedEventTile({
43+
super.key,
44+
required this.timeline,
45+
required this.eventId,
46+
});
47+
48+
final Timeline timeline;
49+
final String eventId;
50+
51+
@override
52+
State<ChatRoomPinnedEventTile> createState() =>
53+
_ChatRoomPinnedEventTileState();
54+
}
55+
56+
class _ChatRoomPinnedEventTileState extends State<ChatRoomPinnedEventTile> {
57+
late Future<Event?> _future;
58+
59+
@override
60+
void initState() {
61+
super.initState();
62+
_future = widget.timeline.room.getEventById(widget.eventId);
63+
}
64+
65+
@override
66+
Widget build(BuildContext context) {
67+
return FutureBuilder(
68+
future: _future,
69+
builder: (c, s) => s.hasData
70+
? ChatEventTile(
71+
event: s.data!,
72+
timeline: widget.timeline,
73+
onReplyOriginClick: (p0) async {},
74+
)
75+
: const Text(''),
76+
);
77+
}
78+
}

lib/chat/chat_room/common/view/chat_room_timeline_list.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:collection/collection.dart';
12
import 'package:flutter/material.dart';
23
import 'package:matrix/matrix.dart';
34
import 'package:scroll_to_index/scroll_to_index.dart';
@@ -9,11 +10,13 @@ import '../../../../common/view/build_context_x.dart';
910
import '../../../../common/view/theme.dart';
1011
import '../../../../common/view/ui_constants.dart';
1112
import '../../../../l10n/l10n.dart';
13+
import '../../../common/chat_model.dart';
1214
import '../../../common/event_x.dart';
1315
import '../../../events/view/chat_event_tile.dart';
1416
import '../../../settings/settings_model.dart';
1517
import '../../titlebar/chat_room_title_bar.dart';
1618
import '../timeline_model.dart';
19+
import 'chat_room_pinned_events_dialog.dart';
1720
import 'chat_seen_by_indicator.dart';
1821

1922
class ChatRoomTimelineList extends StatefulWidget
@@ -35,6 +38,7 @@ class _ChatRoomTimelineListState extends State<ChatRoomTimelineList> {
3538
final AutoScrollController _controller = AutoScrollController();
3639
bool _showScrollButton = false;
3740
int retryCount = 15;
41+
String? scrolledToId;
3842

3943
@override
4044
void initState() {
@@ -57,6 +61,13 @@ class _ChatRoomTimelineListState extends State<ChatRoomTimelineList> {
5761
watchPropertyValue((SettingsModel m) => m.showChatAvatarChanges);
5862
final showDisplayNameChanges =
5963
watchPropertyValue((SettingsModel m) => m.showChatDisplaynameChanges);
64+
final pinnedEvents = watchStream(
65+
(ChatModel m) => m
66+
.getJoinedRoomUpdate(widget.timeline.room.id)
67+
.map((_) => widget.timeline.room.pinnedEventIds),
68+
initialValue: widget.timeline.room.pinnedEventIds,
69+
).data ??
70+
[];
6071

6172
return Stack(
6273
children: [
@@ -132,6 +143,26 @@ class _ChatRoomTimelineListState extends State<ChatRoomTimelineList> {
132143
},
133144
),
134145
),
146+
if (pinnedEvents.isNotEmpty)
147+
Positioned(
148+
right: kBigPadding,
149+
top: kBigPadding,
150+
child: AnimatedContainer(
151+
duration: const Duration(milliseconds: 200),
152+
child: FloatingActionButton.small(
153+
backgroundColor: getMonochromeBg(theme: theme, darkFactor: 5),
154+
onPressed: () => showDialog(
155+
context: context,
156+
builder: (context) =>
157+
ChatRoomPinnedEventsDialog(timeline: widget.timeline),
158+
),
159+
child: Icon(
160+
YaruIcons.pin,
161+
color: theme.colorScheme.onSurface,
162+
),
163+
),
164+
),
165+
),
135166
if (_showScrollButton)
136167
Positioned(
137168
right: kBigPadding,

lib/chat/common/chat_model.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ class ChatModel extends SafeChangeNotifier {
137137
notifyListeners();
138138
}
139139

140+
final Map<String, String> _roomIdsToEventContextIds = {};
141+
String? getEventContextId(String? roomId) =>
142+
roomId == null ? null : _roomIdsToEventContextIds[roomId];
143+
void setContextEventId({
144+
required String roomId,
145+
required String contextEventId,
146+
}) {
147+
_roomIdsToEventContextIds.update(
148+
roomId,
149+
(value) => contextEventId,
150+
ifAbsent: () => contextEventId,
151+
);
152+
notifyListeners();
153+
}
154+
140155
bool _processingJoinOrLeave = false;
141156
bool get processingJoinOrLeave => _processingJoinOrLeave;
142157
void _setProcessingJoinOrLeave(bool value) {

lib/chat/common/event_x.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ extension EventX on Event {
2525
required bool showAvatarChanges,
2626
required bool showDisplayNameChanges,
2727
}) {
28+
if (type == EventTypes.RoomPinnedEvents) {
29+
return true;
30+
}
2831
if (type == EventTypes.RoomMember &&
2932
roomMemberChangeType == RoomMemberChangeType.avatar &&
3033
!showAvatarChanges) {

lib/chat/events/view/chat_event_status_icon.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ class ChatEventStatusIcon extends StatelessWidget {
8383
YaruIcons.pen,
8484
size: iconSize,
8585
),
86+
if (event.room.pinnedEventIds.contains(event.eventId))
87+
const Icon(
88+
YaruIcons.pin,
89+
size: iconSize,
90+
),
8691
if (!userEvent && event.isImage)
8792
ConstrainedBox(
8893
constraints: const BoxConstraints(maxWidth: 100),

lib/chat/events/view/chat_message_menu.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ class _ChatMessageMenuState extends State<ChatMessageMenu> {
3030
@override
3131
Widget build(BuildContext context) {
3232
final style = context.textTheme.bodyMedium;
33+
final pinned =
34+
widget.event.room.pinnedEventIds.contains(widget.event.eventId);
3335
return GestureDetector(
36+
key: ValueKey('${widget.event.eventId}_$pinned'),
3437
onSecondaryTap: () => _controller.open(),
3538
behavior: HitTestBehavior.opaque,
3639
child: MenuAnchor(
@@ -89,6 +92,26 @@ class _ChatMessageMenuState extends State<ChatMessageMenu> {
8992
),
9093
),
9194
),
95+
if (widget.event.room.canSendEvent(EventTypes.RoomPinnedEvents))
96+
MenuItemButton(
97+
trailingIcon: const Icon(YaruIcons.pin),
98+
child: Text(
99+
pinned ? context.l10n.unpin : context.l10n.pinMessage,
100+
style: style,
101+
),
102+
onPressed: () {
103+
if (pinned) {
104+
final newPinned =
105+
List<String>.from(widget.event.room.pinnedEventIds);
106+
newPinned.remove(widget.event.eventId);
107+
widget.event.room.setPinnedEvents(newPinned);
108+
} else {
109+
widget.event.room.setPinnedEvents(
110+
[...widget.event.room.pinnedEventIds, widget.event.eventId],
111+
);
112+
}
113+
},
114+
),
92115
ChatMessageReactionPicker(event: widget.event),
93116
],
94117
child: widget.child,

lib/main.dart

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,13 @@ import 'dart:io';
22

33
import 'package:flutter/material.dart';
44
import 'package:system_theme/system_theme.dart';
5-
import 'package:window_manager/window_manager.dart';
65
import 'package:yaru/yaru.dart';
76

87
import 'app/view/app.dart';
9-
import 'common/view/ui_constants.dart';
108
import 'register.dart';
119

1210
void main() async {
13-
if (isMobile) {
14-
WidgetsFlutterBinding.ensureInitialized();
15-
} else {
16-
await YaruWindowTitleBar.ensureInitialized();
17-
if (Platform.isMacOS) {
18-
windowManager.waitUntilReadyToShow(windowOptions, () async {
19-
await windowManager.show();
20-
await windowManager.focus();
21-
});
22-
}
23-
}
11+
await YaruWindowTitleBar.ensureInitialized();
2412

2513
if (!Platform.isLinux) {
2614
await SystemTheme.accentColor.load();

0 commit comments

Comments
 (0)