Skip to content

Commit 17903aa

Browse files
committed
feat: show device list, improve verification of other devices
1 parent abd7f56 commit 17903aa

File tree

12 files changed

+132
-68
lines changed

12 files changed

+132
-68
lines changed

lib/chat/authentication/authentication_model.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import 'dart:io';
2+
13
import 'package:matrix/matrix.dart';
24
import 'package:safe_change_notifier/safe_change_notifier.dart';
35

46
import '../../common/logging.dart';
7+
import '../../constants.dart';
58

69
class AuthenticationModel extends SafeChangeNotifier {
710
AuthenticationModel({required Client client}) : _client = client;
@@ -38,6 +41,7 @@ class AuthenticationModel extends SafeChangeNotifier {
3841
LoginType.mLoginPassword,
3942
password: password,
4043
identifier: AuthenticationUserIdentifier(user: username),
44+
initialDeviceDisplayName: '$kAppTitle ${Platform.operatingSystem}',
4145
);
4246
await _client.firstSyncReceived;
4347
await _client.roomsLoading;

lib/chat/bootstrap/view/bootstrap_page.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class BootstrapPage extends StatelessWidget with WatchItMixin {
7979
minLines: 2,
8080
maxLines: 4,
8181
readOnly: true,
82-
style: const TextStyle(fontFamily: 'RobotoMono'),
82+
style: const TextStyle(fontFamily: 'UbuntuMono'),
8383
controller: TextEditingController(text: key),
8484
decoration: const InputDecoration(
8585
contentPadding: EdgeInsets.all(16),
@@ -292,7 +292,7 @@ class _OpenExistingSSSSPageState extends State<OpenExistingSSSSPage> {
292292
readOnly: recoveryKeyInputLoading,
293293
autofillHints:
294294
recoveryKeyInputLoading ? null : [AutofillHints.password],
295-
style: const TextStyle(fontFamily: 'RobotoMono'),
295+
style: const TextStyle(fontFamily: 'UbuntuMono'),
296296
decoration: InputDecoration(
297297
prefixIcon: const Icon(YaruIcons.key),
298298
labelText: l10n.recoveryKey,

lib/chat/bootstrap/view/key_verification_dialog.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ class KeyVerificationDialog extends StatefulWidget {
2424
);
2525

2626
final KeyVerification request;
27+
final bool verifyOther;
2728

2829
const KeyVerificationDialog({
2930
super.key,
3031
required this.request,
32+
this.verifyOther = false,
3133
});
3234

3335
@override
@@ -329,12 +331,16 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
329331
context,
330332
rootNavigator: false,
331333
).pop();
332-
Navigator.of(context).pushAndRemoveUntil(
333-
MaterialPageRoute(
334-
builder: (_) => const ChatMasterDetailPage(),
335-
),
336-
(route) => false,
337-
);
334+
if (!widget.verifyOther) {
335+
Navigator.of(context).pushAndRemoveUntil(
336+
MaterialPageRoute(
337+
builder: (_) => const ChatMasterDetailPage(
338+
checkBootstrap: false,
339+
),
340+
),
341+
(route) => false,
342+
);
343+
}
338344
}
339345
},
340346
),

lib/chat/settings/settings_dialog.dart

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
22
import 'package:watch_it/watch_it.dart';
33
import 'package:yaru/yaru.dart';
44

5+
import '../../common/date_time_x.dart';
6+
import '../../common/view/build_context_x.dart';
57
import '../../common/view/snackbars.dart';
68
import '../../common/view/ui_constants.dart';
79
import '../../l10n/l10n.dart';
@@ -26,10 +28,12 @@ class _SettingsDialogState extends State<SettingsDialog> {
2628
super.initState();
2729
_displayNameController = TextEditingController();
2830
_idController = TextEditingController(text: di<ChatModel>().myUserId);
29-
di<SettingsModel>().getMyProfile().then((v) {
30-
_displayNameController.text = v?.displayName ?? '';
31-
_idController.text = v?.userId ?? '';
32-
});
31+
di<SettingsModel>()
32+
..getMyProfile().then((v) {
33+
_displayNameController.text = v?.displayName ?? '';
34+
_idController.text = v?.userId ?? '';
35+
})
36+
..getDevices();
3337
}
3438

3539
@override
@@ -42,6 +46,7 @@ class _SettingsDialogState extends State<SettingsDialog> {
4246
@override
4347
Widget build(BuildContext context) {
4448
final l10n = context.l10n;
49+
final settingsModel = di<SettingsModel>();
4550
watchFuture(
4651
(SettingsModel m) => m.getMyProfile(),
4752
initialValue: di<SettingsModel>().myProfile,
@@ -53,6 +58,8 @@ class _SettingsDialogState extends State<SettingsDialog> {
5358
preserveState: false,
5459
).data;
5560

61+
final devices = watchPropertyValue((SettingsModel m) => m.devices);
62+
5663
return AlertDialog(
5764
titlePadding: EdgeInsets.zero,
5865
title: YaruDialogTitleBar(
@@ -115,6 +122,33 @@ class _SettingsDialogState extends State<SettingsDialog> {
115122
],
116123
),
117124
),
125+
YaruSection(
126+
headline: Text(l10n.devices),
127+
child: Column(
128+
children: devices
129+
.map(
130+
(d) => YaruTile(
131+
trailing: d.deviceId != settingsModel.myDeviceId
132+
? IconButton(
133+
onPressed: () =>
134+
settingsModel.deleteDevice(d.deviceId),
135+
icon: Icon(
136+
YaruIcons.trash,
137+
color: context.colorScheme.error,
138+
),
139+
)
140+
: null,
141+
subtitle: Text(
142+
DateTime.fromMillisecondsSinceEpoch(
143+
d.lastSeenTs ?? 0,
144+
).formatAndLocalize(l10n, simple: true),
145+
),
146+
title: SelectableText(d.displayName ?? d.deviceId),
147+
),
148+
)
149+
.toList(),
150+
),
151+
),
118152
],
119153
),
120154
),

lib/chat/settings/settings_model.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ class SettingsModel extends SafeChangeNotifier {
3030
return _myProfile;
3131
}
3232

33+
String? get myDeviceId => _client.deviceID;
34+
List<Device> _devices = [];
35+
List<Device> get devices => _devices;
36+
Future<void> getDevices() async {
37+
_devices = await _client.getDevices() ?? [];
38+
notifyListeners();
39+
}
40+
41+
// TODO: authenticate for some devices
42+
Future<void> deleteDevice(String id) async {
43+
await _client.deleteDevice(id);
44+
await getDevices();
45+
}
46+
3347
bool _attachingAvatar = false;
3448
bool get attachingAvatar => _attachingAvatar;
3549
void setAttachingAvatar(bool value) {

lib/chat/timeline_model.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ class TimelineModel extends SafeChangeNotifier {
2626
return;
2727
}
2828
await timeline.requestHistory(filter: filter, historyCount: historyCount);
29-
await timeline.setReadMarker();
3029
if (notify) {
3130
setUpdatingTimeline(false);
3231
}
32+
await timeline.setReadMarker();
3333
}
3434

3535
bool _timelineSearchActive = false;

lib/chat/view/chat_master/chat_master_detail_page.dart

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ final GlobalKey<ScaffoldState> masterScaffoldKey = GlobalKey();
1818

1919
class ChatMasterDetailPage extends StatefulWidget
2020
with WatchItStatefulWidgetMixin {
21-
const ChatMasterDetailPage({super.key});
21+
const ChatMasterDetailPage({
22+
super.key,
23+
this.checkBootstrap = true,
24+
});
25+
26+
final bool checkBootstrap;
2227

2328
@override
2429
State<ChatMasterDetailPage> createState() => _ChatMasterDetailPageState();
@@ -28,25 +33,27 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
2833
@override
2934
void initState() {
3035
super.initState();
31-
WidgetsBinding.instance.addPostFrameCallback((_) {
32-
final bootstrapModel = di<BootstrapModel>();
33-
bootstrapModel.checkBootstrap().then((isNeeded) {
34-
if (isNeeded) {
35-
bootstrapModel.startBootstrap(wipe: false).then(
36-
(_) {
37-
if (mounted) {
38-
Navigator.of(context).pushAndRemoveUntil(
39-
MaterialPageRoute(
40-
builder: (_) => const BootstrapPage(),
41-
),
42-
(route) => false,
43-
);
44-
}
45-
},
46-
);
47-
}
36+
if (widget.checkBootstrap) {
37+
WidgetsBinding.instance.addPostFrameCallback((_) {
38+
final bootstrapModel = di<BootstrapModel>();
39+
bootstrapModel.checkBootstrap().then((isNeeded) {
40+
if (isNeeded) {
41+
bootstrapModel.startBootstrap(wipe: false).then(
42+
(_) {
43+
if (mounted) {
44+
Navigator.of(context).pushAndRemoveUntil(
45+
MaterialPageRoute(
46+
builder: (_) => const BootstrapPage(),
47+
),
48+
(route) => false,
49+
);
50+
}
51+
},
52+
);
53+
}
54+
});
4855
});
49-
});
56+
}
5057
}
5158

5259
@override
@@ -55,11 +62,10 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
5562
select: (ChatModel m) => m.onKeyVerificationRequest,
5663
handler: (context, newValue, cancel) {
5764
if (newValue.hasData) {
58-
showDialog(
59-
context: context,
60-
builder: (context) =>
61-
KeyVerificationDialog(request: newValue.data!),
62-
);
65+
KeyVerificationDialog(
66+
request: newValue.data!,
67+
verifyOther: true,
68+
).show(context);
6369
}
6470
},
6571
);

lib/common/date_time_x.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import 'package:intl/intl.dart';
44
import '../l10n/l10n.dart';
55

66
extension DateTimeX on DateTime {
7-
String formatAndLocalize(AppLocalizations l10n) {
7+
String formatAndLocalize(AppLocalizations l10n, {bool simple = false}) {
88
final now = DateTime.now();
99
final locale = WidgetsBinding.instance.platformDispatcher.locale;
1010

11-
if (year == now.year && month == now.month) {
11+
if (!simple && year == now.year && month == now.month) {
1212
if (day == now.day - 1) {
1313
return '${l10n.yesterday}, ${DateFormat.Hm(
1414
locale.countryCode,

lib/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
const kAppName = 'nebuchadnezzar';
2-
const kOrgName = 'org.feichtmeier';
2+
const kOrgName = 'feichtmeier.org';
33
const kAppId = '$kAppName.$kOrgName';
44
const kAppTitle = 'Nebuchadnezzar';

lib/l10n/app_de.arb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -877,8 +877,8 @@
877877
"type": "text",
878878
"placeholders": {}
879879
},
880-
"fluffychat": "FluffyChat",
881-
"@fluffychat": {
880+
"nebuchadnezzar": "Nebuchadnezzar",
881+
"@nebuchadnezzar": {
882882
"type": "text",
883883
"placeholders": {}
884884
},
@@ -1052,7 +1052,7 @@
10521052
"type": "text",
10531053
"placeholders": {}
10541054
},
1055-
"inviteText": "{username} invited you to FluffyChat.\n1. Visit fluffychat.im and install the app \n2. Sign up or sign in \n3. Open the invite link: \n {link}",
1055+
"inviteText": "{username} invited you to Nebuchadnezzar.\n1. Visit https://snapcraft.io/nebuchadnezzar and install the app \n2. Sign up or sign in \n3. Open the invite link: \n {link}",
10561056
"@inviteText": {
10571057
"type": "text",
10581058
"placeholders": {
@@ -1220,8 +1220,8 @@
12201220
"type": "text",
12211221
"placeholders": {}
12221222
},
1223-
"newMessageInFluffyChat": "💬 New message in FluffyChat",
1224-
"@newMessageInFluffyChat": {
1223+
"newMessageInNebuchadnezzar": "💬 New message in Nebuchadnezzar",
1224+
"@newMessageInNebuchadnezzar": {
12251225
"type": "text",
12261226
"placeholders": {}
12271227
},
@@ -1884,7 +1884,7 @@
18841884
"type": "text",
18851885
"placeholders": {}
18861886
},
1887-
"title": "FluffyChat",
1887+
"title": "Nebuchadnezzar",
18881888
"@title": {
18891889
"description": "Title for the application",
18901890
"type": "text",
@@ -2217,7 +2217,7 @@
22172217
"@emailOrUsername": {},
22182218
"indexedDbErrorTitle": "Private mode issues",
22192219
"@indexedDbErrorTitle": {},
2220-
"indexedDbErrorLong": "The message storage is unfortunately not enabled in private mode by default.\nPlease visit\n - about:config\n - set dom.indexedDB.privateBrowsing.enabled to true\nOtherwise, it is not possible to run FluffyChat.",
2220+
"indexedDbErrorLong": "The message storage is unfortunately not enabled in private mode by default.\nPlease visit\n - about:config\n - set dom.indexedDB.privateBrowsing.enabled to true\nOtherwise, it is not possible to run Nebuchadnezzar.",
22212221
"@indexedDbErrorLong": {},
22222222
"switchToAccount": "Switch to account {number}",
22232223
"@switchToAccount": {
@@ -2362,13 +2362,13 @@
23622362
"@callingPermissions": {},
23632363
"callingAccount": "Calling account",
23642364
"@callingAccount": {},
2365-
"callingAccountDetails": "Allows FluffyChat to use the native android dialer app.",
2365+
"callingAccountDetails": "Allows Nebuchadnezzar to use the native android dialer app.",
23662366
"@callingAccountDetails": {},
23672367
"appearOnTop": "Appear on top",
23682368
"@appearOnTop": {},
23692369
"appearOnTopDetails": "Allows the app to appear on top (not needed if you already have Fluffychat setup as a calling account)",
23702370
"@appearOnTopDetails": {},
2371-
"otherCallingPermissions": "Microphone, camera and other FluffyChat permissions",
2371+
"otherCallingPermissions": "Microphone, camera and other Nebuchadnezzar permissions",
23722372
"@otherCallingPermissions": {},
23732373
"whyIsThisMessageEncrypted": "Why is this message unreadable?",
23742374
"@whyIsThisMessageEncrypted": {},

0 commit comments

Comments
 (0)