Skip to content

Commit 4bc10f2

Browse files
authored
feat: improve space handling and refactor to make more use of future loading dialogs instead of holding multiple values in models (#84)
1 parent f7dc61e commit 4bc10f2

34 files changed

+1067
-719
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:watch_it/watch_it.dart';
3+
import 'package:yaru/yaru.dart';
4+
import '../../common/chat_model.dart';
5+
import '../../common/view/build_context_x.dart';
6+
import '../../common/view/sliver_sticky_panel.dart';
7+
import '../../common/view/snackbars.dart';
8+
import '../../common/view/ui_constants.dart';
9+
10+
class ActiveSpaceInfo extends StatelessWidget with WatchItMixin {
11+
const ActiveSpaceInfo({super.key});
12+
13+
@override
14+
Widget build(BuildContext context) {
15+
final theme = context.theme;
16+
final textTheme = theme.textTheme;
17+
final activeSpace = watchPropertyValue((ChatModel m) => m.activeSpace);
18+
19+
if (activeSpace == null) {
20+
return const SliverToBoxAdapter(child: SizedBox.shrink());
21+
}
22+
23+
return SliverStickyPanel(
24+
padding: EdgeInsets.zero,
25+
child: Padding(
26+
padding: const EdgeInsets.only(
27+
left: kSmallPadding,
28+
right: kSmallPadding,
29+
top: kSmallPadding,
30+
),
31+
child: ListTile(
32+
title: Text(activeSpace.getLocalizedDisplayname()),
33+
subtitle: Align(
34+
alignment: Alignment.centerLeft,
35+
child: InkWell(
36+
borderRadius: BorderRadius.circular(kSmallPadding),
37+
onTap: () => showSnackBar(
38+
context,
39+
content: CopyClipboardContent(text: activeSpace.canonicalAlias),
40+
),
41+
child: Text(
42+
activeSpace.canonicalAlias,
43+
style: textTheme.labelMedium?.copyWith(
44+
color: theme.colorScheme.link,
45+
),
46+
textAlign: TextAlign.center,
47+
),
48+
),
49+
),
50+
),
51+
),
52+
);
53+
}
54+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:future_loading_dialog/future_loading_dialog.dart';
4+
import 'package:matrix/matrix.dart';
5+
import 'package:watch_it/watch_it.dart';
6+
7+
import '../../common/chat_model.dart';
8+
import '../../common/room_x.dart';
9+
import '../../common/view/confirm.dart';
10+
import '../../common/view/snackbars.dart';
11+
import '../../l10n/l10n.dart';
12+
13+
class RemoveFromSpaceDialog extends StatefulWidget
14+
with WatchItStatefulWidgetMixin {
15+
const RemoveFromSpaceDialog({super.key, required this.room});
16+
17+
final Room room;
18+
19+
@override
20+
State<RemoveFromSpaceDialog> createState() => _RemoveFromSpaceDialogState();
21+
}
22+
23+
class _RemoveFromSpaceDialogState extends State<RemoveFromSpaceDialog> {
24+
Room? _selectedSpace;
25+
26+
@override
27+
void initState() {
28+
super.initState();
29+
_selectedSpace = di<ChatModel>().spaces.firstWhereOrNull(
30+
(space) =>
31+
space.spaceChildren.map((c) => c.roomId).contains(widget.room.id),
32+
);
33+
}
34+
35+
@override
36+
Widget build(BuildContext context) {
37+
final spaces = watchPropertyValue((ChatModel m) => m.spaces);
38+
39+
return ConfirmationDialog(
40+
title: Text(context.l10n.removeFromSpace),
41+
content: Column(
42+
mainAxisSize: MainAxisSize.min,
43+
children: spaces
44+
.where(
45+
(e) =>
46+
e.spaceChildren.map((c) => c.roomId).contains(widget.room.id),
47+
)
48+
.toList()
49+
.map(
50+
(space) => RadioListTile<Room>(
51+
title: Text(space.getLocalizedDisplayname()),
52+
value: space,
53+
groupValue: _selectedSpace,
54+
onChanged: (value) => setState(() => _selectedSpace = value),
55+
),
56+
)
57+
.toList(),
58+
),
59+
confirmEnabled: _selectedSpace != null,
60+
onConfirm: () => showFutureLoadingDialog(
61+
context: context,
62+
future: () =>
63+
di<ChatModel>().removeFromSpace(widget.room, _selectedSpace!),
64+
onError: (error) {
65+
showErrorSnackBar(context, error.toString());
66+
return error.toString();
67+
},
68+
),
69+
);
70+
}
71+
}
72+
73+
class AddToSpaceDialog extends StatefulWidget with WatchItStatefulWidgetMixin {
74+
const AddToSpaceDialog({super.key, required this.room});
75+
76+
final Room room;
77+
78+
@override
79+
State<AddToSpaceDialog> createState() => _AddToSpaceDialogState();
80+
}
81+
82+
class _AddToSpaceDialogState extends State<AddToSpaceDialog> {
83+
Room? _selectedSpace;
84+
85+
@override
86+
Widget build(BuildContext context) {
87+
final spaces = watchPropertyValue((ChatModel m) => m.spaces);
88+
89+
return ConfirmationDialog(
90+
title: Text(context.l10n.addToSpace),
91+
content: Column(
92+
mainAxisSize: MainAxisSize.min,
93+
children: spaces
94+
.map(
95+
(space) => RadioListTile<Room>(
96+
title: Text(space.getLocalizedDisplayname()),
97+
value: space,
98+
groupValue: _selectedSpace,
99+
onChanged:
100+
space.canEditSpace == true &&
101+
!space.spaceChildren
102+
.map((c) => c.roomId)
103+
.contains(widget.room.id)
104+
? (value) => setState(() => _selectedSpace = value)
105+
: null,
106+
),
107+
)
108+
.toList(),
109+
),
110+
confirmEnabled: _selectedSpace != null,
111+
onConfirm: () => showFutureLoadingDialog(
112+
context: context,
113+
onError: (error) {
114+
showErrorSnackBar(context, error.toString());
115+
return error.toString();
116+
},
117+
future: () => di<ChatModel>().addToSpace(widget.room, _selectedSpace!),
118+
),
119+
);
120+
}
121+
}

lib/chat_master/view/chat_archive_search_field.dart

Lines changed: 0 additions & 37 deletions
This file was deleted.

lib/chat_master/view/chat_master_detail_page.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
8989
final isArchivedRoom = watchPropertyValue(
9090
(ChatModel m) => m.selectedRoom?.isArchived == true,
9191
);
92-
final processingJoinOrLeave = watchPropertyValue(
93-
(ChatModel m) => m.processingJoinOrLeave,
94-
);
92+
9593
final loadingArchive = watchPropertyValue(
9694
(ChatModel m) => m.loadingArchive,
9795
);
@@ -117,7 +115,7 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
117115
),
118116
if (context.showSideBar)
119117
const VerticalDivider(width: 0, thickness: 0),
120-
if (processingJoinOrLeave || loadingArchive)
118+
if (loadingArchive)
121119
const Expanded(child: Center(child: Progress()))
122120
else if (selectedRoom == null)
123121
const Expanded(child: ChatNoSelectedRoomPage())

lib/chat_master/view/chat_master_panel.dart

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import '../../common/rooms_filter.dart';
77
import '../../common/search_model.dart';
88
import '../../common/view/build_context_x.dart';
99
import '../../common/view/common_widgets.dart';
10-
import '../../common/view/search_auto_complete.dart';
1110
import '../../common/view/theme.dart';
1211
import '../../common/view/ui_constants.dart';
1312
import '../../l10n/l10n.dart';
1413
import '../../settings/view/chat_settings_avatar.dart';
1514
import '../../settings/view/chat_settings_dialog.dart';
16-
import 'chat_archive_search_field.dart';
1715
import 'chat_master_list_filter_bar.dart';
1816
import 'chat_master_title_bar.dart';
1917
import 'chat_rooms_list.dart';
20-
import 'chat_space_control_panel.dart';
18+
import 'chat_rooms_search_field.dart';
2119
import 'chat_space_filter.dart';
2220

2321
class ChatMasterSidePanel extends StatelessWidget with WatchItMixin {
@@ -26,57 +24,24 @@ class ChatMasterSidePanel extends StatelessWidget with WatchItMixin {
2624
@override
2725
Widget build(BuildContext context) {
2826
final l10n = context.l10n;
29-
final searchModel = di<SearchModel>();
3027
final searchActive = watchPropertyValue((SearchModel m) => m.searchActive);
31-
final searchType = watchPropertyValue((SearchModel m) => m.searchType);
3228
final archiveActive = watchPropertyValue((ChatModel m) => m.archiveActive);
3329
final loadingArchive = watchPropertyValue(
3430
(ChatModel m) => m.loadingArchive,
3531
);
3632
final roomsFilter = watchPropertyValue((ChatModel m) => m.roomsFilter);
3733
final chatModel = di<ChatModel>();
3834

39-
final suffix = IconButton(
40-
padding: EdgeInsets.zero,
41-
style: textFieldSuffixStyle,
42-
onPressed: () => searchModel.setSearchType(switch (searchType) {
43-
SearchType.profiles => SearchType.rooms,
44-
SearchType.rooms => SearchType.spaces,
45-
SearchType.spaces => SearchType.profiles,
46-
}),
47-
icon: switch (searchType) {
48-
SearchType.profiles => const Icon(YaruIcons.user),
49-
SearchType.rooms => const Icon(YaruIcons.users),
50-
SearchType.spaces => const Icon(YaruIcons.globe),
51-
},
52-
);
53-
5435
return Material(
5536
color: getPanelBg(context.theme),
5637
child: Column(
5738
children: [
5839
const ChatMasterTitleBar(),
59-
if (archiveActive)
60-
const ChatArchiveSearchField()
61-
else if (searchActive)
62-
Padding(
63-
padding: const EdgeInsets.only(
64-
left: kMediumPlusPadding,
65-
right: kMediumPlusPadding,
66-
bottom: kMediumPadding,
67-
),
68-
child: switch (searchType) {
69-
SearchType.profiles => ChatUserSearchAutoComplete(
70-
suffix: suffix,
71-
),
72-
_ => RoomsAutoComplete(suffix: suffix),
73-
},
74-
),
40+
if (searchActive) const ChatRoomsSearchField(),
7541
const ChatMasterListFilterBar(),
7642
if (roomsFilter == RoomsFilter.spaces && !archiveActive)
7743
const ChatSpaceFilter(),
78-
if (roomsFilter == RoomsFilter.spaces && !archiveActive)
79-
const ChatSpaceControlPanel(),
44+
8045
if (loadingArchive)
8146
const Expanded(child: Center(child: Progress()))
8247
else

0 commit comments

Comments
 (0)