| @ -0,0 +1,34 @@ | |||
| package Auth | |||
| import ( | |||
| "net/http" | |||
| "time" | |||
| ) | |||
| func Logout(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| c *http.Cookie | |||
| sessionToken string | |||
| err error | |||
| ) | |||
| c, err = r.Cookie("session_token") | |||
| if err != nil { | |||
| if err == http.ErrNoCookie { | |||
| w.WriteHeader(http.StatusUnauthorized) | |||
| return | |||
| } | |||
| w.WriteHeader(http.StatusBadRequest) | |||
| return | |||
| } | |||
| sessionToken = c.Value | |||
| delete(Sessions, sessionToken) | |||
| http.SetCookie(w, &http.Cookie{ | |||
| Name: "session_token", | |||
| Value: "", | |||
| Expires: time.Now(), | |||
| }) | |||
| } | |||
| @ -0,0 +1,41 @@ | |||
| package Friends | |||
| import ( | |||
| "encoding/json" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| ) | |||
| func EncryptedFriendList(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| userSession Auth.Session | |||
| friends []Models.Friend | |||
| returnJson []byte | |||
| err error | |||
| ) | |||
| userSession, err = Auth.CheckCookie(r) | |||
| if err != nil { | |||
| http.Error(w, "Forbidden", http.StatusUnauthorized) | |||
| return | |||
| } | |||
| friends, err = Database.GetFriendsByUserId(userSession.UserID) | |||
| 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) | |||
| } | |||
| @ -0,0 +1,59 @@ | |||
| package Friends | |||
| import ( | |||
| "encoding/json" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" | |||
| ) | |||
| func FriendRequest(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| user Models.User | |||
| requestBody []byte | |||
| requestJson map[string]interface{} | |||
| friendID []byte | |||
| friendRequest Models.Friend | |||
| err error | |||
| ) | |||
| user, err = Util.GetUserById(w, r) | |||
| if err != nil { | |||
| http.Error(w, "Not Found", http.StatusNotFound) | |||
| return | |||
| } | |||
| requestBody, err = ioutil.ReadAll(r.Body) | |||
| if err != nil { | |||
| http.Error(w, "Not Found", http.StatusNotFound) | |||
| return | |||
| } | |||
| json.Unmarshal(requestBody, &requestJson) | |||
| if requestJson["id"] == nil { | |||
| http.Error(w, "Invalid Data", http.StatusBadRequest) | |||
| return | |||
| } | |||
| friendID, err = Util.ToBytes(requestJson["id"]) | |||
| if requestJson["id"] == nil { | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return | |||
| } | |||
| friendRequest = Models.Friend{ | |||
| UserID: user.ID, | |||
| FriendID: friendID, | |||
| } | |||
| err = Database.CreateFriendRequest(&friendRequest) | |||
| if requestJson["id"] == nil { | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return | |||
| } | |||
| w.WriteHeader(http.StatusNoContent) | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| package Friends | |||
| import ( | |||
| "encoding/json" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" | |||
| ) | |||
| func Friend(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| userData Models.User | |||
| returnJson []byte | |||
| err error | |||
| ) | |||
| userData, err = Util.GetUserById(w, r) | |||
| if err != nil { | |||
| http.Error(w, "Not Found", http.StatusNotFound) | |||
| return | |||
| } | |||
| returnJson, err = json.MarshalIndent(userData, "", " ") | |||
| if err != nil { | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return | |||
| } | |||
| w.WriteHeader(http.StatusOK) | |||
| w.Write(returnJson) | |||
| } | |||
| @ -0,0 +1,43 @@ | |||
| package Messages | |||
| import ( | |||
| "encoding/json" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "github.com/gorilla/mux" | |||
| ) | |||
| func MessageThread(w http.ResponseWriter, r *http.Request) { | |||
| var ( | |||
| messages []Models.Message | |||
| urlVars map[string]string | |||
| threadID string | |||
| returnJson []byte | |||
| ok bool | |||
| err error | |||
| ) | |||
| urlVars = mux.Vars(r) | |||
| threadID, ok = urlVars["threadID"] | |||
| if !ok { | |||
| http.Error(w, "Not Found", http.StatusNotFound) | |||
| return | |||
| } | |||
| messages, err = Database.GetMessagesByThreadId(threadID) | |||
| if !ok { | |||
| http.Error(w, "Not Found", http.StatusNotFound) | |||
| return | |||
| } | |||
| returnJson, err = json.MarshalIndent(messages, "", " ") | |||
| if err != nil { | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return | |||
| } | |||
| w.WriteHeader(http.StatusOK) | |||
| w.Write(returnJson) | |||
| } | |||
| @ -0,0 +1,57 @@ | |||
| package Database | |||
| import ( | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "github.com/gofrs/uuid" | |||
| "gorm.io/gorm" | |||
| "gorm.io/gorm/clause" | |||
| ) | |||
| func GetFriendById(id string) (Models.Friend, error) { | |||
| var ( | |||
| friend Models.Friend | |||
| err error | |||
| ) | |||
| err = DB.Preload(clause.Associations). | |||
| First(&friend, "id = ?", id). | |||
| Error | |||
| return friend, err | |||
| } | |||
| func GetFriendsByUserId(userID string) ([]Models.Friend, error) { | |||
| var ( | |||
| friends []Models.Friend | |||
| err error | |||
| ) | |||
| err = DB.Model(Models.Friend{}). | |||
| Where("user_id = ?", userID). | |||
| Find(&friends). | |||
| Error | |||
| return friends, err | |||
| } | |||
| func CreateFriendRequest(friend *Models.Friend) error { | |||
| var ( | |||
| err error | |||
| ) | |||
| friend.ThreadID, err = uuid.NewV1() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Create(friend). | |||
| Error | |||
| } | |||
| func DeleteFriend(friend *Models.Friend) error { | |||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Delete(friend). | |||
| Error | |||
| } | |||
| @ -0,0 +1,52 @@ | |||
| package Database | |||
| import ( | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "gorm.io/gorm" | |||
| "gorm.io/gorm/clause" | |||
| ) | |||
| func GetMessageById(id string) (Models.Message, error) { | |||
| var ( | |||
| message Models.Message | |||
| err error | |||
| ) | |||
| err = DB.Preload(clause.Associations). | |||
| First(&message, "id = ?", id). | |||
| Error | |||
| return message, err | |||
| } | |||
| func GetMessagesByThreadId(id string) ([]Models.Message, error) { | |||
| var ( | |||
| messages []Models.Message | |||
| err error | |||
| ) | |||
| err = DB.Preload(clause.Associations). | |||
| Find(&messages, "thread_id = ?", id). | |||
| Error | |||
| return messages, err | |||
| } | |||
| func CreateMessage(message *Models.Message) error { | |||
| var ( | |||
| err error | |||
| ) | |||
| err = DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Create(message). | |||
| Error | |||
| return err | |||
| } | |||
| func DeleteMessage(message *Models.Message) error { | |||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||
| Delete(message). | |||
| Error | |||
| } | |||
| @ -0,0 +1,67 @@ | |||
| package Seeder | |||
| import ( | |||
| "time" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| ) | |||
| func seedFriend(user, friendUser Models.User) error { | |||
| var ( | |||
| friend Models.Friend | |||
| err error | |||
| ) | |||
| friend = Models.Friend{ | |||
| UserID: user.ID, | |||
| FriendID: encryptWithPublicKey(friendUser.ID.Bytes(), decodedPublicKey), | |||
| AcceptedAt: time.Now(), | |||
| } | |||
| err = Database.CreateFriendRequest(&friend) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| friend = Models.Friend{ | |||
| UserID: friendUser.ID, | |||
| FriendID: encryptWithPublicKey(user.ID.Bytes(), decodedPublicKey), | |||
| AcceptedAt: time.Now(), | |||
| } | |||
| return Database.CreateFriendRequest(&friend) | |||
| } | |||
| func SeedFriends() { | |||
| var ( | |||
| primaryUser Models.User | |||
| secondaryUser Models.User | |||
| i int | |||
| err error | |||
| ) | |||
| primaryUser, err = Database.GetUserByUsername("testUser") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| secondaryUser, err = Database.GetUserByUsername("testUser2") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| err = seedFriend(primaryUser, secondaryUser) | |||
| for i = 0; i <= 3; i++ { | |||
| secondaryUser, err = Database.GetUserByUsername(userNames[i]) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| err = seedFriend(primaryUser, secondaryUser) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,136 @@ | |||
| package Seeder | |||
| import ( | |||
| "bytes" | |||
| "crypto/aes" | |||
| "crypto/cipher" | |||
| "crypto/rand" | |||
| "crypto/rsa" | |||
| "crypto/sha512" | |||
| "encoding/pem" | |||
| "hash" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "github.com/gofrs/uuid" | |||
| ) | |||
| // EncryptWithPublicKey encrypts data with public key | |||
| func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte { | |||
| var ( | |||
| hash hash.Hash | |||
| ) | |||
| hash = sha512.New() | |||
| ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return ciphertext | |||
| } | |||
| func PKCS5Padding(ciphertext []byte, blockSize int, after int) []byte { | |||
| var ( | |||
| padding int | |||
| padtext []byte | |||
| ) | |||
| padding = (blockSize - len(ciphertext)%blockSize) | |||
| padtext = bytes.Repeat([]byte{byte(padding)}, padding) | |||
| return append(ciphertext, padtext...) | |||
| } | |||
| func seedMessage(primaryUser, secondaryUser Models.User, threadID uuid.UUID, i int) error { | |||
| var ( | |||
| messageKey Models.Message | |||
| messageData Models.MessageData | |||
| block cipher.Block | |||
| mode cipher.BlockMode | |||
| pemBlock *pem.Block | |||
| plaintext string | |||
| ciphertext []byte | |||
| bKey []byte | |||
| bIV []byte | |||
| bPlaintext []byte | |||
| err error | |||
| ) | |||
| plaintext = "Test Message" | |||
| bKey = make([]byte, 32) | |||
| _, err = rand.Read(bKey) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| bIV = make([]byte, 16) | |||
| _, err = rand.Read(bIV) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| bPlaintext = PKCS5Padding([]byte(plaintext), aes.BlockSize, len(plaintext)) | |||
| pemBlock = &pem.Block{ | |||
| Type: "AES KEY", | |||
| Bytes: bKey, | |||
| } | |||
| block, err = aes.NewCipher(bKey) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| ciphertext = make([]byte, len(bPlaintext)) | |||
| mode = cipher.NewCBCEncrypter(block, bIV) | |||
| mode.CryptBlocks(ciphertext, bPlaintext) | |||
| messageData = Models.MessageData{ | |||
| Data: ciphertext, | |||
| } | |||
| messageKey = Models.Message{ | |||
| UserID: primaryUser.ID, | |||
| MessageData: messageData, | |||
| MessageType: "sender", | |||
| RelationalUserId: encryptWithPublicKey(secondaryUser.ID.Bytes(), decodedPublicKey), | |||
| SymmetricKey: string(pem.EncodeToMemory(pemBlock)), | |||
| } | |||
| return Database.CreateMessage(&messageKey) | |||
| } | |||
| func SeedMessages() { | |||
| var ( | |||
| primaryUser Models.User | |||
| secondaryUser Models.User | |||
| threadID uuid.UUID | |||
| i int | |||
| err error | |||
| ) | |||
| primaryUser, err = Database.GetUserByUsername("testUser") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| secondaryUser, err = Database.GetUserByUsername("testUser2") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| threadID, err = uuid.NewV4() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| for i = 0; i <= 20; i++ { | |||
| err = seedMessage(primaryUser, secondaryUser, threadID, i) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| package Seeder | |||
| import ( | |||
| "crypto/rsa" | |||
| "crypto/x509" | |||
| "encoding/pem" | |||
| "log" | |||
| ) | |||
| const ( | |||
| // Encrypted with "password" | |||
| encryptedPrivateKey string = ` | |||
| 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 | |||
| ` | |||
| publicKey string = ` | |||
| -----BEGIN PUBLIC KEY----- | |||
| MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAidHsO9H0fMd6cu9dQiOwu/qtaDq+lG59kudoHGx8WZhucpOSp2df95UzjnXmMw1fz0Mx5fjgBaunG7u1MVBy+7IdYwXp+iz3xZulVy1Yv3e+GMzgQhAuxhz/8wsHkFkUTweDZCrCZPhkTXlCrxDIuKykQ0el+RnpSjyyljOsAWAdTmrz0a2Nh9FOmF1v49pWy3Z3aJG58xl1dmFkpXjT3m36GB4Z30iR1uOMnNdrtfwIfLQAc7nmle2LxCHeEMYzlA9y6JChm5kGI6FmglSKYTxvDm40jA5mYyDCPADeCodbIw4Mtm0nwrM3QqKWj+jlaL8BY7/jjaosmz7VK2do4wIDAQAB | |||
| -----END PUBLIC KEY----- | |||
| ` | |||
| ) | |||
| var ( | |||
| decodedPublicKey *rsa.PublicKey | |||
| ) | |||
| func Seed() { | |||
| var ( | |||
| block *pem.Block | |||
| decKey any | |||
| err error | |||
| ) | |||
| // TODO: Fix this parsing | |||
| block, _ = pem.Decode([]byte(publicKey)) | |||
| decKey, err = x509.ParsePKIXPublicKey(block.Bytes) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| decodedPublicKey = decKey.(*rsa.PublicKey) | |||
| log.Println("Seeding users...") | |||
| SeedUsers() | |||
| log.Println("Seeding friend connections...") | |||
| SeedFriends() | |||
| log.Println("Seeding messages...") | |||
| SeedMessages() | |||
| } | |||
| @ -0,0 +1,68 @@ | |||
| package Seeder | |||
| import ( | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| ) | |||
| var userNames = []string{ | |||
| "assuredcoot", | |||
| "quotesteeve", | |||
| "blueberriessiemens", | |||
| "eliteexaggerate", | |||
| "twotrice", | |||
| "moderagged", | |||
| "duleelderly", | |||
| "stringdetailed", | |||
| "nodesanymore", | |||
| "sacredpolitical", | |||
| "pajamasenergy", | |||
| } | |||
| func createUser(username string) (Models.User, error) { | |||
| var ( | |||
| userData Models.User | |||
| password string | |||
| err error | |||
| ) | |||
| password, err = Auth.HashPassword("password") | |||
| if err != nil { | |||
| return Models.User{}, err | |||
| } | |||
| userData = Models.User{ | |||
| Username: username, | |||
| Password: password, | |||
| AsymmetricPrivateKey: encryptedPrivateKey, | |||
| AsymmetricPublicKey: publicKey, | |||
| } | |||
| err = Database.CreateUser(&userData) | |||
| return userData, err | |||
| } | |||
| func SeedUsers() { | |||
| var ( | |||
| i int | |||
| err error | |||
| ) | |||
| // Seed users used for conversation seeding | |||
| _, err = createUser("testUser") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| _, err = createUser("testUser2") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| for i = 0; i <= 10; i++ { | |||
| _, err = createUser(userNames[i]) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| } | |||
| @ -1,9 +1,18 @@ | |||
| package Models | |||
| import "github.com/gofrs/uuid" | |||
| import ( | |||
| "time" | |||
| "github.com/gofrs/uuid" | |||
| ) | |||
| // Set with User being the requestee, and FriendID being the requester | |||
| type Friend struct { | |||
| Base | |||
| UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` | |||
| FriendId string `gorm:"column:friend_id;not null" json:"friend_id"` // Stored encrypted | |||
| UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` | |||
| User User `json:"user"` | |||
| FriendID []byte `gorm:"not null" json:"friend_id"` // Stored encrypted | |||
| ThreadID uuid.UUID `gorm:"type:uuid;column:thread_id;not null;" json:"thread_id"` | |||
| Thread []Message `gorm:"foreignKey:thread_id" json:"-"` | |||
| AcceptedAt time.Time `json:"accepted_at"` | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| package Util | |||
| import ( | |||
| "bytes" | |||
| "encoding/gob" | |||
| ) | |||
| func ToBytes(key interface{}) ([]byte, error) { | |||
| var ( | |||
| buf bytes.Buffer | |||
| enc *gob.Encoder | |||
| err error | |||
| ) | |||
| enc = gob.NewEncoder(&buf) | |||
| err = enc.Encode(key) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return buf.Bytes(), nil | |||
| } | |||
| @ -0,0 +1,51 @@ | |||
| package Util | |||
| import ( | |||
| "errors" | |||
| "log" | |||
| "net/http" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" | |||
| "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" | |||
| "github.com/gorilla/mux" | |||
| ) | |||
| func GetUserId(r *http.Request) (string, error) { | |||
| var ( | |||
| urlVars map[string]string | |||
| id string | |||
| ok bool | |||
| ) | |||
| urlVars = mux.Vars(r) | |||
| id, ok = urlVars["userID"] | |||
| if !ok { | |||
| return id, errors.New("Could not get id") | |||
| } | |||
| return id, nil | |||
| } | |||
| func GetUserById(w http.ResponseWriter, r *http.Request) (Models.User, error) { | |||
| var ( | |||
| postData Models.User | |||
| id string | |||
| err error | |||
| ) | |||
| id, err = GetUserId(r) | |||
| if err != nil { | |||
| log.Printf("Error encountered getting id\n") | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return postData, err | |||
| } | |||
| postData, err = Database.GetUserById(id) | |||
| if err != nil { | |||
| log.Printf("Could not find user with id %s\n", id) | |||
| http.Error(w, "Error", http.StatusInternalServerError) | |||
| return postData, err | |||
| } | |||
| return postData, nil | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| const messageTypeSender = 'sender'; | |||
| const messageTypeReceiver = 'receiver'; | |||
| class Message { | |||
| String id; | |||
| String conversationId; | |||
| String symmetricKey; | |||
| String data; | |||
| String messageType; | |||
| String? decryptedData; | |||
| Message({ | |||
| required this.id, | |||
| required this.conversationId, | |||
| required this.symmetricKey, | |||
| required this.data, | |||
| required this.messageType, | |||
| this.decryptedData, | |||
| }); | |||
| } | |||
| class Conversation { | |||
| String id; | |||
| String friendId; | |||
| String recentMessageId; | |||
| Conversation({ | |||
| required this.id, | |||
| required this.friendId, | |||
| required this.recentMessageId, | |||
| }); | |||
| } | |||
| @ -0,0 +1,162 @@ | |||
| import 'package:flutter/material.dart'; | |||
| import '/models/conversations.dart'; | |||
| class ConversationDetail extends StatefulWidget{ | |||
| const ConversationDetail({Key? key}) : super(key: key); | |||
| @override | |||
| _ConversationDetailState createState() => _ConversationDetailState(); | |||
| } | |||
| class _ConversationDetailState extends State<ConversationDetail> { | |||
| Conversation conversation = Conversation( | |||
| id: '777', | |||
| friendId: 'abc', | |||
| recentMessageId: '111', | |||
| ); | |||
| List<Message> messages = [ | |||
| Message( | |||
| id: '444', | |||
| conversationId: '777', | |||
| symmetricKey: '', | |||
| data: 'This is a message', | |||
| messageType: messageTypeSender, | |||
| ), | |||
| Message( | |||
| id: '444', | |||
| conversationId: '777', | |||
| symmetricKey: '', | |||
| data: 'This is a message', | |||
| messageType: messageTypeReceiver, | |||
| ), | |||
| Message( | |||
| id: '444', | |||
| conversationId: '777', | |||
| symmetricKey: '', | |||
| data: 'This is a message', | |||
| messageType: messageTypeSender | |||
| ), | |||
| Message( | |||
| id: '444', | |||
| conversationId: '777', | |||
| symmetricKey: '', | |||
| data: 'This is a message', | |||
| messageType: messageTypeReceiver, | |||
| ), | |||
| ]; | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| appBar: AppBar( | |||
| elevation: 0, | |||
| automaticallyImplyLeading: false, | |||
| backgroundColor: Colors.white, | |||
| flexibleSpace: SafeArea( | |||
| child: Container( | |||
| padding: const EdgeInsets.only(right: 16), | |||
| child: Row( | |||
| children: <Widget>[ | |||
| IconButton( | |||
| onPressed: (){ | |||
| Navigator.pop(context); | |||
| }, | |||
| icon: const Icon(Icons.arrow_back,color: Colors.black,), | |||
| ), | |||
| const SizedBox(width: 2,), | |||
| Expanded( | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| mainAxisAlignment: MainAxisAlignment.center, | |||
| children: const <Widget>[ | |||
| Text("Kriss Benwat",style: TextStyle( fontSize: 16 ,fontWeight: FontWeight.w600),), | |||
| ], | |||
| ), | |||
| ), | |||
| const Icon(Icons.settings,color: Colors.black54,), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ), | |||
| body: Stack( | |||
| children: <Widget>[ | |||
| ListView.builder( | |||
| itemCount: messages.length, | |||
| shrinkWrap: true, | |||
| padding: const EdgeInsets.only(top: 10,bottom: 10), | |||
| physics: const NeverScrollableScrollPhysics(), | |||
| itemBuilder: (context, index) { | |||
| return Container( | |||
| padding: const EdgeInsets.only(left: 14,right: 14,top: 10,bottom: 10), | |||
| child: Align( | |||
| alignment: (messages[index].messageType == messageTypeReceiver ? Alignment.topLeft:Alignment.topRight), | |||
| child: Container( | |||
| decoration: BoxDecoration( | |||
| borderRadius: BorderRadius.circular(20), | |||
| color: (messages[index].messageType == messageTypeReceiver ? Colors.grey.shade200:Colors.blue[200]), | |||
| ), | |||
| padding: const EdgeInsets.all(16), | |||
| child: Text(messages[index].data, style: const TextStyle(fontSize: 15)), | |||
| ), | |||
| ), | |||
| ); | |||
| }, | |||
| ), | |||
| Align( | |||
| alignment: Alignment.bottomLeft, | |||
| child: ConstrainedBox( | |||
| constraints: const BoxConstraints( | |||
| maxHeight: 200.0, | |||
| ), | |||
| child: Container( | |||
| padding: const EdgeInsets.only(left: 10,bottom: 10,top: 10), | |||
| // height: 60, | |||
| width: double.infinity, | |||
| color: Colors.white, | |||
| child: Row( | |||
| children: <Widget>[ | |||
| GestureDetector( | |||
| onTap: (){ | |||
| }, | |||
| child: Container( | |||
| height: 30, | |||
| width: 30, | |||
| decoration: BoxDecoration( | |||
| color: Colors.lightBlue, | |||
| borderRadius: BorderRadius.circular(30), | |||
| ), | |||
| child: const Icon(Icons.add, color: Colors.white, size: 20, ), | |||
| ), | |||
| ), | |||
| const SizedBox(width: 15,), | |||
| const Expanded( | |||
| child: TextField( | |||
| decoration: InputDecoration( | |||
| hintText: "Write message...", | |||
| hintStyle: TextStyle(color: Colors.black54), | |||
| border: InputBorder.none, | |||
| ), | |||
| maxLines: null, | |||
| ), | |||
| ), | |||
| const SizedBox(width: 15,), | |||
| FloatingActionButton( | |||
| onPressed: () { | |||
| }, | |||
| child: const Icon(Icons.send,color: Colors.white,size: 18,), | |||
| backgroundColor: Colors.blue, | |||
| elevation: 0, | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @ -0,0 +1,60 @@ | |||
| import 'package:flutter/material.dart'; | |||
| import '/views/main/conversation_detail.dart'; | |||
| class ConversationListItem extends StatefulWidget{ | |||
| final String id; | |||
| final String username; | |||
| const ConversationListItem({ | |||
| Key? key, | |||
| required this.id, | |||
| required this.username, | |||
| }) : super(key: key); | |||
| @override | |||
| _ConversationListItemState createState() => _ConversationListItemState(); | |||
| } | |||
| class _ConversationListItemState extends State<ConversationListItem> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return GestureDetector( | |||
| onTap: () { | |||
| Navigator.push(context, MaterialPageRoute(builder: (context){ | |||
| return ConversationDetail(); | |||
| })); | |||
| }, | |||
| child: Container( | |||
| padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), | |||
| child: Row( | |||
| children: <Widget>[ | |||
| Expanded( | |||
| child: Row( | |||
| children: <Widget>[ | |||
| // CircleAvatar( | |||
| // backgroundImage: NetworkImage(widget.imageUrl), | |||
| // maxRadius: 30, | |||
| // ), | |||
| //const SizedBox(width: 16), | |||
| Expanded( | |||
| child: Container( | |||
| color: Colors.transparent, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: <Widget>[ | |||
| Text(widget.username, style: const TextStyle(fontSize: 16)), | |||
| const SizedBox(height: 6), | |||
| //Text(widget.messageText,style: TextStyle(fontSize: 13,color: Colors.grey.shade600, fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),), | |||
| const Divider(), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @ -0,0 +1,64 @@ | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:shared_preferences/shared_preferences.dart'; | |||
| import '/utils/storage/encryption_keys.dart'; | |||
| class Profile extends StatefulWidget { | |||
| const Profile({Key? key}) : super(key: key); | |||
| @override | |||
| State<Profile> createState() => _ProfileState(); | |||
| } | |||
| class _ProfileState extends State<Profile> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| body: SingleChildScrollView( | |||
| physics: const BouncingScrollPhysics(), | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: <Widget>[ | |||
| SafeArea( | |||
| child: Padding( | |||
| padding: const EdgeInsets.only(left: 16,right: 16,top: 10), | |||
| child: Row( | |||
| 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: Colors.pink[50], | |||
| ), | |||
| child: GestureDetector( | |||
| onTap: () async { | |||
| final preferences = await SharedPreferences.getInstance(); | |||
| preferences.setBool('islogin', false); | |||
| preferences.remove(rsaPrivateKeyName); | |||
| Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); | |||
| }, | |||
| child: Row( | |||
| children: const <Widget>[ | |||
| Icon(Icons.logout, color: Colors.pink, size: 20,), | |||
| SizedBox(width: 2,), | |||
| Text("Logout",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),), | |||
| ], | |||
| ), | |||
| ), | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| const Padding( | |||
| padding: EdgeInsets.only(top: 16,left: 16,right: 16), | |||
| child: Text('Test'), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||