Client statements

This commit is contained in:
Hillel Coren 2021-10-10 10:54:26 +03:00
parent c0804f876e
commit 1fe255befd
3 changed files with 325 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/product/product_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/client/client_pdf_vm.dart';
import 'package:invoiceninja_flutter/ui/client/client_screen.dart';
import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
@ -19,6 +20,7 @@ List<Middleware<AppState>> createStoreClientsMiddleware([
final viewClientList = _viewClientList();
final viewClient = _viewClient();
final editClient = _editClient();
final showPdfClient = _showPdfClient();
final loadClients = _loadClients(repository);
final loadClient = _loadClient(repository);
final saveClient = _saveClient(repository);
@ -31,6 +33,7 @@ List<Middleware<AppState>> createStoreClientsMiddleware([
TypedMiddleware<AppState, ViewClientList>(viewClientList),
TypedMiddleware<AppState, ViewClient>(viewClient),
TypedMiddleware<AppState, EditClient>(editClient),
TypedMiddleware<AppState, ShowPdfClient>(showPdfClient),
TypedMiddleware<AppState, LoadClients>(loadClients),
TypedMiddleware<AppState, LoadClient>(loadClient),
TypedMiddleware<AppState, SaveClientRequest>(saveClient),
@ -269,3 +272,18 @@ Middleware<AppState> _saveDocument(ClientRepository repository) {
next(action);
};
}
Middleware<AppState> _showPdfClient() {
return (Store<AppState> store, dynamic dynamicAction,
NextDispatcher next) async {
final action = dynamicAction as ShowPdfClient;
next(action);
store.dispatch(UpdateCurrentRoute(ClientPdfScreen.route));
if (store.state.prefState.isMobile) {
navigatorKey.currentState.pushNamed(ClientPdfScreen.route);
}
};
}

View File

@ -0,0 +1,254 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io' as file;
import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_share/flutter_share.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:flutter/foundation.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/app_text_button.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
import 'package:invoiceninja_flutter/ui/client/client_pdf_vm.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:native_pdf_view/native_pdf_view.dart';
import 'package:path_provider/path_provider.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
class ClientPdfView extends StatefulWidget {
const ClientPdfView({
Key key,
@required this.viewModel,
this.showAppBar = true,
}) : super(key: key);
final ClientPdfVM viewModel;
final bool showAppBar;
@override
_ClientPdfViewState createState() => _ClientPdfViewState();
}
class _ClientPdfViewState extends State<ClientPdfView> {
bool _isLoading = true;
String _pdfString;
http.Response _response;
PdfController _pdfController;
int _pageNumber = 1, _pageCount = 1;
@override
void didChangeDependencies() {
super.didChangeDependencies();
loadPdf();
}
void loadPdf() {
setState(() {
_isLoading = true;
});
_loadPDF(
context,
widget.viewModel.client,
).then((response) {
setState(() {
_response = response;
_isLoading = false;
});
if (kIsWeb) {
_pdfString =
'data:application/pdf;base64,' + base64Encode(response.bodyBytes);
WebUtils.registerWebView(_pdfString);
} else {
final document = PdfDocument.openData(_response.bodyBytes);
if (_pdfController == null) {
_pdfController = PdfController(document: document);
} else {
// loadDocument is causing an error
//_pdfController.loadDocument(document);
_pdfController?.dispose();
_pdfController = PdfController(document: document);
}
}
}).catchError((Object error) {
_isLoading = false;
});
}
@override
void dispose() {
_pdfController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final localization = AppLocalization.of(context);
final client = widget.viewModel.client;
final pageSelector = _pageCount == 1
? <Widget>[]
: [
IconButton(
icon: Icon(Icons.navigate_before),
onPressed: _pageNumber > 1
? () => _pdfController.previousPage(
duration:
Duration(milliseconds: kDefaultAnimationDuration),
curve: Curves.easeInOutCubic,
)
: null,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(localization.pdfPageInfo
.replaceFirst(':current', '$_pageNumber')
.replaceFirst(':total', '$_pageCount')),
),
IconButton(
icon: Icon(Icons.navigate_next),
onPressed: _pageNumber < _pageCount
? () => _pdfController.nextPage(
duration:
Duration(milliseconds: kDefaultAnimationDuration),
curve: Curves.easeInOutCubic,
)
: null,
),
];
bool showEmail = false; //isDesktop(context);
// TODO: remove this code
// hide email option on web to prevent dialog problem
if (kIsWeb && !client.hasEmailAddress) {
showEmail = false;
}
return Scaffold(
backgroundColor: Colors.grey,
appBar: widget.showAppBar
? AppBar(
centerTitle: false,
automaticallyImplyLeading: isMobile(context),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(EntityPresenter()
.initialize(client, context)
.title()),
),
if (isDesktop(context)) ...pageSelector,
],
),
actions: <Widget>[
if (showEmail)
TextButton(
child: Text(localization.email,
style: TextStyle(color: state.headerTextColor)),
onPressed: () {
handleEntityAction(client,
EntityAction.emailEntityType(client.entityType));
},
),
AppTextButton(
isInHeader: true,
label: localization.download,
onPressed: _response == null
? null
: () async {
final fileName = localization.statement +
'_' +
(client.number) +
'.pdf';
if (kIsWeb) {
WebUtils.downloadBinaryFile(
fileName, _response.bodyBytes);
} else {
final directory =
await getExternalStorageDirectory();
final filePath =
'${directory.path}/${client.number}.pdf';
final pdfData = file.File(filePath);
pdfData.writeAsBytes(_response.bodyBytes);
await FlutterShare.shareFile(
title: fileName, filePath: filePath);
}
},
),
if (isDesktop(context))
TextButton(
child: Text(localization.close,
style: TextStyle(color: state.headerTextColor)),
onPressed: () {
viewEntity(entity: client);
},
),
],
)
: null,
body: _isLoading
? LoadingIndicator()
: kIsWeb
? HtmlElementView(viewType: _pdfString)
: Padding(
padding: const EdgeInsets.all(8),
child: PdfView(
controller: _pdfController,
onDocumentLoaded: (document) {
setState(() {
_pageCount = document?.pagesCount ?? 0;
});
},
onPageChanged: (page) {
setState(() {
_pageNumber = page;
});
},
),
));
}
}
Future<Response> _loadPDF(
BuildContext context,
ClientEntity invoice,
) async {
http.Response response;
//final invitation = invoice.invitations.first;
final url = ''; //invitation.downloadLink;
final client = http.Client();
response = await client.get(Uri.parse(url));
client.close();
if (response.statusCode >= 400) {
String errorMessage =
'${response.statusCode}: ${response.reasonPhrase}\n\n';
try {
errorMessage += jsonDecode(response.body)['message'];
} catch (error) {
errorMessage += response.body;
}
showErrorDialog(context: context, message: errorMessage);
throw errorMessage;
}
return response;
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/ui/client/client_pdf.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class ClientPdfScreen extends StatelessWidget {
const ClientPdfScreen({Key key, this.showAppBar = true}) : super(key: key);
final bool showAppBar;
static const String route = '/invoice/pdf';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ClientPdfVM>(
converter: (Store<AppState> store) {
return ClientPdfVM.fromStore(store);
},
builder: (context, vm) {
return ClientPdfView(
key: ValueKey('__invoice_pdf_${vm.client.id}__'),
viewModel: vm,
showAppBar: showAppBar,
);
},
);
}
}
class ClientPdfVM {
ClientPdfVM({
@required this.state,
@required this.client,
});
factory ClientPdfVM.fromStore(Store<AppState> store) {
final state = store.state;
final clientUIState = state.uiState.clientUIState;
final clientId = clientUIState.selectedId;
final client = state.clientState.get(clientId);
return ClientPdfVM(
state: state,
client: client,
);
}
final AppState state;
final ClientEntity client;
}