From 52576132cc865a65c5e5702cd5bcc37116bed4d8 Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Sun, 5 Jun 2022 15:17:32 +0930 Subject: [PATCH] Add initial conversation list/friend list --- Backend/Database/Init.go | 1 + Backend/Models/Friends.go | 9 ++ Backend/Models/Messages.go | 4 +- mobile/lib/main.dart | 43 +++++-- mobile/lib/models/friends.dart | 9 ++ mobile/lib/views/authentication/login.dart | 56 +++------ mobile/lib/views/authentication/signup.dart | 52 +++----- mobile/lib/views/main/conversation_list.dart | 56 +++++++++ mobile/lib/views/main/conversations_list.dart | 112 ------------------ mobile/lib/views/main/friend_list.dart | 105 ++++++++++++++++ mobile/lib/views/main/friend_list_item.dart | 56 +++++++++ mobile/lib/views/main/home.dart | 72 +++++++++++ mobile/pubspec.lock | 18 +-- .../windows/flutter/generated_plugins.cmake | 8 ++ 14 files changed, 397 insertions(+), 204 deletions(-) create mode 100644 Backend/Models/Friends.go create mode 100644 mobile/lib/models/friends.dart create mode 100644 mobile/lib/views/main/conversation_list.dart delete mode 100644 mobile/lib/views/main/conversations_list.dart create mode 100644 mobile/lib/views/main/friend_list.dart create mode 100644 mobile/lib/views/main/friend_list_item.dart create mode 100644 mobile/lib/views/main/home.dart diff --git a/Backend/Database/Init.go b/Backend/Database/Init.go index 5d4d7df..b9de172 100644 --- a/Backend/Database/Init.go +++ b/Backend/Database/Init.go @@ -19,6 +19,7 @@ var ( func GetModels() []interface{} { return []interface{}{ &Models.User{}, + &Models.Friend{}, &Models.MessageData{}, &Models.MessageKey{}, } diff --git a/Backend/Models/Friends.go b/Backend/Models/Friends.go new file mode 100644 index 0000000..c0b9abd --- /dev/null +++ b/Backend/Models/Friends.go @@ -0,0 +1,9 @@ +package Models + +import "github.com/gofrs/uuid" + +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 +} diff --git a/Backend/Models/Messages.go b/Backend/Models/Messages.go index cd2bd6a..ce2fc1b 100644 --- a/Backend/Models/Messages.go +++ b/Backend/Models/Messages.go @@ -4,12 +4,12 @@ import "github.com/gofrs/uuid" type MessageData struct { Base - Data []byte `gorm:"not null" json:"data"` // Stored encrypted + Data string `gorm:"not null" json:"data"` // Stored encrypted } type MessageKey struct { Base UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"` MessageDataID uuid.UUID `gorm:"type:uuid;column:message_data_id;not null;" json:"message_data_id"` - SymmetricKey []byte `gorm:"not null" json:"symmetric_key"` // Stored encrypted + SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted } diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 3eceed5..8bad991 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,25 +1,52 @@ import 'package:flutter/material.dart'; -import '/views/main/conversations_list.dart'; +import '/views/main/home.dart'; +import '/views/authentication/unauthenticated_landing.dart'; +import '/views/authentication/login.dart'; +import '/views/authentication/signup.dart'; void main() { - runApp(const Home()); + runApp(const MyApp()); } -class Home extends StatelessWidget { - const Home({Key? key}) : super(key: key); +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); static const String _title = 'Envelope'; @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( title: _title, - home: Scaffold( + 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: ConversationsList(), + child: Home(), ) - ) + ), + 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, + ), + + ), + ); } } diff --git a/mobile/lib/models/friends.dart b/mobile/lib/models/friends.dart new file mode 100644 index 0000000..832d637 --- /dev/null +++ b/mobile/lib/models/friends.dart @@ -0,0 +1,9 @@ + +class Friend{ + String id; + String username; + Friend({ + required this.id, + required this.username, + }); +} diff --git a/mobile/lib/views/authentication/login.dart b/mobile/lib/views/authentication/login.dart index 0a087f8..a2d6595 100644 --- a/mobile/lib/views/authentication/login.dart +++ b/mobile/lib/views/authentication/login.dart @@ -2,9 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; - -import '/views/main/conversations_list.dart'; - +import '/views/main/conversation_list.dart'; import '/utils/encryption/rsa_key_helper.dart'; import '/utils/encryption/aes_helper.dart'; import '/utils/storage/encryption_keys.dart'; @@ -67,42 +65,22 @@ class Login extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: 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: LoginWidget(), + 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) + } ) ), - 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, - ), - + body: const SafeArea( + child: LoginWidget(), ), ); } @@ -195,10 +173,14 @@ class _LoginWidgetState extends State { usernameController.text, passwordController.text, ).then((value) { + /* Navigator.of(context).popUntil((route) { print(route.isFirst); return route.isFirst; }); + */ + + Navigator.pushNamedAndRemoveUntil(context, '/home', ModalRoute.withName('/home')); }).catchError((error) { print(error); // TODO: Show error on interface }); diff --git a/mobile/lib/views/authentication/signup.dart b/mobile/lib/views/authentication/signup.dart index 99654cf..43af372 100644 --- a/mobile/lib/views/authentication/signup.dart +++ b/mobile/lib/views/authentication/signup.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; -import '/views/main/conversations_list.dart'; +import '/views/main/conversation_list.dart'; import '/utils/encryption/rsa_key_helper.dart'; import '/utils/encryption/aes_helper.dart'; @@ -71,43 +71,23 @@ class Signup extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: 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(), + 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) + } ) ), - 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, - ), - - ), + body: const SafeArea( + child: SignupWidget(), + ) ); } } diff --git a/mobile/lib/views/main/conversation_list.dart b/mobile/lib/views/main/conversation_list.dart new file mode 100644 index 0000000..1f414d4 --- /dev/null +++ b/mobile/lib/views/main/conversation_list.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ConversationList extends StatefulWidget { + const ConversationList({Key? key}) : super(key: key); + + @override + State createState() => _ConversationListState(); +} + +class _ConversationListState extends State { + final _suggestions = []; + final _biggerFont = const TextStyle(fontSize: 18); + + Widget list() { + + if (_suggestions.isEmpty) { + return const Center( + child: Text('No Conversations'), + ); + } + + return ListView.builder( + itemCount: _suggestions.length, + padding: const EdgeInsets.all(16.0), + itemBuilder: /*1*/ (context, i) { + //if (i >= _suggestions.length) { + // TODO: Check for more conversations here. Remove the itemCount to use this section + //_suggestions.addAll(generateWordPairs().take(10)); /*4*/ + //} + return Column( + children: [ + ListTile( + title: Text( + _suggestions[i], + style: _biggerFont, + ), + ), + const Divider(), + ] + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Envelope'), + ), + body: list(), + ); + + } +} diff --git a/mobile/lib/views/main/conversations_list.dart b/mobile/lib/views/main/conversations_list.dart deleted file mode 100644 index 29e6f2c..0000000 --- a/mobile/lib/views/main/conversations_list.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; - -import '/views/authentication/unauthenticated_landing.dart'; - -class ConversationsList extends StatefulWidget { - const ConversationsList({Key? key}) : super(key: key); - - @override - State createState() => _ConversationsListState(); -} - -class _ConversationsListState extends State { - @override - void initState() { - checkLogin(); - - super.initState(); - } - - final _suggestions = []; - final _biggerFont = const TextStyle(fontSize: 18); - - Future checkLogin() async { - SharedPreferences preferences = await SharedPreferences.getInstance(); - print(preferences.getBool('islogin')); - if (preferences.getBool('islogin') != true) { - setState(() { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const UnauthenticatedLandingWidget(), - )); - }); - } - } - - Widget list() { - - if (_suggestions.isEmpty) { - return const Center( - child: Text('No Conversations'), - ); - } - - return ListView.builder( - itemCount: _suggestions.length, - padding: const EdgeInsets.all(16.0), - itemBuilder: /*1*/ (context, i) { - //if (i >= _suggestions.length) { - // TODO: Check for more conversations here. Remove the itemCount to use this section - //_suggestions.addAll(generateWordPairs().take(10)); /*4*/ - //} - return Column( - children: [ - ListTile( - title: Text( - _suggestions[i], - style: _biggerFont, - ), - ), - const Divider(), - ] - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async => false, - child: Scaffold( - appBar: AppBar( - title: Text('Envelope'), - actions: [ - PopupMenuButton( - icon: const FaIcon(FontAwesomeIcons.ellipsisVertical, color: Colors.white, size: 40), - itemBuilder: (context) => [ - const PopupMenuItem( - value: 0, - child: Text("Settings"), - ), - const PopupMenuItem( - value: 1, - child: Text("Logout"), - ), - - ], - onSelected: (item) => selectedMenuItem(context, item), - ), - ], - ), - body: list(), - ), - ); - } - - void selectedMenuItem(BuildContext context, item) async { - switch (item) { - case 0: - print("Settings"); - break; - case 1: - SharedPreferences preferences = await SharedPreferences.getInstance(); - preferences.setBool('islogin', false); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const UnauthenticatedLandingWidget(), - )); - break; - } - } -} diff --git a/mobile/lib/views/main/friend_list.dart b/mobile/lib/views/main/friend_list.dart new file mode 100644 index 0000000..8d2f4af --- /dev/null +++ b/mobile/lib/views/main/friend_list.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import '/models/friends.dart'; +import '/views/main/friend_list_item.dart'; + +class FriendList extends StatefulWidget { + const FriendList({Key? key}) : super(key: key); + + @override + State createState() => _FriendListState(); +} + +class _FriendListState extends State { + List friends = [ + Friend(id: 'abc', username: 'Test1'), + Friend(id: 'abc', username: 'Test2'), + Friend(id: 'abc', username: 'Test3'), + Friend(id: 'abc', username: 'Test4'), + Friend(id: 'abc', username: 'Test5'), + ]; + + Widget list() { + + if (friends.isEmpty) { + return const Center( + child: Text('No Friends'), + ); + } + + return ListView.builder( + itemCount: friends.length, + shrinkWrap: true, + padding: const EdgeInsets.only(top: 16), + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, i) { + return FriendListItem( + id: friends[i].id, + username: friends[i].username, + ); + }, + ); + } + + @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("Friends",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),), + ], + ), + ) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16,left: 16,right: 16), + child: TextField( + decoration: 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 + ) + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16,left: 16,right: 16), + child: list(), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/views/main/friend_list_item.dart b/mobile/lib/views/main/friend_list_item.dart new file mode 100644 index 0000000..42c5215 --- /dev/null +++ b/mobile/lib/views/main/friend_list_item.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +class FriendListItem extends StatefulWidget{ + final String id; + final String username; + const FriendListItem({ + Key? key, + required this.id, + required this.username, + }) : super(key: key); + + @override + _FriendListItemState createState() => _FriendListItemState(); +} + +class _FriendListItemState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: (){ + }, + child: Container( + padding: const EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + // CircleAvatar( + // backgroundImage: NetworkImage(widget.imageUrl), + // maxRadius: 30, + // ), + //const SizedBox(width: 16), + Expanded( + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/views/main/home.dart b/mobile/lib/views/main/home.dart new file mode 100644 index 0000000..c157055 --- /dev/null +++ b/mobile/lib/views/main/home.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '/views/main/conversation_list.dart'; +import '/views/main/friend_list.dart'; + +class Home extends StatefulWidget { + const Home({Key? key}) : super(key: key); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + @override + void initState() { + checkLogin(); + super.initState(); + } + + Future checkLogin() async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + if (preferences.getBool('islogin') != true) { + Navigator.pushNamedAndRemoveUntil(context, '/landing', ModalRoute.withName('/landing')); + } + } + + int _selectedIndex = 0; + static const List _widgetOptions = [ + ConversationList(), + FriendList(), + Text('Not Implemented'), + ]; + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: Scaffold( + body: _widgetOptions.elementAt(_selectedIndex), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _selectedIndex, + onTap: _onItemTapped, + selectedItemColor: Colors.red, + unselectedItemColor: Colors.grey.shade600, + selectedLabelStyle: const TextStyle(fontWeight: FontWeight.w600), + unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w600), + type: BottomNavigationBarType.fixed, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.message), + label: "Chats", + ), + BottomNavigationBarItem( + icon: Icon(Icons.group_work), + label: "Friends", + ), + BottomNavigationBarItem( + icon: Icon(Icons.account_box), + label: "Profile", + ), + ], + ), + ), + ); + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 1a1eab8..8bafdc8 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -49,7 +49,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -70,7 +70,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: @@ -134,7 +134,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" lints: dependency: transitive description: @@ -155,7 +155,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -169,7 +169,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_provider_linux: dependency: transitive description: @@ -286,7 +286,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -321,7 +321,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" typed_data: dependency: transitive description: @@ -335,7 +335,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" win32: dependency: transitive description: @@ -351,5 +351,5 @@ packages: source: hosted version: "0.2.0+1" sdks: - dart: ">=2.16.2 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=2.8.0" diff --git a/mobile/windows/flutter/generated_plugins.cmake b/mobile/windows/flutter/generated_plugins.cmake index 4d10c25..b93c4c3 100644 --- a/mobile/windows/flutter/generated_plugins.cmake +++ b/mobile/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin)