diff --git a/packages/simplex_app/lib/views/home/home_view_widget.dart b/packages/simplex_app/lib/views/contacts/contacts_view.dart similarity index 88% rename from packages/simplex_app/lib/views/home/home_view_widget.dart rename to packages/simplex_app/lib/views/contacts/contacts_view.dart index d93ddb7b5f..47a4444592 100644 --- a/packages/simplex_app/lib/views/home/home_view_widget.dart +++ b/packages/simplex_app/lib/views/contacts/contacts_view.dart @@ -1,21 +1,21 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:simplex_chat/animations/bottom_animation.dart'; import 'package:simplex_chat/app_routes.dart'; import 'package:simplex_chat/constants.dart'; import 'package:simplex_chat/model/contact.dart'; -import 'package:simplex_chat/model/group.dart'; import 'package:simplex_chat/views/conversation/conversation_view.dart'; -class HomeViewWidget extends StatefulWidget { - const HomeViewWidget({Key? key}) : super(key: key); +class ContactsView extends StatefulWidget { + const ContactsView({Key? key}) : super(key: key); @override - _HomeViewWidgetState createState() => _HomeViewWidgetState(); + _ContactsViewState createState() => _ContactsViewState(); } -class _HomeViewWidgetState extends State { +class _ContactsViewState extends State { bool? _eraseMedia = false; List _contactsList = []; // for storing contacts @@ -50,8 +50,20 @@ class _HomeViewWidgetState extends State { }); } + String? _photo; + String? _displayName; + + void _getUserData() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + setState(() { + _displayName = prefs.getString('displayName'); + _photo = prefs.getString('photo$_displayName'); + }); + } + @override void initState() { + _getUserData(); _getContacts(); super.initState(); } @@ -66,15 +78,23 @@ class _HomeViewWidgetState extends State { child: Center( child: Column( children: [ - Align( - alignment: Alignment.centerRight, - child: GestureDetector( - onTap: _addNewContacts, - child: SvgPicture.asset( - 'assets/logo.svg', - height: 40.0, + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text('Hi! $_displayName', style: kSmallHeadingStyle), + const Text('Good day!'), + ], ), - ), + const SizedBox(width: 10.0), + CircleAvatar( + backgroundImage: _photo == null + ? const AssetImage('assets/dp.png') as ImageProvider + : FileImage(File(_photo!)), + ) + ], ), const SizedBox(height: 15.0), Row( @@ -129,7 +149,7 @@ class _HomeViewWidgetState extends State { context, MaterialPageRoute( builder: (_) => ConversationView( - contact: _contactsList[index], + name: _contactsList[index].name, ), ), ), diff --git a/packages/simplex_app/lib/views/conversation/conversation_view.dart b/packages/simplex_app/lib/views/conversation/conversation_view.dart index fc94585322..0d69d54b31 100644 --- a/packages/simplex_app/lib/views/conversation/conversation_view.dart +++ b/packages/simplex_app/lib/views/conversation/conversation_view.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:simplex_chat/model/contact.dart'; import 'package:simplex_chat/widgets/message_bubble.dart'; class ConversationView extends StatefulWidget { - final Contact? contact; - const ConversationView({Key? key, @required this.contact}) : super(key: key); + final String? name; + const ConversationView({Key? key, @required this.name}) : super(key: key); @override _ConversationViewState createState() => _ConversationViewState(); @@ -43,7 +42,7 @@ class _ConversationViewState extends State { onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( appBar: AppBar( - title: Text('${widget.contact!.name}'), + title: Text('${widget.name}'), ), body: Column( children: [ diff --git a/packages/simplex_app/lib/views/group/add_group_view.dart b/packages/simplex_app/lib/views/group/add_group_view.dart index 45a278c139..b9b6c4c3fa 100644 --- a/packages/simplex_app/lib/views/group/add_group_view.dart +++ b/packages/simplex_app/lib/views/group/add_group_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/model/contact.dart'; import 'package:simplex_chat/model/group.dart'; import 'package:simplex_chat/widgets/custom_text_field.dart'; @@ -15,11 +16,31 @@ class AddGroupView extends StatefulWidget { } class _AddGroupViewState extends State { + bool _addMember = false; + List _contactsList = []; // for storing contacts + + final List _members = []; + final _formKey = GlobalKey(); final _displayNameController = TextEditingController(); final _descController = TextEditingController(); + // getting data from local storage FOR NOW!! + void _getContacts() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? _contacts = prefs.getString('contacts'); + setState(() { + _contactsList = List.from(Contact.decode(_contacts)); + }); + } + + @override + void initState() { + _getContacts(); + super.initState(); + } + @override void dispose() { _displayNameController.dispose(); @@ -73,18 +94,77 @@ class _AddGroupViewState extends State { hintText: 'e.g Friends from UK', ), const SizedBox(height: 10.0), + _members.isNotEmpty + ? const Text('Members Added') + : Container(), + SizedBox(height: _members.isNotEmpty ? 10.0 : 0.0), + _members.isNotEmpty + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate( + _members.length, + (index) => Padding( + padding: + const EdgeInsets.symmetric(horizontal: 5.0), + child: InkWell( + onTap: () { + setState(() { + _members.remove(_members[index]); + }); + }, + child: Container( + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: Colors.grey.withAlpha(100)), + child: Text(_members[index]), + ), + ), + ), + )), + ) + : Container(), + SizedBox(height: _members.isNotEmpty ? 10.0 : 0.0), ListTile( leading: const Icon(Icons.person_add), title: const Text('Add a member'), - onTap: () {}, + onTap: () { + setState(() { + _addMember = !_addMember; + }); + }, ), + SizedBox(height: _addMember ? 10.0 : 0.0), + _addMember ? const Text('Contacts Available') : Container(), + SizedBox(height: _addMember ? 10.0 : 0.0), + _addMember + ? ListView( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: List.generate( + _contactsList.length, + (index) => ListTile( + leading: const CircleAvatar( + backgroundImage: AssetImage('assets/dp.png'), + ), + title: Text(_contactsList[index].name!), + onTap: () { + setState(() { + _members.add(_contactsList[index].name!); + }); + }, + ), + ), + ) + : Container(), const Divider(height: 30.0), const ListTile( leading: CircleAvatar( backgroundImage: AssetImage('assets/dp.png'), ), title: Text('You'), - trailing: Text( + subtitle: Text( 'Owner', style: TextStyle(color: Colors.grey, fontSize: 12.0), )), @@ -123,7 +203,7 @@ class _AddGroupViewState extends State { Group( groupName: name, groupDescription: desc, - members: [], + members: _members, ), ]; _groups = _localList + _groups; diff --git a/packages/simplex_app/lib/views/group/group_view.dart b/packages/simplex_app/lib/views/group/group_view.dart index aae190e1bb..e86d278e5a 100644 --- a/packages/simplex_app/lib/views/group/group_view.dart +++ b/packages/simplex_app/lib/views/group/group_view.dart @@ -121,6 +121,13 @@ class _GroupViewState extends State { style: const TextStyle( fontSize: 11, color: Colors.grey), ), + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ConversationView( + name: _groupList[index].groupName), + ), + ), onLongPress: () => _conversationOptions(_groupList[index]), ), diff --git a/packages/simplex_app/lib/views/home/drawer.dart b/packages/simplex_app/lib/views/home/drawer.dart index cc3cd2ca8e..207e2fd90d 100644 --- a/packages/simplex_app/lib/views/home/drawer.dart +++ b/packages/simplex_app/lib/views/home/drawer.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:simplex_chat/app_routes.dart'; import 'package:simplex_chat/constants.dart'; import 'package:simplex_chat/providers/drawer_providers.dart'; @@ -123,7 +125,7 @@ class MyDrawer extends StatelessWidget { leading: const Icon(Icons.exit_to_app_rounded), title: const Text('Logout'), subtitle: const Text('Good bye! See you soon :)'), - onTap: () => Navigator.pop(context), + onTap: () => _logout(context), ), ], ), @@ -131,4 +133,18 @@ class MyDrawer extends StatelessWidget { ), ); } + + void _logout(BuildContext context) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + + await Navigator.pushNamedAndRemoveUntil( + context, + AppRoutes.setupProfile, + (route) => route.settings.name == AppRoutes.setupProfile ? true : false, + ); + String? _name = prefs.getString('displayName'); + await prefs.remove('displayName'); + await prefs.remove('fullName'); + await prefs.remove('photo$_name'); + } } diff --git a/packages/simplex_app/lib/views/home/home_view.dart b/packages/simplex_app/lib/views/home/home_view.dart index ec2e9fce79..61e123fc5f 100644 --- a/packages/simplex_app/lib/views/home/home_view.dart +++ b/packages/simplex_app/lib/views/home/home_view.dart @@ -1,12 +1,13 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; +import 'package:simplex_chat/constants.dart'; import 'package:simplex_chat/providers/drawer_providers.dart'; +import 'package:simplex_chat/views/contacts/contacts_view.dart'; import 'package:simplex_chat/views/group/group_view.dart'; - import 'package:simplex_chat/views/home/drawer.dart'; -import 'package:simplex_chat/views/home/home_view_widget.dart'; import 'package:simplex_chat/views/invitations/invitation_view.dart'; class HomeView extends StatefulWidget { @@ -26,7 +27,7 @@ class _HomeViewState extends State with TickerProviderStateMixin { // views final List _views = [ - const HomeViewWidget(), + const ContactsView(), const Invitations(), const GroupView(), ]; @@ -34,6 +35,7 @@ class _HomeViewState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); + animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 250)); } @@ -41,64 +43,69 @@ class _HomeViewState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final _drawerProviders = Provider.of(context); - return GestureDetector( - onHorizontalDragStart: _onDragStart, - onHorizontalDragUpdate: _onDragUpdate, - onHorizontalDragEnd: _onDragEnd, - behavior: HitTestBehavior.translucent, - child: AnimatedBuilder( - animation: animationController!, - builder: (context, _) { - return Material( - color: Colors.white70, - child: SafeArea( - child: Stack( - children: [ - Transform.translate( - offset: Offset( - widget.maxSlide! * (animationController!.value - 1), 0), - child: Transform( - transform: Matrix4.identity() - ..setEntry(3, 2, 0.001) - ..rotateY( - math.pi / 2 * (1 - animationController!.value)), - alignment: Alignment.centerRight, - child: MyDrawer( - animationController: animationController, - ), - ), - ), - Transform.translate( - offset: Offset( - widget.maxSlide! * animationController!.value, 0), - child: Transform( + return WillPopScope( + onWillPop: _onWillPop, + child: GestureDetector( + onHorizontalDragStart: _onDragStart, + onHorizontalDragUpdate: _onDragUpdate, + onHorizontalDragEnd: _onDragEnd, + behavior: HitTestBehavior.translucent, + child: AnimatedBuilder( + animation: animationController!, + builder: (context, _) { + return Material( + color: Colors.white70, + child: SafeArea( + child: Stack( + children: [ + Transform.translate( + offset: Offset( + widget.maxSlide! * (animationController!.value - 1), + 0), + child: Transform( transform: Matrix4.identity() ..setEntry(3, 2, 0.001) - ..rotateY(-math.pi / 2 * animationController!.value), - alignment: Alignment.centerLeft, - child: _views[_drawerProviders.currentIndex]), - ), - Positioned( - top: MediaQuery.of(context).padding.top, - left: MediaQuery.of(context).size.width * 0.03 + - animationController!.value * widget.maxSlide!, - child: InkWell( - onTap: () { - _drawerProviders.toggle(animationController); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - 'assets/menu.svg', + ..rotateY( + math.pi / 2 * (1 - animationController!.value)), + alignment: Alignment.centerRight, + child: MyDrawer( + animationController: animationController, ), ), ), - ), - ], + Transform.translate( + offset: Offset( + widget.maxSlide! * animationController!.value, 0), + child: Transform( + transform: Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateY( + -math.pi / 2 * animationController!.value), + alignment: Alignment.centerLeft, + child: _views[_drawerProviders.currentIndex]), + ), + Positioned( + top: MediaQuery.of(context).padding.top, + left: MediaQuery.of(context).size.width * 0.03 + + animationController!.value * widget.maxSlide!, + child: InkWell( + onTap: () { + _drawerProviders.toggle(animationController); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + 'assets/menu.svg', + ), + ), + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ); } @@ -133,4 +140,30 @@ class _HomeViewState extends State with TickerProviderStateMixin { animationController!.forward(); } } + + Future _onWillPop() async { + return (await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Exit Application', style: kMediumHeadingStyle), + content: const Text('Are You Sure?'), + actions: [ + TextButton( + child: const Text( + 'Yes', + style: TextStyle( + color: Colors.red, + ), + ), + onPressed: () => SystemNavigator.pop(), + ), + TextButton( + child: const Text('No'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + )) ?? + false; + } } diff --git a/packages/simplex_app/lib/views/setup_profile_view.dart b/packages/simplex_app/lib/views/setup_profile_view.dart index 368a98797d..03e66b2328 100644 --- a/packages/simplex_app/lib/views/setup_profile_view.dart +++ b/packages/simplex_app/lib/views/setup_profile_view.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:simplex_chat/constants.dart'; import 'package:simplex_chat/views/home/home_view.dart'; import 'package:simplex_chat/widgets/custom_text_field.dart'; @@ -19,6 +20,18 @@ class _SetupProfileViewState extends State { final TextEditingController _displayNameController = TextEditingController(); final TextEditingController _fullNameController = TextEditingController(); + // Image Picker --> DP properties + final imgPicker = ImagePicker(); + File? image; + String photoUrl = ''; + bool _uploading = false; + bool _imageUploaded = false; + + // image buttons options + final _dpBtnText = ['Gallery', 'Camera']; + final _dpBtnColors = [Colors.purple, Colors.green]; + final _dpBtnIcons = [Icons.photo_rounded, Icons.camera_alt_rounded]; + @override void dispose() { _displayNameController.dispose(); @@ -40,13 +53,50 @@ class _SetupProfileViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Align( - alignment: Alignment.centerLeft, - child: BackButton( - onPressed: () => Navigator.pop(context), + const SizedBox(height: 30), + Center( + child: SizedBox( + height: 180.0, + width: 180.0, + child: Stack( + children: [ + _imageUploaded + ? CircleAvatar( + radius: 100.0, + backgroundImage: FileImage(image!), + ) + : const CircleAvatar( + radius: 100.0, + backgroundImage: AssetImage('assets/dp.png'), + ), + Positioned( + right: 0, + bottom: 0, + child: FloatingActionButton( + backgroundColor: kSecondaryColor, + elevation: 2.0, + mini: true, + onPressed: _updateProfilePic, + child: _uploading + ? const SizedBox( + height: 18.0, + width: 18.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + valueColor: + AlwaysStoppedAnimation( + Colors.white), + ), + ) + : const Icon( + Icons.add_a_photo, + size: 20, + ), + ), + ) + ], ), - ), - const Center(child: UserProfilePic()), + )), const SizedBox(height: 25.0), const Text('Display Name', style: kSmallHeadingStyle), const SizedBox(height: 10.0), @@ -91,10 +141,13 @@ class _SetupProfileViewState extends State { visible: MediaQuery.of(context).viewInsets.bottom == 0, child: FloatingActionButton( heroTag: 'setup', - onPressed: () { + onPressed: () async { if (_formKey.currentState!.validate()) { FocusScope.of(context).unfocus(); - Navigator.push( + + await _createProfile(); + + await Navigator.push( context, MaterialPageRoute( builder: (_) => HomeView( @@ -102,6 +155,10 @@ class _SetupProfileViewState extends State { ), ), ); + + _displayNameController.clear(); + _fullNameController.clear(); + image = null; } }, child: const Icon(Icons.check), @@ -109,70 +166,16 @@ class _SetupProfileViewState extends State { ), ); } -} -class UserProfilePic extends StatefulWidget { - const UserProfilePic({Key? key}) : super(key: key); + // create profile and store in local + Future _createProfile() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('displayName', _displayNameController.text.trim()); + await prefs.setString('fullName', _fullNameController.text.trim()); + await prefs.setString( + 'photo${_displayNameController.text.trim()}', photoUrl); - @override - _UserProfilePicState createState() => _UserProfilePicState(); -} - -class _UserProfilePicState extends State { - // Image Picker --> DP properties - final imgPicker = ImagePicker(); - File? image; - String photoUrl = ''; - bool _uploading = false; - bool _imageUploaded = false; - - // image buttons options - final _dpBtnText = ['Gallery', 'Camera']; - final _dpBtnColors = [Colors.purple, Colors.green]; - final _dpBtnIcons = [Icons.photo_rounded, Icons.camera_alt_rounded]; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 180.0, - width: 180.0, - child: Stack( - children: [ - _imageUploaded - ? CircleAvatar( - radius: 100.0, - backgroundImage: FileImage(image!), - ) - : const CircleAvatar( - radius: 100.0, - backgroundImage: AssetImage('assets/dp.png'), - ), - Positioned( - right: 0, - bottom: 0, - child: FloatingActionButton( - backgroundColor: kSecondaryColor, - elevation: 2.0, - mini: true, - onPressed: _updateProfilePic, - child: _uploading - ? const SizedBox( - height: 18.0, - width: 18.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : const Icon( - Icons.add_a_photo, - size: 20, - ), - ), - ) - ], - ), - ); + debugPrint(prefs.getString('photo')); } void _updateProfilePic() { @@ -250,6 +253,7 @@ class _UserProfilePicState extends State { setState(() { _uploading = false; _imageUploaded = true; + photoUrl = file.path; }); } else { setState(() { @@ -279,6 +283,7 @@ class _UserProfilePicState extends State { setState(() { _uploading = false; _imageUploaded = true; + photoUrl = file.path; }); } else { setState(() { diff --git a/packages/simplex_app/lib/views/splash_screen.dart b/packages/simplex_app/lib/views/splash_screen.dart new file mode 100644 index 0000000000..24dbc57a8a --- /dev/null +++ b/packages/simplex_app/lib/views/splash_screen.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:progress_indicators/progress_indicators.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:simplex_chat/animations/entrance_fader.dart'; +import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/views/home/home_view.dart'; +import 'package:simplex_chat/views/onBoarding/intro_view.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + _SplashScreenState createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + // logincheck + void _loginCheck() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? _name = prefs.getString('displayName'); + Future.delayed(const Duration(seconds: 4), () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => _name == null + ? const IntroView() + : HomeView( + maxSlide: MediaQuery.of(context).size.width * 0.82, + ), + ), + ); + }); + } + + @override + void initState() { + _loginCheck(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + EntranceFader( + duration: const Duration(seconds: 1), + offset: const Offset(0, 20), + child: SvgPicture.asset( + 'assets/logo.svg', + height: 70, + ), + ), + EntranceFader( + offset: const Offset(0, 00), + duration: const Duration(seconds: 1), + delay: const Duration(seconds: 1), + child: JumpingDotsProgressIndicator( + fontSize: 40, + color: kPrimaryColor, + ), + ), + ], + ), + ), + ); + } +}