@ -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 | 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 { | type Friend struct { | ||||
Base | 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'), | |||||
), | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} |