diff --git a/packages/simplex_app/android/app/build.gradle b/packages/simplex_app/android/app/build.gradle index 511daef851..04d8f4a42f 100644 --- a/packages/simplex_app/android/app/build.gradle +++ b/packages/simplex_app/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.simplex_chat" - minSdkVersion 16 + minSdkVersion 20 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/packages/simplex_app/android/build.gradle b/packages/simplex_app/android/build.gradle index ed45c65885..e722bc8ffe 100644 --- a/packages/simplex_app/android/build.gradle +++ b/packages/simplex_app/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/simplex_app/android/gradle/wrapper/gradle-wrapper.properties b/packages/simplex_app/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afdd..3df6b33899 100644 --- a/packages/simplex_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/simplex_app/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip diff --git a/packages/simplex_app/assets/code.png b/packages/simplex_app/assets/code.png new file mode 100644 index 0000000000..dad19c705f Binary files /dev/null and b/packages/simplex_app/assets/code.png differ diff --git a/packages/simplex_app/lib/animations/bottomAnimation.dart b/packages/simplex_app/lib/animations/bottomAnimation.dart new file mode 100644 index 0000000000..244214bb0e --- /dev/null +++ b/packages/simplex_app/lib/animations/bottomAnimation.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class Animator extends StatefulWidget { + final Widget child; + final Duration time; + + Animator(this.child, this.time); + + @override + _AnimatorState createState() => _AnimatorState(); +} + +class _AnimatorState extends State + with SingleTickerProviderStateMixin { + Timer? timer; + AnimationController? animationController; + Animation? animation; + + @override + void initState() { + super.initState(); + animationController = + AnimationController(duration: Duration(milliseconds: 290), vsync: this); + animation = + CurvedAnimation(parent: animationController!, curve: Curves.easeInOut); + timer = Timer(widget.time, animationController!.forward); + } + + @override + void dispose() { + animationController!.dispose(); + super.dispose(); + timer!.cancel(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animation!, + child: widget.child, + builder: (BuildContext context, Widget? child) { + return Opacity( + opacity: animation!.value, + child: Transform.translate( + offset: Offset(0.0, (1 - animation!.value) * 20), + child: child, + ), + ); + }, + ); + } +} + +Timer? timer; +Duration duration = Duration(); + +wait() { + if (timer == null || !timer!.isActive) { + timer = Timer(Duration(microseconds: 120), () { + duration = Duration(); + }); + } + duration += Duration(milliseconds: 100); + return duration; +} + +class WidgetAnimator extends StatelessWidget { + final Widget? child; + + WidgetAnimator({this.child}); + + @override + Widget build(BuildContext context) { + return Animator(child!, wait()); + } +} diff --git a/packages/simplex_app/lib/app_routes.dart b/packages/simplex_app/lib/app_routes.dart index d7df18dd6c..ef8c78c3b5 100644 --- a/packages/simplex_app/lib/app_routes.dart +++ b/packages/simplex_app/lib/app_routes.dart @@ -1,4 +1,9 @@ +import 'package:simplex_chat/views/scanInvitation/scan_invitation_view.dart'; + class AppRoutes { static final intro = '/intro'; static final setupProfile = '/setupProfile'; + static final addContact = '/addContact'; + static final scanInvitation = '/ScanInvitation'; + static final addGroup = '/addGroup'; } diff --git a/packages/simplex_app/lib/custom_scroll_behavior.dart b/packages/simplex_app/lib/custom_scroll_behavior.dart new file mode 100644 index 0000000000..f777304d79 --- /dev/null +++ b/packages/simplex_app/lib/custom_scroll_behavior.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class ScrollBehaviorModified extends ScrollBehavior { + const ScrollBehaviorModified(); + @override + ScrollPhysics getScrollPhysics(BuildContext context) { + switch (getPlatform(context)) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.android: + return const BouncingScrollPhysics(); + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return const ClampingScrollPhysics(); + } + } +} diff --git a/packages/simplex_app/lib/main.dart b/packages/simplex_app/lib/main.dart index 098d02410c..a6449903ec 100644 --- a/packages/simplex_app/lib/main.dart +++ b/packages/simplex_app/lib/main.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; import 'package:simplex_chat/app_routes.dart'; import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/custom_scroll_behavior.dart'; +import 'package:simplex_chat/views/contacts/add_contact_view.dart'; +import 'package:simplex_chat/views/group/add_group_view.dart'; import 'package:simplex_chat/views/onBoarding/intro_view.dart'; +import 'package:simplex_chat/views/scanInvitation/scan_invitation_view.dart'; import 'package:simplex_chat/views/setup_profile_view.dart'; void main() { @@ -32,24 +36,10 @@ class MyApp extends StatelessWidget { routes: { AppRoutes.intro: (_) => IntroView(), AppRoutes.setupProfile: (_) => SetupProfileView(), + AppRoutes.addContact: (_) => AddContactView(), + AppRoutes.scanInvitation: (_) => ScanInvitationView(), + AppRoutes.addGroup: (_) => AddGroupView(), }, ); } } - -class ScrollBehaviorModified extends ScrollBehavior { - const ScrollBehaviorModified(); - @override - ScrollPhysics getScrollPhysics(BuildContext context) { - switch (getPlatform(context)) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - case TargetPlatform.android: - return const BouncingScrollPhysics(); - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - return const ClampingScrollPhysics(); - } - } -} diff --git a/packages/simplex_app/lib/views/contacts/add_contact_view.dart b/packages/simplex_app/lib/views/contacts/add_contact_view.dart new file mode 100644 index 0000000000..9d17eaf1c0 --- /dev/null +++ b/packages/simplex_app/lib/views/contacts/add_contact_view.dart @@ -0,0 +1,134 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:simplex_chat/views/contacts/qr_code_details_view.dart'; + +class AddContactView extends StatefulWidget { + const AddContactView({Key? key}) : super(key: key); + + @override + _AddContactViewState createState() => _AddContactViewState(); +} + +class _AddContactViewState extends State { + final qrKey = GlobalKey(debugLabel: 'qr'); + QRViewController? _qrViewController; + Barcode? result; + + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + _qrViewController!.pauseCamera(); + } else if (Platform.isIOS) { + _qrViewController!.resumeCamera(); + } + } + + @override + void dispose() { + _qrViewController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Add Contact'), + actions: [ + IconButton( + icon: FutureBuilder( + future: _qrViewController?.getFlashStatus(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Icon(snapshot.data == false + ? Icons.flash_on + : Icons.flash_off); + } + return Icon(Icons.flash_off); + }, + ), + onPressed: () async { + _qrViewController?.toggleFlash(); + setState(() {}); + }, + ), + IconButton( + icon: FutureBuilder( + future: _qrViewController?.getCameraInfo(), + builder: (context, snapshot) { + return Icon(Icons.camera_alt); + }, + ), + onPressed: () async { + _qrViewController?.flipCamera(); + setState(() {}); + }, + ), + ], + ), + body: Container( + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Center( + child: _qrViewBuild(), + ), + Positioned( + top: MediaQuery.of(context).size.height * 0.15, + left: MediaQuery.of(context).size.width * 0.125, + child: const Text( + 'Position QR Code within the frame.', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 18.0, + ), + ), + ), + ], + ), + ), + ); + } + + QRView _qrViewBuild() { + var scanArea = (MediaQuery.of(context).size.width < 400 || + MediaQuery.of(context).size.height < 400) + ? 220.0 + : 370.0; + + return QRView( + key: qrKey, + onQRViewCreated: (QRViewController controller) { + this._qrViewController = controller; + controller.scannedDataStream.listen((scanData) { + setState(() async { + result = scanData; + controller.pauseCamera(); + if (result != null) { + await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => QRCodeDetailsView( + barcode: result, + ), + ), + ); + controller.resumeCamera(); + } + }); + }); + }, + overlay: QrScannerOverlayShape( + borderColor: Colors.red, + borderRadius: 0, + borderLength: 50, + borderWidth: 10, + cutOutSize: scanArea, + ), + ); + } +} diff --git a/packages/simplex_app/lib/views/contacts/qr_code_details_view.dart b/packages/simplex_app/lib/views/contacts/qr_code_details_view.dart new file mode 100644 index 0000000000..6bcaa6ac27 --- /dev/null +++ b/packages/simplex_app/lib/views/contacts/qr_code_details_view.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/widgets/customBtn.dart'; + +class QRCodeDetailsView extends StatelessWidget { + final Barcode? barcode; + const QRCodeDetailsView({ + Key? key, + this.barcode, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('QR Code Result'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + backgroundImage: AssetImage('assets/dp.png'), + radius: 90, + ), + const SizedBox(height: 30.0), + Text( + barcode!.code, + style: kMediumHeadingStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 30.0), + CustomButton( + width: 200, + height: 40, + onPressed: () => Navigator.pop(context), + color: kPrimaryColor, + child: const Text( + 'Confirm', + style: TextStyle(color: Colors.white), + ), + ), + const SizedBox(height: 10.0), + CustomButton( + width: 200, + height: 40, + onPressed: () => Navigator.pop(context), + color: kSecondaryColor, + child: const Text( + 'Ignore', + style: TextStyle(color: Colors.white), + ), + ), + const SizedBox(height: 40.0), + const Text('Invitation was sent HH:MM') + ], + ), + ), + ); + } +} diff --git a/packages/simplex_app/lib/views/group/add_group_view.dart b/packages/simplex_app/lib/views/group/add_group_view.dart new file mode 100644 index 0000000000..91adf3c897 --- /dev/null +++ b/packages/simplex_app/lib/views/group/add_group_view.dart @@ -0,0 +1,285 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/widgets/customTextField.dart'; + +class AddGroupView extends StatefulWidget { + const AddGroupView({Key? key}) : super(key: key); + + @override + _AddGroupViewState createState() => _AddGroupViewState(); +} + +class _AddGroupViewState extends State { + final _displayNameController = TextEditingController(); + + @override + void dispose() { + _displayNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + appBar: AppBar( + title: const Text('New Group'), + ), + body: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 10.0), + Center( + child: GroupDP(), + ), + const SizedBox(height: 25.0), + const Text('Group Name', style: kSmallHeadingStyle), + const SizedBox(height: 10.0), + CustomTextField( + textEditingController: _displayNameController, + textInputType: TextInputType.name, + hintText: 'e.g College friends', + validatorFtn: (value) { + if (value!.isEmpty) { + return "Group name cannot be empty!"; + } + return null; + }, + ), + const SizedBox(height: 10.0), + ListTile( + leading: const Icon(Icons.person_add), + title: const Text('Add a member'), + onTap: () {}, + ), + const Divider(height: 30.0), + ListTile( + leading: CircleAvatar( + backgroundImage: AssetImage('assets/dp.png'), + ), + title: const Text('You'), + trailing: const Text( + 'Owner', + style: TextStyle(color: Colors.grey, fontSize: 12.0), + )), + ], + ), + ), + floatingActionButton: Visibility( + visible: MediaQuery.of(context).viewInsets.bottom == 0, + child: FloatingActionButton( + heroTag: 'setup', + onPressed: () { + FocusScope.of(context).unfocus(); + Navigator.pop(context); + }, + child: const Icon(Icons.check), + ), + ), + ), + ); + } +} + +class GroupDP extends StatefulWidget { + const GroupDP({Key? key}) : super(key: key); + + @override + _GroupDPState createState() => _GroupDPState(); +} + +class _GroupDPState extends State { + // Image Picker --> DP properties + final imgPicker = ImagePicker(); + File? image; + String photoUrl = ""; + bool _uploading = false; + bool _imageUploaded = false; + + // image buttons options + final _dpBtnText = ["Remove", "Gallery", "Camera"]; + final _dpBtnColors = [Colors.red, Colors.purple, Colors.green]; + final _dpBtnIcons = [ + Icons.delete, + Icons.photo_rounded, + Icons.camera_alt_rounded + ]; + + @override + Widget build(BuildContext context) { + return Container( + height: 180.0, + width: 180.0, + child: Stack( + children: [ + _imageUploaded + ? CircleAvatar( + radius: 100.0, + backgroundImage: FileImage(image!), + ) + : 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 + ? SizedBox( + height: 18.0, + width: 18.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Icon( + Icons.add_a_photo, + size: 20, + ), + ), + ) + ], + ), + ); + } + + void _updateProfilePic() { + showModalBottomSheet( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + ), + ), + context: context, + builder: (context) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(360.0)), + height: 7.0, + width: 50.0, + ), + const SizedBox(height: 20.0), + Align( + alignment: Alignment.centerLeft, + child: Text( + " Profile photo", + style: kHeadingStyle, + ), + ), + const SizedBox(height: 15.0), + Row( + children: List.generate( + 3, + (index) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + MaterialButton( + color: _dpBtnColors.map((e) => e).elementAt(index), + shape: CircleBorder(), + onPressed: index == 0 + ? () => _removePic() + : index == 1 + ? () => _galleryPic() + : () => _cameraPic(), + child: Icon( + _dpBtnIcons.map((e) => e).elementAt(index), + color: Colors.white, + ), + ), + Text( + _dpBtnText.map((e) => e).elementAt(index), + textAlign: TextAlign.center, + ) + ], + ), + )) + ], + ), + ), + ); + } + + void _removePic() { + setState(() { + _imageUploaded = false; + image = null; + }); + Navigator.pop(context); + } + + void _cameraPic() async { + try { + setState(() { + _uploading = true; + }); + + // picking Image from Camera + final file = await imgPicker.getImage( + source: ImageSource.camera, + ); + + if (file != null) { + image = File(file.path); + setState(() { + _uploading = false; + _imageUploaded = true; + }); + } else { + setState(() { + _uploading = false; + }); + } + + Navigator.pop(context); + } catch (e) { + throw e; + } + } + + void _galleryPic() async { + try { + setState(() { + _uploading = true; + }); + + // picking Image from local storage + final file = await imgPicker.getImage( + source: ImageSource.gallery, + ); + + if (file != null) { + image = File(file.path); + setState(() { + _uploading = false; + _imageUploaded = true; + }); + } else { + setState(() { + _uploading = false; + }); + } + + Navigator.pop(context); + } catch (e) { + throw e; + } + } +} diff --git a/packages/simplex_app/lib/views/home/home_view_widget.dart b/packages/simplex_app/lib/views/home/home_view_widget.dart index 124633d5fe..e3822c7be7 100644 --- a/packages/simplex_app/lib/views/home/home_view_widget.dart +++ b/packages/simplex_app/lib/views/home/home_view_widget.dart @@ -1,9 +1,41 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:simplex_chat/animations/bottomAnimation.dart'; +import 'package:simplex_chat/app_routes.dart'; import 'package:simplex_chat/constants.dart'; -class HomeViewWidget extends StatelessWidget { - const HomeViewWidget({Key? key}) : super(key: key); +class HomeViewWidget extends StatefulWidget { + HomeViewWidget({Key? key}) : super(key: key); + + @override + _HomeViewWidgetState createState() => _HomeViewWidgetState(); +} + +class _HomeViewWidgetState extends State { + bool _haveConnections = false; + bool? _eraseMedia = false; + + final List _options = [ + 'Add contact', + 'Scan invitation', + 'New group', + ]; + + final List _userNames = [ + 'Bob', + 'John', + 'Alice', + 'Arya', + 'Maria', + ]; + + final List _lastMsg = [ + 'Okay!', + 'Hey there!', + 'Will get back to you.', + 'Hi', + 'Okay got it', + ]; @override Widget build(BuildContext context) { @@ -17,32 +49,62 @@ class HomeViewWidget extends StatelessWidget { children: [ Align( alignment: Alignment.centerRight, - child: SvgPicture.asset( - 'assets/logo.svg', - height: 40.0, - ), - ), - const SizedBox(height: 30.0), - Container( - height: MediaQuery.of(context).size.height * 0.7, - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - "You don't have any conversation yet!", - style: kMediumHeadingStyle, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8.0), - const Text( - "Click the icon below to add a contact", - textAlign: TextAlign.center, - ), - ], + child: GestureDetector( + onTap: () { + setState(() { + _haveConnections = !_haveConnections; + }); + }, + child: SvgPicture.asset( + 'assets/logo.svg', + height: 40.0, ), ), - ) + ), + const SizedBox(height: 10.0), + !_haveConnections + ? Container( + height: MediaQuery.of(context).size.height * 0.7, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "You don't have any conversation yet!", + style: kMediumHeadingStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8.0), + const Text( + "Click the icon below to add a contact", + textAlign: TextAlign.center, + ), + ], + ), + ), + ) + : ListView( + shrinkWrap: true, + children: List.generate( + _userNames.length, + (index) => WidgetAnimator( + child: ListTile( + leading: CircleAvatar( + backgroundImage: AssetImage('assets/dp.png'), + ), + title: Text(_userNames[index]), + subtitle: Text(_lastMsg[index]), + trailing: const Text( + 'Just now', + style: + TextStyle(color: Colors.grey, fontSize: 12), + ), + onTap: () {}, + onLongPress: _conversationOptions, + ), + ), + ), + ), ], ), ), @@ -53,11 +115,16 @@ class HomeViewWidget extends StatelessWidget { borderRadius: BorderRadius.circular(5.0), ), offset: Offset(-10, -180), - itemBuilder: (context) => [ - 'Add contact', - 'Scan invitation', - 'New group', - ] + onSelected: (value) { + if (value == _options[0]) { + Navigator.pushNamed(context, AppRoutes.addContact); + } else if (value == _options[1]) { + Navigator.pushNamed(context, AppRoutes.scanInvitation); + } else { + Navigator.pushNamed(context, AppRoutes.addGroup); + } + }, + itemBuilder: (context) => _options .map( (opt) => PopupMenuItem( value: opt, @@ -75,4 +142,126 @@ class HomeViewWidget extends StatelessWidget { ), ); } + + void _conversationOptions() { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + _deleteConversation(); + }, + child: const Text( + 'Delete Conversation', + style: TextStyle(color: Colors.red), + )), + TextButton( + onPressed: () { + Navigator.pop(context); + _disconnect(); + }, + child: const Text('Disconnect')), + ], + ), + ), + ); + } + + void _deleteConversation() { + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: const Text('Are you Sure?'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'All conversation history will be deleted from your device!'), + const SizedBox(height: 15.0), + Row( + children: [ + Checkbox( + value: _eraseMedia, + onChanged: (value) { + setState(() { + _eraseMedia = value; + }); + }), + const Text('Erase files & Media') + ], + ), + ], + ), + actions: [ + InkWell( + onTap: () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const Icon(Icons.check, color: Colors.green), + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const Icon(Icons.cancel_outlined, color: Colors.red), + ), + ) + ], + ), + ), + ); + } + + void _disconnect() { + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: const Text('Are you Sure?'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Disconnecting will erase all the data from your device and you will no longer be able to contact again!'), + const SizedBox(height: 15.0), + Row( + children: [ + Checkbox( + value: _eraseMedia, + onChanged: (value) { + setState(() { + _eraseMedia = value; + }); + }), + const Text('Erase files & Media') + ], + ), + ], + ), + actions: [ + InkWell( + onTap: () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const Icon(Icons.check, color: Colors.green), + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const Icon(Icons.cancel_outlined, color: Colors.red), + ), + ) + ], + ), + ), + ); + } } diff --git a/packages/simplex_app/lib/views/scanInvitation/scan_invitation_view.dart b/packages/simplex_app/lib/views/scanInvitation/scan_invitation_view.dart new file mode 100644 index 0000000000..1b494e19c2 --- /dev/null +++ b/packages/simplex_app/lib/views/scanInvitation/scan_invitation_view.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:simplex_chat/constants.dart'; +import 'package:simplex_chat/widgets/customBtn.dart'; +import 'package:share/share.dart'; + +class ScanInvitationView extends StatelessWidget { + const ScanInvitationView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: const Text('Share QR Code'), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Display this QR Code for your contact to scan.', + style: kMediumHeadingStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 25.0), + GestureDetector( + onTap: () => _showConnection(context), + child: Image.asset( + 'assets/code.png', + ), + ), + const SizedBox(height: 25.0), + const Text( + 'If you cannot share your QR Code, send the invitation via a trusted method.', + style: kMediumHeadingStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 30.0), + CustomButton( + width: 200.0, + height: 45.0, + onPressed: _shareLink, + color: kPrimaryColor, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.share, color: Colors.white), + const SizedBox(width: 8.0), + const Text( + 'Share', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void _showConnection(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar( + backgroundImage: AssetImage('assets/dp.png'), + radius: 70, + ), + const SizedBox(height: 30.0), + const Text( + 'Bob wants to connect with you!', + style: kMediumHeadingStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 30.0), + CustomButton( + width: 200, + height: 40, + onPressed: () => Navigator.pop(context), + color: kPrimaryColor, + child: const Text( + 'Confirm', + style: TextStyle(color: Colors.white), + ), + ), + const SizedBox(height: 10.0), + CustomButton( + width: 200, + height: 40, + onPressed: () => Navigator.pop(context), + color: kSecondaryColor, + child: const Text( + 'Ignore', + style: TextStyle(color: Colors.white), + ), + ), + const SizedBox(height: 20.0), + const Text('Invitation was sent HH:MM') + ], + ), + ), + ); + } + + void _shareLink() { + Share.share('Add me to SimpleX Chat via link https://someLinkHere.io'); + } +} diff --git a/packages/simplex_app/lib/views/setup_profile_view.dart b/packages/simplex_app/lib/views/setup_profile_view.dart index 2b4924bdce..f27de68025 100644 --- a/packages/simplex_app/lib/views/setup_profile_view.dart +++ b/packages/simplex_app/lib/views/setup_profile_view.dart @@ -87,22 +87,25 @@ class _SetupProfileViewState extends State { ), ), ), - floatingActionButton: FloatingActionButton( - heroTag: 'setup', - onPressed: () { - if (_formKey.currentState!.validate()) { - FocusScope.of(context).unfocus(); - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => HomeView( - maxSlide: MediaQuery.of(context).size.width * 0.82, + floatingActionButton: Visibility( + visible: MediaQuery.of(context).viewInsets.bottom == 0, + child: FloatingActionButton( + heroTag: 'setup', + onPressed: () { + if (_formKey.currentState!.validate()) { + FocusScope.of(context).unfocus(); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => HomeView( + maxSlide: MediaQuery.of(context).size.width * 0.82, + ), ), - ), - ); - } - }, - child: const Icon(Icons.check), + ); + } + }, + child: const Icon(Icons.check), + ), ), ); } diff --git a/packages/simplex_app/lib/widgets/customBtn.dart b/packages/simplex_app/lib/widgets/customBtn.dart new file mode 100644 index 0000000000..fbe85c9e70 --- /dev/null +++ b/packages/simplex_app/lib/widgets/customBtn.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + const CustomButton({ + @required this.width, + @required this.height, + @required this.onPressed, + @required this.color, + @required this.child, + }); + final double? width; + final double? height; + final void Function()? onPressed; + final Widget? child; + final Color? color; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: width, + height: height, + child: MaterialButton( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)), + color: color, + onPressed: onPressed, + child: child, + ), + ); + } +} diff --git a/packages/simplex_app/pubspec.lock b/packages/simplex_app/pubspec.lock index 80eb307f34..a5af322596 100644 --- a/packages/simplex_app/pubspec.lock +++ b/packages/simplex_app/pubspec.lock @@ -65,7 +65,7 @@ packages: source: hosted version: "1.2.0" file_picker: - dependency: "direct dev" + dependency: "direct main" description: name: file_picker url: "https://pub.dartlang.org" @@ -91,7 +91,7 @@ packages: source: hosted version: "2.0.3" flutter_svg: - dependency: "direct dev" + dependency: "direct main" description: name: flutter_svg url: "https://pub.dartlang.org" @@ -122,7 +122,7 @@ packages: source: hosted version: "4.0.0" image_picker: - dependency: "direct dev" + dependency: "direct main" description: name: image_picker url: "https://pub.dartlang.org" @@ -170,6 +170,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -212,6 +219,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + qr_code_scanner: + dependency: "direct main" + description: + name: qr_code_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.2" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" sky_engine: dependency: transitive description: flutter diff --git a/packages/simplex_app/pubspec.yaml b/packages/simplex_app/pubspec.yaml index 12b6260264..6ebc330d17 100644 --- a/packages/simplex_app/pubspec.yaml +++ b/packages/simplex_app/pubspec.yaml @@ -34,6 +34,19 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + + # QR Code + qr_code_scanner: ^0.5.2 + + # svg + flutter_svg: ^0.22.0 + + # attachments + image_picker: ^0.7.5+3 + file_picker: ^3.0.2+2 + + # share + share: ^2.0.4 dev_dependencies: flutter_test: @@ -46,13 +59,6 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^1.0.0 - # svg - flutter_svg: ^0.22.0 - - # attachments - image_picker: ^0.7.5+3 - file_picker: ^3.0.2+2 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec