1341 lines
59 KiB
Dart
1341 lines
59 KiB
Dart
// Flutter imports:
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
// Package imports:
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_redux/flutter_redux.dart';
|
|
import 'package:http/http.dart';
|
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
|
import 'package:invoiceninja_flutter/data/models/purchase_order_model.dart';
|
|
import 'package:invoiceninja_flutter/data/models/quote_model.dart';
|
|
import 'package:invoiceninja_flutter/data/models/serializers.dart';
|
|
import 'package:invoiceninja_flutter/data/web_client.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
|
import 'package:invoiceninja_flutter/utils/completers.dart';
|
|
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
|
import 'package:invoiceninja_flutter/utils/platforms.dart';
|
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
// Project imports:
|
|
import 'package:invoiceninja_flutter/constants.dart';
|
|
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
|
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
|
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
|
|
import 'package:invoiceninja_flutter/redux/static/static_selectors.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/dialogs/multiselect_dialog.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/bool_dropdown_button.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/color_picker.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/design_picker.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/forms/learn_more.dart';
|
|
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
|
|
import 'package:invoiceninja_flutter/ui/settings/invoice_design_vm.dart';
|
|
import 'package:invoiceninja_flutter/utils/fonts.dart';
|
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
|
import 'package:printing/printing.dart';
|
|
|
|
class InvoiceDesign extends StatefulWidget {
|
|
const InvoiceDesign({
|
|
Key? key,
|
|
required this.viewModel,
|
|
}) : super(key: key);
|
|
|
|
final InvoiceDesignVM viewModel;
|
|
|
|
@override
|
|
_InvoiceDesignState createState() => _InvoiceDesignState();
|
|
}
|
|
|
|
class _InvoiceDesignState extends State<InvoiceDesign>
|
|
with SingleTickerProviderStateMixin {
|
|
static final GlobalKey<FormState> _formKey =
|
|
GlobalKey<FormState>(debugLabel: '_invoiceDesign');
|
|
|
|
TabController? _controller;
|
|
FocusScopeNode? _focusNode;
|
|
|
|
final _logoSizeController = TextEditingController();
|
|
|
|
List<TextEditingController> _controllers = [];
|
|
final _debouncer = Debouncer();
|
|
|
|
bool _wasInvoiceDesignChanged = false;
|
|
bool _wasQuoteDesignChanged = false;
|
|
bool _wasCreditDesignChanged = false;
|
|
bool _wasPurchaseOrderDesignChanged = false;
|
|
|
|
bool? _updateAllInvoiceDesigns = false;
|
|
bool? _updateAllQuoteDesigns = false;
|
|
bool? _updateAllCreditDesigns = false;
|
|
bool? _updateAllPurchaseOrderDesigns = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final viewModel = widget.viewModel;
|
|
final state = viewModel.state;
|
|
final settingsUIState = state.settingsUIState;
|
|
_focusNode = FocusScopeNode();
|
|
|
|
int tabs = 6;
|
|
|
|
if (state.prefState.isMobile) {
|
|
tabs++;
|
|
}
|
|
|
|
if (viewModel.settings.shareInvoiceQuoteColumns == false) {
|
|
tabs++;
|
|
}
|
|
|
|
[
|
|
EntityType.invoice,
|
|
EntityType.quote,
|
|
EntityType.credit,
|
|
EntityType.task,
|
|
EntityType.vendor,
|
|
EntityType.purchaseOrder,
|
|
].forEach((entityType) {
|
|
if (state.company!.isModuleEnabled(entityType)) {
|
|
tabs++;
|
|
}
|
|
});
|
|
|
|
_controller = TabController(
|
|
vsync: this, length: tabs, initialIndex: settingsUIState.tabIndex);
|
|
_controller!.addListener(_onTabChanged);
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
_controllers = [
|
|
_logoSizeController,
|
|
];
|
|
|
|
_controllers
|
|
.forEach((dynamic controller) => controller.removeListener(_onChanged));
|
|
|
|
final settings = widget.viewModel.settings;
|
|
_logoSizeController.text = (settings.companyLogoSize ?? '').isEmpty
|
|
? ''
|
|
: parseInt(settings.companyLogoSize!).toString();
|
|
|
|
_controllers
|
|
.forEach((dynamic controller) => controller.addListener(_onChanged));
|
|
|
|
super.didChangeDependencies();
|
|
}
|
|
|
|
void _onTabChanged() {
|
|
final store = StoreProvider.of<AppState>(context);
|
|
store.dispatch(UpdateSettingsTab(tabIndex: _controller!.index));
|
|
}
|
|
|
|
void _onChanged() {
|
|
final viewModel = widget.viewModel;
|
|
final logoSize = _logoSizeController.text.trim();
|
|
|
|
final settings = viewModel.settings.rebuild((b) => b
|
|
..companyLogoSize = logoSize.isEmpty
|
|
? ''
|
|
: logoSize +
|
|
(viewModel.settings.companyLogoSize!.contains('px')
|
|
? 'px'
|
|
: '%'));
|
|
if (settings != viewModel.settings) {
|
|
_debouncer.run(() {
|
|
viewModel.onSettingsChanged(settings);
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controllers.forEach((dynamic controller) {
|
|
controller.removeListener(_onChanged);
|
|
controller.dispose();
|
|
});
|
|
_controller!.removeListener(_onTabChanged);
|
|
_controller!.dispose();
|
|
_focusNode!.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final store = StoreProvider.of<AppState>(context);
|
|
final localization = AppLocalization.of(context)!;
|
|
final viewModel = widget.viewModel;
|
|
final state = viewModel.state;
|
|
final settings = viewModel.settings;
|
|
final company = viewModel.company;
|
|
final isFiltered = state.uiState.settingsUIState.isFiltered;
|
|
|
|
final tabs = [
|
|
localization.generalSettings,
|
|
if (isMobile(context)) localization.preview,
|
|
localization.clientDetails,
|
|
localization.companyDetails,
|
|
localization.companyAddress,
|
|
if (company.isModuleEnabled(EntityType.invoice))
|
|
localization.invoiceDetails,
|
|
if (company.isModuleEnabled(EntityType.quote)) localization.quoteDetails,
|
|
if (company.isModuleEnabled(EntityType.credit))
|
|
localization.creditDetails,
|
|
if (company.isModuleEnabled(EntityType.vendor))
|
|
localization.vendorDetails,
|
|
if (company.isModuleEnabled(EntityType.purchaseOrder))
|
|
localization.purchaseOrderDetails,
|
|
if (settings.shareInvoiceQuoteColumns == false) ...[
|
|
localization.invoiceProductColumns,
|
|
localization.quoteProductColumns,
|
|
] else
|
|
localization.productColumns,
|
|
if (company.isModuleEnabled(EntityType.task)) localization.taskColumns,
|
|
localization.totalFields,
|
|
];
|
|
|
|
return EditScaffold(
|
|
title: localization.invoiceDesign,
|
|
onSavePressed: (context) {
|
|
viewModel.onSavePressed(context, [
|
|
if (_updateAllInvoiceDesigns!) EntityType.invoice,
|
|
if (_updateAllQuoteDesigns!) EntityType.quote,
|
|
if (_updateAllCreditDesigns!) EntityType.credit,
|
|
if (_updateAllPurchaseOrderDesigns!) EntityType.purchaseOrder,
|
|
]);
|
|
},
|
|
appBarBottom: state.settingsUIState.isFiltered
|
|
? null
|
|
: TabBar(
|
|
key: ValueKey(state.settingsUIState.updatedAt),
|
|
controller: _controller,
|
|
isScrollable: true,
|
|
tabs: tabs
|
|
.map((tab) => Tab(
|
|
child: Text(tab!),
|
|
))
|
|
.toList(),
|
|
),
|
|
body: Row(
|
|
children: [
|
|
Expanded(
|
|
child: AppTabForm(
|
|
tabController: _controller,
|
|
formKey: _formKey,
|
|
focusNode: _focusNode,
|
|
children: <Widget>[
|
|
ScrollableListView(
|
|
primary: true,
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: EdgeInsets.only(
|
|
right: 16,
|
|
left: 16,
|
|
bottom: isMobile(context) ? 10 : 4,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: AppButton(
|
|
label: localization.customize.toUpperCase(),
|
|
iconData: Icons.settings,
|
|
onPressed: () =>
|
|
state.designState.customDesigns.isEmpty
|
|
? createEntity(
|
|
context: context,
|
|
entity: DesignEntity(state: state),
|
|
)
|
|
: store.dispatch(ViewSettings(
|
|
section: kSettingsCustomDesigns,
|
|
)),
|
|
),
|
|
),
|
|
if (isDesktop(context)) ...[
|
|
SizedBox(
|
|
width: kTableColumnGap,
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 18),
|
|
child: SwitchListTile(
|
|
title: Text(localization.showPreview!),
|
|
value: state.settingsUIState.showPdfPreview,
|
|
onChanged: (value) {
|
|
final store =
|
|
StoreProvider.of<AppState>(context);
|
|
store.dispatch(ToggleShowPdfPreview());
|
|
},
|
|
activeColor:
|
|
Theme.of(context).colorScheme.secondary,
|
|
),
|
|
)),
|
|
]
|
|
],
|
|
),
|
|
),
|
|
FormCard(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
if (state.isProPlan) ...[
|
|
if (company.isModuleEnabled(EntityType.invoice)) ...[
|
|
DesignPicker(
|
|
label: localization.invoiceDesign,
|
|
initialValue: settings.defaultInvoiceDesignId,
|
|
onSelected: (value) {
|
|
setState(() {
|
|
_wasInvoiceDesignChanged = true;
|
|
});
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) =>
|
|
b..defaultInvoiceDesignId = value.id));
|
|
},
|
|
),
|
|
if (!isFiltered &&
|
|
_wasInvoiceDesignChanged &&
|
|
state.userCompany!.isAdmin)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: CheckboxListTile(
|
|
activeColor:
|
|
Theme.of(context).colorScheme.secondary,
|
|
title: Text(localization.updateAllRecords!),
|
|
value: _updateAllInvoiceDesigns,
|
|
onChanged: (value) => setState(
|
|
() => _updateAllInvoiceDesigns = value,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
if (company.isModuleEnabled(EntityType.quote)) ...[
|
|
DesignPicker(
|
|
label: localization.quoteDesign,
|
|
initialValue: settings.defaultQuoteDesignId,
|
|
onSelected: (value) {
|
|
setState(() {
|
|
_wasQuoteDesignChanged = true;
|
|
});
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) => b..defaultQuoteDesignId = value.id));
|
|
},
|
|
),
|
|
if (!isFiltered &&
|
|
_wasQuoteDesignChanged &&
|
|
state.userCompany!.isAdmin)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: CheckboxListTile(
|
|
activeColor:
|
|
Theme.of(context).colorScheme.secondary,
|
|
title: Text(localization.updateAllRecords!),
|
|
value: _updateAllQuoteDesigns,
|
|
onChanged: (value) => setState(
|
|
() => _updateAllQuoteDesigns = value,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
if (company.isModuleEnabled(EntityType.credit)) ...[
|
|
DesignPicker(
|
|
label: localization.creditDesign,
|
|
initialValue: settings.defaultCreditDesignId,
|
|
onSelected: (value) {
|
|
setState(() {
|
|
_wasCreditDesignChanged = true;
|
|
});
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) =>
|
|
b..defaultCreditDesignId = value.id));
|
|
},
|
|
),
|
|
if (!isFiltered &&
|
|
_wasCreditDesignChanged &&
|
|
state.userCompany!.isAdmin)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: CheckboxListTile(
|
|
activeColor:
|
|
Theme.of(context).colorScheme.secondary,
|
|
title: Text(localization.updateAllRecords!),
|
|
value: _updateAllCreditDesigns,
|
|
onChanged: (value) => setState(
|
|
() => _updateAllCreditDesigns = value,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
if (company
|
|
.isModuleEnabled(EntityType.purchaseOrder)) ...[
|
|
DesignPicker(
|
|
label: localization.purchaseOrderDesign,
|
|
initialValue:
|
|
settings.defaultPurchaseOrderDesignId,
|
|
onSelected: (value) {
|
|
setState(() {
|
|
_wasPurchaseOrderDesignChanged = true;
|
|
});
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) => b
|
|
..defaultPurchaseOrderDesignId =
|
|
value.id));
|
|
},
|
|
),
|
|
if (!isFiltered &&
|
|
_wasPurchaseOrderDesignChanged &&
|
|
state.userCompany!.isAdmin)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: CheckboxListTile(
|
|
activeColor:
|
|
Theme.of(context).colorScheme.secondary,
|
|
title: Text(localization.updateAllRecords!),
|
|
value: _updateAllPurchaseOrderDesigns,
|
|
onChanged: (value) => setState(
|
|
() =>
|
|
_updateAllPurchaseOrderDesigns = value,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
] else ...[
|
|
OutlinedButton(
|
|
child: Text(
|
|
localization.setDefaultDesign!.toUpperCase()),
|
|
onPressed: () {
|
|
store.dispatch(ViewSettings(
|
|
company: state.company,
|
|
section: kSettingsCompanyDetails,
|
|
tabIndex: 3,
|
|
));
|
|
},
|
|
),
|
|
SizedBox(height: 16),
|
|
],
|
|
AppDropdownButton(
|
|
labelText: localization.pageLayout,
|
|
value: settings.pageLayout,
|
|
onChanged: (dynamic value) =>
|
|
viewModel.onSettingsChanged(settings
|
|
.rebuild((b) => b..pageLayout = value)),
|
|
items: kPageLayouts
|
|
.map((pageLayout) => DropdownMenuItem<String>(
|
|
value: pageLayout,
|
|
child:
|
|
Text(localization.lookup(pageLayout)!),
|
|
))
|
|
.toList(),
|
|
),
|
|
AppDropdownButton(
|
|
labelText: localization.pageSize,
|
|
value: settings.pageSize,
|
|
onChanged: (dynamic value) =>
|
|
viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..pageSize = value)),
|
|
items: kPageSizes
|
|
.map((pageSize) => DropdownMenuItem<String>(
|
|
value: pageSize,
|
|
child: Text(localization.lookup(pageSize)!),
|
|
))
|
|
.toList(),
|
|
),
|
|
AppDropdownButton(
|
|
labelText: localization.fontSize,
|
|
value: settings.fontSize == null
|
|
? ''
|
|
: '${settings.fontSize}',
|
|
onChanged: (dynamic value) =>
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) => b..fontSize = int.parse(value))),
|
|
items:
|
|
List<int>.generate(18, (index) => (index * 2) + 6)
|
|
.map((fontSize) => DropdownMenuItem<String>(
|
|
value: '$fontSize',
|
|
child: fontSize == 0
|
|
? SizedBox()
|
|
: Text('$fontSize'),
|
|
))
|
|
.toList(),
|
|
),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: DecoratedFormField(
|
|
label: localization.logoSize,
|
|
controller: _logoSizeController,
|
|
//onSavePressed: _onSavePressed,
|
|
keyboardType: TextInputType.text,
|
|
),
|
|
),
|
|
SizedBox(width: 16),
|
|
SizedBox(
|
|
width: 150,
|
|
child: AppDropdownButton(
|
|
labelText: '',
|
|
value: (settings.companyLogoSize ?? '')
|
|
.contains('px')
|
|
? localization.pixels
|
|
: localization.percent,
|
|
onChanged: (dynamic value) =>
|
|
viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b
|
|
..companyLogoSize =
|
|
_logoSizeController.text +
|
|
(value == localization.pixels
|
|
? 'px'
|
|
: '%')),
|
|
),
|
|
items: [
|
|
DropdownMenuItem<String>(
|
|
child: Text(localization.percent),
|
|
value: localization.percent,
|
|
),
|
|
DropdownMenuItem<String>(
|
|
child: Text(localization.pixels!),
|
|
value: localization.pixels,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
FormCard(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
LearnMoreUrl(
|
|
url: 'https://fonts.google.com',
|
|
child: EntityDropdown(
|
|
entityType: EntityType.font,
|
|
labelText: localization.primaryFont,
|
|
entityId: settings.primaryFont,
|
|
entityMap: memoizedFontMap(kGoogleFonts),
|
|
onSelected: (font) => viewModel.onSettingsChanged(
|
|
settings.rebuild(
|
|
(b) => b..primaryFont = font?.id)),
|
|
),
|
|
),
|
|
EntityDropdown(
|
|
entityType: EntityType.font,
|
|
labelText: localization.secondaryFont,
|
|
entityId: settings.secondaryFont,
|
|
entityMap: memoizedFontMap(kGoogleFonts),
|
|
onSelected: (font) => viewModel.onSettingsChanged(
|
|
settings.rebuild(
|
|
(b) => b..secondaryFont = font?.id)),
|
|
),
|
|
FormColorPicker(
|
|
labelText: localization.primaryColor,
|
|
onSelected: (value) => viewModel.onSettingsChanged(
|
|
settings
|
|
.rebuild((b) => b..primaryColor = value)),
|
|
initialValue: settings.primaryColor,
|
|
),
|
|
FormColorPicker(
|
|
labelText: localization.secondaryColor,
|
|
onSelected: (value) => viewModel.onSettingsChanged(
|
|
settings
|
|
.rebuild((b) => b..secondaryColor = value)),
|
|
initialValue: settings.secondaryColor,
|
|
),
|
|
]),
|
|
FormCard(
|
|
children: [
|
|
BoolDropdownButton(
|
|
label: localization.showPaidStamp,
|
|
value: settings.showPaidStamp ?? false,
|
|
iconData: Icons.paid,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..showPaidStamp = value),
|
|
),
|
|
),
|
|
BoolDropdownButton(
|
|
label: localization.showShippingAddress,
|
|
value: settings.showShippingAddress ?? false,
|
|
iconData: Icons.local_shipping,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings
|
|
.rebuild((b) => b..showShippingAddress = value),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
FormCard(
|
|
isLast: true,
|
|
children: [
|
|
BoolDropdownButton(
|
|
label: localization.emptyColumns,
|
|
value: !(settings.hideEmptyColumnsOnPdf ?? false),
|
|
iconData: MdiIcons.table,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) =>
|
|
b..hideEmptyColumnsOnPdf = value == false),
|
|
),
|
|
enabledLabel: localization.show,
|
|
disabledLabel: localization.hide,
|
|
),
|
|
BoolDropdownButton(
|
|
label: localization.pageNumbering,
|
|
value: settings.pageNumbering ?? false,
|
|
iconData: Icons.format_list_numbered,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..pageNumbering = value),
|
|
),
|
|
enabledLabel: localization.show,
|
|
disabledLabel: localization.hide,
|
|
),
|
|
AppDropdownButton(
|
|
labelText: localization.pageNumberingAlignment,
|
|
value: settings.pageNumberingAlignment,
|
|
onChanged: (dynamic value) =>
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) => b..pageNumberingAlignment = value)),
|
|
items: [
|
|
DropdownMenuItem<String>(
|
|
child: Text(localization.left!),
|
|
value: SettingsEntity.PAGE_NUMBER_ALIGN_LEFT,
|
|
),
|
|
DropdownMenuItem<String>(
|
|
child: Text(localization.center!),
|
|
value: SettingsEntity.PAGE_NUMBER_ALIGN_CENTER,
|
|
),
|
|
DropdownMenuItem<String>(
|
|
child: Text(localization.right!),
|
|
value: SettingsEntity.PAGE_NUMBER_ALIGN_RIGHT),
|
|
],
|
|
/*
|
|
items: kPageLayouts
|
|
.map((pageLayout) => DropdownMenuItem<String>(
|
|
value: pageLayout,
|
|
child: Text(localization.lookup(pageLayout)),
|
|
))
|
|
.toList(),
|
|
*/
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
if (isMobile(context))
|
|
_PdfPreview(
|
|
settings: viewModel.settings,
|
|
state: state,
|
|
),
|
|
/*
|
|
ScrollableListView(
|
|
padding: const EdgeInsets.all(10),
|
|
children: <Widget>[
|
|
FormCard(
|
|
children: <Widget>[
|
|
BoolDropdownButton(
|
|
label: localization.allPagesHeader,
|
|
value: settings.allPagesHeader,
|
|
iconData: MdiIcons.pageLayoutHeader,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..allPagesHeader = value)),
|
|
enabledLabel: localization.allPages,
|
|
disabledLabel: localization.firstPage,
|
|
),
|
|
BoolDropdownButton(
|
|
label: localization.allPagesFooter,
|
|
value: settings.allPagesFooter,
|
|
iconData: MdiIcons.pageLayoutFooter,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..allPagesFooter = value)),
|
|
enabledLabel: localization.allPages,
|
|
disabledLabel: localization.lastPage,
|
|
),
|
|
],
|
|
),
|
|
FormCard(
|
|
children: <Widget>[
|
|
BoolDropdownButton(
|
|
label: localization.hidePaidToDate,
|
|
helpLabel: localization.hidePaidToDateHelp,
|
|
value: settings.hidePaidToDate,
|
|
iconData: MdiIcons.fileInvoiceDollar,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..hidePaidToDate = value)),
|
|
),
|
|
BoolDropdownButton(
|
|
label: localization.invoiceEmbedDocuments,
|
|
helpLabel: localization.invoiceEmbedDocumentsHelp,
|
|
value: settings.embedDocuments,
|
|
iconData: MdiIcons.image,
|
|
onChanged: (value) => viewModel.onSettingsChanged(
|
|
settings.rebuild((b) => b..embedDocuments = value)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
*/
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
ClientFields.name,
|
|
ClientFields.number,
|
|
ClientFields.idNumber,
|
|
ClientFields.vatNumber,
|
|
ClientFields.website,
|
|
ClientFields.phone,
|
|
ClientFields.address1,
|
|
ClientFields.address2,
|
|
ClientFields.cityStatePostal,
|
|
ClientFields.postalCityState,
|
|
ClientFields.postalCity,
|
|
ClientFields.country,
|
|
ClientFields.custom1,
|
|
ClientFields.custom2,
|
|
ClientFields.custom3,
|
|
ClientFields.custom4,
|
|
].map((field) => '\$client.$field'),
|
|
...[
|
|
ContactFields.fullName,
|
|
ContactFields.email,
|
|
ContactFields.phone,
|
|
ContactFields.custom1,
|
|
ContactFields.custom2,
|
|
ContactFields.custom3,
|
|
ContactFields.custom4,
|
|
].map((field) => '\$contact.$field'),
|
|
],
|
|
defaultSelected: [
|
|
...[
|
|
ClientFields.name,
|
|
ClientFields.number,
|
|
ClientFields.vatNumber,
|
|
ClientFields.address1,
|
|
ClientFields.address2,
|
|
ClientFields.cityStatePostal,
|
|
ClientFields.country,
|
|
ClientFields.phone,
|
|
].map((field) => '\$client.$field'),
|
|
...[
|
|
ContactFields.email,
|
|
].map((field) => '\$contact.$field'),
|
|
],
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsClientDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(settings.setFieldsForSection(
|
|
kPdfFieldsClientDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'client',
|
|
),
|
|
),
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
CompanyFields.name,
|
|
CompanyFields.idNumber,
|
|
CompanyFields.vatNumber,
|
|
CompanyFields.website,
|
|
CompanyFields.email,
|
|
CompanyFields.phone,
|
|
CompanyFields.address1,
|
|
CompanyFields.address2,
|
|
CompanyFields.cityStatePostal,
|
|
CompanyFields.postalCityState,
|
|
CompanyFields.postalCity,
|
|
CompanyFields.country,
|
|
CompanyFields.custom1,
|
|
CompanyFields.custom2,
|
|
CompanyFields.custom3,
|
|
CompanyFields.custom4,
|
|
].map((field) => '\$company.$field').toList(),
|
|
defaultSelected: [
|
|
CompanyFields.name,
|
|
CompanyFields.idNumber,
|
|
CompanyFields.vatNumber,
|
|
CompanyFields.website,
|
|
CompanyFields.email,
|
|
CompanyFields.phone,
|
|
].map((field) => '\$company.$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsCompanyDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(settings.setFieldsForSection(
|
|
kPdfFieldsCompanyDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'company',
|
|
),
|
|
),
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
CompanyFields.name,
|
|
CompanyFields.idNumber,
|
|
CompanyFields.vatNumber,
|
|
CompanyFields.website,
|
|
CompanyFields.email,
|
|
CompanyFields.phone,
|
|
CompanyFields.address1,
|
|
CompanyFields.address2,
|
|
CompanyFields.cityStatePostal,
|
|
CompanyFields.postalCityState,
|
|
CompanyFields.postalCity,
|
|
CompanyFields.country,
|
|
CompanyFields.custom1,
|
|
CompanyFields.custom2,
|
|
CompanyFields.custom3,
|
|
CompanyFields.custom4,
|
|
].map((field) => '\$company.$field').toList(),
|
|
defaultSelected: [
|
|
CompanyFields.address1,
|
|
CompanyFields.address2,
|
|
CompanyFields.cityStatePostal,
|
|
CompanyFields.country,
|
|
].map((field) => '\$company.$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsCompanyAddress),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(settings.setFieldsForSection(
|
|
kPdfFieldsCompanyAddress, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'company',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.invoice))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
InvoiceFields.number,
|
|
InvoiceFields.poNumber,
|
|
InvoiceFields.date,
|
|
InvoiceFields.dueDate,
|
|
InvoiceFields.amount,
|
|
InvoiceFields.balance,
|
|
InvoiceFields.balanceDue,
|
|
InvoiceFields.customValue1,
|
|
InvoiceFields.customValue2,
|
|
InvoiceFields.customValue3,
|
|
InvoiceFields.customValue4,
|
|
InvoiceFields.project,
|
|
InvoiceFields.vendor,
|
|
].map((field) => '\$invoice.$field'),
|
|
...[
|
|
ClientFields.balance,
|
|
].map((field) => '\$client.$field')
|
|
],
|
|
defaultSelected: [
|
|
InvoiceFields.number,
|
|
InvoiceFields.poNumber,
|
|
InvoiceFields.date,
|
|
InvoiceFields.dueDate,
|
|
InvoiceFields.total,
|
|
InvoiceFields.balanceDue,
|
|
].map((field) => '\$invoice.$field').toList(),
|
|
selected: settings
|
|
.getFieldsForSection(kPdfFieldsInvoiceDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsInvoiceDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'invoice',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.quote))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
QuoteFields.number,
|
|
QuoteFields.poNumber,
|
|
QuoteFields.date,
|
|
QuoteFields.validUntil,
|
|
QuoteFields.total,
|
|
QuoteFields.customValue1,
|
|
QuoteFields.customValue2,
|
|
QuoteFields.customValue3,
|
|
QuoteFields.customValue4,
|
|
].map((field) => '\$quote.$field'),
|
|
...[
|
|
ClientFields.balance,
|
|
].map((field) => '\$client.$field')
|
|
],
|
|
defaultSelected: [
|
|
QuoteFields.number,
|
|
QuoteFields.poNumber,
|
|
QuoteFields.date,
|
|
QuoteFields.validUntil,
|
|
QuoteFields.total,
|
|
].map((field) => '\$quote.$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsQuoteDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsQuoteDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'quote',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.credit))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
CreditFields.number,
|
|
CreditFields.poNumber,
|
|
CreditFields.date,
|
|
CreditFields.total,
|
|
CreditFields.balance,
|
|
CreditFields.customValue1,
|
|
CreditFields.customValue2,
|
|
CreditFields.customValue3,
|
|
CreditFields.customValue4,
|
|
].map((field) => '\$credit.$field'),
|
|
...[
|
|
ClientFields.balance,
|
|
].map((field) => '\$client.$field')
|
|
],
|
|
defaultSelected: [
|
|
CreditFields.number,
|
|
CreditFields.poNumber,
|
|
CreditFields.date,
|
|
CreditFields.balance,
|
|
CreditFields.total,
|
|
].map((field) => '\$credit.$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsCreditDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsCreditDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'credit',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.vendor))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
VendorFields.name,
|
|
VendorFields.number,
|
|
VendorFields.vatNumber,
|
|
VendorFields.address1,
|
|
VendorFields.address2,
|
|
VendorFields.cityStatePostal,
|
|
VendorFields.postalCityState,
|
|
VendorFields.postalCity,
|
|
VendorFields.country,
|
|
VendorFields.customValue1,
|
|
VendorFields.customValue2,
|
|
VendorFields.customValue3,
|
|
VendorFields.customValue4,
|
|
].map((field) => '\$vendor.$field'),
|
|
...[
|
|
ContactFields.email,
|
|
].map((field) => '\$contact.$field'),
|
|
],
|
|
defaultSelected: [
|
|
...[
|
|
VendorFields.name,
|
|
VendorFields.number,
|
|
VendorFields.vatNumber,
|
|
VendorFields.address1,
|
|
VendorFields.address2,
|
|
VendorFields.cityStatePostal,
|
|
].map((field) => '\$vendor.$field'),
|
|
...[
|
|
ContactFields.email,
|
|
].map((field) => '\$contact.$field'),
|
|
],
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsVendorDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsVendorDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'vendor',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.purchaseOrder))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
...[
|
|
PurchaseOrderFields.number,
|
|
PurchaseOrderFields.date,
|
|
PurchaseOrderFields.dueDate,
|
|
PurchaseOrderFields.total,
|
|
PurchaseOrderFields.balanceDue,
|
|
PurchaseOrderFields.customValue1,
|
|
PurchaseOrderFields.customValue2,
|
|
PurchaseOrderFields.customValue3,
|
|
PurchaseOrderFields.customValue4,
|
|
].map((field) => '\$purchase_order.$field'),
|
|
/*
|
|
...[
|
|
ClientFields.balance,
|
|
].map((field) => '\$client.$field')
|
|
*/
|
|
],
|
|
defaultSelected: [
|
|
PurchaseOrderFields.number,
|
|
PurchaseOrderFields.date,
|
|
PurchaseOrderFields.dueDate,
|
|
PurchaseOrderFields.total,
|
|
PurchaseOrderFields.balanceDue,
|
|
].map((field) => '\$purchase_order.$field').toList(),
|
|
selected: settings
|
|
.getFieldsForSection(kPdfFieldsPurchaseOrderDetails),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsPurchaseOrderDetails, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'purchase_order',
|
|
),
|
|
),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.max,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Expanded(
|
|
child: FormCard(
|
|
child: MultiSelectList(
|
|
options: [
|
|
ProductItemFields.item,
|
|
ProductItemFields.description,
|
|
if (company.enableProductQuantity)
|
|
ProductItemFields.quantity,
|
|
ProductItemFields.unitCost,
|
|
if (company.hasItemTaxes) ...[
|
|
ProductItemFields.tax,
|
|
ProductItemFields.taxAmount,
|
|
],
|
|
if (company.enableProductDiscount)
|
|
ProductItemFields.discount,
|
|
ProductItemFields.lineTotal,
|
|
ProductItemFields.custom1,
|
|
ProductItemFields.custom2,
|
|
ProductItemFields.custom3,
|
|
ProductItemFields.custom4,
|
|
ProductItemFields.grossLineTotal,
|
|
].map((field) => '\$product.$field').toList(),
|
|
defaultSelected: [
|
|
ProductItemFields.item,
|
|
ProductItemFields.description,
|
|
ProductItemFields.unitCost,
|
|
if (company.enableProductQuantity)
|
|
ProductItemFields.quantity,
|
|
if (company.enableProductDiscount)
|
|
ProductItemFields.discount,
|
|
if (company.hasItemTaxes) ProductItemFields.tax,
|
|
ProductItemFields.lineTotal,
|
|
].map((field) => '\$product.$field').toList(),
|
|
selected: settings
|
|
.getFieldsForSection(kPdfFieldsProductColumns),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsProductColumns, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'product',
|
|
),
|
|
),
|
|
),
|
|
FormCard(
|
|
isLast: true,
|
|
child: SwitchListTile(
|
|
title: Text(localization.shareInvoiceQuoteColumns!),
|
|
value: settings.shareInvoiceQuoteColumns ?? true,
|
|
activeColor: Theme.of(context).colorScheme.secondary,
|
|
onChanged: (value) {
|
|
viewModel.onSettingsChanged(settings.rebuild(
|
|
(b) => b..shareInvoiceQuoteColumns = value));
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
if (settings.shareInvoiceQuoteColumns == false)
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
ProductItemFields.item,
|
|
ProductItemFields.description,
|
|
if (company.enableProductQuantity)
|
|
ProductItemFields.quantity,
|
|
ProductItemFields.unitCost,
|
|
if (company.hasItemTaxes) ...[
|
|
ProductItemFields.tax,
|
|
ProductItemFields.taxAmount,
|
|
],
|
|
if (company.enableProductDiscount)
|
|
ProductItemFields.discount,
|
|
ProductItemFields.lineTotal,
|
|
ProductItemFields.custom1,
|
|
ProductItemFields.custom2,
|
|
ProductItemFields.custom3,
|
|
ProductItemFields.custom4,
|
|
ProductItemFields.grossLineTotal,
|
|
].map((field) => '\$product.$field').toList(),
|
|
defaultSelected: [
|
|
ProductItemFields.item,
|
|
ProductItemFields.description,
|
|
ProductItemFields.unitCost,
|
|
if (company.enableProductQuantity)
|
|
ProductItemFields.quantity,
|
|
if (company.enableProductDiscount)
|
|
ProductItemFields.discount,
|
|
if (company.hasItemTaxes) ProductItemFields.tax,
|
|
ProductItemFields.lineTotal,
|
|
].map((field) => '\$product.$field').toList(),
|
|
selected: settings
|
|
.getFieldsForSection(kPdfFieldsProductQuoteColumns),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsProductQuoteColumns, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'product',
|
|
),
|
|
),
|
|
if (company.isModuleEnabled(EntityType.task))
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
TaskItemFields.service,
|
|
TaskItemFields.description,
|
|
TaskItemFields.hours,
|
|
TaskItemFields.rate,
|
|
if (company.hasItemTaxes) ...[
|
|
TaskItemFields.tax,
|
|
TaskItemFields.taxAmount,
|
|
],
|
|
if (company.enableProductDiscount)
|
|
TaskItemFields.discount,
|
|
TaskItemFields.lineTotal,
|
|
TaskItemFields.custom1,
|
|
TaskItemFields.custom2,
|
|
TaskItemFields.custom3,
|
|
TaskItemFields.custom4,
|
|
TaskItemFields.grossLineTotal,
|
|
].map((field) => '\$task.$field').toList(),
|
|
defaultSelected: [
|
|
TaskItemFields.service,
|
|
TaskItemFields.description,
|
|
TaskItemFields.rate,
|
|
TaskItemFields.hours,
|
|
if (company.enableProductDiscount)
|
|
TaskItemFields.discount,
|
|
if (company.hasItemTaxes) TaskItemFields.tax,
|
|
TaskItemFields.lineTotal,
|
|
].map((field) => '\$task.$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsTaskColumns),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(
|
|
settings.setFieldsForSection(
|
|
kPdfFieldsTaskColumns, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'task',
|
|
),
|
|
),
|
|
FormCard(
|
|
isLast: true,
|
|
child: MultiSelectList(
|
|
options: [
|
|
InvoiceTotalFields.subtotal,
|
|
InvoiceTotalFields.netSubtotal,
|
|
InvoiceTotalFields.discount,
|
|
InvoiceTotalFields.lineTaxes,
|
|
InvoiceTotalFields.totalTaxes,
|
|
InvoiceTotalFields.customSurcharge1,
|
|
InvoiceTotalFields.customSurcharge2,
|
|
InvoiceTotalFields.customSurcharge3,
|
|
InvoiceTotalFields.customSurcharge4,
|
|
InvoiceTotalFields.paidToDate,
|
|
InvoiceTotalFields.total,
|
|
InvoiceTotalFields.outstanding,
|
|
].map((field) => '\$$field').toList(),
|
|
defaultSelected: [
|
|
InvoiceTotalFields.subtotal,
|
|
InvoiceTotalFields.discount,
|
|
InvoiceTotalFields.customSurcharge1,
|
|
InvoiceTotalFields.customSurcharge2,
|
|
InvoiceTotalFields.customSurcharge3,
|
|
InvoiceTotalFields.customSurcharge4,
|
|
InvoiceTotalFields.totalTaxes,
|
|
InvoiceTotalFields.lineTaxes,
|
|
InvoiceTotalFields.total,
|
|
InvoiceTotalFields.paidToDate,
|
|
InvoiceTotalFields.outstanding,
|
|
].map((field) => '\$$field').toList(),
|
|
selected:
|
|
settings.getFieldsForSection(kPdfFieldsTotalFields),
|
|
onSelected: (values) {
|
|
viewModel.onSettingsChanged(settings.setFieldsForSection(
|
|
kPdfFieldsTotalFields, values));
|
|
},
|
|
addTitle: localization.addField,
|
|
liveChanges: true,
|
|
prefix: 'total',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (state.settingsUIState.showPdfPreview)
|
|
Expanded(
|
|
child: _PdfPreview(
|
|
state: state,
|
|
settings: viewModel.settings,
|
|
entityType: tabs[_controller!.index] ==
|
|
localization.vendorDetails ||
|
|
tabs[_controller!.index] ==
|
|
localization.purchaseOrderDetails
|
|
? EntityType.purchaseOrder
|
|
: tabs[_controller!.index] == localization.quoteDetails
|
|
? EntityType.quote
|
|
: tabs[_controller!.index] == localization.creditDetails
|
|
? EntityType.credit
|
|
: EntityType.invoice,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PdfPreview extends StatefulWidget {
|
|
const _PdfPreview({
|
|
Key? key,
|
|
this.state,
|
|
this.settings,
|
|
this.entityType = EntityType.invoice,
|
|
}) : super(key: key);
|
|
|
|
final SettingsEntity? settings;
|
|
final EntityType entityType;
|
|
final AppState? state;
|
|
|
|
@override
|
|
State<_PdfPreview> createState() => _PdfPreviewState();
|
|
}
|
|
|
|
class _PdfPreviewState extends State<_PdfPreview> {
|
|
http.Response? response;
|
|
bool isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_loadPdf();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (oldWidget.settings != widget.settings) {
|
|
_loadPdf();
|
|
}
|
|
}
|
|
|
|
void _loadPdf() async {
|
|
final state = widget.state!;
|
|
final settingsUIState = state.settingsUIState;
|
|
final url = state.credentials.url! + '/live_design';
|
|
|
|
final request = PdfPreviewRequest(
|
|
entityType: widget.entityType.apiValue,
|
|
settingsType: settingsUIState.entityType.apiValue,
|
|
settings: widget.settings!,
|
|
groupId: settingsUIState.group.id ?? '',
|
|
clientId: settingsUIState.client.id ?? '',
|
|
);
|
|
|
|
setState(() => isLoading = true);
|
|
|
|
response = await (WebClient()
|
|
.post(
|
|
url,
|
|
state.credentials.token,
|
|
data: jsonEncode(
|
|
serializers.serializeWith(
|
|
PdfPreviewRequest.serializer,
|
|
request,
|
|
),
|
|
),
|
|
rawResponse: true,
|
|
)
|
|
.catchError((dynamic error) {
|
|
print('## Error: $error');
|
|
}) as FutureOr<Response?>);
|
|
|
|
setState(() => isLoading = false);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (response == null) {
|
|
return LoadingIndicator();
|
|
}
|
|
|
|
return Stack(
|
|
children: [
|
|
PdfPreview(
|
|
build: (format) => response!.bodyBytes,
|
|
canChangeOrientation: false,
|
|
canChangePageFormat: false,
|
|
canDebug: false,
|
|
maxPageWidth: 800,
|
|
allowPrinting: false,
|
|
allowSharing: false,
|
|
),
|
|
if (isLoading) LinearProgressIndicator(),
|
|
],
|
|
);
|
|
}
|
|
}
|