Skip to content

Commit

Permalink
feat: show device list, improve verification of other devices
Browse files Browse the repository at this point in the history
  • Loading branch information
Feichtmeier committed Jan 11, 2025
1 parent abd7f56 commit 17903aa
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 68 deletions.
4 changes: 4 additions & 0 deletions lib/chat/authentication/authentication_model.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'dart:io';

import 'package:matrix/matrix.dart';
import 'package:safe_change_notifier/safe_change_notifier.dart';

import '../../common/logging.dart';
import '../../constants.dart';

class AuthenticationModel extends SafeChangeNotifier {
AuthenticationModel({required Client client}) : _client = client;
Expand Down Expand Up @@ -38,6 +41,7 @@ class AuthenticationModel extends SafeChangeNotifier {
LoginType.mLoginPassword,
password: password,
identifier: AuthenticationUserIdentifier(user: username),
initialDeviceDisplayName: '$kAppTitle ${Platform.operatingSystem}',
);
await _client.firstSyncReceived;
await _client.roomsLoading;
Expand Down
4 changes: 2 additions & 2 deletions lib/chat/bootstrap/view/bootstrap_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class BootstrapPage extends StatelessWidget with WatchItMixin {
minLines: 2,
maxLines: 4,
readOnly: true,
style: const TextStyle(fontFamily: 'RobotoMono'),
style: const TextStyle(fontFamily: 'UbuntuMono'),
controller: TextEditingController(text: key),
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(16),
Expand Down Expand Up @@ -292,7 +292,7 @@ class _OpenExistingSSSSPageState extends State<OpenExistingSSSSPage> {
readOnly: recoveryKeyInputLoading,
autofillHints:
recoveryKeyInputLoading ? null : [AutofillHints.password],
style: const TextStyle(fontFamily: 'RobotoMono'),
style: const TextStyle(fontFamily: 'UbuntuMono'),
decoration: InputDecoration(
prefixIcon: const Icon(YaruIcons.key),
labelText: l10n.recoveryKey,
Expand Down
18 changes: 12 additions & 6 deletions lib/chat/bootstrap/view/key_verification_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ class KeyVerificationDialog extends StatefulWidget {
);

final KeyVerification request;
final bool verifyOther;

const KeyVerificationDialog({
super.key,
required this.request,
this.verifyOther = false,
});

@override
Expand Down Expand Up @@ -329,12 +331,16 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
context,
rootNavigator: false,
).pop();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => const ChatMasterDetailPage(),
),
(route) => false,
);
if (!widget.verifyOther) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => const ChatMasterDetailPage(
checkBootstrap: false,
),
),
(route) => false,
);
}
}
},
),
Expand Down
42 changes: 38 additions & 4 deletions lib/chat/settings/settings_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';

import '../../common/date_time_x.dart';
import '../../common/view/build_context_x.dart';
import '../../common/view/snackbars.dart';
import '../../common/view/ui_constants.dart';
import '../../l10n/l10n.dart';
Expand All @@ -26,10 +28,12 @@ class _SettingsDialogState extends State<SettingsDialog> {
super.initState();
_displayNameController = TextEditingController();
_idController = TextEditingController(text: di<ChatModel>().myUserId);
di<SettingsModel>().getMyProfile().then((v) {
_displayNameController.text = v?.displayName ?? '';
_idController.text = v?.userId ?? '';
});
di<SettingsModel>()
..getMyProfile().then((v) {
_displayNameController.text = v?.displayName ?? '';
_idController.text = v?.userId ?? '';
})
..getDevices();
}

@override
Expand All @@ -42,6 +46,7 @@ class _SettingsDialogState extends State<SettingsDialog> {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final settingsModel = di<SettingsModel>();
watchFuture(
(SettingsModel m) => m.getMyProfile(),
initialValue: di<SettingsModel>().myProfile,
Expand All @@ -53,6 +58,8 @@ class _SettingsDialogState extends State<SettingsDialog> {
preserveState: false,
).data;

final devices = watchPropertyValue((SettingsModel m) => m.devices);

return AlertDialog(
titlePadding: EdgeInsets.zero,
title: YaruDialogTitleBar(
Expand Down Expand Up @@ -115,6 +122,33 @@ class _SettingsDialogState extends State<SettingsDialog> {
],
),
),
YaruSection(
headline: Text(l10n.devices),
child: Column(
children: devices
.map(
(d) => YaruTile(
trailing: d.deviceId != settingsModel.myDeviceId
? IconButton(
onPressed: () =>
settingsModel.deleteDevice(d.deviceId),
icon: Icon(
YaruIcons.trash,
color: context.colorScheme.error,
),
)
: null,
subtitle: Text(
DateTime.fromMillisecondsSinceEpoch(
d.lastSeenTs ?? 0,
).formatAndLocalize(l10n, simple: true),
),
title: SelectableText(d.displayName ?? d.deviceId),
),
)
.toList(),
),
),
],
),
),
Expand Down
14 changes: 14 additions & 0 deletions lib/chat/settings/settings_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ class SettingsModel extends SafeChangeNotifier {
return _myProfile;
}

String? get myDeviceId => _client.deviceID;
List<Device> _devices = [];
List<Device> get devices => _devices;
Future<void> getDevices() async {
_devices = await _client.getDevices() ?? [];
notifyListeners();
}

// TODO: authenticate for some devices
Future<void> deleteDevice(String id) async {
await _client.deleteDevice(id);
await getDevices();
}

bool _attachingAvatar = false;
bool get attachingAvatar => _attachingAvatar;
void setAttachingAvatar(bool value) {
Expand Down
2 changes: 1 addition & 1 deletion lib/chat/timeline_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ class TimelineModel extends SafeChangeNotifier {
return;
}
await timeline.requestHistory(filter: filter, historyCount: historyCount);
await timeline.setReadMarker();
if (notify) {
setUpdatingTimeline(false);
}
await timeline.setReadMarker();
}

bool _timelineSearchActive = false;
Expand Down
54 changes: 30 additions & 24 deletions lib/chat/view/chat_master/chat_master_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ final GlobalKey<ScaffoldState> masterScaffoldKey = GlobalKey();

class ChatMasterDetailPage extends StatefulWidget
with WatchItStatefulWidgetMixin {
const ChatMasterDetailPage({super.key});
const ChatMasterDetailPage({
super.key,
this.checkBootstrap = true,
});

final bool checkBootstrap;

@override
State<ChatMasterDetailPage> createState() => _ChatMasterDetailPageState();
Expand All @@ -28,25 +33,27 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final bootstrapModel = di<BootstrapModel>();
bootstrapModel.checkBootstrap().then((isNeeded) {
if (isNeeded) {
bootstrapModel.startBootstrap(wipe: false).then(
(_) {
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => const BootstrapPage(),
),
(route) => false,
);
}
},
);
}
if (widget.checkBootstrap) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final bootstrapModel = di<BootstrapModel>();
bootstrapModel.checkBootstrap().then((isNeeded) {
if (isNeeded) {
bootstrapModel.startBootstrap(wipe: false).then(
(_) {
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => const BootstrapPage(),
),
(route) => false,
);
}
},
);
}
});
});
});
}
}

@override
Expand All @@ -55,11 +62,10 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
select: (ChatModel m) => m.onKeyVerificationRequest,
handler: (context, newValue, cancel) {
if (newValue.hasData) {
showDialog(
context: context,
builder: (context) =>
KeyVerificationDialog(request: newValue.data!),
);
KeyVerificationDialog(
request: newValue.data!,
verifyOther: true,
).show(context);
}
},
);
Expand Down
4 changes: 2 additions & 2 deletions lib/common/date_time_x.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import 'package:intl/intl.dart';
import '../l10n/l10n.dart';

extension DateTimeX on DateTime {
String formatAndLocalize(AppLocalizations l10n) {
String formatAndLocalize(AppLocalizations l10n, {bool simple = false}) {
final now = DateTime.now();
final locale = WidgetsBinding.instance.platformDispatcher.locale;

if (year == now.year && month == now.month) {
if (!simple && year == now.year && month == now.month) {
if (day == now.day - 1) {
return '${l10n.yesterday}, ${DateFormat.Hm(
locale.countryCode,
Expand Down
2 changes: 1 addition & 1 deletion lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const kAppName = 'nebuchadnezzar';
const kOrgName = 'org.feichtmeier';
const kOrgName = 'feichtmeier.org';
const kAppId = '$kAppName.$kOrgName';
const kAppTitle = 'Nebuchadnezzar';
18 changes: 9 additions & 9 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -877,8 +877,8 @@
"type": "text",
"placeholders": {}
},
"fluffychat": "FluffyChat",
"@fluffychat": {
"nebuchadnezzar": "Nebuchadnezzar",
"@nebuchadnezzar": {
"type": "text",
"placeholders": {}
},
Expand Down Expand Up @@ -1052,7 +1052,7 @@
"type": "text",
"placeholders": {}
},
"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}",
"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}",
"@inviteText": {
"type": "text",
"placeholders": {
Expand Down Expand Up @@ -1220,8 +1220,8 @@
"type": "text",
"placeholders": {}
},
"newMessageInFluffyChat": "💬 New message in FluffyChat",
"@newMessageInFluffyChat": {
"newMessageInNebuchadnezzar": "💬 New message in Nebuchadnezzar",
"@newMessageInNebuchadnezzar": {
"type": "text",
"placeholders": {}
},
Expand Down Expand Up @@ -1884,7 +1884,7 @@
"type": "text",
"placeholders": {}
},
"title": "FluffyChat",
"title": "Nebuchadnezzar",
"@title": {
"description": "Title for the application",
"type": "text",
Expand Down Expand Up @@ -2217,7 +2217,7 @@
"@emailOrUsername": {},
"indexedDbErrorTitle": "Private mode issues",
"@indexedDbErrorTitle": {},
"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.",
"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.",
"@indexedDbErrorLong": {},
"switchToAccount": "Switch to account {number}",
"@switchToAccount": {
Expand Down Expand Up @@ -2362,13 +2362,13 @@
"@callingPermissions": {},
"callingAccount": "Calling account",
"@callingAccount": {},
"callingAccountDetails": "Allows FluffyChat to use the native android dialer app.",
"callingAccountDetails": "Allows Nebuchadnezzar to use the native android dialer app.",
"@callingAccountDetails": {},
"appearOnTop": "Appear on top",
"@appearOnTop": {},
"appearOnTopDetails": "Allows the app to appear on top (not needed if you already have Fluffychat setup as a calling account)",
"@appearOnTopDetails": {},
"otherCallingPermissions": "Microphone, camera and other FluffyChat permissions",
"otherCallingPermissions": "Microphone, camera and other Nebuchadnezzar permissions",
"@otherCallingPermissions": {},
"whyIsThisMessageEncrypted": "Why is this message unreadable?",
"@whyIsThisMessageEncrypted": {},
Expand Down
Loading

0 comments on commit 17903aa

Please sign in to comment.