From b05b90e6d2a9e4c1aac9d0bff8df050b2f563592 Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Tue, 19 Jul 2022 20:24:00 +0930 Subject: [PATCH] Remove friends table due to unnecessary added complexity WIP - Creating conversations --- Backend/Api/Friends/EncryptedFriendsList.go | 38 ---- Backend/Api/Messages/CreateConversation.go | 54 ++++++ Backend/Api/Routes.go | 8 +- Backend/Database/ConversationDetails.go | 2 +- Backend/Database/Friends.go | 74 ------- Backend/Database/Init.go | 11 +- Backend/Database/Seeder/FriendSeeder.go | 36 ++-- Backend/Database/Seeder/MessageSeeder.go | 47 ++--- Backend/Database/Seeder/UserSeeder.go | 40 +--- Backend/Database/UserConversations.go | 20 +- Backend/Models/Friends.go | 20 +- Backend/Models/Users.go | 10 +- mobile/lib/models/conversation_users.dart | 44 ++--- mobile/lib/models/conversations.dart | 183 +++++++++++++----- mobile/lib/models/friends.dart | 58 +++--- mobile/lib/models/my_profile.dart | 11 +- mobile/lib/utils/storage/conversations.dart | 156 ++++++++------- mobile/lib/utils/storage/database.dart | 4 +- mobile/lib/utils/storage/encryption_keys.dart | 27 --- mobile/lib/utils/storage/friends.dart | 60 ++---- mobile/lib/utils/storage/messages.dart | 30 ++- .../main/conversation_create_add_users.dart | 8 +- .../lib/views/main/conversation_detail.dart | 23 ++- mobile/lib/views/main/conversation_list.dart | 8 +- .../views/main/conversation_list_item.dart | 27 ++- mobile/lib/views/main/home.dart | 2 +- mobile/lib/views/main/profile.dart | 15 ++ 27 files changed, 499 insertions(+), 517 deletions(-) create mode 100644 Backend/Api/Messages/CreateConversation.go delete mode 100644 Backend/Database/Friends.go delete mode 100644 mobile/lib/utils/storage/encryption_keys.dart diff --git a/Backend/Api/Friends/EncryptedFriendsList.go b/Backend/Api/Friends/EncryptedFriendsList.go index d9f8d61..441284f 100644 --- a/Backend/Api/Friends/EncryptedFriendsList.go +++ b/Backend/Api/Friends/EncryptedFriendsList.go @@ -3,8 +3,6 @@ package Friends import ( "encoding/json" "net/http" - "net/url" - "strings" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" @@ -40,39 +38,3 @@ func EncryptedFriendRequestList(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write(returnJson) } - -func EncryptedFriendList(w http.ResponseWriter, r *http.Request) { - var ( - friends []Models.Friend - query url.Values - friendIds []string - returnJson []byte - ok bool - err error - ) - - query = r.URL.Query() - friendIds, ok = query["friend_ids"] - if !ok { - http.Error(w, "Invalid Data", http.StatusBadGateway) - return - } - - // TODO: Fix error handling here - friendIds = strings.Split(friendIds[0], ",") - - friends, err = Database.GetFriendsByIds(friendIds) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - returnJson, err = json.MarshalIndent(friends, "", " ") - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(returnJson) -} diff --git a/Backend/Api/Messages/CreateConversation.go b/Backend/Api/Messages/CreateConversation.go new file mode 100644 index 0000000..b2dccd9 --- /dev/null +++ b/Backend/Api/Messages/CreateConversation.go @@ -0,0 +1,54 @@ +package Messages + +import ( + "encoding/json" + "net/http" + "github.com/gofrs/uuid" + + "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" + "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" +) + +type RawConversationData struct { + ID string `json:"id"` + Name string `json:"name"` + Users string `json:"users"` + UserConversations []Models.UserConversation `json:"user_conversations"` +} + +func CreateConversation(w http.ResponseWriter, r *http.Request) { + var ( + rawConversationData RawConversationData + messageThread Models.ConversationDetail + err error + ) + + err = json.NewDecoder(r.Body).Decode(&rawConversationData) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + messageThread = Models.ConversationDetail{ + Base: Models.Base{ + ID: uuid.FromStringOrNil(rawConversationData.ID), + }, + Name: rawConversationData.Name, + Users: rawConversationData.Users, + } + + err = Database.CreateConversationDetail(&messageThread) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + err = Database.CreateUserConversations(&rawConversationData.UserConversations) + if err != nil { + panic(err) + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} diff --git a/Backend/Api/Routes.go b/Backend/Api/Routes.go index 5aee439..a37fc15 100644 --- a/Backend/Api/Routes.go +++ b/Backend/Api/Routes.go @@ -26,9 +26,7 @@ func loggingMiddleware(next http.Handler) http.Handler { func authenticationMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ( - err error - ) + var err error _, err = Auth.CheckCookie(r) if err != nil { @@ -65,11 +63,11 @@ func InitApiEndpoints(router *mux.Router) { authApi.HandleFunc("/friend_requests", Friends.EncryptedFriendRequestList).Methods("GET") authApi.HandleFunc("/friend_request", Friends.CreateFriendRequest).Methods("POST") - authApi.HandleFunc("/friends", Friends.EncryptedFriendList).Methods("GET") - authApi.HandleFunc("/conversations", Messages.EncryptedConversationList).Methods("GET") authApi.HandleFunc("/conversation_details", Messages.EncryptedConversationDetailsList).Methods("GET") + authApi.HandleFunc("/conversations", Messages.CreateConversation).Methods("POST") + // Define routes for messages authApi.HandleFunc("/message", Messages.CreateMessage).Methods("POST") authApi.HandleFunc("/messages/{threadKey}", Messages.Messages).Methods("GET") diff --git a/Backend/Database/ConversationDetails.go b/Backend/Database/ConversationDetails.go index 144e28c..cd1140d 100644 --- a/Backend/Database/ConversationDetails.go +++ b/Backend/Database/ConversationDetails.go @@ -28,7 +28,7 @@ func GetConversationDetailsByIds(id []string) ([]Models.ConversationDetail, erro ) err = DB.Preload(clause.Associations). - Where("id = ?", id). + Where("id IN ?", id). First(&messageThread). Error diff --git a/Backend/Database/Friends.go b/Backend/Database/Friends.go deleted file mode 100644 index 102e657..0000000 --- a/Backend/Database/Friends.go +++ /dev/null @@ -1,74 +0,0 @@ -package Database - -import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -func GetFriendById(id string) (Models.Friend, error) { - var ( - userData Models.Friend - err error - ) - - err = DB.Preload(clause.Associations). - First(&userData, "id = ?", id). - Error - - return userData, err -} - -func GetFriendsByIds(ids []string) ([]Models.Friend, error) { - var ( - userData []Models.Friend - err error - ) - - err = DB.Preload(clause.Associations). - Find(&userData, ids). - Error - - return userData, err -} - -func CreateFriend(userData *Models.Friend) error { - var ( - err error - ) - - err = DB.Session(&gorm.Session{FullSaveAssociations: true}). - Create(userData). - Error - - return err -} - -func UpdateFriend(id string, userData *Models.Friend) error { - var ( - err error - ) - err = DB.Model(&userData). - Omit("id"). - Where("id = ?", id). - Updates(userData). - Error - - if err != nil { - return err - } - - err = DB.Model(Models.Friend{}). - Where("id = ?", id). - First(userData). - Error - - return err -} - -func DeleteFriend(userData *Models.Friend) error { - return DB.Session(&gorm.Session{FullSaveAssociations: true}). - Delete(userData). - Error -} diff --git a/Backend/Database/Init.go b/Backend/Database/Init.go index f8537d4..6241fdb 100644 --- a/Backend/Database/Init.go +++ b/Backend/Database/Init.go @@ -9,18 +9,17 @@ import ( "gorm.io/gorm" ) -const dbUrl = "postgres://postgres:@localhost:5432/envelope" -const dbTestUrl = "postgres://postgres:@localhost:5432/envelope_test" - -var ( - DB *gorm.DB +const ( + dbUrl = "postgres://postgres:@localhost:5432/envelope" + dbTestUrl = "postgres://postgres:@localhost:5432/envelope_test" ) +var DB *gorm.DB + func GetModels() []interface{} { return []interface{}{ &Models.Session{}, &Models.User{}, - &Models.Friend{}, &Models.FriendRequest{}, &Models.MessageData{}, &Models.Message{}, diff --git a/Backend/Database/Seeder/FriendSeeder.go b/Backend/Database/Seeder/FriendSeeder.go index 5d79da2..7b3f960 100644 --- a/Backend/Database/Seeder/FriendSeeder.go +++ b/Backend/Database/Seeder/FriendSeeder.go @@ -11,36 +11,42 @@ import ( func seedFriend(userRequestTo, userRequestFrom Models.User) error { var ( friendRequest Models.FriendRequest - decodedID []byte - id []byte - decodedSymKey []byte - symKey []byte + symKey aesKey + encPublicKey []byte err error ) - decodedID, err = base64.StdEncoding.DecodeString(userRequestFrom.FriendID) - if err != nil { - return err - } - id, err = decryptWithPrivateKey(decodedID, decodedPrivateKey) + symKey, err = generateAesKey() if err != nil { return err } - decodedSymKey, err = base64.StdEncoding.DecodeString(userRequestFrom.FriendSymmetricKey) + encPublicKey, err = symKey.aesEncrypt([]byte(publicKey)) if err != nil { return err } - symKey, err = decryptWithPrivateKey(decodedSymKey, decodedPrivateKey) friendRequest = Models.FriendRequest{ - UserID: userRequestTo.ID, - AcceptedAt: time.Now(), + UserID: userRequestTo.ID, + UserUsername: userRequestTo.Username, + AcceptedAt: time.Now(), FriendID: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(id, decodedPublicKey), + encryptWithPublicKey( + []byte(userRequestFrom.ID.String()), + decodedPublicKey, + ), + ), + FriendUsername: base64.StdEncoding.EncodeToString( + encryptWithPublicKey( + []byte(userRequestFrom.Username), + decodedPublicKey, + ), + ), + FriendPublicAsymmetricKey: base64.StdEncoding.EncodeToString( + encPublicKey, ), SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(symKey, decodedPublicKey), + encryptWithPublicKey(symKey.Key, decodedPublicKey), ), } diff --git a/Backend/Database/Seeder/MessageSeeder.go b/Backend/Database/Seeder/MessageSeeder.go index a725c5f..76ec995 100644 --- a/Backend/Database/Seeder/MessageSeeder.go +++ b/Backend/Database/Seeder/MessageSeeder.go @@ -24,7 +24,6 @@ func seedMessage( plaintext string dataCiphertext []byte senderIdCiphertext []byte - friendId []byte err error ) @@ -45,31 +44,13 @@ func seedMessage( panic(err) } - friendId, err = base64.StdEncoding.DecodeString(primaryUser.FriendID) - if err != nil { - panic(err) - } - friendId, err = decryptWithPrivateKey(friendId, decodedPrivateKey) - if err != nil { - panic(err) - } - - senderIdCiphertext, err = key.aesEncrypt(friendId) + senderIdCiphertext, err = key.aesEncrypt([]byte(primaryUser.ID.String())) if err != nil { panic(err) } if i%2 == 0 { - friendId, err = base64.StdEncoding.DecodeString(secondaryUser.FriendID) - if err != nil { - panic(err) - } - friendId, err = decryptWithPrivateKey(friendId, decodedPrivateKey) - if err != nil { - panic(err) - } - - senderIdCiphertext, err = key.aesEncrypt(friendId) + senderIdCiphertext, err = key.aesEncrypt([]byte(secondaryUser.ID.String())) if err != nil { panic(err) } @@ -198,9 +179,8 @@ func SeedMessages() { primaryUserAssociationKey string secondaryUser Models.User secondaryUserAssociationKey string - primaryUserFriendId []byte - secondaryUserFriendId []byte userJson string + id1, id2 uuid.UUID i int err error ) @@ -233,20 +213,11 @@ func SeedMessages() { key, ) - primaryUserFriendId, err = base64.StdEncoding.DecodeString(primaryUser.FriendID) - if err != nil { - panic(err) - } - primaryUserFriendId, err = decryptWithPrivateKey(primaryUserFriendId, decodedPrivateKey) - if err != nil { - panic(err) - } - - secondaryUserFriendId, err = base64.StdEncoding.DecodeString(secondaryUser.FriendID) + id1, err = uuid.NewV4() if err != nil { panic(err) } - secondaryUserFriendId, err = decryptWithPrivateKey(secondaryUserFriendId, decodedPrivateKey) + id2, err = uuid.NewV4() if err != nil { panic(err) } @@ -256,22 +227,26 @@ func SeedMessages() { [ { "id": "%s", + "user_id": "%s", "username": "%s", "admin": "true", "association_key": "%s" }, { "id": "%s", + "user_id": "%s", "username": "%s", "admin": "false", "association_key": "%s" } ] `, - string(primaryUserFriendId), + id1.String(), + primaryUser.ID.String(), primaryUser.Username, primaryUserAssociationKey, - string(secondaryUserFriendId), + id2.String(), + secondaryUser.ID.String(), secondaryUser.Username, secondaryUserAssociationKey, ) diff --git a/Backend/Database/Seeder/UserSeeder.go b/Backend/Database/Seeder/UserSeeder.go index 3c99c9c..ce13b2a 100644 --- a/Backend/Database/Seeder/UserSeeder.go +++ b/Backend/Database/Seeder/UserSeeder.go @@ -1,8 +1,6 @@ package Seeder import ( - "encoding/base64" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" @@ -24,12 +22,9 @@ var userNames = []string{ func createUser(username string) (Models.User, error) { var ( - userData Models.User - key aesKey - publicUserData Models.Friend - password string - usernameCiphertext []byte - err error + userData Models.User + password string + err error ) password, err = Auth.HashPassword("password") @@ -37,40 +32,11 @@ func createUser(username string) (Models.User, error) { return Models.User{}, err } - key, err = generateAesKey() - if err != nil { - return Models.User{}, err - } - - usernameCiphertext, err = key.aesEncrypt([]byte(username)) - if err != nil { - return Models.User{}, err - } - - publicUserData = Models.Friend{ - Username: base64.StdEncoding.EncodeToString(usernameCiphertext), - AsymmetricPublicKey: publicKey, - } - - err = Database.CreateFriend(&publicUserData) - if err != nil { - return userData, err - } - userData = Models.User{ Username: username, Password: password, AsymmetricPrivateKey: encryptedPrivateKey, AsymmetricPublicKey: publicKey, - FriendID: base64.StdEncoding.EncodeToString( - encryptWithPublicKey( - []byte(publicUserData.ID.String()), - decodedPublicKey, - ), - ), - FriendSymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(key.Key, decodedPublicKey), - ), } err = Database.CreateUser(&userData) diff --git a/Backend/Database/UserConversations.go b/Backend/Database/UserConversations.go index 8e5490b..3623fda 100644 --- a/Backend/Database/UserConversations.go +++ b/Backend/Database/UserConversations.go @@ -30,20 +30,32 @@ func GetUserConversationsByUserId(id string) ([]Models.UserConversation, error) return conversations, err } -func CreateUserConversation(messageThreadUser *Models.UserConversation) error { +func CreateUserConversation(userConversation *Models.UserConversation) error { var ( err error ) err = DB.Session(&gorm.Session{FullSaveAssociations: true}). - Create(messageThreadUser). + Create(userConversation). Error return err } -func DeleteUserConversation(messageThreadUser *Models.UserConversation) error { +func CreateUserConversations(userConversations *[]Models.UserConversation) error { + var ( + err error + ) + + err = DB.Session(&gorm.Session{FullSaveAssociations: true}). + Create(userConversations). + Error + + return err +} + +func DeleteUserConversation(userConversation *Models.UserConversation) error { return DB.Session(&gorm.Session{FullSaveAssociations: true}). - Delete(messageThreadUser). + Delete(userConversation). Error } diff --git a/Backend/Models/Friends.go b/Backend/Models/Friends.go index 5022d9b..183e2dc 100644 --- a/Backend/Models/Friends.go +++ b/Backend/Models/Friends.go @@ -6,19 +6,15 @@ import ( "github.com/gofrs/uuid" ) -// TODO: Add profile picture -type Friend struct { - Base - Username string `gorm:"not null" json:"username"` // Stored encrypted - AsymmetricPublicKey string `gorm:"not null" json:"asymmetric_public_key"` // Stored encrypted -} - // Set with Friend being the requestee, and RequestFromID being the requester type FriendRequest struct { Base - UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` - User User `json:"user"` - FriendID string `gorm:"not null" json:"friend_id"` // Stored encrypted - SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted - AcceptedAt time.Time `json:"accepted_at"` + UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` + User User ` json:"user"` + UserUsername string ` json:"user_username"` + FriendID string `gorm:"not null" json:"friend_id"` // Stored encrypted + FriendUsername string ` json:"friend_username"` + FriendPublicAsymmetricKey string ` json:"asymmetric_public_key"` + SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted + AcceptedAt time.Time ` json:"accepted_at"` } diff --git a/Backend/Models/Users.go b/Backend/Models/Users.go index 0525b52..4727e26 100644 --- a/Backend/Models/Users.go +++ b/Backend/Models/Users.go @@ -16,10 +16,8 @@ func (u *User) BeforeUpdate(tx *gorm.DB) (err error) { type User struct { Base Username string `gorm:"not null;unique" json:"username"` - Password string `gorm:"not null" json:"password"` - ConfirmPassword string `gorm:"-" json:"confirm_password"` - AsymmetricPrivateKey string `gorm:"not null" json:"asymmetric_private_key"` // Stored encrypted - AsymmetricPublicKey string `gorm:"not null" json:"asymmetric_public_key"` - FriendID string `gorm:"not null" json:"public_user_id"` // Stored encrypted - FriendSymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted + Password string `gorm:"not null" json:"password"` + ConfirmPassword string `gorm:"-" json:"confirm_password"` + AsymmetricPrivateKey string `gorm:"not null" json:"asymmetric_private_key"` // Stored encrypted + AsymmetricPublicKey string `gorm:"not null" json:"asymmetric_public_key"` } diff --git a/mobile/lib/models/conversation_users.dart b/mobile/lib/models/conversation_users.dart index e0e000d..34e6e40 100644 --- a/mobile/lib/models/conversation_users.dart +++ b/mobile/lib/models/conversation_users.dart @@ -3,12 +3,14 @@ import '/models/conversations.dart'; class ConversationUser{ String id; + String userId; String conversationId; String username; String associationKey; bool admin; ConversationUser({ required this.id, + required this.userId, required this.conversationId, required this.username, required this.associationKey, @@ -18,6 +20,7 @@ class ConversationUser{ factory ConversationUser.fromJson(Map json, String conversationId) { return ConversationUser( id: json['id'], + userId: json['user_id'], conversationId: conversationId, username: json['username'], associationKey: json['association_key'], @@ -25,9 +28,20 @@ class ConversationUser{ ); } + Map toJson() { + return { + 'id': id, + 'user_id': userId, + 'username': username, + 'associationKey': associationKey, + 'admin': admin ? 'true' : 'false', + }; + } + Map toMap() { return { 'id': id, + 'user_id': userId, 'conversation_id': conversationId, 'username': username, 'association_key': associationKey, @@ -50,6 +64,7 @@ Future> getConversationUsers(Conversation conversation) a return List.generate(maps.length, (i) { return ConversationUser( id: maps[i]['id'], + userId: maps[i]['user_id'], conversationId: maps[i]['conversation_id'], username: maps[i]['username'], associationKey: maps[i]['association_key'], @@ -58,36 +73,14 @@ Future> getConversationUsers(Conversation conversation) a }); } -Future getConversationUserById(Conversation conversation, String id) async { +Future getConversationUser(Conversation conversation, String userId) async { final db = await getDatabaseConnection(); - final List> maps = await db.query( - 'conversation_users', - where: 'conversation_id = ? AND id = ?', - whereArgs: [conversation.id, id], - ); - - if (maps.length != 1) { - throw ArgumentError('Invalid conversation_id or id'); - } - - return ConversationUser( - id: maps[0]['id'], - conversationId: maps[0]['conversation_id'], - username: maps[0]['username'], - associationKey: maps[0]['association_key'], - admin: maps[0]['admin'] == 1, - ); - -} - -Future getConversationUserByUsername(Conversation conversation, String username) async { - final db = await getDatabaseConnection(); final List> maps = await db.query( 'conversation_users', - where: 'conversation_id = ? AND username = ?', - whereArgs: [conversation.id, username], + where: 'conversation_id = ? AND user_id = ?', + whereArgs: [conversation.id, userId], ); if (maps.length != 1) { @@ -96,6 +89,7 @@ Future getConversationUserByUsername(Conversation conversation return ConversationUser( id: maps[0]['id'], + userId: maps[0]['user_id'], conversationId: maps[0]['conversation_id'], username: maps[0]['username'], associationKey: maps[0]['association_key'], diff --git a/mobile/lib/models/conversations.dart b/mobile/lib/models/conversations.dart index 45c4e1e..ed06387 100644 --- a/mobile/lib/models/conversations.dart +++ b/mobile/lib/models/conversations.dart @@ -11,14 +11,10 @@ import '/utils/encryption/aes_helper.dart'; import '/utils/storage/database.dart'; import '/utils/strings.dart'; -Conversation findConversationByDetailId(List conversations, String id) { - for (var conversation in conversations) { - if (conversation.conversationDetailId == id) { - return conversation; - } - } - // Or return `null`. - throw ArgumentError.value(id, "id", "No element with that id"); +enum ConversationStatus { + complete, + pending, + error, } class Conversation { @@ -28,6 +24,7 @@ class Conversation { String symmetricKey; bool admin; String name; + ConversationStatus status; Conversation({ required this.id, @@ -36,35 +33,86 @@ class Conversation { required this.symmetricKey, required this.admin, required this.name, + required this.status, }); factory Conversation.fromJson(Map json, RSAPrivateKey privKey) { var symmetricKeyDecrypted = CryptoUtils.rsaDecrypt( - base64.decode(json['symmetric_key']), - privKey, + base64.decode(json['symmetric_key']), + privKey, ); var detailId = AesHelper.aesDecrypt( - symmetricKeyDecrypted, - base64.decode(json['conversation_detail_id']), + symmetricKeyDecrypted, + base64.decode(json['conversation_detail_id']), ); var admin = AesHelper.aesDecrypt( - symmetricKeyDecrypted, - base64.decode(json['admin']), + symmetricKeyDecrypted, + base64.decode(json['admin']), ); return Conversation( - id: json['id'], - userId: json['user_id'], - conversationDetailId: detailId, - symmetricKey: base64.encode(symmetricKeyDecrypted), - admin: admin == 'true', - name: 'Unknown', + id: json['id'], + userId: json['user_id'], + conversationDetailId: detailId, + symmetricKey: base64.encode(symmetricKeyDecrypted), + admin: admin == 'true', + name: 'Unknown', + status: ConversationStatus.complete, ); } + Future> toJson() async { + MyProfile profile = await MyProfile.getProfile(); + + var symKey = base64.decode(symmetricKey); + + List users = await getConversationUsers(this); + List userConversations = []; + + for (var x in users) { + print(x.toMap()); + } + + for (ConversationUser user in users) { + if (profile.id == user.userId) { + userConversations.add({ + 'id': user.id, + 'user_id': profile.id, + 'conversation_detail_id': AesHelper.aesEncrypt(symKey, Uint8List.fromList(id.codeUnits)), + 'admin': AesHelper.aesEncrypt(symKey, Uint8List.fromList((admin ? 'true' : 'false').codeUnits)), + 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(symKey, profile.publicKey!)), + }); + + continue; + } + + Friend friend = await getFriendByFriendId(user.userId); + RSAPublicKey pubKey = CryptoUtils.rsaPublicKeyFromPem(friend.asymmetricPublicKey); + + userConversations.add({ + 'id': user.id, + 'user_id': friend.userId, + 'conversation_detail_id': AesHelper.aesEncrypt(symKey, Uint8List.fromList(id.codeUnits)), + 'admin': AesHelper.aesEncrypt(symKey, Uint8List.fromList((admin ? 'true' : 'false').codeUnits)), + 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(symKey, pubKey)), + }); + } + + for (var x in userConversations) { + print(x); + } + + return { + 'id': id, + 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), + 'users': AesHelper.aesEncrypt(symKey, Uint8List.fromList(jsonEncode(users).codeUnits)), + 'user_conversations': userConversations, + }; + } + @override String toString() { return ''' @@ -84,6 +132,7 @@ class Conversation { 'symmetric_key': symmetricKey, 'admin': admin ? 1 : 0, 'name': name, + 'status': status.index, }; } } @@ -107,43 +156,57 @@ Future createConversation(String title, List friends) asyn symmetricKey: base64.encode(symmetricKey), admin: true, name: title, + status: ConversationStatus.pending, ); await db.insert( - 'conversations', - conversation.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, + 'conversations', + conversation.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, ); await db.insert( - 'conversation_users', - ConversationUser( - id: uuid.v4(), - conversationId: conversationId, - username: profile.username, - associationKey: associationKey, - admin: false, - ).toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, + 'conversation_users', + ConversationUser( + id: uuid.v4(), + userId: profile.id, + conversationId: conversationId, + username: profile.username, + associationKey: associationKey, + admin: true, + ).toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, ); for (Friend friend in friends) { await db.insert( - 'conversation_users', - ConversationUser( - id: uuid.v4(), - conversationId: conversationId, - username: friend.username, - associationKey: associationKey, - admin: false, - ).toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, + 'conversation_users', + ConversationUser( + id: uuid.v4(), + userId: friend.friendId, + conversationId: conversationId, + username: friend.username, + associationKey: associationKey, + admin: false, + ).toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, ); } return conversation; } +Conversation findConversationByDetailId(List conversations, String id) { + for (var conversation in conversations) { + if (conversation.conversationDetailId == id) { + return conversation; + } + } + // Or return `null`. + throw ArgumentError.value(id, "id", "No element with that id"); +} + + // A method that retrieves all the dogs from the dogs table. Future> getConversations() async { final db = await getDatabaseConnection(); @@ -152,12 +215,38 @@ Future> getConversations() async { return List.generate(maps.length, (i) { return Conversation( - id: maps[i]['id'], - userId: maps[i]['user_id'], - conversationDetailId: maps[i]['conversation_detail_id'], - symmetricKey: maps[i]['symmetric_key'], - admin: maps[i]['admin'] == 1, - name: maps[i]['name'], + id: maps[i]['id'], + userId: maps[i]['user_id'], + conversationDetailId: maps[i]['conversation_detail_id'], + symmetricKey: maps[i]['symmetric_key'], + admin: maps[i]['admin'] == 1, + name: maps[i]['name'], + status: ConversationStatus.values[maps[i]['status']], ); }); } + + +Future getConversationById(String id) async { + final db = await getDatabaseConnection(); + + final List> maps = await db.query( + 'conversations', + where: 'id = ?', + whereArgs: [id], + ); + + if (maps.length != 1) { + throw ArgumentError('Invalid user id'); + } + + return Conversation( + id: maps[0]['id'], + userId: maps[0]['user_id'], + conversationDetailId: maps[0]['conversation_detail_id'], + symmetricKey: maps[0]['symmetric_key'], + admin: maps[0]['admin'] == 1, + name: maps[0]['name'], + status: ConversationStatus.values[maps[0]['status']], + ); +} diff --git a/mobile/lib/models/friends.dart b/mobile/lib/models/friends.dart index d387e11..86d1537 100644 --- a/mobile/lib/models/friends.dart +++ b/mobile/lib/models/friends.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'dart:typed_data'; import "package:pointycastle/export.dart"; +import '../utils/encryption/aes_helper.dart'; import '/utils/encryption/crypto_utils.dart'; import '/utils/storage/database.dart'; @@ -34,23 +36,33 @@ class Friend{ }); factory Friend.fromJson(Map json, RSAPrivateKey privKey) { - var friendIdDecrypted = CryptoUtils.rsaDecrypt( + Uint8List friendIdDecrypted = CryptoUtils.rsaDecrypt( base64.decode(json['friend_id']), privKey, ); - var friendSymmetricKeyDecrypted = CryptoUtils.rsaDecrypt( + Uint8List friendUsername = CryptoUtils.rsaDecrypt( + base64.decode(json['friend_username']), + privKey, + ); + + Uint8List friendSymmetricKeyDecrypted = CryptoUtils.rsaDecrypt( base64.decode(json['symmetric_key']), privKey, ); + String asymmetricPublicKey = AesHelper.aesDecrypt( + friendSymmetricKeyDecrypted, + base64.decode(json['asymmetric_public_key']) + ); + return Friend( id: json['id'], userId: json['user_id'], - username: '', + username: String.fromCharCodes(friendUsername), friendId: String.fromCharCodes(friendIdDecrypted), friendSymmetricKey: base64.encode(friendSymmetricKeyDecrypted), - asymmetricPublicKey: '', + asymmetricPublicKey: asymmetricPublicKey, acceptedAt: json['accepted_at'], ); } @@ -101,27 +113,25 @@ Future> getFriends() async { } Future getFriendByFriendId(String userId) async { - final db = await getDatabaseConnection(); - - List whereArguments = [userId]; + final db = await getDatabaseConnection(); - final List> maps = await db.query( - 'friends', - where: 'friend_id = ?', - whereArgs: whereArguments, - ); + final List> maps = await db.query( + 'friends', + where: 'friend_id = ?', + whereArgs: [userId], + ); - if (maps.length != 1) { - throw ArgumentError('Invalid user id'); - } + if (maps.length != 1) { + throw ArgumentError('Invalid user id'); + } - return Friend( - id: maps[0]['id'], - userId: maps[0]['user_id'], - friendId: maps[0]['friend_id'], - friendSymmetricKey: maps[0]['symmetric_key'], - asymmetricPublicKey: maps[0]['asymmetric_public_key'], - acceptedAt: maps[0]['accepted_at'], - username: maps[0]['username'], - ); + return Friend( + id: maps[0]['id'], + userId: maps[0]['user_id'], + friendId: maps[0]['friend_id'], + friendSymmetricKey: maps[0]['symmetric_key'], + asymmetricPublicKey: maps[0]['asymmetric_public_key'], + acceptedAt: maps[0]['accepted_at'], + username: maps[0]['username'], + ); } diff --git a/mobile/lib/models/my_profile.dart b/mobile/lib/models/my_profile.dart index ccd0eae..0c0207f 100644 --- a/mobile/lib/models/my_profile.dart +++ b/mobile/lib/models/my_profile.dart @@ -7,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart'; class MyProfile { String id; String username; + String? friendId; RSAPrivateKey? privateKey; RSAPublicKey? publicKey; DateTime? loggedInAt; @@ -14,6 +15,7 @@ class MyProfile { MyProfile({ required this.id, required this.username, + this.friendId, this.privateKey, this.publicKey, this.loggedInAt, @@ -25,11 +27,14 @@ class MyProfile { loggedInAt = DateTime.parse(json['logged_in_at']); } + RSAPrivateKey privateKey = CryptoUtils.rsaPrivateKeyFromPem(json['asymmetric_private_key']); + RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(json['asymmetric_public_key']); + return MyProfile( id: json['user_id'], username: json['username'], - privateKey: CryptoUtils.rsaPrivateKeyFromPem(json['asymmetric_private_key']), - publicKey: CryptoUtils.rsaPublicKeyFromPem(json['asymmetric_public_key']), + privateKey: privateKey, + publicKey: publicKey, loggedInAt: loggedInAt, ); } @@ -95,7 +100,7 @@ class MyProfile { ); } - Future getPrivateKey() async { + static Future getPrivateKey() async { MyProfile profile = await MyProfile.getProfile(); if (profile.privateKey == null) { throw Exception('Could not get privateKey'); diff --git a/mobile/lib/utils/storage/conversations.dart b/mobile/lib/utils/storage/conversations.dart index fa4564e..7fd199a 100644 --- a/mobile/lib/utils/storage/conversations.dart +++ b/mobile/lib/utils/storage/conversations.dart @@ -3,97 +3,117 @@ import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:pointycastle/export.dart'; import 'package:sqflite/sqflite.dart'; +import '/models/my_profile.dart'; import '/models/conversations.dart'; import '/models/conversation_users.dart'; import '/utils/storage/database.dart'; import '/utils/storage/session_cookie.dart'; -import '/utils/storage/encryption_keys.dart'; import '/utils/encryption/aes_helper.dart'; +// TODO: Refactor this function Future updateConversations() async { - RSAPrivateKey privKey = await getPrivateKey(); - - try { - var resp = await http.get( - Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversations'), - headers: { - 'cookie': await getSessionCookie(), - } - ); + RSAPrivateKey privKey = await MyProfile.getPrivateKey(); - if (resp.statusCode != 200) { - throw Exception(resp.body); + try { + var resp = await http.get( + Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversations'), + headers: { + 'cookie': await getSessionCookie(), } + ); - List conversations = []; - List conversationsDetailIds = []; - - List conversationsJson = jsonDecode(resp.body); + if (resp.statusCode != 200) { + throw Exception(resp.body); + } - for (var i = 0; i < conversationsJson.length; i++) { - Conversation conversation = Conversation.fromJson( - conversationsJson[i] as Map, - privKey, - ); - conversations.add(conversation); - conversationsDetailIds.add(conversation.conversationDetailId); - } + List conversations = []; + List conversationsDetailIds = []; - Map params = {}; - params['conversation_detail_ids'] = conversationsDetailIds.join(','); - var uri = Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversation_details'); - uri = uri.replace(queryParameters: params); + List conversationsJson = jsonDecode(resp.body); - resp = await http.get( - uri, - headers: { - 'cookie': await getSessionCookie(), - } + for (var i = 0; i < conversationsJson.length; i++) { + Conversation conversation = Conversation.fromJson( + conversationsJson[i] as Map, + privKey, ); + conversations.add(conversation); + conversationsDetailIds.add(conversation.conversationDetailId); + } - if (resp.statusCode != 200) { - throw Exception(resp.body); + Map params = {}; + params['conversation_detail_ids'] = conversationsDetailIds.join(','); + var uri = Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversation_details'); + uri = uri.replace(queryParameters: params); + + resp = await http.get( + uri, + headers: { + 'cookie': await getSessionCookie(), } + ); - final db = await getDatabaseConnection(); + if (resp.statusCode != 200) { + throw Exception(resp.body); + } - List conversationsDetailsJson = jsonDecode(resp.body); - for (var i = 0; i < conversationsDetailsJson.length; i++) { - var conversationDetailJson = conversationsDetailsJson[i] as Map; - var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']); + final db = await getDatabaseConnection(); - conversation.name = AesHelper.aesDecrypt( - base64.decode(conversation.symmetricKey), - base64.decode(conversationDetailJson['name']), - ); + List conversationsDetailsJson = jsonDecode(resp.body); + for (var i = 0; i < conversationsDetailsJson.length; i++) { + var conversationDetailJson = conversationsDetailsJson[i] as Map; + var conversation = findConversationByDetailId(conversations, conversationDetailJson['id']); - await db.insert( - 'conversations', - conversation.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); + conversation.name = AesHelper.aesDecrypt( + base64.decode(conversation.symmetricKey), + base64.decode(conversationDetailJson['name']), + ); - List usersData = json.decode( - AesHelper.aesDecrypt( - base64.decode(conversation.symmetricKey), - base64.decode(conversationDetailJson['users']), - ) + await db.insert( + 'conversations', + conversation.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + List usersData = json.decode( + AesHelper.aesDecrypt( + base64.decode(conversation.symmetricKey), + base64.decode(conversationDetailJson['users']), + ) + ); + + for (var i = 0; i < usersData.length; i++) { + ConversationUser conversationUser = ConversationUser.fromJson( + usersData[i] as Map, + conversation.id, ); - for (var i = 0; i < usersData.length; i++) { - ConversationUser conversationUser = ConversationUser.fromJson( - usersData[i] as Map, - conversation.id, - ); - - await db.insert( - 'conversation_users', - conversationUser.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } + await db.insert( + 'conversation_users', + conversationUser.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); } - } catch (SocketException) { - return; } + } catch (SocketException) { + return; + } +} + +Future uploadConversation(Conversation conversation) async { + String sessionCookie = await getSessionCookie(); + + Map conversationJson = await conversation.toJson(); + + print(conversationJson); + + // var x = await http.post( + // Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/conversations'), + // headers: { + // 'Content-Type': 'application/json; charset=UTF-8', + // 'cookie': sessionCookie, + // }, + // body: jsonEncode(conversationJson), + // ); + + // print(x.statusCode); } diff --git a/mobile/lib/utils/storage/database.dart b/mobile/lib/utils/storage/database.dart index a760960..d2aa0f3 100644 --- a/mobile/lib/utils/storage/database.dart +++ b/mobile/lib/utils/storage/database.dart @@ -39,6 +39,7 @@ Future getDatabaseConnection() async { symmetric_key TEXT, admin INTEGER, name TEXT, + status INTEGER ); '''); @@ -46,6 +47,7 @@ Future getDatabaseConnection() async { ''' CREATE TABLE IF NOT EXISTS conversation_users( id TEXT PRIMARY KEY, + user_id TEXT, conversation_id TEXT, username TEXT, data TEXT, @@ -72,7 +74,7 @@ Future getDatabaseConnection() async { }, // Set the version. This executes the onCreate function and provides a // path to perform database upgrades and downgrades. - version: 1, + version: 2, ); return database; diff --git a/mobile/lib/utils/storage/encryption_keys.dart b/mobile/lib/utils/storage/encryption_keys.dart deleted file mode 100644 index edc2f5c..0000000 --- a/mobile/lib/utils/storage/encryption_keys.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; -import "package:pointycastle/export.dart"; -import '/utils/encryption/crypto_utils.dart'; - -const rsaPrivateKeyName = 'rsaPrivateKey'; - -void setPrivateKey(RSAPrivateKey key) async { - String keyPem = CryptoUtils.encodeRSAPrivateKeyToPem(key); - - final prefs = await SharedPreferences.getInstance(); - prefs.setString(rsaPrivateKeyName, keyPem); -} - -void unsetPrivateKey() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.remove(rsaPrivateKeyName); -} - -Future getPrivateKey() async { - final prefs = await SharedPreferences.getInstance(); - String? keyPem = prefs.getString(rsaPrivateKeyName); - if (keyPem == null) { - throw Exception('No RSA private key set'); - } - - return CryptoUtils.rsaPrivateKeyFromPem(keyPem); -} diff --git a/mobile/lib/utils/storage/friends.dart b/mobile/lib/utils/storage/friends.dart index f3347cb..4da930c 100644 --- a/mobile/lib/utils/storage/friends.dart +++ b/mobile/lib/utils/storage/friends.dart @@ -1,16 +1,17 @@ import 'dart:convert'; + +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; import 'package:pointycastle/export.dart'; import 'package:sqflite/sqflite.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; + import '/models/friends.dart'; +import '/models/my_profile.dart'; import '/utils/storage/database.dart'; -import '/utils/storage/encryption_keys.dart'; import '/utils/storage/session_cookie.dart'; -import '/utils/encryption/aes_helper.dart'; Future updateFriends() async { - RSAPrivateKey privKey = await getPrivateKey(); + RSAPrivateKey privKey = await MyProfile.getPrivateKey(); try { var resp = await http.get( @@ -24,59 +25,24 @@ Future updateFriends() async { throw Exception(resp.body); } - List friends = []; - List friendIds = []; + final db = await getDatabaseConnection(); List friendsRequestJson = jsonDecode(resp.body); for (var i = 0; i < friendsRequestJson.length; i++) { - friends.add( - Friend.fromJson( - friendsRequestJson[i] as Map, - privKey, - ) - ); - - friendIds.add(friends[i].friendId); - } - - Map params = {}; - params['friend_ids'] = friendIds.join(','); - var uri = Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/friends'); - uri = uri.replace(queryParameters: params); - - resp = await http.get( - uri, - headers: { - 'cookie': await getSessionCookie(), - } - ); - - if (resp.statusCode != 200) { - throw Exception(resp.body); - } - - final db = await getDatabaseConnection(); - - List friendsJson = jsonDecode(resp.body); - for (var i = 0; i < friendsJson.length; i++) { - var friendJson = friendsJson[i] as Map; - var friend = findFriendByFriendId(friends, friendJson['id']); - - friend.username = AesHelper.aesDecrypt( - base64.decode(friend.friendSymmetricKey), - base64.decode(friendJson['username']), + Friend friend = Friend.fromJson( + friendsRequestJson[i] as Map, + privKey, ); - friend.asymmetricPublicKey = friendJson['asymmetric_public_key']; - await db.insert( - 'friends', - friend.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, + 'friends', + friend.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, ); } + } catch (SocketException) { return; } diff --git a/mobile/lib/utils/storage/messages.dart b/mobile/lib/utils/storage/messages.dart index ea9457f..bc524b3 100644 --- a/mobile/lib/utils/storage/messages.dart +++ b/mobile/lib/utils/storage/messages.dart @@ -1,23 +1,19 @@ import 'dart:convert'; +import 'package:Envelope/models/my_profile.dart'; import 'package:uuid/uuid.dart'; import 'package:Envelope/models/conversation_users.dart'; -import 'package:intl/intl.dart'; import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:pointycastle/export.dart'; import 'package:sqflite/sqflite.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '/utils/storage/session_cookie.dart'; -import '/utils/storage/encryption_keys.dart'; import '/utils/storage/database.dart'; import '/models/conversations.dart'; import '/models/messages.dart'; -Future updateMessageThread(Conversation conversation, {RSAPrivateKey? privKey}) async { - privKey ??= await getPrivateKey(); - final preferences = await SharedPreferences.getInstance(); - String username = preferences.getString('username')!; - ConversationUser currentUser = await getConversationUserByUsername(conversation, username); +Future updateMessageThread(Conversation conversation, {MyProfile? profile}) async { + profile ??= await MyProfile.getProfile(); + ConversationUser currentUser = await getConversationUser(conversation, profile.id); var resp = await http.get( Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/messages/${currentUser.associationKey}'), @@ -37,10 +33,10 @@ Future updateMessageThread(Conversation conversation, {RSAPrivateKey? priv for (var i = 0; i < messageThreadJson.length; i++) { Message message = Message.fromJson( messageThreadJson[i] as Map, - privKey, + profile.privateKey!, ); - ConversationUser messageUser = await getConversationUserById(conversation, message.senderId); + ConversationUser messageUser = await getConversationUser(conversation, message.senderId); message.senderUsername = messageUser.username; await db.insert( @@ -52,17 +48,17 @@ Future updateMessageThread(Conversation conversation, {RSAPrivateKey? priv } Future updateMessageThreads({List? conversations}) async { - try { - RSAPrivateKey privKey = await getPrivateKey(); + // try { + MyProfile profile = await MyProfile.getProfile(); conversations ??= await getConversations(); for (var i = 0; i < conversations.length; i++) { - await updateMessageThread(conversations[i], privKey: privKey); + await updateMessageThread(conversations[i], profile: profile); } - } catch(SocketException) { - return; - } + // } catch(SocketException) { + // return; + // } } Future sendMessage(Conversation conversation, String data) async { @@ -76,7 +72,7 @@ Future sendMessage(Conversation conversation, String data) async { var uuid = const Uuid(); final String messageDataId = uuid.v4(); - ConversationUser currentUser = await getConversationUserByUsername(conversation, username); + ConversationUser currentUser = await getConversationUser(conversation, username); Message message = Message( id: messageDataId, diff --git a/mobile/lib/views/main/conversation_create_add_users.dart b/mobile/lib/views/main/conversation_create_add_users.dart index 29ed1ab..7880dc8 100644 --- a/mobile/lib/views/main/conversation_create_add_users.dart +++ b/mobile/lib/views/main/conversation_create_add_users.dart @@ -1,9 +1,9 @@ import 'package:Envelope/models/conversations.dart'; +import 'package:Envelope/utils/storage/conversations.dart'; import 'package:Envelope/views/main/conversation_create_add_users_list.dart'; import 'package:Envelope/views/main/conversation_detail.dart'; import 'package:flutter/material.dart'; import '/models/friends.dart'; -import '/views/main/friend_list_item.dart'; class ConversationAddFriendsList extends StatefulWidget { final List friends; @@ -150,8 +150,12 @@ class _ConversationAddFriendsListState extends State child: FloatingActionButton( onPressed: () async { Conversation conversation = await createConversation(widget.title, friendsSelected); + uploadConversation(conversation); + + setState(() { + friendsSelected = []; + }); - friendsSelected = []; Navigator.of(context).popUntil((route) => route.isFirst); Navigator.push(context, MaterialPageRoute(builder: (context){ return ConversationDetail( diff --git a/mobile/lib/views/main/conversation_detail.dart b/mobile/lib/views/main/conversation_detail.dart index 440c000..02bb964 100644 --- a/mobile/lib/views/main/conversation_detail.dart +++ b/mobile/lib/views/main/conversation_detail.dart @@ -1,6 +1,6 @@ +import 'package:Envelope/models/my_profile.dart'; import 'package:Envelope/views/main/conversation_settings.dart'; import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import '/models/conversations.dart'; import '/models/messages.dart'; import '/utils/storage/messages.dart'; @@ -38,7 +38,7 @@ class ConversationDetail extends StatefulWidget{ class _ConversationDetailState extends State { List messages = []; - String username = ''; + MyProfile profile = MyProfile(id: '', username: ''); TextEditingController msgController = TextEditingController(); @@ -49,14 +49,13 @@ class _ConversationDetailState extends State { } Future fetchMessages() async { - final preferences = await SharedPreferences.getInstance(); - username = preferences.getString('username')!; + profile = await MyProfile.getProfile(); messages = await getMessagesForThread(widget.conversation); setState(() {}); } Widget usernameOrFailedToSend(int index) { - if (messages[index].senderUsername != username) { + if (messages[index].senderUsername != profile.username) { return Text( messages[index].senderUsername, style: TextStyle( @@ -152,12 +151,12 @@ class _ConversationDetailState extends State { padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 0), child: Align( alignment: ( - messages[index].senderUsername == username ? + messages[index].senderUsername == profile.username ? Alignment.topRight : Alignment.topLeft ), child: Column( - crossAxisAlignment: messages[index].senderUsername == username ? + crossAxisAlignment: messages[index].senderUsername == profile.username ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ @@ -165,7 +164,7 @@ class _ConversationDetailState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: ( - messages[index].senderUsername == username ? + messages[index].senderUsername == profile.username ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.tertiary ), @@ -175,7 +174,7 @@ class _ConversationDetailState extends State { messages[index].data, style: TextStyle( fontSize: 15, - color: messages[index].senderUsername == username ? + color: messages[index].senderUsername == profile.username ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onTertiary, ) @@ -183,7 +182,7 @@ class _ConversationDetailState extends State { ), const SizedBox(height: 1.5), Row( - mainAxisAlignment: messages[index].senderUsername == username ? + mainAxisAlignment: messages[index].senderUsername == profile.username ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ @@ -193,14 +192,14 @@ class _ConversationDetailState extends State { ), const SizedBox(height: 1.5), Row( - mainAxisAlignment: messages[index].senderUsername == username ? + mainAxisAlignment: messages[index].senderUsername == profile.username ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ const SizedBox(width: 10), Text( convertToAgo(messages[index].createdAt), - textAlign: messages[index].senderUsername == username ? + textAlign: messages[index].senderUsername == profile.username ? TextAlign.left : TextAlign.right, style: TextStyle( diff --git a/mobile/lib/views/main/conversation_list.dart b/mobile/lib/views/main/conversation_list.dart index b827ea6..7de543c 100644 --- a/mobile/lib/views/main/conversation_list.dart +++ b/mobile/lib/views/main/conversation_list.dart @@ -35,11 +35,11 @@ class _ConversationListState extends State { if(query.isNotEmpty) { List dummyListData = []; - dummySearchList.forEach((item) { + for (Conversation item in dummySearchList) { if (item.name.toLowerCase().contains(query)) { dummyListData.add(item); } - }); + } setState(() { conversations.clear(); conversations.addAll(dummyListData); @@ -135,7 +135,9 @@ class _ConversationListState extends State { ); } - onGoBack(dynamic value) { + onGoBack(dynamic value) async { + conversations = await getConversations(); + friends = await getFriends(); setState(() {}); } } diff --git a/mobile/lib/views/main/conversation_list_item.dart b/mobile/lib/views/main/conversation_list_item.dart index 885400f..b26c0fc 100644 --- a/mobile/lib/views/main/conversation_list_item.dart +++ b/mobile/lib/views/main/conversation_list_item.dart @@ -15,27 +15,37 @@ class ConversationListItem extends StatefulWidget{ } class _ConversationListItemState extends State { + late Conversation conversation; + bool loaded = false; + + @override + void initState() { + super.initState(); + conversation = widget.conversation; + loaded = true; + setState(() {}); + } @override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context){ + loaded ? Navigator.push(context, MaterialPageRoute(builder: (context){ return ConversationDetail( - conversation: widget.conversation, + conversation: conversation, ); - })); + })).then(onGoBack) : null; }, child: Container( padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), - child: Row( + child: !loaded ? null : Row( children: [ Expanded( child: Row( children: [ CustomCircleAvatar( - initials: widget.conversation.name[0].toUpperCase(), + initials: conversation.name[0].toUpperCase(), imagePath: null, ), const SizedBox(width: 16), @@ -48,7 +58,7 @@ class _ConversationListItemState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.conversation.name, + conversation.name, style: const TextStyle(fontSize: 16) ), //Text(widget.messageText,style: TextStyle(fontSize: 13,color: Colors.grey.shade600, fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),), @@ -65,4 +75,9 @@ class _ConversationListItemState extends State { ), ); } + + onGoBack(dynamic value) async { + conversation = await getConversationById(widget.conversation.id); + setState(() {}); + } } diff --git a/mobile/lib/views/main/home.dart b/mobile/lib/views/main/home.dart index 31a1a36..cb077d4 100644 --- a/mobile/lib/views/main/home.dart +++ b/mobile/lib/views/main/home.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '/views/main/conversation_list.dart'; @@ -51,6 +50,7 @@ class _HomeState extends State { if (!await checkLogin()) { return; } + await updateFriends(); await updateConversations(); await updateMessageThreads(); diff --git a/mobile/lib/views/main/profile.dart b/mobile/lib/views/main/profile.dart index f064ef3..4009961 100644 --- a/mobile/lib/views/main/profile.dart +++ b/mobile/lib/views/main/profile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '/utils/storage/database.dart'; import '/models/my_profile.dart'; @@ -90,6 +91,7 @@ class _ProfileState extends State { } Widget logout() { + bool isTesting = dotenv.env["ENVIRONMENT"] == 'development'; return Align( alignment: Alignment.centerLeft, child: Column( @@ -110,6 +112,19 @@ class _ProfileState extends State { Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); } ), + isTesting ? TextButton.icon( + label: const Text( + 'Delete Database', + style: TextStyle(fontSize: 16) + ), + icon: const Icon(Icons.delete_forever), + style: const ButtonStyle( + alignment: Alignment.centerLeft, + ), + onPressed: () { + deleteDb(); + } + ) : const SizedBox.shrink(), ], ), );