diff --git a/.gitignore b/.gitignore index 85a41e99d000bc4fa33a675c051e5ca4e596c1b0..70a345547351bcc003769653a8e64c8c979eff35 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ key.jks # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. -#.vscode/ +.vscode/ # Flutter/Dart/Pub related **/doc/api/ diff --git a/lib/Pages/Bank/model/bank.dart b/lib/Pages/Bank/model/bank.dart index 8ddc78897e28550f98a67b93d1846bbca75b750c..566016256295d1adc35cccad418afee5064c3199 100644 --- a/lib/Pages/Bank/model/bank.dart +++ b/lib/Pages/Bank/model/bank.dart @@ -7,14 +7,16 @@ part 'bank.g.dart'; class Bank extends Equatable { final String id; final String bankName; + final String bankCodeNumber; final String bankAccountNumber; final String bankAccountName; - const Bank( - this.id, this.bankName, this.bankAccountNumber, this.bankAccountName); + const Bank(this.id, this.bankName, this.bankCodeNumber, + this.bankAccountNumber, this.bankAccountName); @override - List get props => [id, bankName, bankAccountNumber, bankAccountName]; + List get props => + [id, bankName, bankCodeNumber, bankAccountNumber, bankAccountName]; factory Bank.fromJson(Map json) => _$BankFromJson(json); diff --git a/lib/Pages/Bank/model/bank.g.dart b/lib/Pages/Bank/model/bank.g.dart index 0c099a5b04107e837a119b3fbb880f11bfcf55cb..dacfdd478f12c29128cb44ed05d5b0e6d9fcc45c 100644 --- a/lib/Pages/Bank/model/bank.g.dart +++ b/lib/Pages/Bank/model/bank.g.dart @@ -10,6 +10,7 @@ Bank _$BankFromJson(Map json) { return Bank( json['id'] as String, json['bank_name'] as String, + json['bank_code_number'] as String, json['bank_account_number'] as String, json['bank_account_name'] as String, ); @@ -18,6 +19,7 @@ Bank _$BankFromJson(Map json) { Map _$BankToJson(Bank instance) => { 'id': instance.id, 'bank_name': instance.bankName, + 'bank_code_number': instance.bankCodeNumber, 'bank_account_number': instance.bankAccountNumber, 'bank_account_name': instance.bankAccountName, }; diff --git a/lib/Pages/Bank/rekening.dart b/lib/Pages/Bank/rekening.dart index 2e4845b664e16528b7914370f1496d5fb0d27a41..5177bc336faa582e1b5e1a8a9c3973c5efd68046 100644 --- a/lib/Pages/Bank/rekening.dart +++ b/lib/Pages/Bank/rekening.dart @@ -224,13 +224,25 @@ class _SelectedBankWidget extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + ), + child: Text('${context.watch().selectedBank.bankName}', + style: + Theme.of(context).textTheme.headline6.copyWith(fontSize: 18)), + ), + Text( + 'Kode bank: ' + '${context.watch().selectedBank.bankCodeNumber}', + style: + Theme.of(context).textTheme.headline6.copyWith(fontSize: 18)), Row( children: [ Text( - '${context.watch().selectedBank.bankName}: ', - style: const TextStyle(fontSize: 18), - ), - Text(context.watch().selectedBank.bankAccountNumber, + 'Nomor rekening: ' + '${context.watch().selectedBank.bankAccountNumber}', style: Theme.of(context) .textTheme .headline6 @@ -250,11 +262,16 @@ class _SelectedBankWidget extends StatelessWidget { ), ], ), - Text( - 'Atas nama: ' - '${context.watch().selectedBank.bankAccountName}', - style: - Theme.of(context).textTheme.headline6.copyWith(fontSize: 18)), + Padding( + padding: const EdgeInsets.only( + bottom: 10, + ), + child: Text( + 'Atas nama: ' + '${context.watch().selectedBank.bankAccountName}', + style: + Theme.of(context).textTheme.headline6.copyWith(fontSize: 18)), + ), ], ); } diff --git a/lib/Pages/DonationHistory/model/donation.dart b/lib/Pages/DonationHistory/model/donation.dart index 88317332831adb816beb85e65a8ff4fe4dca3ac1..302a382bd9dbf96b7b2384f50e6a2836340a4111 100644 --- a/lib/Pages/DonationHistory/model/donation.dart +++ b/lib/Pages/DonationHistory/model/donation.dart @@ -34,6 +34,9 @@ class Donation { @JsonKey(nullable: true) final String transferDestinationBankAccountName; + @JsonKey(nullable: true) + final String transferDestinationBankCodeNumber; + @JsonKey(nullable: true) final String transferDestinationBankAccountNumber; final DateTime createdAt; @@ -60,6 +63,7 @@ class Donation { this.userBankName, this.transferDestinationBankName, this.transferDestinationBankAccountName, + this.transferDestinationBankCodeNumber, this.transferDestinationBankAccountNumber, this.programCode, this.userFullName, diff --git a/lib/Pages/DonationHistory/model/donation.g.dart b/lib/Pages/DonationHistory/model/donation.g.dart index 4fb63a261e5c340c6722ac25d26975fe20b54805..f21b72bd29374f4180bf16e2bc7c06a6b9555496 100644 --- a/lib/Pages/DonationHistory/model/donation.g.dart +++ b/lib/Pages/DonationHistory/model/donation.g.dart @@ -20,6 +20,8 @@ Donation _$DonationFromJson(Map json) { json['transfer_destination_bank_name'] as String, transferDestinationBankAccountName: json['transfer_destination_bank_account_name'] as String, + transferDestinationBankCodeNumber: + json['transfer_destination_bank_code_number'] as String, transferDestinationBankAccountNumber: json['transfer_destination_bank_account_number'] as String, programCode: json['program_code'] as String, @@ -62,6 +64,8 @@ Map _$DonationToJson(Donation instance) => { 'transfer_destination_bank_name': instance.transferDestinationBankName, 'transfer_destination_bank_account_name': instance.transferDestinationBankAccountName, + 'transfer_destination_bank_code_number': + instance.transferDestinationBankCodeNumber, 'transfer_destination_bank_account_number': instance.transferDestinationBankAccountNumber, 'created_at': instance.createdAt.toIso8601String(), diff --git a/lib/Pages/Product/model/product/product.g.dart b/lib/Pages/Product/model/product/product.g.dart index 8eba80f1e911ae44bff46da16be214e66661c3f6..5345a853668f1ec559f3b48560a86ecbda411f5c 100644 --- a/lib/Pages/Product/model/product/product.g.dart +++ b/lib/Pages/Product/model/product/product.g.dart @@ -33,9 +33,9 @@ Map _$ProductToJson(Product instance) => { 'subcategory_name': instance.subcategoryName, 'description': instance.description, 'price': instance.price, + 'unit': instance.unit, 'preorder': instance.preorder, 'preorder_duration': instance.preorder_duration, 'stock': instance.stock, 'image': instance.image, - 'unit': instance.unit, }; diff --git a/lib/Pages/ProductPayment/product_payment.dart b/lib/Pages/ProductPayment/product_payment.dart index af32da84b13e7d5762aa36a68daccaa375009d2d..d29a41872040de480de82273ce9592cdf2431405 100644 --- a/lib/Pages/ProductPayment/product_payment.dart +++ b/lib/Pages/ProductPayment/product_payment.dart @@ -1,546 +1,546 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:home_industry/Component/blue_button.dart'; -import 'package:home_industry/Component/border_blue_button.dart'; -import 'package:home_industry/Component/custom_circular.dart'; -import 'package:home_industry/Component/custom_input_form.dart'; -import 'package:home_industry/Component/image_dialog.dart'; -import 'package:home_industry/Component/price_text_formatter.dart'; -import 'package:home_industry/Component/product_image.dart'; -import 'package:home_industry/Pages/Bank/model/bank.dart'; -import 'package:home_industry/Pages/Bank/rekening.dart'; -import 'package:home_industry/Pages/ProductPayment/bloc/bloc.dart'; -import 'package:home_industry/Pages/Transactions/model/transaction_entity.dart'; -import 'package:home_industry/Pages/Transactions/model/transaction_item_entity.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:provider/provider.dart'; - -class SelectedBank extends ChangeNotifier { - Bank _selectedBank; - - Bank get selectedBank => _selectedBank; - - set selectedBank(Bank value) { - _selectedBank = value; - notifyListeners(); - } -} - -class ProductPaymentView extends StatelessWidget { - const ProductPaymentView({Key key}) : super(key: key); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: const Text('Pembayaran'), - ), - body: LayoutBuilder( - builder: (BuildContext context, BoxConstraints viewport) { - return ChangeNotifierProvider( - create: (_) => SelectedBank(), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 10), - constraints: BoxConstraints(minHeight: viewport.maxHeight), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox( - height: 120, - child: BlocBuilder( - condition: (prev, current) { - return current is PaymentLoaded || - current is PaymentLoading || - current is PaymentError; - }, builder: (BuildContext context, PaymentState state) { - if (state is PaymentError) { - return const Center( - child: Text('Mohon perika koneksi anda'), - ); - } else if (state is PaymentLoaded) { - return Rekening( - previousBankDestinationId: - state.transaction.bankAccountTransferDestination, - ); - } - return const Center( - child: CustomCircularIndicator(), - ); - }), - ), - const Divider( - thickness: 2, - height: 5, - ), - const SizedBox(height: 450, child: _PaymentBuilder()) - ], - ), - ), - ), - ); - }), - ); - } -} - -class _PaymentBuilder extends StatelessWidget { - const _PaymentBuilder({Key key}) : super(key: key); - @override - Widget build(BuildContext context) { - return BlocConsumer( - listenWhen: (previous, current) { - return current is UploadProofSuccess || - current is UploadProofLoading || - current is UploadProofError; - }, listener: (context, state) { - if (state is UploadProofLoading) { - EasyLoading.show(status: 'Bukti sedang diunggah...'); - } else if (state is UploadProofError) { - EasyLoading.showError('Gagal unggah bukti. \nUnggah bisa dilakukan dilain waktu'); - } else if (state is UploadProofSuccess) { - EasyLoading.showSuccess('Bukti berhasil diunggah'); - Navigator.maybePop(context); - } - }, buildWhen: (previous, current) { - return current is PaymentLoaded || - current is PaymentLoading || - current is PaymentError; - }, builder: (context, state) { - if (state is PaymentLoading || state is InitialPaymentState) { - return const Center( - child: CustomCircularIndicator(), - ); - } else if (state is PaymentError) { - return const Center( - child: Text('Mohon perika koneksi anda'), - ); - } else if (state is PaymentLoaded) { - return _PaymentBody( - transaction: state.transaction, - ); - } - return null; - }); - } -} - -class _PaymentBody extends StatefulWidget { - final Transaction transaction; - - const _PaymentBody({Key key, @required this.transaction}) : super(key: key); - @override - __PaymentBodyState createState() => __PaymentBodyState(); -} - -class __PaymentBodyState extends State<_PaymentBody> { - final _formKey = GlobalKey(); - - final _picker = ImagePicker(); - File _image; - String senderName; - String senderBankName; - - Future _retrieveLostData() async { - final response = await _picker.getLostData(); - if (response.isEmpty) { - return; - } - if (response.file != null && response.type == RetrieveType.image) { - setState(() { - _image = File(response.file.path); - }); - } - } - - void setSenderName(String name) { - setState(() { - senderName = name; - }); - } - - void setSenderBankName(String name) { - setState(() { - senderBankName = name; - }); - } - - Widget _dynamicImage() { - if (_image != null) { - return Container( - height: 150, - width: 150, - decoration: - BoxDecoration(image: DecorationImage(image: FileImage(_image)))); - } else if (widget.transaction.proofOfPayment != null) { - return GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - showDialog( - context: context, - builder: (context) => - ImageDialog(urlImage: widget.transaction.proofOfPayment)); - }, - child: SizedBox( - height: 150, - width: 150, - child: CachedImage(url: widget.transaction.proofOfPayment), - ), - ); - } - return const Text('Belum ada bukti transfer'); - } - - Future _getImageFromCamera() async { - final img = await _picker.getImage(source: ImageSource.camera); - _setImage(img); - } - - void _setImage(PickedFile img) { - if (img == null) { - return; - } - final image = File(img.path); - setState(() { - _image = image ?? _image; - }); - } - - Future _getImageFromGallery() async { - final img = await _picker.getImage(source: ImageSource.gallery); - _setImage(img); - } - - void _showChoosePicture() { - FocusScope.of(context).unfocus(); - showDialog( - context: context, - child: SimpleDialog( - contentPadding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), - title: const Text('Choose Image From'), - children: [ - BorderBlueButton( - onPressed: () async { - await _getImageFromCamera(); - Navigator.pop(context); - }, - label: 'Camera'), - BorderBlueButton( - onPressed: () async { - await _getImageFromGallery(); - Navigator.pop(context); - }, - label: 'Gallery', - ) - ], - )); - } - - void buttonSendClicked() { - FocusScope.of(context).unfocus(); - final selectedBank = context.read().selectedBank; - if (_formKey.currentState.validate() && - _image != null && - selectedBank != null) { - _formKey.currentState.save(); - BlocProvider.of(context).add(ButtonSendClicked( - transaction: widget.transaction.id, - accountName: senderName, - bankName: senderBankName, - bankIdDestination: selectedBank.id, - image: _image)); - } - if (_image == null) { - EasyLoading.showError('Bukti foto tidak ditemukan'); - } else if (selectedBank == null) { - EasyLoading.showError('Anda belum memilih bank tujuan'); - } - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Flexible( - child: Text( - 'Nominal', - style: Theme.of(context).textTheme.headline6, - ), - ), - Flexible( - child: _SummaryBody( - transaction: widget.transaction, - ), - ), - Flexible( - flex: 4, - child: _FormPayment( - transaction: widget.transaction, - formKey: _formKey, - setName: setSenderName, - setSenderBank: setSenderBankName, - )), - Flexible( - flex: 2, - child: Platform.isAndroid && _image == null - ? FutureBuilder( - future: _retrieveLostData(), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text( - 'Pick image/video error: ${snapshot.error}}', - textAlign: TextAlign.center, - ); - } else if (snapshot.connectionState == - ConnectionState.done) { - return _dynamicImage(); - } - return const Text( - 'You have not yet picked an image.', - textAlign: TextAlign.center, - ); - }, - ) - : _dynamicImage()), - Flexible( - child: OutlineButton.icon( - borderSide: - BorderSide(color: Theme.of(context).primaryColor, width: 2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), - onPressed: _showChoosePicture, - icon: const Icon(Icons.file_upload), - label: const Text('Upload bukti transfer')), - ), - Flexible( - child: SizedBox( - width: double.infinity, - height: 40, - child: - BlueButton(onPressed: buttonSendClicked, label: 'KIRIM'))) - ], - ); - } -} - -class _SummaryBody extends StatelessWidget { - const _SummaryBody({ - Key key, - @required this.transaction, - }) : assert(transaction != null), - super(key: key); - - final Transaction transaction; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - PriceTextFormatter( - price: transaction.subtotal, - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w700, - color: Theme.of(context).primaryColor), - ), - FlatButton.icon( - onPressed: () { - FocusScope.of(context).unfocus(); - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return FractionallySizedBox( - heightFactor: 0.5, - child: ModalSeeItemOrdered( - transaction: transaction, - )); - }, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical(top: Radius.circular(25))), - ); - }, - icon: Icon( - Icons.description, - color: Theme.of(context).primaryColor, - ), - label: Text( - 'Lihat barang', - style: TextStyle(color: Theme.of(context).primaryColor), - )) - ], - ); - } -} - -class _FormPayment extends StatefulWidget { - final Key formKey; - final Function(String) setName; - final Function(String) setSenderBank; - final Transaction transaction; - const _FormPayment( - {Key key, - @required this.formKey, - @required this.setName, - @required this.transaction, - @required this.setSenderBank}) - : super(key: key); - @override - __FormPaymentState createState() => __FormPaymentState(); -} - -class __FormPaymentState extends State<_FormPayment> { - FocusScopeNode _focusScopeNode; - - @override - void initState() { - _focusScopeNode = FocusScopeNode(); - super.initState(); - } - - @override - void dispose() { - _focusScopeNode.dispose(); - super.dispose(); - } - - void _handleSubmitted(_) { - _focusScopeNode.nextFocus(); - } - - @override - Widget build(BuildContext context) { - return Form( - key: widget.formKey, - child: FocusScope( - node: _focusScopeNode, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - CustomTextInputForm( - initialValue: widget.transaction.userBankName, - label: 'Nama Bank', - prefixIcon: Icons.payment, - onSave: widget.setSenderBank, - onSubmit: _handleSubmitted, - textInputAction: TextInputAction.next, - ), - CustomTextInputForm( - initialValue: widget.transaction.userBankAccountName, - label: 'Nama Pengirim', - prefixIcon: Icons.person_outline, - onSave: widget.setName, - ) - ], - ), - )); - } -} - -class ModalSeeItemOrdered extends StatefulWidget { - final Transaction transaction; - - const ModalSeeItemOrdered({Key key, @required this.transaction}) - : assert(transaction != null), - super(key: key); - @override - _ModalSeeItemOrderedState createState() => _ModalSeeItemOrderedState(); -} - -class _ModalSeeItemOrderedState extends State { - final ScrollController _scrollController = ScrollController(); - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) => _scrollController - .animateTo(_scrollController.position.maxScrollExtent / 2, - duration: const Duration(seconds: 1), - curve: Curves.fastLinearToSlowEaseIn)); - return Column( - children: [ - const Center( - child: Icon(Icons.arrow_drop_down), - ), - Expanded( - child: ListWheelScrollView.useDelegate( - controller: _scrollController, - diameterRatio: 6, - childDelegate: ListWheelChildBuilderDelegate( - childCount: widget.transaction.transactionItems.length, - builder: (context, index) { - return _ItemOrdered( - transactionItem: widget.transaction.transactionItems[index], - ); - }), - itemExtent: 70, - physics: const BouncingScrollPhysics(), - ), - ), - ], - ); - } -} - -class _ItemOrdered extends StatelessWidget { - final TransactionItem transactionItem; - - const _ItemOrdered({Key key, @required this.transactionItem}) - : super(key: key); - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: ListTile( - title: Text( - transactionItem.productName, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context).primaryColor), - ), - trailing: PriceTextFormatter( - price: - '${transactionItem.quantity - * double.parse(transactionItem.productPrice)}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - leading: Text( - '${transactionItem.productPrice}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - subtitle: PriceTextFormatter( - price: transactionItem.productPrice, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - ), - ), - ), - ); - } -} +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:home_industry/Component/blue_button.dart'; +import 'package:home_industry/Component/border_blue_button.dart'; +import 'package:home_industry/Component/custom_circular.dart'; +import 'package:home_industry/Component/custom_input_form.dart'; +import 'package:home_industry/Component/image_dialog.dart'; +import 'package:home_industry/Component/price_text_formatter.dart'; +import 'package:home_industry/Component/product_image.dart'; +import 'package:home_industry/Pages/Bank/model/bank.dart'; +import 'package:home_industry/Pages/Bank/rekening.dart'; +import 'package:home_industry/Pages/ProductPayment/bloc/bloc.dart'; +import 'package:home_industry/Pages/Transactions/model/transaction_entity.dart'; +import 'package:home_industry/Pages/Transactions/model/transaction_item_entity.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:provider/provider.dart'; + +class SelectedBank extends ChangeNotifier { + Bank _selectedBank; + + Bank get selectedBank => _selectedBank; + + set selectedBank(Bank value) { + _selectedBank = value; + notifyListeners(); + } +} + +class ProductPaymentView extends StatelessWidget { + const ProductPaymentView({Key key}) : super(key: key); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('Pembayaran'), + ), + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints viewport) { + return ChangeNotifierProvider( + create: (_) => SelectedBank(), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + constraints: BoxConstraints(minHeight: viewport.maxHeight), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + height: 120, + child: BlocBuilder( + condition: (prev, current) { + return current is PaymentLoaded || + current is PaymentLoading || + current is PaymentError; + }, builder: (BuildContext context, PaymentState state) { + if (state is PaymentError) { + return const Center( + child: Text('Mohon perika koneksi anda'), + ); + } else if (state is PaymentLoaded) { + return Rekening( + previousBankDestinationId: + state.transaction.bankAccountTransferDestination, + ); + } + return const Center( + child: CustomCircularIndicator(), + ); + }), + ), + const Divider( + thickness: 2, + height: 5, + ), + const SizedBox(height: 450, child: _PaymentBuilder()) + ], + ), + ), + ), + ); + }), + ); + } +} + +class _PaymentBuilder extends StatelessWidget { + const _PaymentBuilder({Key key}) : super(key: key); + @override + Widget build(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) { + return current is UploadProofSuccess || + current is UploadProofLoading || + current is UploadProofError; + }, listener: (context, state) { + if (state is UploadProofLoading) { + EasyLoading.show(status: 'Bukti sedang diunggah...'); + } else if (state is UploadProofError) { + EasyLoading.showError( + 'Gagal unggah bukti. \nUnggah bisa dilakukan dilain waktu'); + } else if (state is UploadProofSuccess) { + EasyLoading.showSuccess('Bukti berhasil diunggah'); + Navigator.maybePop(context); + } + }, buildWhen: (previous, current) { + return current is PaymentLoaded || + current is PaymentLoading || + current is PaymentError; + }, builder: (context, state) { + if (state is PaymentLoading || state is InitialPaymentState) { + return const Center( + child: CustomCircularIndicator(), + ); + } else if (state is PaymentError) { + return const Center( + child: Text('Mohon perika koneksi anda'), + ); + } else if (state is PaymentLoaded) { + return _PaymentBody( + transaction: state.transaction, + ); + } + return null; + }); + } +} + +class _PaymentBody extends StatefulWidget { + final Transaction transaction; + + const _PaymentBody({Key key, @required this.transaction}) : super(key: key); + @override + __PaymentBodyState createState() => __PaymentBodyState(); +} + +class __PaymentBodyState extends State<_PaymentBody> { + final _formKey = GlobalKey(); + + final _picker = ImagePicker(); + File _image; + String senderName; + String senderBankName; + + Future _retrieveLostData() async { + final response = await _picker.getLostData(); + if (response.isEmpty) { + return; + } + if (response.file != null && response.type == RetrieveType.image) { + setState(() { + _image = File(response.file.path); + }); + } + } + + void setSenderName(String name) { + setState(() { + senderName = name; + }); + } + + void setSenderBankName(String name) { + setState(() { + senderBankName = name; + }); + } + + Widget _dynamicImage() { + if (_image != null) { + return Container( + height: 150, + width: 150, + decoration: + BoxDecoration(image: DecorationImage(image: FileImage(_image)))); + } else if (widget.transaction.proofOfPayment != null) { + return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + showDialog( + context: context, + builder: (context) => + ImageDialog(urlImage: widget.transaction.proofOfPayment)); + }, + child: SizedBox( + height: 150, + width: 150, + child: CachedImage(url: widget.transaction.proofOfPayment), + ), + ); + } + return const Text('Belum ada bukti transfer'); + } + + Future _getImageFromCamera() async { + final img = await _picker.getImage(source: ImageSource.camera); + _setImage(img); + } + + void _setImage(PickedFile img) { + if (img == null) { + return; + } + final image = File(img.path); + setState(() { + _image = image ?? _image; + }); + } + + Future _getImageFromGallery() async { + final img = await _picker.getImage(source: ImageSource.gallery); + _setImage(img); + } + + void _showChoosePicture() { + FocusScope.of(context).unfocus(); + showDialog( + context: context, + child: SimpleDialog( + contentPadding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + title: const Text('Choose Image From'), + children: [ + BorderBlueButton( + onPressed: () async { + await _getImageFromCamera(); + Navigator.pop(context); + }, + label: 'Camera'), + BorderBlueButton( + onPressed: () async { + await _getImageFromGallery(); + Navigator.pop(context); + }, + label: 'Gallery', + ) + ], + )); + } + + void buttonSendClicked() { + FocusScope.of(context).unfocus(); + final selectedBank = context.read().selectedBank; + if (_formKey.currentState.validate() && + _image != null && + selectedBank != null) { + _formKey.currentState.save(); + BlocProvider.of(context).add(ButtonSendClicked( + transaction: widget.transaction.id, + accountName: senderName, + bankName: senderBankName, + bankIdDestination: selectedBank.id, + image: _image)); + } + if (_image == null) { + EasyLoading.showError('Bukti foto tidak ditemukan'); + } else if (selectedBank == null) { + EasyLoading.showError('Anda belum memilih bank tujuan'); + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Flexible( + child: Text( + 'Nominal', + style: Theme.of(context).textTheme.headline6, + ), + ), + Flexible( + child: _SummaryBody( + transaction: widget.transaction, + ), + ), + Flexible( + flex: 4, + child: _FormPayment( + transaction: widget.transaction, + formKey: _formKey, + setName: setSenderName, + setSenderBank: setSenderBankName, + )), + Flexible( + flex: 2, + child: Platform.isAndroid && _image == null + ? FutureBuilder( + future: _retrieveLostData(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text( + 'Pick image/video error: ${snapshot.error}}', + textAlign: TextAlign.center, + ); + } else if (snapshot.connectionState == + ConnectionState.done) { + return _dynamicImage(); + } + return const Text( + 'You have not yet picked an image.', + textAlign: TextAlign.center, + ); + }, + ) + : _dynamicImage()), + Flexible( + child: OutlineButton.icon( + borderSide: + BorderSide(color: Theme.of(context).primaryColor, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + onPressed: _showChoosePicture, + icon: const Icon(Icons.file_upload), + label: const Text('Upload bukti transfer')), + ), + Flexible( + child: SizedBox( + width: double.infinity, + height: 40, + child: + BlueButton(onPressed: buttonSendClicked, label: 'KIRIM'))) + ], + ); + } +} + +class _SummaryBody extends StatelessWidget { + const _SummaryBody({ + Key key, + @required this.transaction, + }) : assert(transaction != null), + super(key: key); + + final Transaction transaction; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PriceTextFormatter( + price: transaction.subtotal, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w700, + color: Theme.of(context).primaryColor), + ), + FlatButton.icon( + onPressed: () { + FocusScope.of(context).unfocus(); + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return FractionallySizedBox( + heightFactor: 0.5, + child: ModalSeeItemOrdered( + transaction: transaction, + )); + }, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical(top: Radius.circular(25))), + ); + }, + icon: Icon( + Icons.description, + color: Theme.of(context).primaryColor, + ), + label: Text( + 'Lihat barang', + style: TextStyle(color: Theme.of(context).primaryColor), + )) + ], + ); + } +} + +class _FormPayment extends StatefulWidget { + final Key formKey; + final Function(String) setName; + final Function(String) setSenderBank; + final Transaction transaction; + const _FormPayment( + {Key key, + @required this.formKey, + @required this.setName, + @required this.transaction, + @required this.setSenderBank}) + : super(key: key); + @override + __FormPaymentState createState() => __FormPaymentState(); +} + +class __FormPaymentState extends State<_FormPayment> { + FocusScopeNode _focusScopeNode; + + @override + void initState() { + _focusScopeNode = FocusScopeNode(); + super.initState(); + } + + @override + void dispose() { + _focusScopeNode.dispose(); + super.dispose(); + } + + void _handleSubmitted(_) { + _focusScopeNode.nextFocus(); + } + + @override + Widget build(BuildContext context) { + return Form( + key: widget.formKey, + child: FocusScope( + node: _focusScopeNode, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + CustomTextInputForm( + initialValue: widget.transaction.userBankName, + label: 'Nama Bank', + prefixIcon: Icons.payment, + onSave: widget.setSenderBank, + onSubmit: _handleSubmitted, + textInputAction: TextInputAction.next, + ), + CustomTextInputForm( + initialValue: widget.transaction.userBankAccountName, + label: 'Nama Pengirim', + prefixIcon: Icons.person_outline, + onSave: widget.setName, + ) + ], + ), + )); + } +} + +class ModalSeeItemOrdered extends StatefulWidget { + final Transaction transaction; + + const ModalSeeItemOrdered({Key key, @required this.transaction}) + : assert(transaction != null), + super(key: key); + @override + _ModalSeeItemOrderedState createState() => _ModalSeeItemOrderedState(); +} + +class _ModalSeeItemOrderedState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) => _scrollController + .animateTo(_scrollController.position.maxScrollExtent / 2, + duration: const Duration(seconds: 1), + curve: Curves.fastLinearToSlowEaseIn)); + return Column( + children: [ + const Center( + child: Icon(Icons.arrow_drop_down), + ), + Expanded( + child: ListWheelScrollView.useDelegate( + controller: _scrollController, + diameterRatio: 6, + childDelegate: ListWheelChildBuilderDelegate( + childCount: widget.transaction.transactionItems.length, + builder: (context, index) { + return _ItemOrdered( + transactionItem: widget.transaction.transactionItems[index], + ); + }), + itemExtent: 70, + physics: const BouncingScrollPhysics(), + ), + ), + ], + ); + } +} + +class _ItemOrdered extends StatelessWidget { + final TransactionItem transactionItem; + + const _ItemOrdered({Key key, @required this.transactionItem}) + : super(key: key); + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ListTile( + title: Text( + transactionItem.productName, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryColor), + ), + trailing: PriceTextFormatter( + price: + '${transactionItem.quantity * double.parse(transactionItem.productPrice)}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + leading: Text( + '${transactionItem.productPrice}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + subtitle: PriceTextFormatter( + price: transactionItem.productPrice, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } +} diff --git a/lib/Pages/Transactions/model/transaction_entity.dart b/lib/Pages/Transactions/model/transaction_entity.dart index c90a87b24038e1dce1791d00237d2fa76574a72b..ff7ad9272f8f15562f5582bace6a87221bc052c1 100644 --- a/lib/Pages/Transactions/model/transaction_entity.dart +++ b/lib/Pages/Transactions/model/transaction_entity.dart @@ -43,6 +43,9 @@ class Transaction { @JsonKey(nullable: true) final String transferDestinationBankAccountName; + @JsonKey(nullable: true) + final String transferDestinationBankCodeNumber; + @JsonKey(nullable: true) final String transferDestinationBankAccountNumber; final DateTime createdAt; @@ -63,6 +66,7 @@ class Transaction { this.userBankName, this.transferDestinationBankName, this.transferDestinationBankAccountName, + this.transferDestinationBankCodeNumber, this.transferDestinationBankAccountNumber, this.userBankAccountName, this.transactionItemSubtotal, diff --git a/lib/Pages/Transactions/model/transaction_entity.g.dart b/lib/Pages/Transactions/model/transaction_entity.g.dart index b293005c5644cb4e0115790b3b649461fd2668bd..7d127a26015518db7375398a73b6561ec169bc13 100644 --- a/lib/Pages/Transactions/model/transaction_entity.g.dart +++ b/lib/Pages/Transactions/model/transaction_entity.g.dart @@ -16,6 +16,8 @@ Transaction _$TransactionFromJson(Map json) { json['transfer_destination_bank_name'] as String, transferDestinationBankAccountName: json['transfer_destination_bank_account_name'] as String, + transferDestinationBankCodeNumber: + json['transfer_destination_bank_code_number'] as String, transferDestinationBankAccountNumber: json['transfer_destination_bank_account_number'] as String, userBankAccountName: json['user_bank_account_name'] as String, @@ -46,7 +48,9 @@ Transaction _$TransactionFromJson(Map json) { subtotal: json['subtotal'] as String, batch: json['batch'] as String, batchName: json['batch_name'] as String, - batchEndDate: json['end_date'] != null ? DateTime.parse(json['end_date'] as String) : DateTime.now(), + batchEndDate: json['end_date'] != null + ? DateTime.parse(json['end_date'] as String) + : DateTime.now(), ); } @@ -79,6 +83,8 @@ Map _$TransactionToJson(Transaction instance) => 'transfer_destination_bank_name': instance.transferDestinationBankName, 'transfer_destination_bank_account_name': instance.transferDestinationBankAccountName, + 'transfer_destination_bank_code_number': + instance.transferDestinationBankCodeNumber, 'transfer_destination_bank_account_number': instance.transferDestinationBankAccountNumber, 'created_at': instance.createdAt.toIso8601String(), @@ -88,7 +94,7 @@ Map _$TransactionToJson(Transaction instance) => instance.transactionItems.map((e) => e.toJson()).toList(), 'batch': instance.batch, 'batch_name': instance.batchName, - 'end_date': instance.batchEndDate.toIso8601String(), + 'batch_end_date': instance.batchEndDate?.toIso8601String(), }; const _$PaymentMethodEnumMap = { diff --git a/test/mock/donation.json b/test/mock/donation.json index 0b83f968240565ce07865e920dc0900c1160b023..2d65e3285ff3f9612f4e9102295c730fcca6df08 100644 --- a/test/mock/donation.json +++ b/test/mock/donation.json @@ -15,8 +15,9 @@ "user_bank_name": null, "user_bank_account_name": "michael", "transfer_destination_bank_name": null, - "transfer_destination_bank_account_name": null, + "transfer_destination_bank_code_number": null, "transfer_destination_bank_account_number": null, + "transfer_destination_bank_account_name": null, "donation_type": "CSH", "created_at": "2020-05-02T17:20:33.493414+07:00", "updated_at": "2020-05-02T17:20:33.493505+07:00" diff --git a/test/mock/transaction.json b/test/mock/transaction.json index bdd5893fa291a3b5a85ff30e18d536d225819345..5c0be2f72df2ee48b7439b07791e7b7c2ec1bab3 100644 --- a/test/mock/transaction.json +++ b/test/mock/transaction.json @@ -42,8 +42,9 @@ "user_bank_account_name": "adada", "bank_account_transfer_destination": "f1291c48-793c-4d44-89fe-6f64452f8aa9", "transfer_destination_bank_name": null, - "transfer_destination_bank_account_name": null, + "transfer_destination_bank_code_number": null, "transfer_destination_bank_account_number": null, + "transfer_destination_bank_account_name": null, "created_at": "2020-04-16T15:04:22.073683+07:00", "updated_at": "2020-04-20T16:58:37.049871+07:00", "subtotal": "155040.00", diff --git a/test/my_donations_test.dart b/test/my_donations_test.dart index f2fb24f8399560bf480831cf287f345d6cbdd2fe..9184e8a64bcb267f17377278868a6b4171421803 100644 --- a/test/my_donations_test.dart +++ b/test/my_donations_test.dart @@ -161,6 +161,7 @@ void main() { 'bank_account_transfer_destination': null, 'transfer_destination_bank_name': null, 'transfer_destination_bank_account_name': null, + 'transfer_destination_bank_code_number': null, 'transfer_destination_bank_account_number': null, 'goods_quantity': '1', 'goods_description': 'kaos', diff --git a/test/product_payment_test.dart b/test/product_payment_test.dart index fb04b20bcd0593c5f4e30ad60f43a40287e57e31..7b19572de87f303353703467825746b476ff56bf 100644 --- a/test/product_payment_test.dart +++ b/test/product_payment_test.dart @@ -221,6 +221,7 @@ void main() { await tester.pump(); await tester.tap(find.text('KIRIM')); await tester.pump(); + await tester.pump(const Duration(seconds: 5)); verifyNever(bloc.add(ButtonSendClicked( transaction: json['id'], bankName: 'test', @@ -230,8 +231,8 @@ void main() { }); testWidgets('Payment Loaded select bank', (WidgetTester tester) async { - final bank = - Bank('1', 'bankName', 'bankAccountNumber', 'bankAccountName'); + final bank = Bank('1', 'bankName', 'bankCodeNumber', 'bankAccountNumber', + 'bankAccountName'); when(dio.get('/bank-account-transfer-destinations/', queryParameters: {'page': 1, 'page_size': 10})) .thenAnswer((_) async => Response>( @@ -252,8 +253,8 @@ void main() { await tester.tap(find.byType(ListTile).first); await tester.pump(Duration(seconds: 2)); expect(find.text('Silahkan pilih bank tujuan'), findsNothing); - expect(find.text('bankName: '), findsOneWidget); - expect(find.text('bankAccountNumber'), findsOneWidget); + expect(find.text('bankName'), findsWidgets); + expect(find.text('Nomor rekening: bankAccountNumber'), findsOneWidget); expect(find.text('Atas nama: bankAccountName'), findsOneWidget); expect(find.byType(IconButton), findsOneWidget); await tester.pump(Duration(seconds: 2)); @@ -290,18 +291,23 @@ void main() { const id = 'test'; const bankName = 'ada'; const bankAccountName = 'aku'; + const bankCodeNumber = '111'; const bankAccountNumber = '123'; - var bankModel = Bank(id, bankName, bankAccountNumber, bankAccountName); - expect( - bankModel.props, [id, bankName, bankAccountNumber, bankAccountName]); + var bankModel = Bank( + id, bankName, bankCodeNumber, bankAccountNumber, bankAccountName); + expect(bankModel.props, + [id, bankName, bankCodeNumber, bankAccountNumber, bankAccountName]); }); test('list bank model props', () async { const id = 'test'; const bankName = 'ada'; const bankAccountName = 'aku'; + const bankCodeNumber = '111'; const bankAccountNumber = '123'; - var bankModel = [Bank(id, bankName, bankAccountNumber, bankAccountName)]; + var bankModel = [ + Bank(id, bankName, bankCodeNumber, bankAccountNumber, bankAccountName) + ]; const count = 1; const previous = 'prev'; const next = 'next';