From 229201350746c3197dd0591a35db64be1db3ee1c Mon Sep 17 00:00:00 2001 From: mhmzdev Date: Thu, 7 Oct 2021 17:44:05 +0500 Subject: [PATCH] initial push --- packages/simplex_app/android/app/build.gradle | 2 +- packages/simplex_app/android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/simplex_app/assets/code.png | Bin 0 -> 3962 bytes .../lib/animations/bottomAnimation.dart | 78 +++++ packages/simplex_app/lib/app_routes.dart | 5 + .../lib/custom_scroll_behavior.dart | 18 ++ packages/simplex_app/lib/main.dart | 24 +- .../lib/views/contacts/add_contact_view.dart | 134 ++++++++ .../views/contacts/qr_code_details_view.dart | 62 ++++ .../lib/views/group/add_group_view.dart | 285 ++++++++++++++++++ .../lib/views/home/home_view_widget.dart | 251 +++++++++++++-- .../scanInvitation/scan_invitation_view.dart | 115 +++++++ .../lib/views/setup_profile_view.dart | 33 +- .../simplex_app/lib/widgets/customBtn.dart | 30 ++ packages/simplex_app/pubspec.lock | 27 +- packages/simplex_app/pubspec.yaml | 20 +- 17 files changed, 1013 insertions(+), 77 deletions(-) create mode 100644 packages/simplex_app/assets/code.png create mode 100644 packages/simplex_app/lib/animations/bottomAnimation.dart create mode 100644 packages/simplex_app/lib/custom_scroll_behavior.dart create mode 100644 packages/simplex_app/lib/views/contacts/add_contact_view.dart create mode 100644 packages/simplex_app/lib/views/contacts/qr_code_details_view.dart create mode 100644 packages/simplex_app/lib/views/group/add_group_view.dart create mode 100644 packages/simplex_app/lib/views/scanInvitation/scan_invitation_view.dart create mode 100644 packages/simplex_app/lib/widgets/customBtn.dart 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 0000000000000000000000000000000000000000..dad19c705f0172965da26d841f86265e4c402b50 GIT binary patch literal 3962 zcmd5 z+>=qMFx(EY<$PC|<8~)=Xym!-`Tg#vo_@c7pVxD}_I`a{*XMhEuYJCs_vduoaCdXq zsGy|)0Ki5kM_W$-SO@zIIayGXK)4eJ3hA)Ju7?4jim12}ECb%}IOXW+3IK`v0B|`O z06v1M%M$<)X9NIKK>%R!001=4=G1yvf(x=IT^wvd30})b9$x~5{8>kI3@C%X;7_bg z2LNosJJ}vaUg)143U+(vsG0KS?eSz)arI@?j*P3mwnvyWea4M8qKC_ec4>*$$e=~( z3c~Z^?Q~?{_|fxbgz0fSg8T!pW$A=S1~{aaB=bb}zXaDpf_~g|Jw7Ie+3NGWwY9aE z-u33}&kYw>wa!>!hp!9VJKC4=h{eF?4d7!7%gcSZtpT=Y9D2oBCFKM>{%v&-gfrm;b@eXm}59o)7_b8P^0U`I#CFV-tKYXtIx5Y3sFTvcRvFKZ4%cAm zit?KlMg^}Xe|@dzc`(han%*o4nSn<8OPZP-CPv0FRyJoGhHD;)zPN(rl@`&{SD+FR8P*!$d zY*6mrquq5pYWHBs?=v^DkC$HlwDi7^!{Kbi5p?Vcn{Zna-rFEw=3PwI?!(YA~h(8O09^ zxl+~XxBpBnkz&laKu!Q3UfQI1d85?VXm#hke@|-a+`AgTHwF?uR_}iEiE(-y9zV8k zUP(#m#5L~)WH*OX%I{xA}}j&V5XYiy)cTcY74wH*vu6grRgz**r}o{59v@2+{b>Uc*> zX}-sZaGx0~93*@cRW;6?R!tytGpAyfu7?C+27e3BF5-D{#wi>J9^N9?c|G)0JBe>F zhv>~OcCI$dM%lLY_vhVABY7RttwvDi`pW7M=$^br>+jCp^2Bp`erEZ%g5X>I#{jneYPixc)=?G9mT?2d9p$*sVjUFo^cYCHtkZs^_Yfdh z3Wtj3T7ei@$(~#GxGmEnVgP@DwExA-?fmu7=|QVvF)~BF*Ev7+MgJ|hkET1cv?~5MQcUY4%;zA5q4MG3dUMe#k2%-5|YXHUq+Mon@e7F z@uq4`yXK1g7Mm)Hvf1Mll~5!PZnEm(Y8ND9YnbL2gc<^`>q+FwsmsOoxqu6LNpEA%(iVqAA})lo$>4INBEm7R2sAcAk3_tD2+B(DtPG@lx_WSdbI z5HUQpwH$qXs3xp8WEP4Q6Gw5qzbv{Gys)m3CuhmHu$p?Su8)CTW#p6EdY4Fr zvUk!*c%g>YkLz7#wJM*2Hhfcs9c44R)jB6)xwl4CypKJj74^J|em*+T;3S*DRqKSm z+Fj#zZf2i6^PV5ONB(})TK;)uT1{$rRv*1W;`s}NUE)u9v`E8E`H$0n>T9GR;d}Ob zjb@-o0ZiN0;s{*FRIoY7^(=w`xp@h#Q`1&;$Sk7kfX!@R*}aubih$ux2Jm{v|KK$t zh#hy;-f%NS{hDrdYJKXWO|Ni$Yk8+xVv>8Y(Y7?yJc^@Pov8UVz1(db>X)J^q~~p# zxr8J>Z5Ajv{a6&d$0J9IV(QdeQKW^ADWQcmzPAX|Wk~fnKZpQ5Gp(9$y4wWX9>F&s z5Q4G$$G82#?+*(ZlevKc?hTqVg>U&WN(lC>{W+zV?e!meyM>^-PPHE|?RvW%{C~q> zg^9WfY&#@{61croU?8dY2^47mw7zvL__*XDY!;hc^4itqZv3K!G7g^ECu6ls_5XL4*vE-ngV~ zFf<;E#wML@x8<>Yh@L;cNPex&H<(`b(Cce_R_936Hy$%Sbp;cvGkuRXS>l0GJxQe> zOO8&dXP@G7nafo-os`poT{4-2sM=j!FoW##Pimwz$XE9Zh;9HvBz0=i6 zc{#(Os=>xub4Z^Fx}oMkx7t(9uh)SPiF7=GwT@Rpe zJ!slbnn|hMSPwLJ<*Bu_UC)e zm^RR$7RySjBbY2^t$+5uWz!EAG3ggbE);2RmEtF%H zO3^1X0l#u@B5EyNKhryqEI0Pv5}bk{lBFecYzc;P)Ucusp?q}Ps%&cE0H%U{GJ7JM z&(j^sHOLGdTCBkE^Cp7zZur|bX?ypYbd}xG9SShWv={n})`z7B;NH-a9~7&3SB!0& ztQWKt3vs&N5gEYx~}PL=GmYDWkVcqO|mAlIx zlA%sb^bpFl(#u>A1l)Y0)x$ek6oIZPXfwBcIw!@NEE(y3>~ro$cXI2L&vRbKgvX|B zt8!v<^djf2MHt=Smq@U|qz$BX*-;R8@bkJo0^~wo^~HGUQ~9h=JaV;+A_e`3MsMT$ zvlnTE+ZS_}|BvC#Ua7ox%j9q1L_}4`lIZ)BFh64xsvY)qpKDjg`9OJ;6JafW>jD2~ z3Zpntdr_D}*AEbJ*cD}J8!Lb;NQ_&bu0LDhf`}XWJ+GBJ8X=HA( s{{Z-d8W~v_8JUz5SH9U085I&9n(+T`u$w<{2;2ZT*}2(P+5}zs2Ou9j?*IS* literal 0 HcmV?d00001 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