diff --git a/Backend/Database/Seeder/FriendSeeder.go b/Backend/Database/Seeder/FriendSeeder.go index 033b7a6..5d79da2 100644 --- a/Backend/Database/Seeder/FriendSeeder.go +++ b/Backend/Database/Seeder/FriendSeeder.go @@ -60,7 +60,7 @@ func SeedFriends() { panic(err) } - secondaryUser, err = Database.GetUserByUsername("testUser2") + secondaryUser, err = Database.GetUserByUsername("ATestUser2") if err != nil { panic(err) } diff --git a/Backend/Database/Seeder/MessageSeeder.go b/Backend/Database/Seeder/MessageSeeder.go index 2139eb1..a725c5f 100644 --- a/Backend/Database/Seeder/MessageSeeder.go +++ b/Backend/Database/Seeder/MessageSeeder.go @@ -222,7 +222,7 @@ func SeedMessages() { key, ) - secondaryUser, err = Database.GetUserByUsername("testUser2") + secondaryUser, err = Database.GetUserByUsername("ATestUser2") if err != nil { panic(err) } @@ -263,7 +263,7 @@ func SeedMessages() { { "id": "%s", "username": "%s", - "admin": "true", + "admin": "false", "association_key": "%s" } ] diff --git a/Backend/Database/Seeder/UserSeeder.go b/Backend/Database/Seeder/UserSeeder.go index 69cd543..3c99c9c 100644 --- a/Backend/Database/Seeder/UserSeeder.go +++ b/Backend/Database/Seeder/UserSeeder.go @@ -88,7 +88,7 @@ func SeedUsers() { if err != nil { panic(err) } - _, err = createUser("testUser2") + _, err = createUser("ATestUser2") if err != nil { panic(err) } diff --git a/mobile/lib/components/custom_circle_avatar.dart b/mobile/lib/components/custom_circle_avatar.dart index 8492623..bf7d1b8 100644 --- a/mobile/lib/components/custom_circle_avatar.dart +++ b/mobile/lib/components/custom_circle_avatar.dart @@ -1,13 +1,23 @@ import 'package:flutter/material.dart'; +enum AvatarTypes { + initials, + icon, + image, +} + class CustomCircleAvatar extends StatefulWidget { - final String initials; + final String? initials; + final Icon? icon; final String? imagePath; + final double radius; const CustomCircleAvatar({ Key? key, - required this.initials, + this.initials, + this.icon, this.imagePath, + this.radius = 20, }) : super(key: key); @override @@ -15,25 +25,55 @@ class CustomCircleAvatar extends StatefulWidget { } class _CustomCircleAvatarState extends State{ - - bool _checkLoading = true; + AvatarTypes type = AvatarTypes.image; @override void initState() { super.initState(); + if (widget.imagePath != null) { - _checkLoading = false; + type = AvatarTypes.image; + return; } + + if (widget.icon != null) { + type = AvatarTypes.icon; + return; + } + + if (widget.initials != null) { + type = AvatarTypes.initials; + return; + } + + throw ArgumentError('Invalid arguments passed to CustomCircleAvatar'); + } + + Widget avatar() { + if (type == AvatarTypes.initials) { + return CircleAvatar( + backgroundColor: Colors.grey[300], + child: Text(widget.initials!), + radius: widget.radius, + ); + } + + if (type == AvatarTypes.icon) { + return CircleAvatar( + backgroundColor: Colors.grey[300], + child: widget.icon, + radius: widget.radius, + ); + } + + return CircleAvatar( + backgroundImage: AssetImage(widget.imagePath!), + radius: widget.radius, + ); } @override Widget build(BuildContext context) { - return _checkLoading == true ? - CircleAvatar( - backgroundColor: Colors.grey[300], - child: Text(widget.initials) - ) : CircleAvatar( - backgroundImage: AssetImage(widget.imagePath!) - ); + return avatar(); } } diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 0bd22e7..7890c86 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -6,49 +6,97 @@ import '/views/authentication/login.dart'; import '/views/authentication/signup.dart'; void main() async { - await dotenv.load(fileName: ".env"); - runApp(const MyApp()); + await dotenv.load(fileName: ".env"); + runApp(const MyApp()); } class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({Key? key}) : super(key: key); - static const String _title = 'Envelope'; + static const String _title = 'Envelope'; - @override - Widget build(BuildContext context) { - return MaterialApp( - title: _title, - routes: { - '/home': (context) => const Home(), - '/landing': (context) => const UnauthenticatedLandingWidget(), - '/login': (context) => const Login(), - '/signup': (context) => const Signup(), - }, - home: const Scaffold( - backgroundColor: Colors.cyan, - body: SafeArea( - child: Home(), - ) + @override + Widget build(BuildContext context) { + return MaterialApp( + title: _title, + routes: { + '/home': (context) => const Home(), + '/landing': (context) => const UnauthenticatedLandingWidget(), + '/login': (context) => const Login(), + '/signup': (context) => const Signup(), + }, + home: const Scaffold( + body: SafeArea( + child: Home(), + ) + ), + theme: ThemeData( + brightness: Brightness.light, + primaryColor: Colors.red, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.cyan, + elevation: 0, + ), + inputDecorationTheme: const InputDecorationTheme( + labelStyle: TextStyle( + color: Colors.white, + fontSize: 30, ), - theme: ThemeData( - appBarTheme: const AppBarTheme( - backgroundColor: Colors.cyan, - elevation: 0, - ), - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - focusedBorder: OutlineInputBorder(), - labelStyle: TextStyle( - color: Colors.white, - fontSize: 30, - ), - filled: true, - fillColor: Colors.white, - ), + filled: false, + ), + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + primaryColor: Colors.orange.shade900, + backgroundColor: Colors.grey.shade900, + colorScheme: ColorScheme( + brightness: Brightness.dark, + primary: Colors.orange.shade900, + onPrimary: Colors.white, + secondary: Colors.blue.shade400, + onSecondary: Colors.white, + tertiary: Colors.grey.shade600, + onTertiary: Colors.black, + error: Colors.red, + onError: Colors.yellow, + background: Colors.grey.shade900, + onBackground: Colors.white, + surface: Colors.grey.shade700, + onSurface: Colors.white, + ), + hintColor: Colors.grey.shade500, + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.grey.shade800, + hintStyle: TextStyle( + color: Colors.grey.shade500, + ), + iconColor: Colors.grey.shade500, + contentPadding: const EdgeInsets.all(8), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: const BorderSide( + color: Colors.transparent, + ) + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: const BorderSide( + color: Colors.transparent, + ) + ), - ), - - ); - } + ), + appBarTheme: AppBarTheme( + color: Colors.grey.shade800, + iconTheme: IconThemeData( + color: Colors.grey.shade400 + ), + toolbarTextStyle: TextStyle( + color: Colors.grey.shade400 + ), + ), + ), + ); + } } diff --git a/mobile/lib/models/conversation_users.dart b/mobile/lib/models/conversation_users.dart index 6c94a0d..e0e000d 100644 --- a/mobile/lib/models/conversation_users.dart +++ b/mobile/lib/models/conversation_users.dart @@ -6,7 +6,7 @@ class ConversationUser{ String conversationId; String username; String associationKey; - String admin; + bool admin; ConversationUser({ required this.id, required this.conversationId, @@ -21,7 +21,7 @@ class ConversationUser{ conversationId: conversationId, username: json['username'], associationKey: json['association_key'], - admin: json['admin'], + admin: json['admin'] == 'true', ); } @@ -31,7 +31,7 @@ class ConversationUser{ 'conversation_id': conversationId, 'username': username, 'association_key': associationKey, - 'admin': admin, + 'admin': admin ? 1 : 0, }; } } @@ -44,6 +44,7 @@ Future> getConversationUsers(Conversation conversation) a 'conversation_users', where: 'conversation_id = ?', whereArgs: [conversation.id], + orderBy: 'admin', ); return List.generate(maps.length, (i) { @@ -52,7 +53,7 @@ Future> getConversationUsers(Conversation conversation) a conversationId: maps[i]['conversation_id'], username: maps[i]['username'], associationKey: maps[i]['association_key'], - admin: maps[i]['admin'], + admin: maps[i]['admin'] == 1, ); }); } @@ -75,7 +76,7 @@ Future getConversationUserById(Conversation conversation, Stri conversationId: maps[0]['conversation_id'], username: maps[0]['username'], associationKey: maps[0]['association_key'], - admin: maps[0]['admin'], + admin: maps[0]['admin'] == 1, ); } @@ -98,7 +99,7 @@ Future getConversationUserByUsername(Conversation conversation conversationId: maps[0]['conversation_id'], username: maps[0]['username'], associationKey: maps[0]['association_key'], - admin: maps[0]['admin'], + admin: maps[0]['admin'] == 1, ); } diff --git a/mobile/lib/models/my_profile.dart b/mobile/lib/models/my_profile.dart new file mode 100644 index 0000000..c584018 --- /dev/null +++ b/mobile/lib/models/my_profile.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'package:Envelope/utils/encryption/aes_helper.dart'; +import 'package:Envelope/utils/encryption/crypto_utils.dart'; +import 'package:pointycastle/impl.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MyProfile { + String id; + String username; + RSAPrivateKey privateKey; + RSAPublicKey publicKey; + DateTime loggedInAt; + + MyProfile({ + required this.id, + required this.username, + required this.privateKey, + required this.publicKey, + required this.loggedInAt, + }); + + factory MyProfile._fromJson(Map json) { + DateTime loggedInAt = DateTime.now(); + if (json.containsKey('logged_in_at')) { + loggedInAt = DateTime.parse(json['logged_in_at']); + } + + return MyProfile( + id: json['user_id'], + username: json['username'], + privateKey: CryptoUtils.rsaPrivateKeyFromPem(json['asymmetric_private_key']), + publicKey: CryptoUtils.rsaPublicKeyFromPem(json['asymmetric_public_key']), + loggedInAt: loggedInAt, + ); + } + + @override + String toString() { + return ''' + user_id: $id + username: $username + logged_in_at: $loggedInAt + public_key: $publicKey + private_key: $privateKey + '''; + } + + String toJson() { + return jsonEncode({ + 'user_id': id, + 'username': username, + 'asymmetric_private_key': CryptoUtils.encodeRSAPrivateKeyToPem(privateKey), + 'asymmetric_public_key': CryptoUtils.encodeRSAPublicKeyToPem(publicKey), + 'logged_in_at': loggedInAt.toIso8601String(), + }); + } + + static Future login(Map json, String password) async { + json['asymmetric_private_key'] = AesHelper.aesDecrypt( + password, + base64.decode(json['asymmetric_private_key']) + ); + MyProfile profile = MyProfile._fromJson(json); + final preferences = await SharedPreferences.getInstance(); + print(profile.toJson()); + preferences.setString('profile', profile.toJson()); + return profile; + } + + static Future logout() async { + final preferences = await SharedPreferences.getInstance(); + preferences.remove('profile'); + } + + static Future getProfile() async { + final preferences = await SharedPreferences.getInstance(); + String? profileJson = preferences.getString('profile'); + if (profileJson == null) { + throw Exception('No profile'); + } + return MyProfile._fromJson(json.decode(profileJson)); + } + + static Future isLoggedIn() async { + MyProfile profile = await MyProfile.getProfile(); + return profile.loggedInAt.isBefore((DateTime.now()).add(const Duration(hours: 12))); + } + + Future getPrivateKey() async { + MyProfile profile = await MyProfile.getProfile(); + return profile.privateKey; + } +} + diff --git a/mobile/lib/utils/storage/database.dart b/mobile/lib/utils/storage/database.dart index 86e2a31..162a5c5 100644 --- a/mobile/lib/utils/storage/database.dart +++ b/mobile/lib/utils/storage/database.dart @@ -51,7 +51,7 @@ Future getDatabaseConnection() async { username TEXT, data TEXT, association_key TEXT, - admin TEXT + admin INTEGER ); '''); diff --git a/mobile/lib/views/authentication/login.dart b/mobile/lib/views/authentication/login.dart index 7c60879..c1c7fb7 100644 --- a/mobile/lib/views/authentication/login.dart +++ b/mobile/lib/views/authentication/login.dart @@ -1,11 +1,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import '/utils/encryption/crypto_utils.dart'; -import '/utils/encryption/aes_helper.dart'; -import '/utils/storage/encryption_keys.dart'; +import '/models/my_profile.dart'; import '/utils/storage/session_cookie.dart'; class LoginResponse { @@ -37,7 +34,7 @@ class LoginResponse { } } -Future login(context, String username, String password) async { +Future login(context, String username, String password) async { final resp = await http.post( Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/login'), headers: { @@ -59,20 +56,7 @@ Future login(context, String username, String password) async { setSessionCookie((index == -1) ? rawCookie : rawCookie.substring(0, index)); } - LoginResponse response = LoginResponse.fromJson(jsonDecode(resp.body)); - - var rsaPrivPem = AesHelper.aesDecrypt(password, base64.decode(response.asymmetricPrivateKey)); - - var rsaPriv = CryptoUtils.rsaPrivateKeyFromPem(rsaPrivPem); - setPrivateKey(rsaPriv); - - final preferences = await SharedPreferences.getInstance(); - preferences.setString('logged_in_at', (DateTime.now()).toIso8601String()); - preferences.setString('userId', response.userId); - preferences.setString('username', response.username); - preferences.setString('asymmetricPublicKey', response.asymmetricPublicKey); - - return response; + return await MyProfile.login(json.decode(resp.body), password); } class Login extends StatelessWidget { @@ -81,17 +65,16 @@ class Login extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.cyan, appBar: AppBar( title: null, automaticallyImplyLeading: true, - //`true` if you want Flutter to automatically add Back Button when needed, - //or `false` if you want to force your own back button every where leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed:() => { Navigator.pop(context) } - ) + ), + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, ), body: const SafeArea( child: LoginWidget(), @@ -115,17 +98,26 @@ class _LoginWidgetState extends State { @override Widget build(BuildContext context) { - const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black); + const TextStyle inputTextStyle = TextStyle( + fontSize: 18, + ); - final ButtonStyle _buttonStyle = ElevatedButton.styleFrom( - primary: Colors.white, - onPrimary: Colors.cyan, + final OutlineInputBorder inputBorderStyle = OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.transparent, + ) + ); + + final ButtonStyle buttonStyle = ElevatedButton.styleFrom( + primary: Theme.of(context).colorScheme.surface, + onPrimary: Theme.of(context).colorScheme.onSurface, minimumSize: const Size.fromHeight(50), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - textStyle: const TextStyle( + textStyle: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, - color: Colors.red, + color: Theme.of(context).colorScheme.error, ), ); @@ -134,37 +126,52 @@ class _LoginWidgetState extends State { key: _formKey, child: Center( child: Padding( - padding: const EdgeInsets.all(15), + padding: const EdgeInsets.only( + left: 20, + right: 20, + top: 0, + bottom: 80, + ), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('Login', style: TextStyle(fontSize: 35, color: Colors.white),), + Text( + 'Login', + style: TextStyle( + fontSize: 35, + color: Theme.of(context).colorScheme.onBackground, + ), + ), const SizedBox(height: 30), TextFormField( controller: usernameController, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: 'Username', + enabledBorder: inputBorderStyle, + focusedBorder: inputBorderStyle, ), - style: _inputTextStyle, + style: inputTextStyle, // The validator receives the text that the user has entered. validator: (value) { if (value == null || value.isEmpty) { - return 'Create a username'; + return 'Enter a username'; } return null; }, ), - const SizedBox(height: 5), + const SizedBox(height: 10), TextFormField( controller: passwordController, obscureText: true, enableSuggestions: false, autocorrect: false, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: 'Password', + enabledBorder: inputBorderStyle, + focusedBorder: inputBorderStyle, ), - style: _inputTextStyle, + style: inputTextStyle, // The validator receives the text that the user has entered. validator: (value) { if (value == null || value.isEmpty) { @@ -173,9 +180,9 @@ class _LoginWidgetState extends State { return null; }, ), - const SizedBox(height: 5), + const SizedBox(height: 15), ElevatedButton( - style: _buttonStyle, + style: buttonStyle, onPressed: () { if (_formKey.currentState!.validate()) { ScaffoldMessenger.of(context).showSnackBar( @@ -186,7 +193,7 @@ class _LoginWidgetState extends State { context, usernameController.text, passwordController.text, - ).then((value) { + ).then((val) { Navigator. pushNamedAndRemoveUntil( context, diff --git a/mobile/lib/views/authentication/signup.dart b/mobile/lib/views/authentication/signup.dart index 74425c3..0847bf7 100644 --- a/mobile/lib/views/authentication/signup.dart +++ b/mobile/lib/views/authentication/signup.dart @@ -2,211 +2,233 @@ import 'dart:typed_data'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'package:shared_preferences/shared_preferences.dart'; - import '/utils/encryption/aes_helper.dart'; -import '/utils/storage/encryption_keys.dart'; import '/utils/encryption/crypto_utils.dart'; class SignupResponse { - final String status; - final String message; - - const SignupResponse({ - required this.status, - required this.message, - }); - - factory SignupResponse.fromJson(Map json) { - return SignupResponse( - status: json['status'], - message: json['message'], - ); - } + final String status; + final String message; + + const SignupResponse({ + required this.status, + required this.message, + }); + + factory SignupResponse.fromJson(Map json) { + return SignupResponse( + status: json['status'], + message: json['message'], + ); + } } Future signUp(context, String username, String password, String confirmPassword) async { - var keyPair = CryptoUtils.generateRSAKeyPair(); - - var rsaPubPem = CryptoUtils.encodeRSAPublicKeyToPem(keyPair.publicKey); - var rsaPrivPem = CryptoUtils.encodeRSAPrivateKeyToPem(keyPair.privateKey); - - var encRsaPriv = AesHelper.aesEncrypt(password, Uint8List.fromList(rsaPrivPem.codeUnits)); - - // TODO: Check for timeout here - final resp = await http.post( - Uri.parse('http://192.168.1.5:8080/api/v1/signup'), - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: jsonEncode({ - 'username': username, - 'password': password, - 'confirm_password': confirmPassword, - 'asymmetric_public_key': rsaPubPem, - 'asymmetric_private_key': encRsaPriv, - }), - ); - - SignupResponse response = SignupResponse.fromJson(jsonDecode(resp.body)); - - if (resp.statusCode != 201) { - throw Exception(response.message); - } - - return response; + var keyPair = CryptoUtils.generateRSAKeyPair(); + + var rsaPubPem = CryptoUtils.encodeRSAPublicKeyToPem(keyPair.publicKey); + var rsaPrivPem = CryptoUtils.encodeRSAPrivateKeyToPem(keyPair.privateKey); + + var encRsaPriv = AesHelper.aesEncrypt(password, Uint8List.fromList(rsaPrivPem.codeUnits)); + + // TODO: Check for timeout here + final resp = await http.post( + Uri.parse('http://192.168.1.5:8080/api/v1/signup'), + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode({ + 'username': username, + 'password': password, + 'confirm_password': confirmPassword, + 'asymmetric_public_key': rsaPubPem, + 'asymmetric_private_key': encRsaPriv, + }), + ); + + SignupResponse response = SignupResponse.fromJson(jsonDecode(resp.body)); + + if (resp.statusCode != 201) { + throw Exception(response.message); + } + + return response; } class Signup extends StatelessWidget { - const Signup({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.cyan, - appBar: AppBar( - title: null, - automaticallyImplyLeading: true, - //`true` if you want Flutter to automatically add Back Button when needed, - //or `false` if you want to force your own back button every where - leading: IconButton(icon: const Icon(Icons.arrow_back), - //onPressed:() => Navigator.pop(context, false), - onPressed:() => { - Navigator.pop(context) - } - ) - ), - body: const SafeArea( - child: SignupWidget(), - ) - ); - } + const Signup({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: null, + automaticallyImplyLeading: true, + //`true` if you want Flutter to automatically add Back Button when needed, + //or `false` if you want to force your own back button every where + leading: IconButton(icon: const Icon(Icons.arrow_back), + onPressed:() => { + Navigator.pop(context) + } + ), + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + ), + body: const SafeArea( + child: SignupWidget(), + ) + ); + } } class SignupWidget extends StatefulWidget { - const SignupWidget({Key? key}) : super(key: key); + const SignupWidget({Key? key}) : super(key: key); - @override - State createState() => _SignupWidgetState(); + @override + State createState() => _SignupWidgetState(); } class _SignupWidgetState extends State { - final _formKey = GlobalKey(); - - TextEditingController usernameController = TextEditingController(); - TextEditingController passwordController = TextEditingController(); - TextEditingController passwordConfirmController = TextEditingController(); - - @override - Widget build(BuildContext context) { - const TextStyle _inputTextStyle = TextStyle(fontSize: 18, color: Colors.black); - - final ButtonStyle _buttonStyle = ElevatedButton.styleFrom( - primary: Colors.white, - onPrimary: Colors.cyan, - minimumSize: const Size.fromHeight(50), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - textStyle: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.red, - ), - ); - - return Center( - child: Form( - key: _formKey, - child: Center( - child: Padding( - padding: const EdgeInsets.all(15), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text('Sign Up', style: TextStyle(fontSize: 35, color: Colors.white),), - const SizedBox(height: 30), - TextFormField( - controller: usernameController, - decoration: const InputDecoration( - hintText: 'Username', - ), - style: _inputTextStyle, - // The validator receives the text that the user has entered. - validator: (value) { - if (value == null || value.isEmpty) { - return 'Create a username'; - } - return null; - }, - ), - const SizedBox(height: 5), - TextFormField( - controller: passwordController, - obscureText: true, - enableSuggestions: false, - autocorrect: false, - decoration: const InputDecoration( - hintText: 'Password', - ), - style: _inputTextStyle, - // The validator receives the text that the user has entered. - validator: (value) { - if (value == null || value.isEmpty) { - return 'Enter a password'; - } - return null; - }, - ), - const SizedBox(height: 5), - TextFormField( - controller: passwordConfirmController, - obscureText: true, - enableSuggestions: false, - autocorrect: false, - decoration: const InputDecoration( - hintText: 'Password', - ), - style: _inputTextStyle, - // The validator receives the text that the user has entered. - validator: (value) { - if (value == null || value.isEmpty) { - return 'Confirm your password'; - } - if (value != passwordController.text) { - return 'Passwords do not match'; - } - return null; - }, - ), - const SizedBox(height: 5), - ElevatedButton( - style: _buttonStyle, - onPressed: () { - if (_formKey.currentState!.validate()) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Processing Data')), - ); - - signUp( - context, - usernameController.text, - passwordController.text, - passwordConfirmController.text - ).then((value) { - Navigator.of(context).popUntil((route) => route.isFirst); - }).catchError((error) { - print(error); // TODO: Show error on interface - }); - } - }, - child: const Text('Submit'), - ), - ], - ) - ) - ) - - ) - ); - } + final _formKey = GlobalKey(); + + TextEditingController usernameController = TextEditingController(); + TextEditingController passwordController = TextEditingController(); + TextEditingController passwordConfirmController = TextEditingController(); + + @override + Widget build(BuildContext context) { + const TextStyle inputTextStyle = TextStyle( + fontSize: 18, + ); + + final OutlineInputBorder inputBorderStyle = OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Colors.transparent, + ) + ); + + final ButtonStyle buttonStyle = ElevatedButton.styleFrom( + primary: Theme.of(context).colorScheme.surface, + onPrimary: Theme.of(context).colorScheme.onSurface, + minimumSize: const Size.fromHeight(50), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + textStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.error, + ), + ); + + return Center( + child: Form( + key: _formKey, + child: Center( + child: Padding( + padding: const EdgeInsets.only( + left: 20, + right: 20, + top: 0, + bottom: 100, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Sign Up', + style: TextStyle( + fontSize: 35, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + const SizedBox(height: 30), + TextFormField( + controller: usernameController, + decoration: InputDecoration( + hintText: 'Username', + enabledBorder: inputBorderStyle, + focusedBorder: inputBorderStyle, + ), + style: inputTextStyle, + // The validator receives the text that the user has entered. + validator: (value) { + if (value == null || value.isEmpty) { + return 'Create a username'; + } + return null; + }, + ), + const SizedBox(height: 10), + TextFormField( + controller: passwordController, + obscureText: true, + enableSuggestions: false, + autocorrect: false, + decoration: InputDecoration( + hintText: 'Password', + enabledBorder: inputBorderStyle, + focusedBorder: inputBorderStyle, + ), + style: inputTextStyle, + // The validator receives the text that the user has entered. + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter a password'; + } + return null; + }, + ), + const SizedBox(height: 10), + TextFormField( + controller: passwordConfirmController, + obscureText: true, + enableSuggestions: false, + autocorrect: false, + decoration: InputDecoration( + hintText: 'Password', + enabledBorder: inputBorderStyle, + focusedBorder: inputBorderStyle, + ), + style: inputTextStyle, + // The validator receives the text that the user has entered. + validator: (value) { + if (value == null || value.isEmpty) { + return 'Confirm your password'; + } + if (value != passwordController.text) { + return 'Passwords do not match'; + } + return null; + }, + ), + const SizedBox(height: 15), + ElevatedButton( + style: buttonStyle, + onPressed: () { + if (_formKey.currentState!.validate()) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Processing Data')), + ); + + signUp( + context, + usernameController.text, + passwordController.text, + passwordConfirmController.text + ).then((value) { + Navigator.of(context).popUntil((route) => route.isFirst); + }).catchError((error) { + print(error); // TODO: Show error on interface + }); + } + }, + child: const Text('Submit'), + ), + ], + ) + ) + ) + ) + ); + } } diff --git a/mobile/lib/views/authentication/unauthenticated_landing.dart b/mobile/lib/views/authentication/unauthenticated_landing.dart index 1bb6c31..adbdd19 100644 --- a/mobile/lib/views/authentication/unauthenticated_landing.dart +++ b/mobile/lib/views/authentication/unauthenticated_landing.dart @@ -15,21 +15,19 @@ class _UnauthenticatedLandingWidgetState extends State false, child: Scaffold( - backgroundColor: Colors.cyan, body: SafeArea( child: Center( child: Column( @@ -40,16 +38,31 @@ class _UnauthenticatedLandingWidgetState extends State { Widget usernameOrFailedToSend(int index) { if (messages[index].senderUsername != username) { - return Text(messages[index].senderUsername); + return Text( + messages[index].senderUsername, + style: TextStyle( + fontSize: 12, + color: Colors.grey[300], + ), + ); } if (messages[index].failedToSend) { @@ -86,7 +93,6 @@ class _ConversationDetailState extends State { appBar: AppBar( elevation: 0, automaticallyImplyLeading: false, - backgroundColor: Colors.white, flexibleSpace: SafeArea( child: Container( padding: const EdgeInsets.only(right: 16), @@ -96,7 +102,10 @@ class _ConversationDetailState extends State { onPressed: (){ Navigator.pop(context); }, - icon: const Icon(Icons.arrow_back,color: Colors.black,), + icon: Icon( + Icons.arrow_back, + color: Theme.of(context).appBarTheme.iconTheme?.color, + ), ), const SizedBox(width: 2,), Expanded( @@ -106,14 +115,26 @@ class _ConversationDetailState extends State { children: [ Text( widget.conversation.name, - style: const TextStyle( + style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w600), + fontWeight: FontWeight.w600, + color: Theme.of(context).appBarTheme.toolbarTextStyle?.color + ), ), ], ), ), - const Icon(Icons.settings,color: Colors.black54), + IconButton( + onPressed: (){ + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => ConversationSettings(conversation: widget.conversation)), + ); + }, + icon: Icon( + Icons.settings, + color: Theme.of(context).appBarTheme.iconTheme?.color, + ), + ), ], ), ), @@ -128,7 +149,7 @@ class _ConversationDetailState extends State { reverse: true, itemBuilder: (context, index) { return Container( - padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 10), + padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 0), child: Align( alignment: ( messages[index].senderUsername == username ? @@ -145,23 +166,54 @@ class _ConversationDetailState extends State { borderRadius: BorderRadius.circular(20), color: ( messages[index].senderUsername == username ? - Colors.blue[200] : - Colors.grey.shade200 + Theme.of(context).colorScheme.primary : + Theme.of(context).colorScheme.tertiary ), ), padding: const EdgeInsets.all(12), - child: Text(messages[index].data, style: const TextStyle(fontSize: 15)), + child: Text( + messages[index].data, + style: TextStyle( + fontSize: 15, + color: messages[index].senderUsername == username ? + Theme.of(context).colorScheme.onPrimary : + Theme.of(context).colorScheme.onTertiary, + ) + ), ), - usernameOrFailedToSend(index), - Text( - convertToAgo(messages[index].createdAt), - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], + const SizedBox(height: 1.5), + Row( + mainAxisAlignment: messages[index].senderUsername == username ? + MainAxisAlignment.end : + MainAxisAlignment.start, + children: [ + const SizedBox(width: 10), + usernameOrFailedToSend(index), + ], + ), + const SizedBox(height: 1.5), + Row( + mainAxisAlignment: messages[index].senderUsername == username ? + MainAxisAlignment.end : + MainAxisAlignment.start, + children: [ + const SizedBox(width: 10), + Text( + convertToAgo(messages[index].createdAt), + textAlign: messages[index].senderUsername == username ? + TextAlign.left : + TextAlign.right, + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + ], ), - ), - ] + index != 0 ? + const SizedBox(height: 20) : + const SizedBox.shrink(), + ], ) ), ); @@ -177,7 +229,7 @@ class _ConversationDetailState extends State { padding: const EdgeInsets.only(left: 10,bottom: 10,top: 10), // height: 60, width: double.infinity, - color: Colors.white, + color: Theme.of(context).backgroundColor, child: Row( children: [ GestureDetector( @@ -187,18 +239,24 @@ class _ConversationDetailState extends State { height: 30, width: 30, decoration: BoxDecoration( - color: Colors.lightBlue, + color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(30), ), - child: const Icon(Icons.add, color: Colors.white, size: 20, ), + child: Icon( + Icons.add, + color: Theme.of(context).colorScheme.onPrimary, + size: 20 + ), ), ), const SizedBox(width: 15,), Expanded( child: TextField( - decoration: const InputDecoration( + decoration: InputDecoration( hintText: "Write message...", - hintStyle: TextStyle(color: Colors.black54), + hintStyle: TextStyle( + color: Theme.of(context).hintColor, + ), border: InputBorder.none, ), maxLines: null, @@ -206,18 +264,28 @@ class _ConversationDetailState extends State { ), ), const SizedBox(width: 15), - FloatingActionButton( - onPressed: () async { - if (msgController.text == '') { - return; - } - await sendMessage(widget.conversation, msgController.text); - messages = await getMessagesForThread(widget.conversation); - setState(() {}); - msgController.text = ''; - }, - child: const Icon(Icons.send,color: Colors.white,size: 18,), - backgroundColor: Colors.blue, + Container( + width: 45, + height: 45, + child: FittedBox( + child: FloatingActionButton( + onPressed: () async { + if (msgController.text == '') { + return; + } + await sendMessage(widget.conversation, msgController.text); + messages = await getMessagesForThread(widget.conversation); + setState(() {}); + msgController.text = ''; + }, + child: Icon( + Icons.send, + color: Theme.of(context).colorScheme.onPrimary, + size: 22 + ), + backgroundColor: Theme.of(context).primaryColor, + ), + ), ), const SizedBox(width: 10), ], diff --git a/mobile/lib/views/main/conversation_list.dart b/mobile/lib/views/main/conversation_list.dart index 1636f4f..8ba20f7 100644 --- a/mobile/lib/views/main/conversation_list.dart +++ b/mobile/lib/views/main/conversation_list.dart @@ -80,23 +80,8 @@ class _ConversationListState extends State { padding: const EdgeInsets.only(left: 16,right: 16,top: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text("Conversations",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: Row( - children: const [ - Icon(Icons.add,color: Colors.pink,size: 20,), - SizedBox(width: 2,), - Text("Add",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),), - ], - ), - ) + children: const [ + Text("Conversations",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),), ], ), ), @@ -104,20 +89,13 @@ class _ConversationListState extends State { Padding( padding: const EdgeInsets.only(top: 16,left: 16,right: 16), child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( hintText: "Search...", - hintStyle: TextStyle(color: Colors.grey.shade600), - prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: 20,), - filled: true, - fillColor: Colors.grey.shade100, - contentPadding: const EdgeInsets.all(8), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide( - color: Colors.grey.shade100 - ) + prefixIcon: Icon( + Icons.search, + size: 20 ), - ), + ), onChanged: (value) => filterSearchResults(value.toLowerCase()) ), ), diff --git a/mobile/lib/views/main/conversation_settings.dart b/mobile/lib/views/main/conversation_settings.dart new file mode 100644 index 0000000..c4e04c5 --- /dev/null +++ b/mobile/lib/views/main/conversation_settings.dart @@ -0,0 +1,124 @@ +import 'package:Envelope/models/conversation_users.dart'; +import 'package:flutter/material.dart'; +import '/views/main/conversation_settings_user_list_item.dart'; +import '/models/conversations.dart'; +import 'package:Envelope/components/custom_circle_avatar.dart'; + +class ConversationSettings extends StatefulWidget { + final Conversation conversation; + const ConversationSettings({ + Key? key, + required this.conversation, + }) : super(key: key); + + @override + State createState() => _ConversationSettingsState(); +} + +class _ConversationSettingsState extends State { + final _formKey = GlobalKey(); + + List users = []; + + TextEditingController nameController = TextEditingController(); + + @override + void initState() { + nameController.text = widget.conversation.name; + super.initState(); + getUsers(); + } + + Future getUsers() async { + users = await getConversationUsers(widget.conversation); + setState(() {}); + } + + Widget conversationName() { + return Row( + children: [ + const CustomCircleAvatar( + icon: Icon(Icons.people, size: 40), + imagePath: null, // TODO: Add image here + radius: 30, + ), + const SizedBox(width: 10), + Text( + widget.conversation.name, + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + Widget usersList() { + return ListView.builder( + itemCount: users.length, + shrinkWrap: true, + padding: const EdgeInsets.only(top: 16), + itemBuilder: (context, i) { + return ConversationSettingsUserListItem( + user: users[i], + isAdmin: widget.conversation.admin, + ); + } + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + automaticallyImplyLeading: false, + flexibleSpace: SafeArea( + child: Container( + padding: const EdgeInsets.only(right: 16), + child: Row( + children: [ + IconButton( + onPressed: (){ + Navigator.pop(context); + }, + icon: const Icon(Icons.arrow_back), + ), + const SizedBox(width: 2,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.conversation.name + " Settings", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600 + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.all(15), + child: Column( + children: [ + const SizedBox(height: 30), + conversationName(), + const SizedBox(height: 10), + const Text('Users', style: TextStyle(fontSize: 20)), + usersList(), + ], + ), + ), + ); + } +} + diff --git a/mobile/lib/views/main/conversation_settings_user_list_item.dart b/mobile/lib/views/main/conversation_settings_user_list_item.dart new file mode 100644 index 0000000..590fc06 --- /dev/null +++ b/mobile/lib/views/main/conversation_settings_user_list_item.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:Envelope/models/conversation_users.dart'; +import 'package:Envelope/components/custom_circle_avatar.dart'; + +class ConversationSettingsUserListItem extends StatefulWidget{ + final ConversationUser user; + final bool isAdmin; + const ConversationSettingsUserListItem({ + Key? key, + required this.user, + required this.isAdmin, + }) : super(key: key); + + @override + _ConversationSettingsUserListItemState createState() => _ConversationSettingsUserListItemState(); +} + +class _ConversationSettingsUserListItemState extends State { + + Widget admin() { + if (widget.user.admin) { + return const SizedBox.shrink(); + } + + return Row( + children: const [ + SizedBox(width: 5), + Icon(Icons.circle, size: 6), + SizedBox(width: 5), + Text( + 'Admin', + style: TextStyle( + fontSize: 14, + ), + ), + ] + ); + } + + Widget adminUserActions() { + if (!widget.isAdmin) { + return const SizedBox.shrink(); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + IconButton(icon: const Icon(Icons.cancel), + padding: const EdgeInsets.all(0), + onPressed:() => { + print('Cancel') + } + ), + IconButton(icon: const Icon(Icons.admin_panel_settings), + padding: const EdgeInsets.all(0), + onPressed:() => { + print('Admin') + } + ) + ], + ); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + child: Row( + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomCircleAvatar( + initials: widget.user.username[0].toUpperCase(), + imagePath: null, // TODO: Add image here + radius: 15, + ), + const SizedBox(width: 16), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + Text( + widget.user.username, + style: const TextStyle(fontSize: 16) + ), + admin(), + ], + ), + ), + ), + ), + ], + ), + ), + adminUserActions(), + ], + ), + ); + } +} diff --git a/mobile/lib/views/main/friend_list.dart b/mobile/lib/views/main/friend_list.dart index 719c57c..f331659 100644 --- a/mobile/lib/views/main/friend_list.dart +++ b/mobile/lib/views/main/friend_list.dart @@ -88,14 +88,24 @@ class _FriendListState extends State { padding: const EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2), height: 30, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Colors.pink[50], + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.tertiary ), child: Row( - children: const [ - Icon(Icons.add,color: Colors.pink,size: 20,), - SizedBox(width: 2,), - Text("Add",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),), + children: [ + Icon( + Icons.add, + color: Theme.of(context).primaryColor, + size: 20 + ), + const SizedBox(width: 2,), + const Text( + "Add", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold + ) + ), ], ), ) @@ -106,18 +116,11 @@ class _FriendListState extends State { Padding( padding: const EdgeInsets.only(top: 16,left: 16,right: 16), child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( hintText: "Search...", - hintStyle: TextStyle(color: Colors.grey.shade600), - prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: 20,), - filled: true, - fillColor: Colors.grey.shade100, - contentPadding: const EdgeInsets.all(8), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide( - color: Colors.grey.shade100 - ) + prefixIcon: Icon( + Icons.search, + size: 20 ), ), onChanged: (value) => filterSearchResults(value.toLowerCase()) diff --git a/mobile/lib/views/main/friend_list_item.dart b/mobile/lib/views/main/friend_list_item.dart index 6fbc26b..3dece66 100644 --- a/mobile/lib/views/main/friend_list_item.dart +++ b/mobile/lib/views/main/friend_list_item.dart @@ -24,44 +24,44 @@ class _FriendListItemState extends State { onTap: (){ }, child: Container( - padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - CustomCircleAvatar( - initials: widget.username[0].toUpperCase(), - imagePath: widget.imagePath, - ), - const SizedBox(width: 16), - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Container( - color: Colors.transparent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(widget.username, style: const TextStyle(fontSize: 16)), - // Text( - // widget.messageText, - // style: TextStyle(fontSize: 13, - // color: Colors.grey.shade600, - // fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal - // ), - // ), - ], - ), - ), + padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + CustomCircleAvatar( + initials: widget.username[0].toUpperCase(), + imagePath: widget.imagePath, + ), + const SizedBox(width: 16), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.username, style: const TextStyle(fontSize: 16)), + // Text( + // widget.messageText, + // style: TextStyle(fontSize: 13, + // color: Colors.grey.shade600, + // fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal + // ), + // ), + ], ), ), - ], - ), - ), - ], + ), + ), + ], + ), ), - ), - ); + ], + ), + ), + ); } } diff --git a/mobile/lib/views/main/home.dart b/mobile/lib/views/main/home.dart index f7a0d6d..768ef48 100644 --- a/mobile/lib/views/main/home.dart +++ b/mobile/lib/views/main/home.dart @@ -11,6 +11,7 @@ import '/utils/storage/messages.dart'; import '/utils/storage/session_cookie.dart'; import '/models/conversations.dart'; import '/models/friends.dart'; +import '/models/my_profile.dart'; class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @@ -33,12 +34,14 @@ class _HomeState extends State { @override void initState() { - super.initState(); updateData(); + super.initState(); } void updateData() async { - await checkLogin(); + if (!await checkLogin()) { + return; + } await updateFriends(); await updateConversations(); await updateMessageThreads(); @@ -56,23 +59,23 @@ class _HomeState extends State { }); } - Future checkLogin() async { - SharedPreferences preferences = await SharedPreferences.getInstance(); + Future checkLogin() async { + bool isLoggedIn = false; - var loggedInTime = preferences.getString('logged_in_at'); - if (loggedInTime == null) { - preferences.remove('logged_in_at'); - preferences.remove('username'); - preferences.remove('userId'); - preferences.remove('asymmetricPublicKey'); + try { + isLoggedIn = await MyProfile.isLoggedIn(); + } catch (Exception) { Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); - return; + return false; } - DateTime loggedInAt = DateTime.parse(loggedInTime); - bool isAfter = loggedInAt.isAfter((DateTime.now()).add(const Duration(hours: 12))); - int statusCode = 200; + if (!isLoggedIn) { + await MyProfile.logout(); + Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); + return false; + } + int statusCode = 200; try { var resp = await http.get( Uri.parse('${dotenv.env["SERVER_URL"]}api/v1/auth/check'), @@ -82,20 +85,18 @@ class _HomeState extends State { ); statusCode = resp.statusCode; } catch(SocketException) { - if (!isAfter) { - return; + if (await MyProfile.isLoggedIn()) { + return true; } } - if (!isAfter && statusCode == 200) { - return; + if (isLoggedIn && statusCode == 200) { + return true; } - preferences.remove('logged_in_at'); - preferences.remove('username'); - preferences.remove('userId'); - preferences.remove('asymmetricPublicKey'); + MyProfile.logout(); Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); + return false; } void _onItemTapped(int index) { @@ -130,13 +131,13 @@ class _HomeState extends State { Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => false, - child: Scaffold( - body: isLoading ? loading() : _widgetOptions.elementAt(_selectedIndex), + child: isLoading ? loading() : Scaffold( + body: _widgetOptions.elementAt(_selectedIndex), bottomNavigationBar: isLoading ? const SizedBox.shrink() : BottomNavigationBar( currentIndex: _selectedIndex, onTap: _onItemTapped, - selectedItemColor: Colors.red, - unselectedItemColor: Colors.grey.shade600, + selectedItemColor: Theme.of(context).primaryColor, + unselectedItemColor: Theme.of(context).hintColor, selectedLabelStyle: const TextStyle(fontWeight: FontWeight.w600), unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w600), type: BottomNavigationBarType.fixed, diff --git a/mobile/lib/views/main/profile.dart b/mobile/lib/views/main/profile.dart index ccc84d5..9222403 100644 --- a/mobile/lib/views/main/profile.dart +++ b/mobile/lib/views/main/profile.dart @@ -1,68 +1,77 @@ import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '/utils/storage/encryption_keys.dart'; import '/utils/storage/database.dart'; +import '/models/my_profile.dart'; class Profile extends StatefulWidget { - const Profile({Key? key}) : super(key: key); + const Profile({Key? key}) : super(key: key); - @override - State createState() => _ProfileState(); + @override + State createState() => _ProfileState(); } class _ProfileState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SafeArea( - child: Padding( - padding: const EdgeInsets.only(left: 16,right: 16,top: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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 { - deleteDb(); - final preferences = await SharedPreferences.getInstance(); - preferences.remove('logged_in_at'); - preferences.remove('username'); - preferences.remove('userId'); - preferences.remove(rsaPrivateKeyName); - Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); - }, - child: Row( - children: const [ - 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'), - ), - ], - ), - ), - ); + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SafeArea( + child: Padding( + padding: const EdgeInsets.only(left: 16,right: 16,top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text("Profile",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),), + Container( + padding: const EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2), + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).colorScheme.tertiary + ), + child: GestureDetector( + onTap: () { + deleteDb(); + MyProfile.logout(); + Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); + }, + child: Row( + children: [ + Icon( + Icons.logout, + color: Theme.of(context).primaryColor, + size: 20 + ), + const SizedBox(width: 2,), + const Text( + 'Logout', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold + ) + ), + ], + ), + ), + ) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16,left: 16,right: 16), + child: Row( + children: const [ + Text('FUCK'), + ], + ) + ), + ], + ), + ), + ); } }