LCOV - code coverage report
Current view: top level - lib - apptive_grid_form.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 160 160 100.0 %
Date: 2021-11-04 14:59:29 Functions: 0 0 -

          Line data    Source code
       1             : library apptive_grid_form;
       2             : 
       3             : import 'package:apptive_grid_core/apptive_grid_core.dart';
       4             : import 'package:apptive_grid_form/translation/apptive_grid_localization.dart';
       5             : import 'package:apptive_grid_form/widgets/apptive_grid_form_widgets.dart';
       6             : import 'package:flutter/material.dart';
       7             : import 'package:flutter/widgets.dart';
       8             : import 'package:lottie/lottie.dart';
       9             : 
      10             : export 'package:apptive_grid_core/apptive_grid_core.dart';
      11             : export 'package:apptive_grid_form/translation/apptive_grid_localization.dart';
      12             : export 'package:apptive_grid_form/translation/apptive_grid_translation.dart';
      13             : 
      14             : /// A Widget to display a ApptiveGrid Form
      15             : ///
      16             : /// In order to use this there needs to be a [ApptiveGrid] Widget in the Widget tree
      17             : class ApptiveGridForm extends StatefulWidget {
      18             :   /// Creates a ApptiveGridForm.
      19             :   ///
      20             :   /// The [formId] determines what Form is displayed. It works with empty and pre-filled forms.
      21           2 :   const ApptiveGridForm({
      22             :     Key? key,
      23             :     required this.formUri,
      24             :     this.titleStyle,
      25             :     this.contentPadding,
      26             :     this.titlePadding,
      27             :     this.hideTitle = false,
      28             :     this.onFormLoaded,
      29             :     this.onActionSuccess,
      30             :     this.onError,
      31           2 :   }) : super(key: key);
      32             : 
      33             :   /// [FormUri] of the Form to display
      34             :   ///
      35             :   /// If you copied the id from a EditLink or Preview Window on apptivegrid you should use:
      36             :   /// [FormUri..fromRedirect] with the id
      37             :   /// If you display Data gathered from a Grid you more likely want to use [FormUri..directForm]
      38             :   final FormUri formUri;
      39             : 
      40             :   /// Style for the Form Title. If no style is provided [headline5] of the [TextTheme] will be used
      41             :   final TextStyle? titleStyle;
      42             : 
      43             :   /// Padding of the Items in the Form. If no Padding is provided a EdgeInsets.all(8.0) will be used.
      44             :   final EdgeInsets? contentPadding;
      45             : 
      46             :   /// Padding for the title. If no Padding is provided the [contentPadding] is used
      47             :   final EdgeInsets? titlePadding;
      48             : 
      49             :   /// Flag to hide the form title, default is false
      50             :   final bool hideTitle;
      51             : 
      52             :   /// Callback after [FormData] loads successfully
      53             :   ///
      54             :   /// Use this to modify the UI Displaying the Form
      55             :   /// ```dart
      56             :   /// ApptiveGridForm(
      57             :   ///   id: [YOUR_FORM_ID],
      58             :   ///   onFormLoaded: (data) {
      59             :   ///     setState(() {
      60             :   ///       title = data.title;
      61             :   ///     });
      62             :   ///   }
      63             :   /// ),
      64             :   /// ```
      65             :   final void Function(FormData)? onFormLoaded;
      66             : 
      67             :   /// Callback after [FormAction] completes Successfully
      68             :   ///
      69             :   /// If this returns false the default success screen is not shown.
      70             :   /// This functionality can be used to do a custom Widget or Transition
      71             :   final Future<bool> Function(FormAction, FormData)? onActionSuccess;
      72             : 
      73             :   /// Callback if an Error occurs
      74             :   ///
      75             :   /// If this returns false the default error screen is not shown.
      76             :   /// This functionality can be used to do a custom Widget or Transition
      77             :   final Future<bool> Function(dynamic)? onError;
      78             : 
      79           2 :   @override
      80           2 :   ApptiveGridFormState createState() => ApptiveGridFormState();
      81             : }
      82             : 
      83             : /// [State] for an [ApptiveGridForm]. Use this to access [currentData] to get the most up to date version
      84             : class ApptiveGridFormState extends State<ApptiveGridForm> {
      85             :   FormData? _formData;
      86             :   late ApptiveGridClient _client;
      87             : 
      88             :   dynamic _error;
      89             : 
      90             :   final _dataKey = GlobalKey<ApptiveGridFormDataState>();
      91             : 
      92             :   /// Returns the data currently being edited
      93           4 :   FormData? get currentData => _dataKey.currentState?.currentData;
      94             : 
      95           2 :   @override
      96             :   void didChangeDependencies() {
      97           2 :     super.didChangeDependencies();
      98           6 :     _client = ApptiveGrid.getClient(context);
      99           2 :     _loadForm();
     100             :   }
     101             : 
     102           2 :   @override
     103             :   Widget build(BuildContext context) {
     104           2 :     return ApptiveGridFormData(
     105           2 :       key: _dataKey,
     106           2 :       formData: _formData,
     107           2 :       error: _error,
     108           4 :       titleStyle: widget.titleStyle,
     109           4 :       contentPadding: widget.contentPadding,
     110           4 :       titlePadding: widget.titlePadding,
     111           4 :       hideTitle: widget.hideTitle,
     112           4 :       onActionSuccess: widget.onActionSuccess,
     113           4 :       onError: widget.onError,
     114           2 :       triggerReload: _loadForm,
     115             :     );
     116             :   }
     117             : 
     118           2 :   void _loadForm() {
     119          12 :     _client.loadForm(formUri: widget.formUri).then((value) {
     120           4 :       if (widget.onFormLoaded != null) {
     121           2 :         widget.onFormLoaded!(value);
     122             :       }
     123           4 :       setState(() {
     124           2 :         _formData = value;
     125             :       });
     126           3 :     }).catchError((error) {
     127           1 :       _onError(error);
     128             :     });
     129             :   }
     130             : 
     131           1 :   void _onError(dynamic error) async {
     132           3 :     if (await widget.onError?.call(error) ?? true) {
     133           2 :       setState(() {
     134           1 :         _error = error;
     135             :       });
     136             :     }
     137             :   }
     138             : }
     139             : 
     140             : /// A Widget to display [FormData]
     141             : class ApptiveGridFormData extends StatefulWidget {
     142             :   /// Creates a Widget to display [formData]
     143             :   ///
     144             :   /// if [error] is not null it will display a error
     145           2 :   const ApptiveGridFormData({
     146             :     Key? key,
     147             :     this.formData,
     148             :     this.error,
     149             :     this.titleStyle,
     150             :     this.contentPadding,
     151             :     this.titlePadding,
     152             :     this.hideTitle = false,
     153             :     this.onActionSuccess,
     154             :     this.onError,
     155             :     this.triggerReload,
     156           2 :   }) : super(key: key);
     157             : 
     158             :   /// [FormData] that should be displayed
     159             :   final FormData? formData;
     160             : 
     161             :   /// Error that should be displayed. Having a error will have priority over [formData]
     162             :   final dynamic error;
     163             : 
     164             :   /// Style for the Form Title. If no style is provided [headline5] of the [TextTheme] will be used
     165             :   final TextStyle? titleStyle;
     166             : 
     167             :   /// Padding of the Items in the Form. If no Padding is provided a EdgeInsets.all(8.0) will be used.
     168             :   final EdgeInsets? contentPadding;
     169             : 
     170             :   /// Padding for the title. If no Padding is provided the [contentPadding] is used
     171             :   final EdgeInsets? titlePadding;
     172             : 
     173             :   /// Flag to hide the form title, default is false
     174             :   final bool hideTitle;
     175             : 
     176             :   /// Callback after [FormAction] completes Successfully
     177             :   ///
     178             :   /// If this returns false the default success screen is not shown.
     179             :   /// This functionality can be used to do a custom Widget or Transition
     180             :   final Future<bool> Function(FormAction, FormData)? onActionSuccess;
     181             : 
     182             :   /// Callback if an Error occurs
     183             :   ///
     184             :   /// If this returns false the default error screen is not shown.
     185             :   /// This functionality can be used to do a custom Widget or Transition
     186             :   final Future<bool> Function(dynamic)? onError;
     187             : 
     188             :   /// Will be called when [formData] should be reloaded
     189             :   final void Function()? triggerReload;
     190             : 
     191           2 :   @override
     192           2 :   ApptiveGridFormDataState createState() => ApptiveGridFormDataState();
     193             : }
     194             : 
     195             : /// [State] for [ApptiveGridFormData]
     196             : ///
     197             : /// Use this to access [currentData]
     198             : class ApptiveGridFormDataState extends State<ApptiveGridFormData> {
     199             :   FormData? _formData;
     200             :   late ApptiveGridClient _client;
     201             : 
     202             :   final _formKey = GlobalKey<FormState>();
     203             : 
     204             :   bool _success = false;
     205             : 
     206             :   dynamic _error;
     207             : 
     208             :   bool _saved = false;
     209             : 
     210             :   /// Returns the current [FormData] held in this Widget
     211           1 :   FormData? get currentData {
     212           2 :     if (!_success && !_saved) {
     213           1 :       return _formData;
     214             :     } else {
     215             :       return null;
     216             :     }
     217             :   }
     218             : 
     219           2 :   @override
     220             :   void didUpdateWidget(covariant ApptiveGridFormData oldWidget) {
     221           2 :     super.didUpdateWidget(oldWidget);
     222           2 :     _updateView();
     223             :   }
     224             : 
     225           2 :   @override
     226             :   void didChangeDependencies() {
     227           2 :     super.didChangeDependencies();
     228           6 :     _client = ApptiveGrid.getClient(context);
     229             :   }
     230             : 
     231           2 :   void _updateView({
     232             :     bool resetFormData = true,
     233             :   }) {
     234           4 :     setState(() {
     235             :       if (resetFormData) {
     236           6 :         _formData = widget.formData != null
     237           8 :             ? FormData.fromJson(widget.formData!.toJson())
     238             :             : null;
     239             :       }
     240           6 :       _error = widget.error;
     241           2 :       _success = false;
     242           2 :       _saved = false;
     243             :     });
     244             :   }
     245             : 
     246           2 :   @override
     247             :   Widget build(BuildContext context) {
     248           2 :     return ApptiveGridLocalization(
     249           2 :       child: Builder(
     250           2 :         builder: (buildContext) {
     251           2 :           if (_error != null) {
     252           1 :             return _buildError(buildContext);
     253           2 :           } else if (_saved) {
     254           1 :             return _buildSaved(buildContext);
     255           2 :           } else if (_success) {
     256           2 :             return _buildSuccess(buildContext);
     257           2 :           } else if (_formData == null) {
     258           2 :             return _buildLoading(buildContext);
     259             :           } else {
     260           4 :             return _buildForm(buildContext, _formData!);
     261             :           }
     262             :         },
     263             :       ),
     264             :     );
     265             :   }
     266             : 
     267           2 :   Widget _buildLoading(BuildContext context) {
     268             :     return const Center(
     269             :       child: CircularProgressIndicator(),
     270             :     );
     271             :   }
     272             : 
     273           2 :   Widget _buildForm(BuildContext context, FormData data) {
     274           2 :     final localization = ApptiveGridLocalization.of(context)!;
     275           2 :     return Form(
     276           2 :       key: _formKey,
     277           2 :       child: ListView.builder(
     278          12 :         itemCount: 1 + data.components.length + data.actions.length,
     279           2 :         itemBuilder: (context, index) {
     280             :           // Title
     281           2 :           if (index == 0) {
     282           4 :             if (widget.hideTitle) {
     283             :               return const SizedBox();
     284             :             } else {
     285           2 :               return Padding(
     286           4 :                 padding: widget.titlePadding ??
     287           4 :                     widget.contentPadding ??
     288           2 :                     _defaultPadding,
     289           2 :                 child: Text(
     290           2 :                   data.title,
     291           4 :                   style: widget.titleStyle ??
     292           6 :                       Theme.of(context).textTheme.headline5,
     293             :                 ),
     294             :               );
     295             :             }
     296           8 :           } else if (index < data.components.length + 1) {
     297           1 :             final componentIndex = index - 1;
     298           1 :             return Padding(
     299           3 :               padding: widget.contentPadding ?? _defaultPadding,
     300           3 :               child: fromModel(data.components[componentIndex]),
     301             :             );
     302             :           } else {
     303           8 :             final actionIndex = index - 1 - data.components.length;
     304           2 :             return ActionButton(
     305           4 :               action: data.actions[actionIndex],
     306           2 :               onPressed: _performAction,
     307           4 :               child: Text(localization.actionSend),
     308             :             );
     309             :           }
     310             :         },
     311             :       ),
     312             :     );
     313             :   }
     314             : 
     315           2 :   Widget _buildSuccess(BuildContext context) {
     316           2 :     final localization = ApptiveGridLocalization.of(context)!;
     317           2 :     return ListView(
     318             :       padding: const EdgeInsets.all(32.0),
     319           2 :       children: [
     320           2 :         AspectRatio(
     321             :           aspectRatio: 1,
     322           2 :           child: Lottie.asset(
     323             :             'packages/apptive_grid_form/assets/success.json',
     324             :             repeat: false,
     325             :           ),
     326             :         ),
     327           2 :         Text(
     328           2 :           localization.sendSuccess,
     329             :           textAlign: TextAlign.center,
     330           6 :           style: Theme.of(context).textTheme.headline4,
     331             :         ),
     332           2 :         Center(
     333           2 :           child: TextButton(
     334           1 :             onPressed: () {
     335           2 :               widget.triggerReload?.call();
     336           1 :               _updateView();
     337             :             },
     338           2 :             child: Text(
     339           2 :               localization.additionalAnswer,
     340             :             ),
     341             :           ),
     342             :         )
     343             :       ],
     344             :     );
     345             :   }
     346             : 
     347           1 :   Widget _buildSaved(BuildContext context) {
     348           1 :     final localization = ApptiveGridLocalization.of(context)!;
     349           1 :     return ListView(
     350             :       padding: const EdgeInsets.all(32.0),
     351           1 :       children: [
     352           1 :         AspectRatio(
     353             :           aspectRatio: 1,
     354           1 :           child: Lottie.asset(
     355             :             'packages/apptive_grid_form/assets/saved.json',
     356             :             repeat: false,
     357             :           ),
     358             :         ),
     359           1 :         Text(
     360           1 :           localization.savedForLater,
     361             :           textAlign: TextAlign.center,
     362           3 :           style: Theme.of(context).textTheme.headline4,
     363             :         ),
     364           1 :         Center(
     365           1 :           child: TextButton(
     366           1 :             onPressed: () {
     367           2 :               widget.triggerReload?.call();
     368           1 :               _updateView();
     369             :             },
     370           2 :             child: Text(localization.additionalAnswer),
     371             :           ),
     372             :         )
     373             :       ],
     374             :     );
     375             :   }
     376             : 
     377           1 :   Widget _buildError(BuildContext context) {
     378           1 :     final localization = ApptiveGridLocalization.of(context)!;
     379           1 :     return ListView(
     380             :       padding: const EdgeInsets.all(32.0),
     381           1 :       children: [
     382           1 :         AspectRatio(
     383             :           aspectRatio: 1,
     384           1 :           child: Lottie.asset(
     385             :             'packages/apptive_grid_form/assets/error.json',
     386             :             repeat: false,
     387             :           ),
     388             :         ),
     389           1 :         Text(
     390           1 :           localization.errorTitle,
     391             :           textAlign: TextAlign.center,
     392           3 :           style: Theme.of(context).textTheme.headline4,
     393             :         ),
     394           1 :         Center(
     395           1 :           child: TextButton(
     396           1 :             onPressed: () {
     397           2 :               widget.triggerReload?.call();
     398           1 :               _updateView(resetFormData: false);
     399             :             },
     400           2 :             child: Text(localization.backToForm),
     401             :           ),
     402             :         )
     403             :       ],
     404             :     );
     405             :   }
     406             : 
     407           2 :   EdgeInsets get _defaultPadding => const EdgeInsets.all(8.0);
     408             : 
     409           2 :   void _performAction(FormAction action) {
     410           6 :     if (_formKey.currentState!.validate()) {
     411          10 :       _client.performAction(action, _formData!).then((response) async {
     412           4 :         if (response.statusCode < 400) {
     413           7 :           if (await widget.onActionSuccess?.call(action, _formData!) ?? true) {
     414           4 :             setState(() {
     415           2 :               _success = true;
     416             :             });
     417             :           }
     418             :         } else {
     419             :           // FormData was saved to [ApptiveGridCache]
     420           1 :           _onSavedOffline();
     421             :         }
     422           3 :       }).catchError((error) {
     423           1 :         _onError(error);
     424             :       });
     425             :     }
     426             :   }
     427             : 
     428           1 :   void _onSavedOffline() {
     429           1 :     if (mounted) {
     430           2 :       setState(() {
     431           1 :         _saved =
     432           4 :             ApptiveGrid.getClient(context, listen: false).options.cache != null;
     433             :       });
     434             :     }
     435             :   }
     436             : 
     437           1 :   void _onError(dynamic error) async {
     438           3 :     if (await widget.onError?.call(error) ?? true) {
     439           2 :       setState(() {
     440           1 :         _error = error;
     441             :       });
     442             :     }
     443             :   }
     444             : }

Generated by: LCOV version 1.15