From 386f736c08ee42aca3fb8e0737d55f9be04900eb Mon Sep 17 00:00:00 2001 From: Sumit Bhanushali Date: Thu, 21 Oct 2021 16:25:17 +0530 Subject: [PATCH] Enhancements 1.1 (#102) * feat: use filters from user settings #91 * fix: timeformat exception * fix: null check * feat: add reference link in linkfield #90 * feat: add dynamic link #90 #80 * fix: empty list in child table, shortcut filters for basefields, * feat: date format according to system settings #99 * feat: bigger session expiry for device mobile * fix: ios downloader fix * fix: parsedate empty string handling --- assets/icons/arrow_right_2.svg | 4 + ios/Podfile | 1 + ios/Podfile.lock | 8 +- lib/app/locator.config.dart | 52 ++-- lib/config/frappe_icons.dart | 1 + lib/form/controls/control.dart | 11 + lib/form/controls/custom_table.dart | 2 +- lib/form/controls/date.dart | 11 + lib/form/controls/dynamic_link.dart | 200 ++++++++++++++ lib/form/controls/link_field.dart | 23 +- lib/form/controls/time.dart | 21 +- lib/model/doctype_response.dart | 4 +- lib/model/login_request.dart | 4 + lib/model/system_settings_response.dart | 98 +++++++ lib/services/api/api.dart | 9 + lib/services/api/dio_api.dart | 85 ++++++ lib/utils/constants.dart | 17 ++ lib/utils/helpers.dart | 2 +- lib/views/base_widget.dart | 44 +++ lib/views/desk/desk_viewmodel.dart | 24 +- .../view_attachments_bottom_sheet_view.dart | 2 +- lib/views/form_view/form_view.dart | 177 ++++++------ lib/views/form_view/form_view_viewmodel.dart | 86 +++--- lib/views/list_view/list_view.dart | 1 + lib/views/list_view/list_view_viewmodel.dart | 92 ++++++- lib/views/login/login_viewmodel.dart | 9 + lib/views/new_doc/new_doc_viewmodel.dart | 2 +- lib/views/queue.dart | 254 +++++++++--------- lib/widgets/custom_form.dart | 10 +- lib/widgets/form_builder_table.dart | 12 +- 30 files changed, 962 insertions(+), 304 deletions(-) create mode 100644 assets/icons/arrow_right_2.svg create mode 100644 lib/form/controls/dynamic_link.dart create mode 100644 lib/model/system_settings_response.dart create mode 100644 lib/views/base_widget.dart diff --git a/assets/icons/arrow_right_2.svg b/assets/icons/arrow_right_2.svg new file mode 100644 index 00000000..1a5adf44 --- /dev/null +++ b/assets/icons/arrow_right_2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios/Podfile b/ios/Podfile index 1e8c3c90..5f4742cd 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -29,6 +29,7 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! + pod 'SQLite.swift' use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index efa2db40..75624901 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -78,6 +78,9 @@ PODS: - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) + - SQLite.swift (0.13.0): + - SQLite.swift/standard (= 0.13.0) + - SQLite.swift/standard (0.13.0) - Toast (4.0.0) - url_launcher (0.0.1): - Flutter @@ -103,6 +106,7 @@ DEPENDENCIES: - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - SQLite.swift - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - video_player (from `.symlinks/plugins/video_player/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) @@ -118,6 +122,7 @@ SPEC REPOS: - Reachability - SDWebImage - SDWebImageFLPlugin + - SQLite.swift - Toast EXTERNAL SOURCES: @@ -181,12 +186,13 @@ SPEC CHECKSUMS: SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + SQLite.swift: 8add701609fd0ef78d097dcc75d20a9782e6f5fc Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e wakelock: b0843b2479edbf6504d8d262c2959446f35373aa webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: c990cab2bdc393dc88aba776a5ef266622e941de COCOAPODS: 1.10.1 diff --git a/lib/app/locator.config.dart b/lib/app/locator.config.dart index fad46db7..c5531b5f 100644 --- a/lib/app/locator.config.dart +++ b/lib/app/locator.config.dart @@ -8,7 +8,7 @@ import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; import '../services/connectivity_service.dart' as _i7; -import '../services/storage_service.dart' as _i19; +import '../services/storage_service.dart' as _i18; import '../views/awesome_bar/awesome_bar_viewmodel.dart' as _i6; import '../views/desk/desk_viewmodel.dart' as _i9; import '../views/form_view/bottom_sheets/assignees/assignees_bottom_sheet_viewmodel.dart' @@ -16,26 +16,25 @@ import '../views/form_view/bottom_sheets/assignees/assignees_bottom_sheet_viewmo import '../views/form_view/bottom_sheets/attachments/add_attachments_bottom_sheet_viewmodel.dart' as _i3; import '../views/form_view/bottom_sheets/attachments/view_attachments_bottom_sheet_viewmodel.dart' - as _i21; + as _i20; import '../views/form_view/bottom_sheets/reviews/add_review_bottom_sheet_viewmodel.dart' as _i4; import '../views/form_view/bottom_sheets/reviews/view_reviews_bottom_sheet_viewmodel.dart' - as _i22; + as _i21; import '../views/form_view/bottom_sheets/share/share_bottom_sheet_viewmodel.dart' - as _i17; + as _i16; import '../views/form_view/bottom_sheets/tags/tags_bottom_sheet_viewmodel.dart' - as _i20; -import '../views/form_view/form_view_viewmodel.dart' as _i12; + as _i19; import '../views/list_view/bottom_sheets/edit_filter_bottom_sheet_viewmodel.dart' as _i10; import '../views/list_view/bottom_sheets/filters_bottom_sheet_viewmodel.dart' as _i11; import '../views/list_view/bottom_sheets/sort_by_fields_bottom_sheet_viewmodel.dart' - as _i18; -import '../views/list_view/list_view_viewmodel.dart' as _i13; -import '../views/login/login_viewmodel.dart' as _i14; -import '../views/new_doc/new_doc_viewmodel.dart' as _i15; -import '../views/send_email/send_email_viewmodel.dart' as _i16; + as _i17; +import '../views/list_view/list_view_viewmodel.dart' as _i12; +import '../views/login/login_viewmodel.dart' as _i13; +import '../views/new_doc/new_doc_viewmodel.dart' as _i14; +import '../views/send_email/send_email_viewmodel.dart' as _i15; import '../widgets/custom_form.dart' as _i8; // ignore_for_file: unnecessary_lambdas @@ -58,21 +57,20 @@ _i1.GetIt $initGetIt(_i1.GetIt get, () => _i10.EditFilterBottomSheetViewModel()); gh.lazySingleton<_i11.FiltersBottomSheetViewModel>( () => _i11.FiltersBottomSheetViewModel()); - gh.lazySingleton<_i12.FormViewViewModel>(() => _i12.FormViewViewModel()); - gh.lazySingleton<_i13.ListViewViewModel>(() => _i13.ListViewViewModel()); - gh.lazySingleton<_i14.LoginViewModel>(() => _i14.LoginViewModel()); - gh.lazySingleton<_i15.NewDocViewModel>(() => _i15.NewDocViewModel()); - gh.lazySingleton<_i16.SendEmailViewModel>(() => _i16.SendEmailViewModel()); - gh.lazySingleton<_i17.ShareBottomSheetViewModel>( - () => _i17.ShareBottomSheetViewModel()); - gh.lazySingleton<_i18.SortByFieldsBottomSheetViewModel>( - () => _i18.SortByFieldsBottomSheetViewModel()); - gh.lazySingleton<_i19.StorageService>(() => _i19.StorageService()); - gh.lazySingleton<_i20.TagsBottomSheetViewModel>( - () => _i20.TagsBottomSheetViewModel()); - gh.lazySingleton<_i21.ViewAttachmenetsBottomSheetViewModel>( - () => _i21.ViewAttachmenetsBottomSheetViewModel()); - gh.lazySingleton<_i22.ViewReviewsBottomSheetViewModel>( - () => _i22.ViewReviewsBottomSheetViewModel()); + gh.lazySingleton<_i12.ListViewViewModel>(() => _i12.ListViewViewModel()); + gh.lazySingleton<_i13.LoginViewModel>(() => _i13.LoginViewModel()); + gh.lazySingleton<_i14.NewDocViewModel>(() => _i14.NewDocViewModel()); + gh.lazySingleton<_i15.SendEmailViewModel>(() => _i15.SendEmailViewModel()); + gh.lazySingleton<_i16.ShareBottomSheetViewModel>( + () => _i16.ShareBottomSheetViewModel()); + gh.lazySingleton<_i17.SortByFieldsBottomSheetViewModel>( + () => _i17.SortByFieldsBottomSheetViewModel()); + gh.lazySingleton<_i18.StorageService>(() => _i18.StorageService()); + gh.lazySingleton<_i19.TagsBottomSheetViewModel>( + () => _i19.TagsBottomSheetViewModel()); + gh.lazySingleton<_i20.ViewAttachmenetsBottomSheetViewModel>( + () => _i20.ViewAttachmenetsBottomSheetViewModel()); + gh.lazySingleton<_i21.ViewReviewsBottomSheetViewModel>( + () => _i21.ViewReviewsBottomSheetViewModel()); return get; } diff --git a/lib/config/frappe_icons.dart b/lib/config/frappe_icons.dart index ca5a0aab..e907fbf3 100644 --- a/lib/config/frappe_icons.dart +++ b/lib/config/frappe_icons.dart @@ -33,6 +33,7 @@ class FrappeIcons { static const home_outlined = '$_basePath/home_outlined.svg'; static const select = '$_basePath/select.svg'; static const arrow_right = '$_basePath/arrow_right.svg'; + static const arrow_right_2 = '$_basePath/arrow_right_2.svg'; static const email = '$_basePath/email.svg'; static const unlock = '$_basePath/unlock.svg'; static const sort_ascending = '$_basePath/sort_ascending.svg'; diff --git a/lib/form/controls/control.dart b/lib/form/controls/control.dart index 89064aba..c27dcdde 100644 --- a/lib/form/controls/control.dart +++ b/lib/form/controls/control.dart @@ -3,6 +3,7 @@ import 'package:frappe_app/config/frappe_palette.dart'; import 'package:frappe_app/config/palette.dart'; import 'package:frappe_app/form/controls/currency.dart'; +import 'package:frappe_app/form/controls/dynamic_link.dart'; import 'package:frappe_app/form/controls/read_only.dart'; import 'package:frappe_app/form/controls/text.dart'; import 'package:frappe_app/model/common.dart'; @@ -47,6 +48,16 @@ Widget makeControl({ } break; + case "Dynamic Link": + { + control = DynamicLink( + doctypeField: field, + doc: doc, + onControlChanged: onControlChanged, + ); + } + break; + case "Autocomplete": { control = AutoComplete( diff --git a/lib/form/controls/custom_table.dart b/lib/form/controls/custom_table.dart index d209a78c..f9d5efc7 100644 --- a/lib/form/controls/custom_table.dart +++ b/lib/form/controls/custom_table.dart @@ -19,7 +19,7 @@ class CustomTable extends StatelessWidget { name: doctypeField.fieldname, context: context, doctype: doctypeField.options, - value: doc[doctypeField.fieldname], + initialValue: doc[doctypeField.fieldname], ); } } diff --git a/lib/form/controls/date.dart b/lib/form/controls/date.dart index d8800853..0430c5d5 100644 --- a/lib/form/controls/date.dart +++ b/lib/form/controls/date.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:frappe_app/model/offline_storage.dart'; +import 'package:frappe_app/model/system_settings_response.dart'; +import 'package:frappe_app/utils/constants.dart'; +import 'package:intl/intl.dart'; import '../../config/palette.dart'; import '../../model/doctype_response.dart'; @@ -32,12 +36,19 @@ class Date extends StatelessWidget with Control, ControlInput { ); } + var dateFormat = SystemSettingsResponse.fromJson( + OfflineStorage.getItem("systemSettings")["data"], + ).message.defaults.dateFormat; + return FormBuilderDateTimePicker( key: key, inputType: InputType.date, valueTransformer: (val) { return val?.toIso8601String(); }, + format: DateFormat( + Constants.frappeFlutterDateFormatMapping[dateFormat], + ), initialValue: doc != null ? parseDate(doc![doctypeField.fieldname]) : null, keyboardType: TextInputType.number, diff --git a/lib/form/controls/dynamic_link.dart b/lib/form/controls/dynamic_link.dart new file mode 100644 index 00000000..a73fa6fe --- /dev/null +++ b/lib/form/controls/dynamic_link.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:frappe_app/config/frappe_icons.dart'; +import 'package:frappe_app/config/palette.dart'; +import 'package:frappe_app/model/common.dart'; +import 'package:frappe_app/utils/frappe_icon.dart'; +import 'package:frappe_app/views/form_view/form_view.dart'; +import 'package:frappe_app/widgets/form_builder_typeahead.dart'; +import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart'; + +import '../../model/doctype_response.dart'; +import '../../app/locator.dart'; +import '../../services/api/api.dart'; + +import '../../utils/helpers.dart'; +import '../../model/offline_storage.dart'; + +import 'base_control.dart'; +import 'base_input.dart'; + +class DynamicLink extends StatefulWidget { + final DoctypeField doctypeField; + final Map doc; + final OnControlChanged? onControlChanged; + + final key; + final bool showInputBorder; + final Function? onSuggestionSelected; + final Function? noItemsFoundBuilder; + final Widget? prefixIcon; + final ItemBuilder? itemBuilder; + final SuggestionsCallback? suggestionsCallback; + final AxisDirection direction; + final TextEditingController? controller; + + DynamicLink({ + this.key, + required this.doctypeField, + this.onControlChanged, + required this.doc, + this.prefixIcon, + this.onSuggestionSelected, + this.noItemsFoundBuilder, + this.showInputBorder = false, + this.itemBuilder, + this.suggestionsCallback, + this.controller, + this.direction = AxisDirection.down, + }); + + @override + _DynamicLinkState createState() => _DynamicLinkState(); +} + +class _DynamicLinkState extends State with Control, ControlInput { + @override + Widget build(BuildContext context) { + List validators = []; + var f = setMandatory(widget.doctypeField); + late bool enabled; + + if (f != null) { + validators.add( + f(context), + ); + } + + // if (widget.doctypeField.readOnly == 1 || + // (widget.doc != null && widget.doctypeField.setOnlyOnce == 1)) { + // enabled = false; + // } else { + // enabled = true; + // } + + if (widget.doc[widget.doctypeField.fieldname] != null && + widget.doctypeField.setOnlyOnce == 1) { + enabled = false; + } else { + enabled = true; + } + + return Theme( + data: Theme.of(context).copyWith(primaryColor: Colors.black), + child: FormBuilderTypeAhead( + key: widget.key, + enabled: enabled, + onChanged: (val) { + if (widget.onControlChanged != null) { + widget.onControlChanged!( + FieldValue( + field: widget.doctypeField, + value: val, + ), + ); + } + }, + controller: widget.controller, + initialValue: widget.doc[widget.doctypeField.fieldname], + direction: AxisDirection.up, + onSuggestionSelected: (item) { + if (widget.onSuggestionSelected != null) { + if (item is String) { + widget.onSuggestionSelected!(item); + } else if (item is Map) { + widget.onSuggestionSelected!(item["value"]); + } + } + }, + validator: FormBuilderValidators.compose(validators), + decoration: Palette.formFieldDecoration( + label: widget.doctypeField.label, + suffixIcon: widget.doc[widget.doctypeField.fieldname] != null && + widget.doc[widget.doctypeField.fieldname] != "" + ? IconButton( + onPressed: () { + pushNewScreen( + context, + screen: FormView( + doctype: widget.doc[widget.doctypeField.options], + name: widget.doc[widget.doctypeField.fieldname]), + ); + }, + icon: FrappeIcon( + FrappeIcons.arrow_right_2, + size: 14, + ), + ) + : null, + ), + selectionToTextTransformer: (item) { + if (item != null) { + if (item is Map) { + return item["value"]; + } + } + return item.toString(); + }, + name: widget.doctypeField.fieldname, + itemBuilder: widget.itemBuilder ?? + (context, item) { + if (item is Map) { + return ListTile( + title: Text( + item["value"], + ), + subtitle: item["description"] != null + ? Text( + item["description"], + ) + : null, + ); + } else { + return ListTile( + title: Text(item.toString()), + ); + } + }, + suggestionsCallback: widget.suggestionsCallback ?? + (query) async { + var lowercaseQuery = query.toLowerCase(); + // var isOnline = await verifyOnline(); + var isOnline = true; + if (!isOnline) { + var linkFull = await OfflineStorage.getItem( + '${widget.doctypeField.options}LinkFull'); + linkFull = linkFull["data"]; + + if (linkFull != null) { + return linkFull["results"].where( + (link) { + return (link["value"] as String) + .toLowerCase() + .contains(lowercaseQuery); + }, + ).toList(); + } else { + var queryLink = await OfflineStorage.getItem( + '$lowercaseQuery${widget.doctypeField.options}Link'); + queryLink = queryLink["data"]; + + if (queryLink != null) { + return queryLink["results"]; + } else { + return []; + } + } + } else { + var response = await locator().searchLink( + doctype: widget.doc[widget.doctypeField.options], + txt: lowercaseQuery, + ); + + return response["results"]; + } + }, + ), + ); + } +} diff --git a/lib/form/controls/link_field.dart b/lib/form/controls/link_field.dart index 75f1bdb7..3aac3d84 100644 --- a/lib/form/controls/link_field.dart +++ b/lib/form/controls/link_field.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:frappe_app/config/frappe_icons.dart'; import 'package:frappe_app/config/palette.dart'; import 'package:frappe_app/model/common.dart'; +import 'package:frappe_app/utils/frappe_icon.dart'; +import 'package:frappe_app/views/form_view/form_view.dart'; import 'package:frappe_app/widgets/form_builder_typeahead.dart'; -import 'package:provider/provider.dart'; +import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart'; import '../../model/doctype_response.dart'; import '../../app/locator.dart'; @@ -12,7 +15,6 @@ import '../../services/api/api.dart'; import '../../utils/helpers.dart'; import '../../model/offline_storage.dart'; -import '../../utils/enums.dart'; import 'base_control.dart'; import 'base_input.dart'; @@ -109,6 +111,23 @@ class _LinkFieldState extends State with Control, ControlInput { validator: FormBuilderValidators.compose(validators), decoration: Palette.formFieldDecoration( label: widget.doctypeField.label, + suffixIcon: widget.doc?[widget.doctypeField.fieldname] != null && + widget.doc?[widget.doctypeField.fieldname] != "" + ? IconButton( + onPressed: () { + pushNewScreen( + context, + screen: FormView( + doctype: widget.doctypeField.options, + name: widget.doc![widget.doctypeField.fieldname]), + ); + }, + icon: FrappeIcon( + FrappeIcons.arrow_right_2, + size: 14, + ), + ) + : null, ), selectionToTextTransformer: (item) { if (item != null) { diff --git a/lib/form/controls/time.dart b/lib/form/controls/time.dart index d44b619f..7ccc03f1 100644 --- a/lib/form/controls/time.dart +++ b/lib/form/controls/time.dart @@ -31,13 +31,24 @@ class Time extends StatelessWidget with Control, ControlInput { ); } + var initialValue; + + if (doc != null) { + var value = doc![doctypeField.fieldname]; + + if (value != null) { + if ((value as String).contains("T")) { + value = doc![doctypeField.fieldname].split("T")[1]; + } + initialValue = DateFormat.Hms().parse( + value, + ); + } + } + return FormBuilderDateTimePicker( key: key, - initialValue: doc != null - ? DateFormat.Hms().parse( - doc![doctypeField.fieldname], - ) - : null, + initialValue: initialValue, inputType: InputType.time, valueTransformer: (val) { return val?.toIso8601String(); diff --git a/lib/model/doctype_response.dart b/lib/model/doctype_response.dart index f68a82c8..c6c34e9d 100644 --- a/lib/model/doctype_response.dart +++ b/lib/model/doctype_response.dart @@ -1,8 +1,8 @@ class DoctypeResponse { late List docs; - late String? userSettings; + late String userSettings; - DoctypeResponse({required this.docs, this.userSettings}); + DoctypeResponse({required this.docs, required this.userSettings}); DoctypeResponse.fromJson(Map json) { if (json['docs'] != null) { diff --git a/lib/model/login_request.dart b/lib/model/login_request.dart index 14c9559e..7b8b7219 100644 --- a/lib/model/login_request.dart +++ b/lib/model/login_request.dart @@ -4,6 +4,7 @@ class LoginRequest { late String? cmd; late String? otp; late String? tmpId; + late String device; LoginRequest({ this.usr, @@ -11,6 +12,7 @@ class LoginRequest { this.cmd, this.otp, this.tmpId, + this.device = "mobile", }) : assert( (usr != null && pwd != null) || (cmd != null && otp != null && tmpId != null), @@ -22,6 +24,7 @@ class LoginRequest { cmd = json['cmd']; otp = json['otp']; tmpId = json['tmp_id']; + device = json['device']; } Map toJson() { @@ -31,6 +34,7 @@ class LoginRequest { data['cmd'] = this.cmd; data['otp'] = this.otp; data['tmp_id'] = this.tmpId; + data['device'] = this.device; return data; } } diff --git a/lib/model/system_settings_response.dart b/lib/model/system_settings_response.dart new file mode 100644 index 00000000..d8d1ccd3 --- /dev/null +++ b/lib/model/system_settings_response.dart @@ -0,0 +1,98 @@ +class SystemSettingsResponse { + late Message message; + + SystemSettingsResponse({ + required this.message, + }); + + SystemSettingsResponse.fromJson(Map json) { + message = Message.fromJson(json['message']); + } + + Map toJson() { + final Map data = new Map(); + data['message'] = this.message.toJson(); + return data; + } +} + +class Message { + late List timezones; + late Defaults defaults; + + Message({required this.timezones, required this.defaults}); + + Message.fromJson(Map json) { + timezones = json['timezones'].cast(); + defaults = Defaults.fromJson(json['defaults']); + } + + Map toJson() { + final Map data = new Map(); + data['timezones'] = this.timezones; + data['defaults'] = this.defaults.toJson(); + return data; + } +} + +class Defaults { + late String appName; + late String timeZone; + late String dateFormat; + late String timeFormat; + late String numberFormat; + late String floatPrecision; + late String currencyPrecision; + late String sessionExpiry; + late String sessionExpiryMobile; + late String minimumPasswordScore; + late String twoFactorMethod; + late String otpIssuerName; + + Defaults({ + required this.appName, + required this.timeZone, + required this.dateFormat, + required this.timeFormat, + required this.numberFormat, + required this.floatPrecision, + required this.currencyPrecision, + required this.sessionExpiry, + required this.sessionExpiryMobile, + required this.minimumPasswordScore, + required this.twoFactorMethod, + required this.otpIssuerName, + }); + + Defaults.fromJson(Map json) { + appName = json['app_name']; + timeZone = json['time_zone']; + dateFormat = json['date_format']; + timeFormat = json['time_format']; + numberFormat = json['number_format']; + floatPrecision = json['float_precision']; + currencyPrecision = json['currency_precision']; + sessionExpiry = json['session_expiry']; + sessionExpiryMobile = json['session_expiry_mobile']; + minimumPasswordScore = json['minimum_password_score']; + twoFactorMethod = json['two_factor_method']; + otpIssuerName = json['otp_issuer_name']; + } + + Map toJson() { + final Map data = new Map(); + data['app_name'] = this.appName; + data['time_zone'] = this.timeZone; + data['date_format'] = this.dateFormat; + data['time_format'] = this.timeFormat; + data['number_format'] = this.numberFormat; + data['float_precision'] = this.floatPrecision; + data['currency_precision'] = this.currencyPrecision; + data['session_expiry'] = this.sessionExpiry; + data['session_expiry_mobile'] = this.sessionExpiryMobile; + data['minimum_password_score'] = this.minimumPasswordScore; + data['two_factor_method'] = this.twoFactorMethod; + data['otp_issuer_name'] = this.otpIssuerName; + return data; + } +} diff --git a/lib/services/api/api.dart b/lib/services/api/api.dart index e01d4d90..b4b994f7 100644 --- a/lib/services/api/api.dart +++ b/lib/services/api/api.dart @@ -3,6 +3,7 @@ import 'package:frappe_app/model/common.dart'; import 'package:frappe_app/model/get_doc_response.dart'; import 'package:frappe_app/model/group_by_count_response.dart'; import 'package:frappe_app/model/login_request.dart'; +import 'package:frappe_app/model/system_settings_response.dart'; import 'package:frappe_app/model/upload_file_response.dart'; import '../../model/doctype_response.dart'; @@ -122,4 +123,12 @@ abstract class Api { required List currentFilters, required String field, }); + + Future getReportViewCount({ + required String doctype, + required Map filters, + required List fields, + }); + + Future getSystemSettings(); } diff --git a/lib/services/api/dio_api.dart b/lib/services/api/dio_api.dart index 9b5782ce..e2ece3dd 100644 --- a/lib/services/api/dio_api.dart +++ b/lib/services/api/dio_api.dart @@ -9,6 +9,7 @@ import 'package:frappe_app/model/common.dart'; import 'package:frappe_app/model/get_doc_response.dart'; import 'package:frappe_app/model/group_by_count_response.dart'; import 'package:frappe_app/model/login_request.dart'; +import 'package:frappe_app/model/system_settings_response.dart'; import 'package:frappe_app/model/upload_file_response.dart'; import '../../model/doctype_response.dart'; @@ -1002,4 +1003,88 @@ class DioApi implements Api { } } } + + Future getReportViewCount({ + @required String doctype, + @required Map filters, + @required List fields, + }) async { + var reqData = { + "doctype": doctype, + "filters": filters, + "fields": fields, + "distinct": false, + }; + + try { + final response = await DioHelper.dio.post( + '/method/frappe.desk.reportview.get_count', + data: reqData, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + + if (response.statusCode == 200) { + return response.data["message"]; + } else if (response.statusCode == HttpStatus.forbidden) { + throw ErrorResponse( + statusCode: response.statusCode, + statusMessage: response.statusMessage, + ); + } else { + throw ErrorResponse(); + } + } catch (e) { + if (e is DioError) { + var error = e.error; + if (error is SocketException) { + throw ErrorResponse( + statusCode: HttpStatus.serviceUnavailable, + statusMessage: error.message, + ); + } else { + throw ErrorResponse(statusMessage: error.message); + } + } else { + throw ErrorResponse(); + } + } + } + + Future getSystemSettings() async { + try { + final response = await DioHelper.dio.post( + '/method/frappe.core.doctype.system_settings.system_settings.load', + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + + if (response.statusCode == 200) { + return SystemSettingsResponse.fromJson(response.data); + } else if (response.statusCode == HttpStatus.forbidden) { + throw ErrorResponse( + statusCode: response.statusCode, + statusMessage: response.statusMessage, + ); + } else { + throw ErrorResponse(); + } + } catch (e) { + if (e is DioError) { + var error = e.error; + if (error is SocketException) { + throw ErrorResponse( + statusCode: HttpStatus.serviceUnavailable, + statusMessage: error.message, + ); + } else { + throw ErrorResponse(statusMessage: error.message); + } + } else { + throw ErrorResponse(); + } + } + } } diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 2b88cc1f..8c4c26cf 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -16,4 +16,21 @@ class Constants { // FilterOperator(label: "Not In", value: "not in"), FilterOperator(label: "Is", value: "is"), ]; + + static var filterOperatorLabelMapping = { + "like": "Like", + "=": "Equals", + "!=": "Not Equals", + "not like": "Not Like", + "is": "Is", + }; + + static var frappeFlutterDateFormatMapping = { + "dd-mm-yyyy": "d-M-y", + "yyyy-mm-dd": "y-M-d", + "dd/mm/yyyy": "d/M/y", + "dd.mm.yyyy": "d.M.y", + "mm/dd/yyyy": "M/d/y", + "mm-dd-yyyy": "M-d-y", + }; } diff --git a/lib/utils/helpers.dart b/lib/utils/helpers.dart index ed20ca00..d59f7f0f 100644 --- a/lib/utils/helpers.dart +++ b/lib/utils/helpers.dart @@ -80,7 +80,7 @@ String toTitleCase(String str) { } DateTime parseDate(val) { - if (val == null) { + if (val == null || val == "") { return null; } else if (val == "Today") { return DateTime.now(); diff --git a/lib/views/base_widget.dart b/lib/views/base_widget.dart new file mode 100644 index 00000000..98cf3901 --- /dev/null +++ b/lib/views/base_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class BaseWidget extends StatefulWidget { + final Widget Function(BuildContext context, T model, Widget? child) builder; + final T model; + final Widget? child; + final Function(T)? onModelReady; + + BaseWidget({ + required this.builder, + required this.model, + this.child, + this.onModelReady, + }); + + _BaseWidgetState createState() => _BaseWidgetState(); +} + +class _BaseWidgetState extends State> { + late T model; + + @override + void initState() { + model = widget.model; + + if (widget.onModelReady != null) { + widget.onModelReady!(model); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => model, + child: Consumer( + builder: widget.builder, + child: widget.child, + ), + ); + } +} diff --git a/lib/views/desk/desk_viewmodel.dart b/lib/views/desk/desk_viewmodel.dart index af9791d6..058d6d18 100644 --- a/lib/views/desk/desk_viewmodel.dart +++ b/lib/views/desk/desk_viewmodel.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:frappe_app/model/common.dart'; +import 'package:frappe_app/model/config.dart'; import 'package:frappe_app/utils/frappe_alert.dart'; import 'package:frappe_app/utils/loading_indicator.dart'; @@ -101,6 +102,27 @@ class DeskViewModel extends BaseViewModel { } desktopPage = _desktopPage; + // TODO + // desktopPage.message.shortcuts.items.forEach( + // (element) async { + // if (element.format != null && element.statsFilter != null) { + // var filters = element.statsFilter; + + // filters = filters!.replaceAll( + // "frappe.session.user", + // Config().userId!, + // ); + + // var count = await locator().getReportViewCount( + // doctype: element.linkTo, + // filters: jsonDecode(filters), + // fields: [], + // ); + + // element.format = element.format!.replaceAll("{}", count.toString()); + // } + // }, + // ); } getData() async { @@ -160,7 +182,7 @@ class DeskViewModel extends BaseViewModel { pushNewScreen( context, screen: FormView( - meta: meta, + meta: meta.docs[0], name: meta.docs[0].name, ), withNavBar: true, diff --git a/lib/views/form_view/bottom_sheets/attachments/view_attachments_bottom_sheet_view.dart b/lib/views/form_view/bottom_sheets/attachments/view_attachments_bottom_sheet_view.dart index 7ad7244c..8a16d0b3 100644 --- a/lib/views/form_view/bottom_sheets/attachments/view_attachments_bottom_sheet_view.dart +++ b/lib/views/form_view/bottom_sheets/attachments/view_attachments_bottom_sheet_view.dart @@ -177,7 +177,7 @@ class ViewFilesToAttach extends StatelessWidget { width: 12, ), Text( - filesToUpload[idx].file.name!, + filesToUpload[idx].file.name, ) ], ), diff --git a/lib/views/form_view/form_view.dart b/lib/views/form_view/form_view.dart index c86ce021..406ce5a5 100644 --- a/lib/views/form_view/form_view.dart +++ b/lib/views/form_view/form_view.dart @@ -35,41 +35,33 @@ import '../../utils/enums.dart'; import '../../widgets/custom_form.dart'; import '../../widgets/frappe_button.dart'; +import '../base_widget.dart'; import 'bottom_sheets/reviews/add_review_bottom_sheet_view.dart'; class FormView extends StatelessWidget { - final String? name; - final bool queued; - final Map? queuedData; - final DoctypeResponse meta; + final String name; + + final DoctypeDoc? meta; + final String? doctype; FormView({ - required this.meta, - this.name, - this.queued = false, - this.queuedData, + required this.name, + this.meta, + this.doctype, }); final GlobalKey _fbKey = GlobalKey(); - @override Widget build(BuildContext context) { - Provider.of( - context, - ); - return BaseView( + return BaseWidget( onModelReady: (model) { - model.communicationOnly = true; - model.meta = meta; - model.queued = queued; - model.queuedData = queuedData; - model.name = name; - model.isDirty = false; - model.getData(); - }, - onModelClose: (model) { - model.error = null; + model.init( + doctype: doctype, + constName: name, + constMeta: meta, + ); }, + model: FormViewViewModel(), builder: (context, model, child) => model.state == ViewState.busy ? Scaffold( body: Center( @@ -87,12 +79,13 @@ class FormView extends StatelessWidget { model.getData(); }); } + var docs = model.formData.docs; late String status; if (docs[0]["status"] == null) { var value = docs[0]["docstatus"]; - if (isSubmittable(meta.docs[0])) { + if (isSubmittable(model.meta)) { if (value == 0) { value = "Draft"; } else if (value == 1) { @@ -115,9 +108,14 @@ class FormView extends StatelessWidget { // var isLikedByUser = likedBy.contains(model.user); return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + print("abc${_fbKey.currentState!.value}"); + }, + ), backgroundColor: FrappePalette.grey[50], appBar: buildAppBar( - title: '${meta.docs[0].name} Details', + title: '${model.meta.name} Details', actions: [ Padding( padding: const EdgeInsets.symmetric( @@ -163,7 +161,7 @@ class FormView extends StatelessWidget { children: [ Flexible( child: Text( - getTitle(meta.docs[0], docs[0]) ?? "", + getTitle(model.meta, docs[0]) ?? "", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -172,28 +170,27 @@ class FormView extends StatelessWidget { ), ), Indicator.buildStatusButton( - meta.docs[0].name, + model.meta.name, status, ) ], ), ), - if (!queued) - DocInfo( - name: name!, - meta: meta.docs[0], - doc: docs[0], - doctype: meta.docs[0].name, - docInfo: model.docinfo!, - refreshCallback: () { - model.getData(); - }, - ), + DocInfo( + name: name, + meta: model.meta, + doc: docs[0], + doctype: model.meta.name, + docInfo: model.docinfo!, + refreshCallback: () { + model.getData(); + }, + ), CustomForm( onChanged: () { model.handleFormDataChange(); }, - fields: meta.docs[0].fields.where( + fields: model.meta.fields.where( (field) { return field.hidden != 1 && field.fieldtype != "Column Break"; @@ -202,62 +199,59 @@ class FormView extends StatelessWidget { formKey: _fbKey, doc: docs[0], ), - if (!queued) - Padding( - padding: const EdgeInsets.only( - bottom: 10, - ), - child: ListTileTheme( - tileColor: Colors.white, - child: CustomExpansionTile( - maintainState: true, - title: Text( - "Add a comment", - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 16, - ), + Padding( + padding: const EdgeInsets.only( + bottom: 10, + ), + child: ListTileTheme( + tileColor: Colors.white, + child: CustomExpansionTile( + maintainState: true, + title: Text( + "Add a comment", + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, ), - children: [ - Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - bottom: 24, - ), - child: CommentInput( - name: name!, - doctype: meta.docs[0].name, - callback: () { - model.getDocinfo(); - }, - ), - ) - ], ), + children: [ + Padding( + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + bottom: 24, + ), + child: CommentInput( + name: name, + doctype: model.meta.name, + callback: () { + model.getDocinfo(); + }, + ), + ) + ], ), ), - if (!queued) - Timeline( - docinfo: model.docinfo!, - doctype: meta.docs[0].name, - name: name!, - communicationOnly: model.communicationOnly, - switchCallback: (val) { - model.toggleSwitch(val); - }, - refreshCallback: () { - model.getDocinfo(); - }, - emailSubjectField: - docs[0][meta.docs[0].subjectField] ?? - getTitle( - meta.docs[0], - docs[0], - ), - emailSenderField: docs[0] - [meta.docs[0].senderField], - ), + ), + Timeline( + docinfo: model.docinfo!, + doctype: model.meta.name, + name: name, + communicationOnly: model.communicationOnly, + switchCallback: (val) { + model.toggleSwitch(val); + }, + refreshCallback: () { + model.getDocinfo(); + }, + emailSubjectField: + docs[0][model.meta.subjectField] ?? + getTitle( + model.meta, + docs[0], + ), + emailSenderField: docs[0][model.meta.senderField], + ), ], ), ); @@ -282,7 +276,6 @@ class FormView extends StatelessWidget { await model.handleUpdate( formValue: formValue, doc: doc, - queuedData: queuedData, ); FrappeAlert.infoAlert( title: 'Changes Saved', diff --git a/lib/views/form_view/form_view_viewmodel.dart b/lib/views/form_view/form_view_viewmodel.dart index 9264eccf..3a6e0eec 100644 --- a/lib/views/form_view/form_view_viewmodel.dart +++ b/lib/views/form_view/form_view_viewmodel.dart @@ -16,13 +16,10 @@ import '../../utils/enums.dart'; import '../../utils/helpers.dart'; import '../../model/queue.dart'; -@lazySingleton class FormViewViewModel extends BaseViewModel { - String? name; - late DoctypeResponse meta; - late bool queued; + late String name; + late DoctypeDoc meta; late bool isDirty; - Map? queuedData; ErrorResponse? error; late GetDocResponse formData; @@ -34,6 +31,26 @@ class FormViewViewModel extends BaseViewModel { notifyListeners(); } + init({ + String? doctype, + DoctypeDoc? constMeta, + required String constName, + }) async { + setState(ViewState.busy); + communicationOnly = true; + name = constName; + isDirty = false; + if (constMeta == null) { + if (doctype != null) { + var metaResponse = await locator().getDoctype(doctype); + meta = metaResponse.docs[0]; + } + } else { + meta = constMeta; + } + getData(); + } + handleFormDataChange() { if (!isDirty) { isDirty = true; @@ -48,54 +65,51 @@ class FormViewViewModel extends BaseViewModel { Future getData() async { setState(ViewState.busy); - if (queued && queuedData != null) { - formData = GetDocResponse( - docs: queuedData!["data"], - ); - } else { - try { - var isOnline = await verifyOnline(); - var doctype = meta.docs[0].name; - if (!isOnline) { - var response = OfflineStorage.getItem( - '$doctype$name', - ); - response = response["data"]; - if (response != null) { - formData = GetDocResponse.fromJson(response); - docinfo = formData.docinfo; - } else { - error = ErrorResponse( - statusCode: HttpStatus.serviceUnavailable, - ); - } + try { + // var isOnline = await verifyOnline(); + var isOnline = true; + var doctype = meta.name; + + if (!isOnline) { + var response = OfflineStorage.getItem( + '$doctype$name', + ); + response = response["data"]; + if (response != null) { + formData = GetDocResponse.fromJson(response); + docinfo = formData.docinfo; } else { - formData = await locator().getdoc( - doctype, - name!, + error = ErrorResponse( + statusCode: HttpStatus.serviceUnavailable, ); - docinfo = formData.docinfo; } - } catch (e) { - error = e as ErrorResponse; + } else { + formData = await locator().getdoc( + doctype, + name, + ); + docinfo = formData.docinfo; } + } catch (e) { + error = e as ErrorResponse; } + setState(ViewState.idle); } getDocinfo() async { - docinfo = await locator().getDocinfo(meta.docs[0].name, name!); + docinfo = await locator().getDocinfo(meta.name, name); notifyListeners(); } Future handleUpdate({ required Map formValue, required Map doc, - required Map? queuedData, }) async { LoadingIndicator.loadingWithBackgroundDisabled("Saving"); - var isOnline = await verifyOnline(); + // var isOnline = await verifyOnline(); + var isOnline = true; if (!isOnline) { // if (queuedData != null) { // queuedData["data"] = [ @@ -149,7 +163,7 @@ class FormViewViewModel extends BaseViewModel { try { var response = await locator().saveDocs( - meta.docs[0].name, + meta.name, formValue, ); diff --git a/lib/views/list_view/list_view.dart b/lib/views/list_view/list_view.dart index de0bac2f..f5497b36 100644 --- a/lib/views/list_view/list_view.dart +++ b/lib/views/list_view/list_view.dart @@ -42,6 +42,7 @@ class CustomListView extends StatelessWidget { return BaseView( onModelReady: (model) { model.meta = meta; + model.init(); model.getData(); model.getDesktopPage(module); model.getSortableFields(); diff --git a/lib/views/list_view/list_view_viewmodel.dart b/lib/views/list_view/list_view_viewmodel.dart index f3af8bcb..5a55c14c 100644 --- a/lib/views/list_view/list_view_viewmodel.dart +++ b/lib/views/list_view/list_view_viewmodel.dart @@ -94,6 +94,94 @@ class ListViewViewModel extends BaseViewModel { bool get hasError => error != null; + init() { + var userSettings = jsonDecode(meta.userSettings); + var userSettingsList = userSettings["List"]; + var userSettingsReport = userSettings["Report"]; + + if (userSettingsList != null && + (userSettingsList["filters"] as List).isNotEmpty) { + (userSettingsList["filters"] as List).forEach( + (listFilter) { + filters.add( + Filter( + field: meta.docs[0].fields.firstWhere( + (metaField) => metaField.fieldname == listFilter[1], + orElse: () { + return DoctypeField( + fieldname: listFilter[1], + label: listFilter[1], + ); + }, + ), + filterOperator: FilterOperator( + label: Constants.filterOperatorLabelMapping[listFilter[2]]!, + value: listFilter[2], + ), + value: listFilter[3].toString(), + ), + ); + }, + ); + + if (userSettingsList["sort_by"] != null) { + sortField = meta.docs[0].fields.firstWhere( + (metaField) => metaField.fieldname == userSettingsList["sort_by"], + orElse: () { + return DoctypeField( + fieldname: userSettingsList["sort_by"], + label: userSettingsList["sort_by"], + ); + }, + ); + } + + if (userSettingsList["sort_order"] != null) { + sortOrder = userSettingsList["sort_order"]; + } + } else if (userSettingsReport != null && + (userSettingsReport["filters"] as List).isNotEmpty) { + (userSettingsReport["filters"] as List).forEach( + (reportFilter) { + filters.add( + Filter( + field: meta.docs[0].fields.firstWhere( + (metaField) => metaField.fieldname == reportFilter[1], + orElse: () { + return DoctypeField( + fieldname: reportFilter[1], + label: reportFilter[1], + ); + }, + ), + filterOperator: FilterOperator( + label: Constants.filterOperatorLabelMapping[reportFilter[2]]!, + value: reportFilter[2], + ), + value: reportFilter[3].toString(), + ), + ); + }, + ); + + if (userSettingsReport["sort_by"] != null) { + sortField = meta.docs[0].fields.firstWhere( + (metaField) => metaField.fieldname == userSettingsReport["sort_by"], + orElse: () { + return DoctypeField( + fieldname: userSettingsReport["sort_by"], + label: userSettingsReport["sort_by"], + ); + }, + ); + } + + if (userSettingsReport["sort_order"] != null) { + sortOrder = userSettingsReport["sort_order"]; + } + } + } + getData() async { setState(ViewState.busy); try { @@ -169,7 +257,7 @@ class ListViewViewModel extends BaseViewModel { context, screen: FormView( name: name, - meta: meta, + meta: meta.docs[0], ), withNavBar: true, ); @@ -256,7 +344,7 @@ class ListViewViewModel extends BaseViewModel { pushNewScreen( context, screen: FormView( - meta: _meta, + meta: _meta.docs[0], name: _meta.docs[0].name, ), withNavBar: true, diff --git a/lib/views/login/login_viewmodel.dart b/lib/views/login/login_viewmodel.dart index 7601f694..d9592a61 100644 --- a/lib/views/login/login_viewmodel.dart +++ b/lib/views/login/login_viewmodel.dart @@ -52,6 +52,14 @@ class LoginViewModel extends BaseViewModel { ); } + getSystemSettings() async { + var systemSettings = await locator().getSystemSettings(); + OfflineStorage.putItem( + 'systemSettings', + systemSettings.toJson(), + ); + } + Future login(LoginRequest loginRequest) async { loginButtonLabel = "Verifying..."; notifyListeners(); @@ -75,6 +83,7 @@ class LoginViewModel extends BaseViewModel { await cacheAllUsers(); await initAwesomeItems(); await DioHelper.initCookies(); + getSystemSettings(); loginButtonLabel = "Success"; notifyListeners(); diff --git a/lib/views/new_doc/new_doc_viewmodel.dart b/lib/views/new_doc/new_doc_viewmodel.dart index 4f95b2bc..294e845b 100644 --- a/lib/views/new_doc/new_doc_viewmodel.dart +++ b/lib/views/new_doc/new_doc_viewmodel.dart @@ -95,7 +95,7 @@ class NewDocViewModel extends BaseViewModel { NavigationHelper.pushReplacement( context: context, page: FormView( - meta: meta, + meta: meta.docs[0], name: response.data["docs"][0]["name"], ), ); diff --git a/lib/views/queue.dart b/lib/views/queue.dart index 568fd436..04cf0afa 100644 --- a/lib/views/queue.dart +++ b/lib/views/queue.dart @@ -1,136 +1,136 @@ -import 'package:flutter/material.dart'; -import 'package:frappe_app/model/offline_storage.dart'; -import 'package:frappe_app/views/form_view/form_view.dart'; -import 'package:provider/provider.dart'; +// import 'package:flutter/material.dart'; +// import 'package:frappe_app/model/offline_storage.dart'; +// import 'package:frappe_app/views/form_view/form_view.dart'; +// import 'package:provider/provider.dart'; -import '../config/frappe_icons.dart'; -import '../config/palette.dart'; +// import '../config/frappe_icons.dart'; +// import '../config/palette.dart'; -import '../widgets/card_list_tile.dart'; +// import '../widgets/card_list_tile.dart'; -import '../utils/frappe_alert.dart'; -import '../utils/frappe_icon.dart'; -import '../utils/enums.dart'; -import '../utils/helpers.dart'; -import '../model/queue.dart'; +// import '../utils/frappe_alert.dart'; +// import '../utils/frappe_icon.dart'; +// import '../utils/enums.dart'; +// import '../utils/helpers.dart'; +// import '../model/queue.dart'; -class QueueList extends StatefulWidget { - @override - _QueueListState createState() => _QueueListState(); -} +// class QueueList extends StatefulWidget { +// @override +// _QueueListState createState() => _QueueListState(); +// } -class _QueueListState extends State { - void _refresh() { - setState(() {}); - } +// class _QueueListState extends State { +// void _refresh() { +// setState(() {}); +// } - @override - Widget build(BuildContext context) { - var connectionStatus = Provider.of( - context, - ); +// @override +// Widget build(BuildContext context) { +// var connectionStatus = Provider.of( +// context, +// ); - return Scaffold( - backgroundColor: Palette.bgColor, - appBar: AppBar( - title: Text('Queue'), - ), - body: RefreshIndicator( - onRefresh: () async { - _refresh(); - }, - child: Builder( - builder: ( - context, - ) { - var l = Queue.getQueueItems(); - if (l.length < 1) { - return Padding( - padding: EdgeInsets.all(8), - child: Text( - "Queue is Empty", - style: TextStyle( - fontSize: 20, - color: Palette.secondaryTxtColor, - fontWeight: FontWeight.bold, - ), - ), - ); - } - return ListView.builder( - itemCount: l.length, - itemBuilder: (context, index) { - var q = l[index]; - return CardListTile( - leading: IconButton( - icon: Icon(Icons.sync), - onPressed: () async { - var isOnline = await verifyOnline(); - if (connectionStatus == ConnectivityStatus.offline && - !isOnline) { - FrappeAlert.errorAlert( - title: 'Cant Sync, App is offline', - context: context, - ); - return; - } else if (q["error"] != null) { - FrappeAlert.errorAlert( - title: - "There was some error while processing this item", - context: context, - ); - return; - } +// return Scaffold( +// backgroundColor: Palette.bgColor, +// appBar: AppBar( +// title: Text('Queue'), +// ), +// body: RefreshIndicator( +// onRefresh: () async { +// _refresh(); +// }, +// child: Builder( +// builder: ( +// context, +// ) { +// var l = Queue.getQueueItems(); +// if (l.length < 1) { +// return Padding( +// padding: EdgeInsets.all(8), +// child: Text( +// "Queue is Empty", +// style: TextStyle( +// fontSize: 20, +// color: Palette.secondaryTxtColor, +// fontWeight: FontWeight.bold, +// ), +// ), +// ); +// } +// return ListView.builder( +// itemCount: l.length, +// itemBuilder: (context, index) { +// var q = l[index]; +// return CardListTile( +// leading: IconButton( +// icon: Icon(Icons.sync), +// onPressed: () async { +// var isOnline = await verifyOnline(); +// if (connectionStatus == ConnectivityStatus.offline && +// !isOnline) { +// FrappeAlert.errorAlert( +// title: 'Cant Sync, App is offline', +// context: context, +// ); +// return; +// } else if (q["error"] != null) { +// FrappeAlert.errorAlert( +// title: +// "There was some error while processing this item", +// context: context, +// ); +// return; +// } - await Queue.processQueueItem(q, index); - _refresh(); - }, - ), - title: Text(q['title'] ?? ""), - subtitle: Row( - children: [ - Text( - q['doctype'], - ), - VerticalDivider(), - Text( - q["type"], - ), - VerticalDivider(), - if (q["error"] != null) - FrappeIcon( - FrappeIcons.error, - size: 20, - ), - ], - ), - trailing: IconButton( - onPressed: () { - Queue.deleteAt(index); - setState(() {}); - }, - icon: Icon(Icons.clear), - ), - onTap: () async { - q["qIdx"] = index; - var meta = await OfflineStorage.getMeta(q['doctype']); +// await Queue.processQueueItem(q, index); +// _refresh(); +// }, +// ), +// title: Text(q['title'] ?? ""), +// subtitle: Row( +// children: [ +// Text( +// q['doctype'], +// ), +// VerticalDivider(), +// Text( +// q["type"], +// ), +// VerticalDivider(), +// if (q["error"] != null) +// FrappeIcon( +// FrappeIcons.error, +// size: 20, +// ), +// ], +// ), +// trailing: IconButton( +// onPressed: () { +// Queue.deleteAt(index); +// setState(() {}); +// }, +// icon: Icon(Icons.clear), +// ), +// onTap: () async { +// q["qIdx"] = index; +// var meta = await OfflineStorage.getMeta(q['doctype']); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) { - return FormView( - queued: true, - queuedData: q, - meta: meta, - ); - }, - )); - }, - ); - }, - ); - }, - ), - ), - ); - } -} +// Navigator.of(context).push(MaterialPageRoute( +// builder: (context) { +// return FormView( +// queued: true, +// queuedData: q, +// meta: meta.docs[0], +// ); +// }, +// )); +// }, +// ); +// }, +// ); +// }, +// ), +// ), +// ); +// } +// } diff --git a/lib/widgets/custom_form.dart b/lib/widgets/custom_form.dart index c4bf4aae..774897b5 100644 --- a/lib/widgets/custom_form.dart +++ b/lib/widgets/custom_form.dart @@ -24,6 +24,9 @@ class CustomForm extends StatelessWidget { @override Widget build(BuildContext context) { return BaseView( + onModelReady: (model) { + model.doc = doc; + }, builder: (context, model, child) => FormBuilder( onChanged: () { if (formKey.currentState != null) { @@ -41,7 +44,7 @@ class CustomForm extends StatelessWidget { child: Column( children: generateLayout( fields: fields, - doc: doc, + doc: model.doc, onControlChanged: (v) {}, ), ), @@ -53,7 +56,10 @@ class CustomForm extends StatelessWidget { @lazySingleton class CustomFormViewModel extends BaseViewModel { + late Map doc; + handleFormDataChange(Map formValue) { - // print(formValue); + doc = formValue; + notifyListeners(); } } diff --git a/lib/widgets/form_builder_table.dart b/lib/widgets/form_builder_table.dart index e33bfdea..8642ba51 100644 --- a/lib/widgets/form_builder_table.dart +++ b/lib/widgets/form_builder_table.dart @@ -15,7 +15,7 @@ class FormBuilderTable extends FormBuilderField { required String name, required BuildContext context, required String doctype, - required List value, + required T initialValue, Key? key, FormFieldValidator? validator, bool enabled = true, @@ -23,11 +23,13 @@ class FormBuilderTable extends FormBuilderField { key: key, name: name, validator: validator, + initialValue: initialValue, builder: (FormFieldState field) { return FutureBuilder( future: locator().getDoctype(doctype), builder: (context, snapshot) { if (snapshot.hasData) { + var value = (initialValue as List); var metaFields = (snapshot.data as DoctypeResponse).docs[0].fields; var tableFields = metaFields.where((field) { @@ -87,6 +89,8 @@ class FormBuilderTable extends FormBuilderField { return TableElement( doc: val, fields: tableFields, + meta: (snapshot.data as DoctypeResponse) + .docs[0], ); }, ), @@ -232,12 +236,14 @@ class FormBuilderTableState extends FormBuilderFieldState, T> {} class TableElement extends StatefulWidget { + final DoctypeDoc meta; final List fields; final Map doc; TableElement({ - required this.fields, + required this.meta, required this.doc, + required this.fields, }); @override @@ -272,7 +278,7 @@ class _TableElementState extends State { ], ), body: CustomForm( - fields: widget.fields, + fields: widget.meta.fields, formKey: _fbKey, doc: widget.doc, ),