Browse Source

Update the conversation settings and profile page

pull/1/head
Tovi Jaeschke-Rogers 2 years ago
parent
commit
283054bfd5
7 changed files with 333 additions and 74 deletions
  1. +23
    -12
      mobile/lib/models/my_profile.dart
  2. +113
    -6
      mobile/lib/views/main/conversation_settings.dart
  3. +47
    -17
      mobile/lib/views/main/conversation_settings_user_list_item.dart
  4. +12
    -2
      mobile/lib/views/main/home.dart
  5. +123
    -37
      mobile/lib/views/main/profile.dart
  6. +14
    -0
      mobile/pubspec.lock
  7. +1
    -0
      mobile/pubspec.yaml

+ 23
- 12
mobile/lib/models/my_profile.dart View File

@ -7,16 +7,16 @@ import 'package:shared_preferences/shared_preferences.dart';
class MyProfile { class MyProfile {
String id; String id;
String username; String username;
RSAPrivateKey privateKey;
RSAPublicKey publicKey;
DateTime loggedInAt;
RSAPrivateKey? privateKey;
RSAPublicKey? publicKey;
DateTime? loggedInAt;
MyProfile({ MyProfile({
required this.id, required this.id,
required this.username, required this.username,
required this.privateKey,
required this.publicKey,
required this.loggedInAt,
this.privateKey,
this.publicKey,
this.loggedInAt,
}); });
factory MyProfile._fromJson(Map<String, dynamic> json) { factory MyProfile._fromJson(Map<String, dynamic> json) {
@ -49,9 +49,13 @@ class MyProfile {
return jsonEncode(<String, dynamic>{ return jsonEncode(<String, dynamic>{
'user_id': id, 'user_id': id,
'username': username, '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); MyProfile profile = MyProfile._fromJson(json);
final preferences = await SharedPreferences.getInstance(); final preferences = await SharedPreferences.getInstance();
print(profile.toJson());
preferences.setString('profile', profile.toJson()); preferences.setString('profile', profile.toJson());
return profile; return profile;
} }
@ -83,12 +86,20 @@ class MyProfile {
static Future<bool> isLoggedIn() async { static Future<bool> isLoggedIn() async {
MyProfile profile = await MyProfile.getProfile(); 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<RSAPrivateKey> getPrivateKey() async { Future<RSAPrivateKey> getPrivateKey() async {
MyProfile profile = await MyProfile.getProfile(); MyProfile profile = await MyProfile.getProfile();
return profile.privateKey;
if (profile.privateKey == null) {
throw Exception('Could not get privateKey');
}
return profile.privateKey!;
} }
} }

+ 113
- 6
mobile/lib/views/main/conversation_settings.dart View File

@ -1,4 +1,5 @@
import 'package:Envelope/models/conversation_users.dart'; import 'package:Envelope/models/conversation_users.dart';
import 'package:Envelope/models/my_profile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '/views/main/conversation_settings_user_list_item.dart'; import '/views/main/conversation_settings_user_list_item.dart';
import '/models/conversations.dart'; import '/models/conversations.dart';
@ -16,9 +17,8 @@ class ConversationSettings extends StatefulWidget {
} }
class _ConversationSettingsState extends State<ConversationSettings> { class _ConversationSettingsState extends State<ConversationSettings> {
final _formKey = GlobalKey<FormState>();
List<ConversationUser> users = []; List<ConversationUser> users = [];
MyProfile? profile;
TextEditingController nameController = TextEditingController(); TextEditingController nameController = TextEditingController();
@ -31,6 +31,7 @@ class _ConversationSettingsState extends State<ConversationSettings> {
Future<void> getUsers() async { Future<void> getUsers() async {
users = await getConversationUsers(widget.conversation); users = await getConversationUsers(widget.conversation);
profile = await MyProfile.getProfile();
setState(() {}); setState(() {});
} }
@ -50,7 +51,101 @@ class _ConversationSettingsState extends State<ConversationSettings> {
fontWeight: FontWeight.w500, 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<Color>(
(Set<MaterialState> 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<Color>(
(Set<MaterialState> 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<ConversationSettings> {
return ListView.builder( return ListView.builder(
itemCount: users.length, itemCount: users.length,
shrinkWrap: true, shrinkWrap: true,
padding: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.only(top: 5, bottom: 0),
itemBuilder: (context, i) { itemBuilder: (context, i) {
return ConversationSettingsUserListItem( return ConversationSettingsUserListItem(
user: users[i], user: users[i],
isAdmin: widget.conversation.admin, isAdmin: widget.conversation.admin,
profile: profile!, // TODO: Fix this
); );
} }
); );
@ -112,9 +208,20 @@ class _ConversationSettingsState extends State<ConversationSettings> {
children: <Widget> [ children: <Widget> [
const SizedBox(height: 30), const SizedBox(height: 30),
conversationName(), 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(), usersList(),
const SizedBox(height: 25),
myAccess(),
], ],
), ),
), ),


+ 47
- 17
mobile/lib/views/main/conversation_settings_user_list_item.dart View File

@ -1,3 +1,4 @@
import 'package:Envelope/models/my_profile.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:Envelope/models/conversation_users.dart'; import 'package:Envelope/models/conversation_users.dart';
import 'package:Envelope/components/custom_circle_avatar.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{ class ConversationSettingsUserListItem extends StatefulWidget{
final ConversationUser user; final ConversationUser user;
final bool isAdmin; final bool isAdmin;
final MyProfile profile;
const ConversationSettingsUserListItem({ const ConversationSettingsUserListItem({
Key? key, Key? key,
required this.user, required this.user,
required this.isAdmin, required this.isAdmin,
required this.profile,
}) : super(key: key); }) : super(key: key);
@override @override
@ -18,7 +21,7 @@ class ConversationSettingsUserListItem extends StatefulWidget{
class _ConversationSettingsUserListItemState extends State<ConversationSettingsUserListItem> { class _ConversationSettingsUserListItemState extends State<ConversationSettingsUserListItem> {
Widget admin() { Widget admin() {
if (widget.user.admin) {
if (!widget.user.admin) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@ -38,33 +41,60 @@ class _ConversationSettingsUserListItemState extends State<ConversationSettingsU
} }
Widget adminUserActions() { Widget adminUserActions() {
if (!widget.isAdmin) {
if (!widget.isAdmin || widget.user.username == widget.profile.username) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget> [
IconButton(icon: const Icon(Icons.cancel),
padding: const EdgeInsets.all(0),
onPressed:() => {
print('Cancel')
}
return PopupMenuButton<String>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( 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( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(


+ 12
- 2
mobile/lib/views/main/home.dart View File

@ -23,13 +23,22 @@ class Home extends StatefulWidget {
class _HomeState extends State<Home> { class _HomeState extends State<Home> {
List<Conversation> conversations = []; List<Conversation> conversations = [];
List<Friend> friends = []; List<Friend> friends = [];
MyProfile profile = MyProfile(
id: '',
username: '',
);
bool isLoading = true; bool isLoading = true;
int _selectedIndex = 0; int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[ List<Widget> _widgetOptions = <Widget>[
const ConversationList(conversations: []), const ConversationList(conversations: []),
const FriendList(friends: []), const FriendList(friends: []),
const Profile(),
Profile(
profile: MyProfile(
id: '',
username: '',
)
),
]; ];
@override @override
@ -48,12 +57,13 @@ class _HomeState extends State<Home> {
conversations = await getConversations(); conversations = await getConversations();
friends = await getFriends(); friends = await getFriends();
profile = await MyProfile.getProfile();
setState(() { setState(() {
_widgetOptions = <Widget>[ _widgetOptions = <Widget>[
ConversationList(conversations: conversations), ConversationList(conversations: conversations),
FriendList(friends: friends), FriendList(friends: friends),
const Profile(),
Profile(profile: profile),
]; ];
isLoading = false; isLoading = false;
}); });


+ 123
- 37
mobile/lib/views/main/profile.dart View File

@ -1,15 +1,120 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '/utils/storage/database.dart'; import '/utils/storage/database.dart';
import '/models/my_profile.dart'; import '/models/my_profile.dart';
import '/components/custom_circle_avatar.dart';
class Profile extends StatefulWidget { 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 @override
State<Profile> createState() => _ProfileState(); State<Profile> createState() => _ProfileState();
} }
class _ProfileState extends State<Profile> { class _ProfileState extends State<Profile> {
Widget usernameHeading() {
return Row(
children: <Widget> [
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<Color>(
(Set<MaterialState> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -23,49 +128,30 @@ class _ProfileState extends State<Profile> {
padding: const EdgeInsets.only(left: 16,right: 16,top: 10), padding: const EdgeInsets.only(left: 16,right: 16,top: 10),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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: <Widget>[
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 <Widget>[
Text(
'Profile',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
), ),
)
),
], ],
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 16,left: 16,right: 16), padding: const EdgeInsets.only(top: 16,left: 16,right: 16),
child: Row(
children: const <Widget>[
Text('FUCK'),
child: Column(
children: <Widget>[
const SizedBox(height: 30),
usernameHeading(),
const SizedBox(height: 30),
_profileQrCode(),
const SizedBox(height: 30),
settings(),
const SizedBox(height: 30),
logout(),
], ],
) )
), ),


+ 14
- 0
mobile/pubspec.lock View File

@ -240,6 +240,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.2.4" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:


+ 1
- 0
mobile/pubspec.yaml View File

@ -22,6 +22,7 @@ dependencies:
flutter_dotenv: ^5.0.2 flutter_dotenv: ^5.0.2
intl: ^0.17.0 intl: ^0.17.0
uuid: ^3.0.6 uuid: ^3.0.6
qr_flutter: ^4.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:


Loading…
Cancel
Save