From 283054bfd58dfef6f9f98bf7c7104fce6b026f2d Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Sat, 9 Jul 2022 15:44:54 +0930 Subject: [PATCH] Update the conversation settings and profile page --- mobile/lib/models/my_profile.dart | 35 ++-- .../lib/views/main/conversation_settings.dart | 119 ++++++++++++- .../conversation_settings_user_list_item.dart | 64 +++++-- mobile/lib/views/main/home.dart | 14 +- mobile/lib/views/main/profile.dart | 160 ++++++++++++++---- mobile/pubspec.lock | 14 ++ mobile/pubspec.yaml | 1 + 7 files changed, 333 insertions(+), 74 deletions(-) diff --git a/mobile/lib/models/my_profile.dart b/mobile/lib/models/my_profile.dart index c584018..4e95cdb 100644 --- a/mobile/lib/models/my_profile.dart +++ b/mobile/lib/models/my_profile.dart @@ -7,16 +7,16 @@ import 'package:shared_preferences/shared_preferences.dart'; class MyProfile { String id; String username; - RSAPrivateKey privateKey; - RSAPublicKey publicKey; - DateTime loggedInAt; + RSAPrivateKey? privateKey; + RSAPublicKey? publicKey; + DateTime? loggedInAt; MyProfile({ required this.id, required this.username, - required this.privateKey, - required this.publicKey, - required this.loggedInAt, + this.privateKey, + this.publicKey, + this.loggedInAt, }); factory MyProfile._fromJson(Map json) { @@ -49,9 +49,13 @@ class MyProfile { return jsonEncode({ 'user_id': id, 'username': username, - 'asymmetric_private_key': CryptoUtils.encodeRSAPrivateKeyToPem(privateKey), - 'asymmetric_public_key': CryptoUtils.encodeRSAPublicKeyToPem(publicKey), - 'logged_in_at': loggedInAt.toIso8601String(), + 'asymmetric_private_key': privateKey != null ? + CryptoUtils.encodeRSAPrivateKeyToPem(privateKey!) : + null, + 'asymmetric_public_key': publicKey != null ? + CryptoUtils.encodeRSAPublicKeyToPem(publicKey!) : + null, + 'logged_in_at': loggedInAt?.toIso8601String(), }); } @@ -62,7 +66,6 @@ class MyProfile { ); MyProfile profile = MyProfile._fromJson(json); final preferences = await SharedPreferences.getInstance(); - print(profile.toJson()); preferences.setString('profile', profile.toJson()); return profile; } @@ -83,12 +86,20 @@ class MyProfile { static Future isLoggedIn() async { MyProfile profile = await MyProfile.getProfile(); - return profile.loggedInAt.isBefore((DateTime.now()).add(const Duration(hours: 12))); + if (profile.loggedInAt == null) { + return false; + } + return profile.loggedInAt!.isBefore( + (DateTime.now()).add(const Duration(hours: 12)) + ); } Future getPrivateKey() async { MyProfile profile = await MyProfile.getProfile(); - return profile.privateKey; + if (profile.privateKey == null) { + throw Exception('Could not get privateKey'); + } + return profile.privateKey!; } } diff --git a/mobile/lib/views/main/conversation_settings.dart b/mobile/lib/views/main/conversation_settings.dart index c4e04c5..caf917d 100644 --- a/mobile/lib/views/main/conversation_settings.dart +++ b/mobile/lib/views/main/conversation_settings.dart @@ -1,4 +1,5 @@ import 'package:Envelope/models/conversation_users.dart'; +import 'package:Envelope/models/my_profile.dart'; import 'package:flutter/material.dart'; import '/views/main/conversation_settings_user_list_item.dart'; import '/models/conversations.dart'; @@ -16,9 +17,8 @@ class ConversationSettings extends StatefulWidget { } class _ConversationSettingsState extends State { - final _formKey = GlobalKey(); - List users = []; + MyProfile? profile; TextEditingController nameController = TextEditingController(); @@ -31,6 +31,7 @@ class _ConversationSettingsState extends State { Future getUsers() async { users = await getConversationUsers(widget.conversation); + profile = await MyProfile.getProfile(); setState(() {}); } @@ -50,7 +51,101 @@ class _ConversationSettingsState extends State { fontWeight: FontWeight.w500, ), ), - ], + widget.conversation.admin ? IconButton( + iconSize: 20, + icon: const Icon(Icons.edit), + padding: const EdgeInsets.all(5.0), + splashRadius: 25, + onPressed: () { + // TODO: Redirect to edit screen + }, + ) : const SizedBox.shrink(), + ], + ); + } + + Widget sectionTitle(String title) { + return Align( + alignment: Alignment.centerLeft, + child: Container( + padding: const EdgeInsets.only(left: 12), + child: Text( + title, + style: const TextStyle(fontSize: 20), + ), + ), + ); + } + + Widget settings() { + return Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 5), + TextButton.icon( + label: const Text( + 'Disappearing Messages', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.timer), + style: ButtonStyle( + alignment: Alignment.centerLeft, + foregroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return Theme.of(context).colorScheme.onBackground; + }, + ) + ), + onPressed: () { + print('Disappearing Messages'); + } + ), + TextButton.icon( + label: const Text( + 'Permissions', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.lock), + style: ButtonStyle( + alignment: Alignment.centerLeft, + foregroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return Theme.of(context).colorScheme.onBackground; + }, + ) + ), + onPressed: () { + print('Permissions'); + } + ), + ], + ), + ); + } + + Widget myAccess() { + return Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextButton.icon( + label: const Text( + 'Leave Conversation', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.exit_to_app), + style: const ButtonStyle( + alignment: Alignment.centerLeft, + ), + onPressed: () { + print('Leave Group'); + } + ), + ], + ), ); } @@ -58,11 +153,12 @@ class _ConversationSettingsState extends State { return ListView.builder( itemCount: users.length, shrinkWrap: true, - padding: const EdgeInsets.only(top: 16), + padding: const EdgeInsets.only(top: 5, bottom: 0), itemBuilder: (context, i) { return ConversationSettingsUserListItem( user: users[i], isAdmin: widget.conversation.admin, + profile: profile!, // TODO: Fix this ); } ); @@ -112,9 +208,20 @@ class _ConversationSettingsState extends State { children: [ const SizedBox(height: 30), conversationName(), - const SizedBox(height: 10), - const Text('Users', style: TextStyle(fontSize: 20)), + const SizedBox(height: 25), + widget.conversation.admin ? + sectionTitle('Settings') : + const SizedBox.shrink(), + widget.conversation.admin ? + settings() : + const SizedBox.shrink(), + widget.conversation.admin ? + const SizedBox(height: 25) : + const SizedBox.shrink(), + sectionTitle('Members'), usersList(), + const SizedBox(height: 25), + myAccess(), ], ), ), diff --git a/mobile/lib/views/main/conversation_settings_user_list_item.dart b/mobile/lib/views/main/conversation_settings_user_list_item.dart index 590fc06..b261aad 100644 --- a/mobile/lib/views/main/conversation_settings_user_list_item.dart +++ b/mobile/lib/views/main/conversation_settings_user_list_item.dart @@ -1,3 +1,4 @@ +import 'package:Envelope/models/my_profile.dart'; import 'package:flutter/material.dart'; import 'package:Envelope/models/conversation_users.dart'; import 'package:Envelope/components/custom_circle_avatar.dart'; @@ -5,10 +6,12 @@ import 'package:Envelope/components/custom_circle_avatar.dart'; class ConversationSettingsUserListItem extends StatefulWidget{ final ConversationUser user; final bool isAdmin; + final MyProfile profile; const ConversationSettingsUserListItem({ Key? key, required this.user, required this.isAdmin, + required this.profile, }) : super(key: key); @override @@ -18,7 +21,7 @@ class ConversationSettingsUserListItem extends StatefulWidget{ class _ConversationSettingsUserListItemState extends State { Widget admin() { - if (widget.user.admin) { + if (!widget.user.admin) { return const SizedBox.shrink(); } @@ -38,33 +41,60 @@ class _ConversationSettingsUserListItemState extends State [ - IconButton(icon: const Icon(Icons.cancel), - padding: const EdgeInsets.all(0), - onPressed:() => { - print('Cancel') - } + return PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: 'admin', + // row with 2 children + child: Row( + children: const [ + Icon(Icons.admin_panel_settings), + SizedBox( + width: 10, + ), + Text('Promote to Admin') + ], + ), + ), + PopupMenuItem( + value: 'remove', + // row with 2 children + child: Row( + children: const [ + Icon(Icons.cancel), + SizedBox( + width: 10, + ), + Text('Remove from chat') + ], + ), ), - IconButton(icon: const Icon(Icons.admin_panel_settings), - padding: const EdgeInsets.all(0), - onPressed:() => { - print('Admin') - } - ) ], + offset: const Offset(0, 50), + elevation: 2, + // on selected we show the dialog box + onSelected: (String value) { + // if value 1 show dialog + if (value == 'admin') { + print('admin'); + return; + // if value 2 show dialog + } + if (value == 'remove') { + print('remove'); + } + }, ); } @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + padding: const EdgeInsets.only(left: 12,right: 5,top: 0,bottom: 0), child: Row( children: [ Expanded( diff --git a/mobile/lib/views/main/home.dart b/mobile/lib/views/main/home.dart index 768ef48..85f4874 100644 --- a/mobile/lib/views/main/home.dart +++ b/mobile/lib/views/main/home.dart @@ -23,13 +23,22 @@ class Home extends StatefulWidget { class _HomeState extends State { List conversations = []; List friends = []; + MyProfile profile = MyProfile( + id: '', + username: '', + ); bool isLoading = true; int _selectedIndex = 0; List _widgetOptions = [ const ConversationList(conversations: []), const FriendList(friends: []), - const Profile(), + Profile( + profile: MyProfile( + id: '', + username: '', + ) + ), ]; @override @@ -48,12 +57,13 @@ class _HomeState extends State { conversations = await getConversations(); friends = await getFriends(); + profile = await MyProfile.getProfile(); setState(() { _widgetOptions = [ ConversationList(conversations: conversations), FriendList(friends: friends), - const Profile(), + Profile(profile: profile), ]; isLoading = false; }); diff --git a/mobile/lib/views/main/profile.dart b/mobile/lib/views/main/profile.dart index 9222403..f064ef3 100644 --- a/mobile/lib/views/main/profile.dart +++ b/mobile/lib/views/main/profile.dart @@ -1,15 +1,120 @@ import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; import '/utils/storage/database.dart'; import '/models/my_profile.dart'; +import '/components/custom_circle_avatar.dart'; class Profile extends StatefulWidget { - const Profile({Key? key}) : super(key: key); + final MyProfile profile; + const Profile({ + Key? key, + required this.profile, + }) : super(key: key); @override State createState() => _ProfileState(); } class _ProfileState extends State { + Widget usernameHeading() { + return Row( + children: [ + const CustomCircleAvatar( + icon: Icon(Icons.person, size: 40), + imagePath: null, // TODO: Add image here + radius: 30, + ), + const SizedBox(width: 20), + Text( + widget.profile.username, + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.w500, + ), + ), + // widget.conversation.admin ? IconButton( + // iconSize: 20, + // icon: const Icon(Icons.edit), + // padding: const EdgeInsets.all(5.0), + // splashRadius: 25, + // onPressed: () { + // // TODO: Redirect to edit screen + // }, + // ) : const SizedBox.shrink(), + ], + ); + } + + Widget _profileQrCode() { + return Container( + child: QrImage( + data: 'This is a simple QR code', + version: QrVersions.auto, + size: 130, + gapless: true, + ), + width: 130, + height: 130, + color: Theme.of(context).colorScheme.onPrimary, + ); + } + + Widget settings() { + return Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 5), + TextButton.icon( + label: const Text( + 'Disappearing Messages', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.timer), + style: ButtonStyle( + alignment: Alignment.centerLeft, + foregroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return Theme.of(context).colorScheme.onBackground; + }, + ) + ), + onPressed: () { + print('Disappearing Messages'); + } + ), + ], + ), + ); + } + + Widget logout() { + return Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextButton.icon( + label: const Text( + 'Logout', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.exit_to_app), + style: const ButtonStyle( + alignment: Alignment.centerLeft, + ), + onPressed: () { + deleteDb(); + MyProfile.logout(); + Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); + } + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -23,49 +128,30 @@ class _ProfileState extends State { padding: const EdgeInsets.only(left: 16,right: 16,top: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text("Profile",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),), - Container( - padding: const EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2), - height: 30, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Theme.of(context).colorScheme.tertiary - ), - child: GestureDetector( - onTap: () { - deleteDb(); - MyProfile.logout(); - Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); - }, - child: Row( - children: [ - Icon( - Icons.logout, - color: Theme.of(context).primaryColor, - size: 20 - ), - const SizedBox(width: 2,), - const Text( - 'Logout', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold - ) - ), - ], - ), + children: const [ + Text( + 'Profile', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, ), - ) + ), ], ), ), ), Padding( padding: const EdgeInsets.only(top: 16,left: 16,right: 16), - child: Row( - children: const [ - Text('FUCK'), + child: Column( + children: [ + const SizedBox(height: 30), + usernameHeading(), + const SizedBox(height: 30), + _profileQrCode(), + const SizedBox(height: 30), + settings(), + const SizedBox(height: 30), + logout(), ], ) ), diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 0f77a70..cfa6a60 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -240,6 +240,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + qr: + dependency: transitive + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" shared_preferences: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index ded0ef6..ea62a83 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: flutter_dotenv: ^5.0.2 intl: ^0.17.0 uuid: ^3.0.6 + qr_flutter: ^4.0.0 dev_dependencies: flutter_test: