diff --git a/android/app/build.gradle b/android/app/build.gradle index 68b82cf..0530d98 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -51,7 +51,7 @@ android { applicationId "com.example.cims_apps" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c536cf0..1c0330d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + SubmissionDataViewModel(), + ) + ], + builder: (context, child) { + return Consumer( + builder: (context, provider, child) { + return Scaffold( + appBar: AppBar( + title: const Text('Preview'), + automaticallyImplyLeading: false, + ), + body: Container( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: SizeConfig.width, + height: SizeConfig.height * .4, + child: Image.file(File(imagePath))), + const Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text( + 'Make sure the photo meets the requirements:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + color: ColorPalette.slate800, + ), + ), + ), + Wrap( + alignment: WrapAlignment.spaceBetween, + spacing: 8, + runSpacing: 8, + children: List.generate(4, (index) { + List filteredList = listIcons + .where((element) => element['key'] == content) + .toList(); + final urlImg = filteredList[index]['urlImg']; + final tag = filteredList[index]['tag']; + + return Column( + children: [ + Container( + width: SizeConfig.width * .42, + height: SizeConfig.height * .15, + padding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 8.0), + decoration: BoxDecoration( + color: ColorPalette.blue50, + borderRadius: BorderRadius.circular(6.0)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.only(bottom: 8.0), + child: ImageView( + image: urlImg, + width: SizeConfig.width * .1, + ), + ), + Expanded( + child: Text( + tag, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + color: ColorPalette.slate800, + fontWeight: FontWeight.normal), + ), + ), + ], + ), + ), + ], + ); + }), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: SizeConfig.width * .42, + child: ButtonView( + name: 'Retake', + isOutlined: true, + marginVertical: 8.0, + onPressed: () { + provider.initCamera().then((cameras) { + routePush(context, + page: TakePictureScreen( + camera: cameras.first, + takeContent: content, + )); + }); + }, + ), + ), + SizedBox( + width: SizeConfig.width * .42, + child: ButtonView( + marginVertical: 8.0, + name: 'Next', + onPressed: () { + provider.nextSubmission(context); + routePush(context, + page: const SubmissionParent()); + }, + ), + ), + ], + ) + ], + )), + ); + }); + }); + } +} diff --git a/lib/application/component/take_picture_screen/take_picture_screen.dart b/lib/application/component/take_picture_screen/take_picture_screen.dart new file mode 100644 index 0000000..a659c32 --- /dev/null +++ b/lib/application/component/take_picture_screen/take_picture_screen.dart @@ -0,0 +1,140 @@ +import 'package:camera/camera.dart'; +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/component/take_picture_screen/DisplayPictureScreen.dart'; +import 'package:cims_apps/core/route/route.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:flutter/material.dart'; + +class TakePictureScreen extends StatefulWidget { + const TakePictureScreen({ + super.key, + required this.camera, + required this.takeContent, + }); + + final CameraDescription camera; + final String takeContent; + + @override + TakePictureScreenState createState() => TakePictureScreenState(); +} + +class TakePictureScreenState extends State { + late CameraController _controller; + late Future _initializeControllerFuture; + bool isFlash = false; + late String _takeContent; + + Future changeFlash() async { + await _controller.setFlashMode(FlashMode.auto); + setState(() { + isFlash = !isFlash; + }); + } + + @override + void initState() { + super.initState(); + _controller = CameraController( + widget.camera, + ResolutionPreset.medium, + enableAudio: false, + ); + // Next, initialize the controller. This returns a Future. + _initializeControllerFuture = _controller.initialize(); + _takeContent = widget.takeContent; + } + + @override + void dispose() { + // Dispose of the controller when the widget is disposed. + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Fill this out in the next steps. + return Scaffold( + appBar: AppBar( + title: const Text('Registration'), + actions: [ + IconButton( + onPressed: () { + changeFlash(); + }, + icon: Icon(isFlash ? Icons.flash_on : Icons.flash_off)) + ], + ), + body: FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview. + return Stack( + children: [ + SizedBox( + width: SizeConfig.width, + height: SizeConfig.height, + child: CameraPreview(_controller)), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.5), + ], + ), + ), + child: ImageView( + image: _takeContent == 'ktp' + ? PathAssets.imgBgKtp + : PathAssets.imgBgSelfie, + width: SizeConfig.width, + height: SizeConfig.height, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: IconButton( + onPressed: () async { + try { + // Ensure that the camera is initialized. + await _initializeControllerFuture; + + // Attempt to take a picture and get the file `image` + // where it was saved. + final image = await _controller.takePicture(); + + if (!mounted) return; + routePush(context, + page: DisplayPictureScreen( + imagePath: image.path, + content: _takeContent, + ), + routeType: RouteType.pushReplace); + } catch (e) { + // If an error occurs, log the error to the console. + debugPrint(e.toString()); + } + }, + icon: Icon( + Icons.album_outlined, + color: Colors.white, + size: SizeConfig.width * .16, + ), + ), + ) + ], + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } +} diff --git a/lib/application/component/text_form/text_form_view.dart b/lib/application/component/text_form/text_form_view.dart index 542c6f6..4da4e2e 100644 --- a/lib/application/component/text_form/text_form_view.dart +++ b/lib/application/component/text_form/text_form_view.dart @@ -70,7 +70,6 @@ class TextFormView extends StatelessWidget { this.preffixIconConstraints, this.disableColor = false, this.enableInteractiveSelection = true, - this.contentPadding, this.focusNode, this.isTextAlignCenter = false, this.prefix}) @@ -97,8 +96,6 @@ class TextFormView extends StatelessWidget { name, style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.w600, - color: ColorPalette.slate800 // color: ColorPalette.greyLight, ), ), @@ -116,8 +113,6 @@ class TextFormView extends StatelessWidget { name, style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.w600, - color: ColorPalette.slate800 ), ) : const SizedBox(), @@ -133,7 +128,7 @@ class TextFormView extends StatelessWidget { initialValue: initialValue, enabled: enabled, controller: ctrl, - // maxLength: maxLength, + maxLength: maxLength, keyboardType: keyboardType, onTap: onTap, onEditingComplete: onSubmit, @@ -195,7 +190,10 @@ class TextFormView extends StatelessWidget { suffixIconConstraints: suffixIconConstraints, prefixIconConstraints: preffixIconConstraints, prefix: prefix, - contentPadding: contentPadding ?? EdgeInsets.zero + contentPadding: contentPadding ?? const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 16.0, + ) ), ) ], diff --git a/lib/application/theme/color_palette.dart b/lib/application/theme/color_palette.dart index 584a872..22711ea 100644 --- a/lib/application/theme/color_palette.dart +++ b/lib/application/theme/color_palette.dart @@ -92,7 +92,6 @@ class ColorPalette { static const Color green400 = Color(0xFF4ADE80); static const Color green500 = Color(0xFF16A34A); - static const Map investTypeColor = { 'Money Market': purple500, 'Shares': orange500, diff --git a/lib/features/auth/registration/view/submission_data/initial_take_photo.dart b/lib/features/auth/registration/view/submission_data/initial_take_photo.dart deleted file mode 100644 index 10f20a2..0000000 --- a/lib/features/auth/registration/view/submission_data/initial_take_photo.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:cims_apps/application/assets/path_assets.dart'; -import 'package:cims_apps/application/component/button/button_view.dart'; -import 'package:cims_apps/application/component/image/image_view.dart'; -import 'package:cims_apps/application/component/text_caption/text_caption.dart'; -import 'package:cims_apps/application/theme/color_palette.dart'; -import 'package:cims_apps/core/utils/size_config.dart'; -import 'package:flutter/material.dart'; - -class InitialTakePhoto extends StatelessWidget { - const InitialTakePhoto({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - List listImg = [ - {'urlImg': PathAssets.imgKtpBlur, 'tag': 'Blurry Photo'}, - {'urlImg': PathAssets.imgKtpLight, 'tag': 'Light Reflection'}, - {'urlImg': PathAssets.imgKtpCropped, 'tag': 'Cropped Photo'}, - {'urlImg': PathAssets.imgKtpClear, 'tag': 'Clear Photo'}, - ]; - return SizedBox( - height: SizeConfig.height * .75, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const TextCaption( - title: 'Take a photo your ID card', - subtitle: - 'Make sure your photo is clearly legible for identity verification purposes', - ), - SizedBox( - width: SizeConfig.height, - child: Wrap( - alignment: WrapAlignment.spaceBetween, - spacing: 10, - runSpacing: 10, - children: List.generate(listImg.length, (index) { - final urlList = listImg[index]['urlImg']; - final tag = listImg[index]['tag']; - return Column( - children: [ - ImageView( - image: urlList, - width: SizeConfig.width * .42, - ), - const SizedBox( - height: 8, - ), - Text( - tag, - style: const TextStyle( - color: ColorPalette.slate800, - fontWeight: FontWeight.w600), - ), - ], - ); - }), - ), - ), - // const Spacer(), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ImageView( - image: PathAssets.iconShield, - width: 20, - height: 22, - ), - SizedBox( - width: 8, - ), - Expanded( - child: Text( - 'In accordance with OJK regulations, an ID card is required to purchase mutual funds.', - style: TextStyle( - fontWeight: FontWeight.w600, - color: ColorPalette.primary, - ), - ), - ) - ], - ), - ButtonView( - name: 'Take a Photo', - marginVertical: 16.0, - onPressed: () {}, - ) - ], - ), - ); - } -} diff --git a/lib/features/auth/registration/view/submission_data/submission_parent.dart b/lib/features/auth/registration/view/submission_data/submission_parent.dart index 087e8dc..585d7c3 100644 --- a/lib/features/auth/registration/view/submission_data/submission_parent.dart +++ b/lib/features/auth/registration/view/submission_data/submission_parent.dart @@ -2,7 +2,7 @@ import 'package:cims_apps/application/component/button/button_view.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; import 'package:cims_apps/core/route/route.dart'; import 'package:cims_apps/core/utils/size_config.dart'; -import 'package:cims_apps/features/auth/registration/view/submission_data/initial_take_photo.dart'; +import 'package:cims_apps/features/auth/registration/view/submission_data/submit_photo_ktp.dart'; import 'package:cims_apps/features/auth/registration/view/submission_data/submit_email.dart'; import 'package:cims_apps/features/auth/registration/view/submission_data/submit_personal_data.dart'; import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart'; @@ -40,7 +40,7 @@ class _SubmissionParentState extends State { case 2: return const SubmitEmail(); case 3: - return const InitialTakePhoto(); + return const SubmitPhotoKtp(); case 4: return Container( child: Text("Step 4"), @@ -101,7 +101,7 @@ class _SubmissionParentState extends State { provider.stepAmount, (index) => _stepItem( isCurrentStep: - provider.currentStep == index + 1, + provider.getCurrentStep == index + 1, ), ), ), @@ -110,10 +110,10 @@ class _SubmissionParentState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: _content(provider.currentStep), + child: _content(provider.getCurrentStep), ), ), - provider.currentStep == 3 + provider.getCurrentStep == 3 ? const SizedBox() : Align( alignment: Alignment.bottomCenter, diff --git a/lib/features/auth/registration/view/submission_data/submit_photo_ktp.dart b/lib/features/auth/registration/view/submission_data/submit_photo_ktp.dart new file mode 100644 index 0000000..d01b583 --- /dev/null +++ b/lib/features/auth/registration/view/submission_data/submit_photo_ktp.dart @@ -0,0 +1,114 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/component/take_picture_screen/take_picture_screen.dart'; +import 'package:cims_apps/application/component/text_caption/text_caption.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/route/route.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SubmitPhotoKtp extends StatelessWidget { + const SubmitPhotoKtp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + List listImg = [ + {'urlImg': PathAssets.imgKtpBlur, 'tag': 'Blurry Photo'}, + {'urlImg': PathAssets.imgKtpLight, 'tag': 'Light Reflection'}, + {'urlImg': PathAssets.imgKtpCropped, 'tag': 'Cropped Photo'}, + {'urlImg': PathAssets.imgKtpClear, 'tag': 'Clear Photo'}, + ]; + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => SubmissionDataViewModel(), + ), + ], + builder: (context, child) { + return SizedBox( + height: SizeConfig.height * .75, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const TextCaption( + title: 'Take a photo your ID card', + subtitle: + 'Make sure your photo is clearly legible for identity verification purposes', + ), + SizedBox( + width: SizeConfig.height, + child: Wrap( + alignment: WrapAlignment.spaceBetween, + spacing: 10, + runSpacing: 10, + children: List.generate(listImg.length, (index) { + final urlList = listImg[index]['urlImg']; + final tag = listImg[index]['tag']; + return Column( + children: [ + ImageView( + image: urlList, + width: SizeConfig.width * .42, + ), + const SizedBox( + height: 8, + ), + Text( + tag, + style: const TextStyle( + color: ColorPalette.slate800, + fontWeight: FontWeight.w600), + ), + ], + ); + }), + ), + ), + // const Spacer(), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageView( + image: PathAssets.iconShield, + width: 20, + height: 22, + ), + SizedBox( + width: 8, + ), + Expanded( + child: Text( + 'In accordance with OJK regulations, an ID card is required to purchase mutual funds.', + style: TextStyle( + fontWeight: FontWeight.w600, + color: ColorPalette.primary, + ), + ), + ) + ], + ), + Consumer( + builder: (context, provider, child) { + return ButtonView( + name: 'Take a Photo', + marginVertical: 16.0, + onPressed: () { + provider.initCamera().then((cameras) { + routePush(context, + page: TakePictureScreen( + camera: cameras.first, + takeContent: 'ktp', + )); + }); + }, + ); + }) + ], + ), + ); + }); + } +} diff --git a/lib/features/auth/registration/viewmodel/submission_data_viewmodel.dart b/lib/features/auth/registration/viewmodel/submission_data_viewmodel.dart index c7a97fb..045cc61 100644 --- a/lib/features/auth/registration/viewmodel/submission_data_viewmodel.dart +++ b/lib/features/auth/registration/viewmodel/submission_data_viewmodel.dart @@ -1,19 +1,27 @@ +import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; class SubmissionDataViewModel extends ChangeNotifier { - int currentStep = 1; + static int _currentStep = 1; + int get getCurrentStep => _currentStep; int stepAmount = 9; bool _isEmailVerify = false; bool get isEmailVerify => _isEmailVerify; + Future> initCamera() async { + final cameras = await availableCameras(); + final camerasDesc = cameras; + return camerasDesc; + } + submitEmail() { _isEmailVerify = !_isEmailVerify; notifyListeners(); } onWillPopSubmission(BuildContext context) { - if (currentStep != 1) { - currentStep--; + if (getCurrentStep != 1) { + _currentStep--; notifyListeners(); } else { Navigator.of(context).pop(true); @@ -21,8 +29,8 @@ class SubmissionDataViewModel extends ChangeNotifier { } nextSubmission(BuildContext context) { - if (currentStep < stepAmount) { - currentStep++; + if (getCurrentStep < stepAmount) { + _currentStep++; } else { //ToDo : Go To next step after completing the submission } diff --git a/pubspec.lock b/pubspec.lock index 18050bf..fe9bc01 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + camera: + dependency: "direct main" + description: + name: camera + sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797" + url: "https://pub.dev" + source: hosted + version: "0.10.5+9" + camera_android: + dependency: transitive + description: + name: camera_android + sha256: "351429510121d179b9aac5a2e8cb525c3cd6c39f4d709c5f72dfb21726e52371" + url: "https://pub.dev" + source: hosted + version: "0.10.8+16" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "608b56b0880722f703871329c4d7d4c2f379c8e2936940851df7fc041abc6f51" + url: "https://pub.dev" + source: hosted + version: "0.9.13+10" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: fceb2c36038b6392317b1d5790c6ba9e6ca9f1da3031181b8bea03882bf9387a + url: "https://pub.dev" + source: hosted + version: "2.7.3" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: f18ccfb33b2a7c49a52ad5aa3f07330b7422faaecbdfd9b9fe8e51182f6ad67d + url: "https://pub.dev" + source: hosted + version: "0.3.2+4" carousel_slider: dependency: "direct main" description: @@ -89,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + url: "https://pub.dev" + source: hosted + version: "0.3.3+8" crypto: dependency: transitive description: @@ -174,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + url: "https://pub.dev" + source: hosted + version: "2.0.17" flutter_svg: dependency: "direct main" description: @@ -289,7 +345,7 @@ packages: source: hosted version: "2.0.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" @@ -313,7 +369,7 @@ packages: source: hosted version: "1.0.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b @@ -485,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -493,6 +557,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: e8580e201c7197feac830b501889e877796a9fabbe20dcdbe90a981603939101 + url: "https://pub.dev" + source: hosted + version: "24.2.4" + syncfusion_flutter_signaturepad: + dependency: "direct main" + description: + name: syncfusion_flutter_signaturepad + sha256: "878e1063b909a83c83677627261780d42d532d0b5e7e259d84da805008e7fb0d" + url: "https://pub.dev" + source: hosted + version: "24.2.4" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f26ba10..b5fe5ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,10 @@ dependencies: provider: ^6.1.1 group_button: ^5.3.4 pinput: ^2.2.21 + camera: ^0.10.5+9 + path_provider: ^2.1.2 + path: ^1.8.3 + syncfusion_flutter_signaturepad: ^24.2.4