-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add settings dialog and display edit events properly (#11)
- Loading branch information
1 parent
31415f3
commit 6b33d84
Showing
25 changed files
with
602 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:watch_it/watch_it.dart'; | ||
|
||
import '../../common/view/confirm.dart'; | ||
import '../../l10n/l10n.dart'; | ||
import '../authentication/authentication_model.dart'; | ||
import '../authentication/chat_login_page.dart'; | ||
import '../chat_model.dart'; | ||
|
||
class LogoutButton extends StatelessWidget { | ||
const LogoutButton({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final chatModel = di<ChatModel>(); | ||
final l10n = context.l10n; | ||
return ElevatedButton( | ||
onPressed: () => showDialog( | ||
context: context, | ||
builder: (context) => ConfirmationDialog( | ||
title: Text(l10n.logout), | ||
content: Text(l10n.areYouSureYouWantToLogout), | ||
onConfirm: () { | ||
chatModel.setSelectedRoom(null); | ||
Navigator.of(context).pushAndRemoveUntil( | ||
MaterialPageRoute( | ||
builder: (_) => const ChatLoginPage(), | ||
), | ||
(route) => false, | ||
); | ||
di<AuthenticationModel>().logout( | ||
onFail: (e) => ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar( | ||
content: Text(e.toString()), | ||
), | ||
), | ||
); | ||
}, | ||
), | ||
), | ||
child: Text(l10n.logout), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:watch_it/watch_it.dart'; | ||
import 'package:yaru/yaru.dart'; | ||
|
||
import '../../common/view/snackbars.dart'; | ||
import '../../common/view/ui_constants.dart'; | ||
import '../../l10n/l10n.dart'; | ||
import '../chat_model.dart'; | ||
import '../view/chat_master/chat_my_user_avatar.dart'; | ||
import 'logout_button.dart'; | ||
import 'settings_model.dart'; | ||
|
||
class SettingsDialog extends StatefulWidget with WatchItStatefulWidgetMixin { | ||
const SettingsDialog({super.key}); | ||
|
||
@override | ||
State<SettingsDialog> createState() => _SettingsDialogState(); | ||
} | ||
|
||
class _SettingsDialogState extends State<SettingsDialog> { | ||
late final TextEditingController _displayNameController; | ||
late final TextEditingController _idController; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
_displayNameController = TextEditingController(); | ||
_idController = TextEditingController(text: di<ChatModel>().myUserId); | ||
di<SettingsModel>().getMyProfile().then((v) { | ||
_displayNameController.text = v?.displayName ?? ''; | ||
_idController.text = v?.userId ?? ''; | ||
}); | ||
} | ||
|
||
@override | ||
void dispose() { | ||
super.dispose(); | ||
_displayNameController.dispose(); | ||
_idController.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final l10n = context.l10n; | ||
watchFuture( | ||
(SettingsModel m) => m.getMyProfile(), | ||
initialValue: di<SettingsModel>().myProfile, | ||
preserveState: false, | ||
); | ||
final profile = watchStream( | ||
(SettingsModel m) => m.myProfileStream, | ||
initialValue: di<SettingsModel>().myProfile, | ||
preserveState: false, | ||
).data; | ||
|
||
return AlertDialog( | ||
titlePadding: EdgeInsets.zero, | ||
title: YaruDialogTitleBar( | ||
title: Text(l10n.settings), | ||
border: BorderSide.none, | ||
backgroundColor: Colors.transparent, | ||
), | ||
scrollable: true, | ||
content: SizedBox( | ||
height: 800, | ||
width: 500, | ||
child: Column( | ||
spacing: 2 * kBigPadding, | ||
children: [ | ||
ChatMyUserAvatar( | ||
key: ValueKey(profile?.avatarUrl), | ||
uri: profile?.avatarUrl, | ||
dimension: 100, | ||
iconSize: 70, | ||
), | ||
YaruSection( | ||
child: Column( | ||
children: [ | ||
YaruTile( | ||
title: TextField( | ||
controller: _displayNameController, | ||
decoration: InputDecoration( | ||
suffixIcon: IconButton( | ||
padding: EdgeInsets.zero, | ||
style: IconButton.styleFrom( | ||
shape: const RoundedRectangleBorder( | ||
borderRadius: BorderRadius.only( | ||
topRight: Radius.circular(6), | ||
bottomRight: Radius.circular(6), | ||
), | ||
), | ||
), | ||
onPressed: profile?.displayName != | ||
_displayNameController.text | ||
? () => di<SettingsModel>().setDisplayName( | ||
name: _displayNameController.text, | ||
onFail: (e) => | ||
showErrorSnackBar(context, e), | ||
) | ||
: null, | ||
icon: const Icon(YaruIcons.save), | ||
), | ||
contentPadding: const EdgeInsets.all(10.5), | ||
label: Text(l10n.editDisplayname), | ||
), | ||
), | ||
), | ||
YaruTile( | ||
title: TextField( | ||
enabled: false, | ||
controller: _idController, | ||
), | ||
trailing: const LogoutButton(), | ||
), | ||
], | ||
), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:file_picker/file_picker.dart'; | ||
import 'package:file_selector/file_selector.dart'; | ||
import 'package:matrix/matrix.dart'; | ||
import 'package:mime/mime.dart'; | ||
import 'package:safe_change_notifier/safe_change_notifier.dart'; | ||
|
||
import '../../common/logging.dart'; | ||
|
||
class SettingsModel extends SafeChangeNotifier { | ||
SettingsModel({required Client client}) : _client = client; | ||
final Client _client; | ||
|
||
Profile? _myProfile; | ||
Profile? get myProfile => _myProfile; | ||
Future<Profile?> getMyProfile({ | ||
Function(String error)? onFail, | ||
}) async { | ||
if (_client.userID == null) return null; | ||
try { | ||
_myProfile = await _client.getProfileFromUserId( | ||
_client.userID!, | ||
); | ||
} on Exception catch (e, s) { | ||
onFail?.call(e.toString()); | ||
printMessageInDebugMode(e, s); | ||
} | ||
|
||
return _myProfile; | ||
} | ||
|
||
bool _attachingAvatar = false; | ||
bool get attachingAvatar => _attachingAvatar; | ||
void setAttachingAvatar(bool value) { | ||
if (value == _attachingAvatar) return; | ||
_attachingAvatar = value; | ||
|
||
notifyListeners(); | ||
} | ||
|
||
Future<void> setMyProfilAvatar({ | ||
required Function(String error) onFail, | ||
required Function() onWrongFileFormat, | ||
}) async { | ||
setAttachingAvatar(true); | ||
|
||
try { | ||
XFile? xFile; | ||
if (Platform.isLinux) { | ||
xFile = await openFile(); | ||
} else { | ||
final result = await FilePicker.platform.pickFiles( | ||
allowMultiple: false, | ||
type: FileType.any, | ||
); | ||
xFile = result?.files | ||
.map( | ||
(f) => XFile( | ||
f.path!, | ||
mimeType: lookupMimeType(f.path!), | ||
), | ||
) | ||
.toList() | ||
.firstOrNull; | ||
} | ||
|
||
if (xFile == null) { | ||
setAttachingAvatar(false); | ||
return; | ||
} | ||
|
||
final mime = xFile.mimeType; | ||
final bytes = await xFile.readAsBytes(); | ||
MatrixFile? avatarDraftFile; | ||
if (mime?.startsWith('image') == true) { | ||
avatarDraftFile = await MatrixImageFile.shrink( | ||
bytes: bytes, | ||
name: xFile.name, | ||
mimeType: mime, | ||
maxDimension: 1000, | ||
nativeImplementations: _client.nativeImplementations, | ||
); | ||
} else { | ||
onWrongFileFormat(); | ||
} | ||
|
||
if (avatarDraftFile != null) { | ||
await _client.setAvatar(avatarDraftFile); | ||
} | ||
} on Exception catch (e, s) { | ||
onFail(e.toString()); | ||
printMessageInDebugMode(e, s); | ||
} | ||
|
||
setAttachingAvatar(false); | ||
} | ||
|
||
Future<void> setDisplayName({ | ||
required String name, | ||
required Function(String error) onFail, | ||
}) async { | ||
if (_client.userID == null) return; | ||
try { | ||
await _client.setDisplayName(_client.userID!, name); | ||
} on Exception catch (e, s) { | ||
onFail(e.toString()); | ||
printMessageInDebugMode(e, s); | ||
} | ||
} | ||
|
||
Future<void> removeProfilAvatar() async { | ||
await _client.setAvatar(null); | ||
} | ||
|
||
Stream<Profile?> get myProfileStream => | ||
_client.onUserProfileUpdate.stream.asyncMap((u) async => getMyProfile()); | ||
} |
Oops, something went wrong.