Browse Source

Fix state management on home page, and fix user conversations

pull/1/head
Tovi Jaeschke-Rogers 2 years ago
parent
commit
2a5f243825
12 changed files with 105 additions and 63 deletions
  1. +6
    -2
      Backend/Api/Messages/CreateConversation.go
  2. +1
    -2
      Backend/Api/Routes.go
  3. +28
    -29
      mobile/lib/models/conversations.dart
  4. +29
    -8
      mobile/lib/utils/storage/conversations.dart
  5. +2
    -2
      mobile/lib/views/authentication/unauthenticated_landing.dart
  6. +1
    -3
      mobile/lib/views/main/conversation/detail.dart
  7. +1
    -1
      mobile/lib/views/main/conversation/list.dart
  8. +2
    -4
      mobile/lib/views/main/conversation/list_item.dart
  9. +3
    -3
      mobile/lib/views/main/conversation/settings.dart
  10. +1
    -1
      mobile/lib/views/main/conversation/settings_user_list_item.dart
  11. +5
    -2
      mobile/lib/views/main/friend/list_item.dart
  12. +26
    -6
      mobile/lib/views/main/home.dart

+ 6
- 2
Backend/Api/Messages/CreateConversation.go View File

@ -10,13 +10,16 @@ import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
) )
// RawCreateConversationData for holding POST payload
type RawCreateConversationData struct { type RawCreateConversationData struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
TwoUser string `json:"two_user"`
Users []Models.ConversationDetailUser `json:"users"` Users []Models.ConversationDetailUser `json:"users"`
UserConversations []Models.UserConversation `json:"user_conversations"` UserConversations []Models.UserConversation `json:"user_conversations"`
} }
// CreateConversation creates ConversationDetail, ConversationDetailUsers and UserConversations
func CreateConversation(w http.ResponseWriter, r *http.Request) { func CreateConversation(w http.ResponseWriter, r *http.Request) {
var ( var (
rawConversationData RawCreateConversationData rawConversationData RawCreateConversationData
@ -34,8 +37,9 @@ func CreateConversation(w http.ResponseWriter, r *http.Request) {
Base: Models.Base{ Base: Models.Base{
ID: uuid.FromStringOrNil(rawConversationData.ID), ID: uuid.FromStringOrNil(rawConversationData.ID),
}, },
Name: rawConversationData.Name,
Users: rawConversationData.Users,
Name: rawConversationData.Name,
TwoUser: rawConversationData.TwoUser,
Users: rawConversationData.Users,
} }
err = Database.CreateConversationDetail(&messageThread) err = Database.CreateConversationDetail(&messageThread)


+ 1
- 2
Backend/Api/Routes.go View File

@ -70,8 +70,7 @@ func InitAPIEndpoints(router *mux.Router) {
authAPI.HandleFunc("/conversations", Messages.EncryptedConversationList).Methods("GET") authAPI.HandleFunc("/conversations", Messages.EncryptedConversationList).Methods("GET")
authAPI.HandleFunc("/conversation_details", Messages.EncryptedConversationDetailsList).Methods("GET") authAPI.HandleFunc("/conversation_details", Messages.EncryptedConversationDetailsList).Methods("GET")
authAPI.HandleFunc("/conversations", Messages.CreateConversation).Methods("POST")
authAPI.HandleFunc("/conversations", Messages.reateConversation).Methods("POST")
authAPI.HandleFunc("/conversations", Messages.UpdateConversation).Methods("PUT") authAPI.HandleFunc("/conversations", Messages.UpdateConversation).Methods("PUT")
authAPI.HandleFunc("/message", Messages.CreateMessage).Methods("POST") authAPI.HandleFunc("/message", Messages.CreateMessage).Methods("POST")


+ 28
- 29
mobile/lib/models/conversations.dart View File

@ -34,7 +34,7 @@ Future<Conversation> createConversation(String title, List<Friend> friends, bool
status: ConversationStatus.pending, status: ConversationStatus.pending,
isRead: true, isRead: true,
); );
await db.insert( await db.insert(
'conversations', 'conversations',
conversation.toMap(), conversation.toMap(),
@ -65,12 +65,32 @@ Future<Conversation> createConversation(String title, List<Friend> friends, bool
username: friend.username, username: friend.username,
associationKey: uuid.v4(), associationKey: uuid.v4(),
publicKey: friend.publicKey, publicKey: friend.publicKey,
admin: false,
admin: twoUser ? true : false,
).toMap(), ).toMap(),
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
} }
if (twoUser) {
List<Map<String, dynamic>> maps = await db.query(
'conversation_users',
where: 'conversation_id = ? AND user_id != ?',
whereArgs: [ conversation.id, profile.id ],
);
if (maps.length != 1) {
throw ArgumentError('Invalid user id');
}
conversation.name = maps[0]['username'];
await db.insert(
'conversations',
conversation.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
return conversation; return conversation;
} }
@ -138,7 +158,10 @@ Future<Conversation> getConversationById(String id) async {
Future<List<Conversation>> getConversations() async { Future<List<Conversation>> getConversations() async {
final db = await getDatabaseConnection(); final db = await getDatabaseConnection();
final List<Map<String, dynamic>> maps = await db.query('conversations');
final List<Map<String, dynamic>> maps = await db.query(
'conversations',
orderBy: 'name',
);
return List.generate(maps.length, (i) { return List.generate(maps.length, (i) {
return Conversation( return Conversation(
@ -159,9 +182,6 @@ Future<Conversation?> getTwoUserConversation(String userId) async {
MyProfile profile = await MyProfile.getProfile(); MyProfile profile = await MyProfile.getProfile();
print(userId);
print(profile.id);
final List<Map<String, dynamic>> maps = await db.rawQuery( final List<Map<String, dynamic>> maps = await db.rawQuery(
''' '''
SELECT conversations.* FROM conversations SELECT conversations.* FROM conversations
@ -281,6 +301,7 @@ class Conversation {
'id': id, 'id': id,
'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)),
'users': await getEncryptedConversationUsers(this, symKey), 'users': await getEncryptedConversationUsers(this, symKey),
'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)),
'user_conversations': userConversations, 'user_conversations': userConversations,
}; };
} }
@ -320,7 +341,7 @@ class Conversation {
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 1; LIMIT 1;
''', ''',
[id],
[ id ],
); );
if (maps.isEmpty) { if (maps.isEmpty) {
@ -339,28 +360,6 @@ class Conversation {
failedToSend: maps[0]['failed_to_send'] == 1, failedToSend: maps[0]['failed_to_send'] == 1,
); );
} }
Future<String> getName() async {
if (!twoUser) {
return name;
}
MyProfile profile = await MyProfile.getProfile();
final db = await getDatabaseConnection();
List<Map<String, dynamic>> maps = await db.query(
'conversation_users',
where: 'conversation_id = ? AND user_id != ?',
whereArgs: [ id, profile.id ],
);
if (maps.length != 1) {
throw ArgumentError('Invalid user id');
}
return maps[0]['username'];
}
} }


+ 29
- 8
mobile/lib/utils/storage/conversations.dart View File

@ -1,5 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:Envelope/components/flash_message.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
@ -87,16 +89,34 @@ Future<void> updateConversations() async {
var conversationDetailJson = conversationsDetailsJson[i] as Map<String, dynamic>; var conversationDetailJson = conversationsDetailsJson[i] as Map<String, dynamic>;
var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']); var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']);
conversation.name = AesHelper.aesDecrypt(
base64.decode(conversation.symmetricKey),
base64.decode(conversationDetailJson['name']),
);
conversation.twoUser = AesHelper.aesDecrypt( conversation.twoUser = AesHelper.aesDecrypt(
base64.decode(conversation.symmetricKey), base64.decode(conversation.symmetricKey),
base64.decode(conversationDetailJson['two_user']), base64.decode(conversationDetailJson['two_user']),
) == 'true'; ) == 'true';
if (conversation.twoUser) {
MyProfile profile = await MyProfile.getProfile();
final db = await getDatabaseConnection();
List<Map<String, dynamic>> maps = await db.query(
'conversation_users',
where: 'conversation_id = ? AND user_id != ?',
whereArgs: [ conversation.id, profile.id ],
);
if (maps.length != 1) {
throw ArgumentError('Invalid user id');
}
conversation.name = maps[0]['username'];
} else {
conversation.name = AesHelper.aesDecrypt(
base64.decode(conversation.symmetricKey),
base64.decode(conversationDetailJson['name']),
);
}
await db.insert( await db.insert(
'conversations', 'conversations',
conversation.toMap(), conversation.toMap(),
@ -124,7 +144,7 @@ Future<void> updateConversations() async {
} }
Future<void> uploadConversation(Conversation conversation) async {
Future<void> uploadConversation(Conversation conversation, BuildContext context) async {
String sessionCookie = await getSessionCookie(); String sessionCookie = await getSessionCookie();
Map<String, dynamic> conversationJson = await conversation.payloadJson(); Map<String, dynamic> conversationJson = await conversation.payloadJson();
@ -138,7 +158,8 @@ Future<void> uploadConversation(Conversation conversation) async {
body: jsonEncode(conversationJson), body: jsonEncode(conversationJson),
); );
// TODO: Handle errors here
print(x.statusCode);
if (x.statusCode != 200) {
showMessage('Failed to create conversation', context);
}
} }

+ 2
- 2
mobile/lib/views/authentication/unauthenticated_landing.dart View File

@ -18,9 +18,9 @@ class _UnauthenticatedLandingWidgetState extends State<UnauthenticatedLandingWid
primary: Theme.of(context).colorScheme.surface, primary: Theme.of(context).colorScheme.surface,
onPrimary: Theme.of(context).colorScheme.onSurface, onPrimary: Theme.of(context).colorScheme.onSurface,
minimumSize: const Size.fromHeight(50), minimumSize: const Size.fromHeight(50),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
textStyle: const TextStyle( textStyle: const TextStyle(
fontSize: 22,
fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
); );


+ 1
- 3
mobile/lib/views/main/conversation/detail.dart View File

@ -21,7 +21,6 @@ class ConversationDetail extends StatefulWidget{
} }
class _ConversationDetailState extends State<ConversationDetail> { class _ConversationDetailState extends State<ConversationDetail> {
String conversationName = '';
List<Message> messages = []; List<Message> messages = [];
MyProfile profile = MyProfile(id: '', username: ''); MyProfile profile = MyProfile(id: '', username: '');
@ -32,7 +31,7 @@ class _ConversationDetailState extends State<ConversationDetail> {
return Scaffold( return Scaffold(
appBar: CustomTitleBar( appBar: CustomTitleBar(
title: Text( title: Text(
conversationName,
widget.conversation.name,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -138,7 +137,6 @@ class _ConversationDetailState extends State<ConversationDetail> {
} }
Future<void> fetchMessages() async { Future<void> fetchMessages() async {
conversationName = await widget.conversation.getName();
profile = await MyProfile.getProfile(); profile = await MyProfile.getProfile();
messages = await getMessagesForThread(widget.conversation); messages = await getMessagesForThread(widget.conversation);
setState(() {}); setState(() {});


+ 1
- 1
mobile/lib/views/main/conversation/list.dart View File

@ -77,7 +77,7 @@ class _ConversationListState extends State<ConversationList> {
false, false,
); );
uploadConversation(conversation);
uploadConversation(conversation, context);
Navigator.of(context).popUntil((route) => route.isFirst); Navigator.of(context).popUntil((route) => route.isFirst);
Navigator.push(context, MaterialPageRoute(builder: (context){ Navigator.push(context, MaterialPageRoute(builder: (context){


+ 2
- 4
mobile/lib/views/main/conversation/list_item.dart View File

@ -19,7 +19,6 @@ class ConversationListItem extends StatefulWidget{
class _ConversationListItemState extends State<ConversationListItem> { class _ConversationListItemState extends State<ConversationListItem> {
late Conversation conversation; late Conversation conversation;
late String conversationName;
late Message? recentMessage; late Message? recentMessage;
bool loaded = false; bool loaded = false;
@ -42,7 +41,7 @@ class _ConversationListItemState extends State<ConversationListItem> {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
CustomCircleAvatar( CustomCircleAvatar(
initials: conversationName[0].toUpperCase(),
initials: widget.conversation.name[0].toUpperCase(),
imagePath: null, imagePath: null,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
@ -55,7 +54,7 @@ class _ConversationListItemState extends State<ConversationListItem> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
conversationName,
widget.conversation.name,
style: const TextStyle(fontSize: 16) style: const TextStyle(fontSize: 16)
), ),
recentMessage != null ? recentMessage != null ?
@ -108,7 +107,6 @@ class _ConversationListItemState extends State<ConversationListItem> {
Future<void> getConversationData() async { Future<void> getConversationData() async {
conversation = widget.conversation; conversation = widget.conversation;
conversationName = await widget.conversation.getName();
recentMessage = await conversation.getRecentMessage(); recentMessage = await conversation.getRecentMessage();
loaded = true; loaded = true;
setState(() {}); setState(() {});


+ 3
- 3
mobile/lib/views/main/conversation/settings.dart View File

@ -35,7 +35,7 @@ class _ConversationSettingsState extends State<ConversationSettings> {
return Scaffold( return Scaffold(
appBar: CustomTitleBar( appBar: CustomTitleBar(
title: Text( title: Text(
widget.conversation.name + " Settings",
widget.conversation.name + ' Settings',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -61,7 +61,7 @@ class _ConversationSettingsState extends State<ConversationSettings> {
widget.conversation.admin ? widget.conversation.admin ?
const SizedBox(height: 25) : const SizedBox(height: 25) :
const SizedBox.shrink(), const SizedBox.shrink(),
sectionTitle('Members', showUsersAdd: true),
sectionTitle('Members', showUsersAdd: widget.conversation.admin && !widget.conversation.twoUser),
usersList(), usersList(),
const SizedBox(height: 25), const SizedBox(height: 25),
myAccess(), myAccess(),
@ -88,7 +88,7 @@ class _ConversationSettingsState extends State<ConversationSettings> {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
widget.conversation.admin ? IconButton(
widget.conversation.admin && !widget.conversation.twoUser ? IconButton(
iconSize: 20, iconSize: 20,
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),


+ 1
- 1
mobile/lib/views/main/conversation/settings_user_list_item.dart View File

@ -42,7 +42,7 @@ class _ConversationSettingsUserListItemState extends State<ConversationSettingsU
} }
Widget adminUserActions() { Widget adminUserActions() {
if (!widget.isAdmin || widget.user.username == widget.profile.username) {
if (!widget.isAdmin || widget.user.admin || widget.user.username == widget.profile.username) {
return const SizedBox(height: 50); return const SizedBox(height: 50);
} }


+ 5
- 2
mobile/lib/views/main/friend/list_item.dart View File

@ -1,6 +1,7 @@
import 'package:Envelope/components/custom_circle_avatar.dart'; import 'package:Envelope/components/custom_circle_avatar.dart';
import 'package:Envelope/models/conversations.dart'; import 'package:Envelope/models/conversations.dart';
import 'package:Envelope/models/friends.dart'; import 'package:Envelope/models/friends.dart';
import 'package:Envelope/utils/storage/conversations.dart';
import 'package:Envelope/utils/strings.dart'; import 'package:Envelope/utils/strings.dart';
import 'package:Envelope/views/main/conversation/detail.dart'; import 'package:Envelope/views/main/conversation/detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -22,7 +23,7 @@ class _FriendListItemState extends State<FriendListItem> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: findOrCreateConversation,
onTap: () { findOrCreateConversation(context); },
child: Container( child: Container(
padding: const EdgeInsets.only(left: 16,right: 16,top: 0,bottom: 20), padding: const EdgeInsets.only(left: 16,right: 16,top: 0,bottom: 20),
child: Row( child: Row(
@ -58,7 +59,7 @@ class _FriendListItemState extends State<FriendListItem> {
); );
} }
Future<void> findOrCreateConversation() async {
Future<void> findOrCreateConversation(BuildContext context) async {
Conversation? conversation = await getTwoUserConversation(widget.friend.friendId); Conversation? conversation = await getTwoUserConversation(widget.friend.friendId);
conversation ??= await createConversation( conversation ??= await createConversation(
@ -67,6 +68,8 @@ class _FriendListItemState extends State<FriendListItem> {
true, true,
); );
uploadConversation(conversation, context);
Navigator.push(context, MaterialPageRoute(builder: (context){ Navigator.push(context, MaterialPageRoute(builder: (context){
return ConversationDetail( return ConversationDetail(
conversation: conversation!, conversation: conversation!,


+ 26
- 6
mobile/lib/views/main/home.dart View File

@ -59,15 +59,15 @@ class _HomeState extends State<Home> {
items: const [ items: const [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.message), icon: Icon(Icons.message),
label: "Chats",
label: 'Chats',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.group_work), icon: Icon(Icons.group_work),
label: "Friends",
label: 'Friends',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.account_box), icon: Icon(Icons.account_box),
label: "Profile",
label: 'Profile',
), ),
], ],
), ),
@ -166,7 +166,7 @@ class _HomeState extends State<Home> {
FriendList( FriendList(
friends: friends, friends: friends,
friendRequests: friendRequests, friendRequests: friendRequests,
callback: initFriends,
callback: reinitDatabaseRecords,
), ),
Profile(profile: profile), Profile(profile: profile),
]; ];
@ -174,12 +174,32 @@ class _HomeState extends State<Home> {
}); });
} }
Future<void> initFriends() async {
Future<void> reinitDatabaseRecords() async {
conversations = await getConversations();
friends = await getFriends(accepted: true); friends = await getFriends(accepted: true);
friendRequests = await getFriends(accepted: false); friendRequests = await getFriends(accepted: false);
profile = await MyProfile.getProfile();
setState(() {
_widgetOptions = <Widget>[
ConversationList(
conversations: conversations,
friends: friends,
),
FriendList(
friends: friends,
friendRequests: friendRequests,
callback: reinitDatabaseRecords,
),
Profile(profile: profile),
];
isLoading = false;
});
} }
void _onItemTapped(int index) {
void _onItemTapped(int index) async {
await reinitDatabaseRecords();
setState(() { setState(() {
_selectedIndex = index; _selectedIndex = index;
}); });


Loading…
Cancel
Save