Rework file upload

This commit is contained in:
Hillel Coren 2020-12-14 21:23:28 +02:00
parent 6203cdfae2
commit ca37d4094e
51 changed files with 213 additions and 210 deletions

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:invoiceninja_flutter/data/web_client.dart';
@ -80,15 +81,15 @@ class ClientRepository {
return clientResponse.data; return clientResponse.data;
} }
Future<ClientEntity> uploadDocument( Future<ClientEntity> uploadDocument(Credentials credentials,
Credentials credentials, BaseEntity entity, String filePath) async { BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/clients/${entity.id}', credentials.token, '${credentials.url}/clients/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final ClientItemResponse clientResponse = final ClientItemResponse clientResponse =
serializers.deserializeWith(ClientItemResponse.serializer, response); serializers.deserializeWith(ClientItemResponse.serializer, response);

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -99,14 +100,14 @@ class CreditRepository {
} }
Future<InvoiceEntity> uploadDocument( Future<InvoiceEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/credits/${entity.id}', credentials.token, '${credentials.url}/credits/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final InvoiceItemResponse invoiceResponse = final InvoiceItemResponse invoiceResponse =
serializers.deserializeWith(InvoiceItemResponse.serializer, response); serializers.deserializeWith(InvoiceItemResponse.serializer, response);

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -74,14 +75,14 @@ class ExpenseRepository {
} }
Future<ExpenseEntity> uploadDocument( Future<ExpenseEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/expenses/${entity.id}', credentials.token, '${credentials.url}/expenses/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final ExpenseItemResponse expenseResponse = final ExpenseItemResponse expenseResponse =
serializers.deserializeWith(ExpenseItemResponse.serializer, response); serializers.deserializeWith(ExpenseItemResponse.serializer, response);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -100,14 +101,14 @@ class InvoiceRepository {
} }
Future<InvoiceEntity> uploadDocument( Future<InvoiceEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/invoices/${entity.id}', credentials.token, '${credentials.url}/invoices/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final InvoiceItemResponse invoiceResponse = final InvoiceItemResponse invoiceResponse =
serializers.deserializeWith(InvoiceItemResponse.serializer, response); serializers.deserializeWith(InvoiceItemResponse.serializer, response);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -76,7 +77,7 @@ class ProductRepository {
} }
Future<ProductEntity> uploadDocument( Future<ProductEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
@ -84,7 +85,7 @@ class ProductRepository {
// TODO remove this include // TODO remove this include
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/products/${entity.id}', credentials.token, '${credentials.url}/products/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final ProductItemResponse productResponse = final ProductItemResponse productResponse =
serializers.deserializeWith(ProductItemResponse.serializer, response); serializers.deserializeWith(ProductItemResponse.serializer, response);

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -73,15 +74,15 @@ class ProjectRepository {
return projectResponse.data; return projectResponse.data;
} }
Future<ProjectEntity> uploadDocument( Future<ProjectEntity> uploadDocument(Credentials credentials,
Credentials credentials, BaseEntity entity, String filePath) async { BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/projects/${entity.id}', credentials.token, '${credentials.url}/projects/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final ProjectItemResponse projectResponse = final ProjectItemResponse projectResponse =
serializers.deserializeWith(ProjectItemResponse.serializer, response); serializers.deserializeWith(ProjectItemResponse.serializer, response);

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -96,14 +97,14 @@ class QuoteRepository {
} }
Future<InvoiceEntity> uploadDocument( Future<InvoiceEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/quotes/${entity.id}', credentials.token, '${credentials.url}/quotes/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final InvoiceItemResponse invoiceResponse = final InvoiceItemResponse invoiceResponse =
serializers.deserializeWith(InvoiceItemResponse.serializer, response); serializers.deserializeWith(InvoiceItemResponse.serializer, response);

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/group_model.dart'; import 'package:invoiceninja_flutter/data/models/group_model.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -67,7 +68,7 @@ class SettingsRepository {
} }
Future<BaseEntity> uploadLogo(Credentials credentials, String entityId, Future<BaseEntity> uploadLogo(Credentials credentials, String entityId,
String path, EntityType type) async { MultipartFile multipartFile, EntityType type) async {
final route = type == EntityType.company final route = type == EntityType.company
? 'companies' ? 'companies'
: type == EntityType.group : type == EntityType.group
@ -76,7 +77,7 @@ class SettingsRepository {
final url = '${credentials.url}/$route/$entityId'; final url = '${credentials.url}/$route/$entityId';
final dynamic response = await webClient.post(url, credentials.token, final dynamic response = await webClient.post(url, credentials.token,
data: {'_method': 'PUT'}, filePath: path, fileIndex: 'company_logo'); data: {'_method': 'PUT'}, multipartFile: multipartFile);
if (type == EntityType.client) { if (type == EntityType.client) {
return serializers return serializers

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -73,14 +74,14 @@ class TaskRepository {
} }
Future<TaskEntity> uploadDocument( Future<TaskEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/tasks/${entity.id}', credentials.token, '${credentials.url}/tasks/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final TaskItemResponse taskResponse = final TaskItemResponse taskResponse =
serializers.deserializeWith(TaskItemResponse.serializer, response); serializers.deserializeWith(TaskItemResponse.serializer, response);

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
@ -74,14 +75,14 @@ class VendorRepository {
} }
Future<VendorEntity> uploadDocument( Future<VendorEntity> uploadDocument(
Credentials credentials, BaseEntity entity, String filePath) async { Credentials credentials, BaseEntity entity, MultipartFile multipartFile) async {
final fields = <String, String>{ final fields = <String, String>{
'_method': 'put', '_method': 'put',
}; };
final dynamic response = await webClient.post( final dynamic response = await webClient.post(
'${credentials.url}/vendors/${entity.id}', credentials.token, '${credentials.url}/vendors/${entity.id}', credentials.token,
data: fields, filePath: filePath, fileIndex: 'documents[]'); data: fields, multipartFile: multipartFile);
final VendorItemResponse vendorResponse = final VendorItemResponse vendorResponse =
serializers.deserializeWith(VendorItemResponse.serializer, response); serializers.deserializeWith(VendorItemResponse.serializer, response);

View File

@ -1,13 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/.env.dart'; import 'package:invoiceninja_flutter/.env.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/utils/strings.dart'; import 'package:invoiceninja_flutter/utils/strings.dart';
import 'package:path/path.dart';
import 'package:version/version.dart'; import 'package:version/version.dart';
class WebClient { class WebClient {
@ -55,8 +54,7 @@ class WebClient {
String url, String url,
String token, { String token, {
dynamic data, dynamic data,
String filePath, MultipartFile multipartFile,
String fileIndex,
String secret, String secret,
String password, String password,
bool rawResponse = false, bool rawResponse = false,
@ -75,9 +73,8 @@ class WebClient {
} }
http.Response response; http.Response response;
if (filePath != null) { if (multipartFile != null) {
response = await _uploadFile(url, token, filePath, response = await _uploadFile(url, token, multipartFile, data: data);
fileIndex: fileIndex, data: data);
} else { } else {
response = await http.Client() response = await http.Client()
.post(url, .post(url,
@ -100,7 +97,7 @@ class WebClient {
String url, String url,
String token, { String token, {
dynamic data, dynamic data,
String filePath, MultipartFile multipartFile,
String fileIndex = 'file', String fileIndex = 'file',
String password, String password,
}) async { }) async {
@ -118,8 +115,8 @@ class WebClient {
} }
http.Response response; http.Response response;
if (filePath != null) { if (multipartFile != null) {
response = await _uploadFile(url, token, filePath, response = await _uploadFile(url, token, multipartFile,
fileIndex: fileIndex, data: data, method: 'PUT'); fileIndex: fileIndex, data: data, method: 'PUT');
} else { } else {
response = await http.Client().put( response = await http.Client().put(
@ -238,27 +235,9 @@ String _parseError(int code, String response) {
return '$code: $message'; return '$code: $message';
} }
Future<http.Response> _uploadFile(String url, String token, String filePath, Future<http.Response> _uploadFile(
String url, String token, MultipartFile multipartFile,
{String method = 'POST', String fileIndex = 'file', dynamic data}) async { {String method = 'POST', String fileIndex = 'file', dynamic data}) async {
dynamic multipartFile;
if (filePath.startsWith('data:')) {
final parts = filePath.split(',');
final prefix = parts[0];
final startIndex = prefix.indexOf('/') + 1;
final endIndex = prefix.indexOf(';');
final fileExt = prefix.substring(startIndex, endIndex);
final bytes = base64.decode(parts[1]);
multipartFile = http.MultipartFile.fromBytes(fileIndex, bytes,
filename: 'file.$fileExt');
} else {
final file = File(filePath);
final stream = http.ByteStream(file.openRead().cast());
final length = await file.length();
multipartFile = http.MultipartFile(fileIndex, stream, length,
filename: basename(file.path));
}
final request = http.MultipartRequest(method, Uri.parse(url)) final request = http.MultipartRequest(method, Uri.parse(url))
..fields.addAll(data ?? {}) ..fields.addAll(data ?? {})
..headers.addAll(_getHeaders(url, token)) ..headers.addAll(_getHeaders(url, token))

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -434,12 +435,12 @@ class ClearClientMultiselect {}
class SaveClientDocumentRequest implements StartSaving { class SaveClientDocumentRequest implements StartSaving {
SaveClientDocumentRequest({ SaveClientDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.client, @required this.client,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final ClientEntity client; final ClientEntity client;
} }

View File

@ -251,7 +251,7 @@ Middleware<AppState> _saveDocument(ClientRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.client, action.filePath) store.state.credentials, action.client, action.multipartFile)
.then((client) { .then((client) {
store.dispatch(SaveClientSuccess(client)); store.dispatch(SaveClientSuccess(client));
action.completer.complete(null); action.completer.complete(null);

View File

@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -374,12 +375,12 @@ class FilterCreditsByCustom4 implements PersistUI {
class SaveCreditDocumentRequest implements StartSaving { class SaveCreditDocumentRequest implements StartSaving {
SaveCreditDocumentRequest({ SaveCreditDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.credit, @required this.credit,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final InvoiceEntity credit; final InvoiceEntity credit;
} }

View File

@ -348,7 +348,7 @@ Middleware<AppState> _saveDocument(CreditRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.credit, action.filePath) store.state.credentials, action.credit, action.multipartFile)
.then((credit) { .then((credit) {
store.dispatch(SaveCreditSuccess(credit)); store.dispatch(SaveCreditSuccess(credit));
action.completer.complete(null); action.completer.complete(null);

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -115,12 +116,12 @@ class LoadDocumentsSuccess implements StopLoading {
class SaveDocumentRequest implements StartSaving { class SaveDocumentRequest implements StartSaving {
SaveDocumentRequest({ SaveDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.entity, @required this.entity,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final BaseEntity entity; final BaseEntity entity;
} }

View File

@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -356,12 +357,12 @@ class ClearExpenseMultiselect {}
class SaveExpenseDocumentRequest implements StartSaving { class SaveExpenseDocumentRequest implements StartSaving {
SaveExpenseDocumentRequest({ SaveExpenseDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.expense, @required this.expense,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final ExpenseEntity expense; final ExpenseEntity expense;
} }

View File

@ -247,7 +247,7 @@ Middleware<AppState> _saveDocument(ExpenseRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.expense, action.filePath) store.state.credentials, action.expense, action.multipartFile)
.then((expense) { .then((expense) {
store.dispatch(SaveExpenseSuccess(expense)); store.dispatch(SaveExpenseSuccess(expense));
action.completer.complete(null); action.completer.complete(null);

View File

@ -3,6 +3,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -445,12 +446,12 @@ class ClearInvoiceMultiselect {}
class SaveInvoiceDocumentRequest implements StartSaving { class SaveInvoiceDocumentRequest implements StartSaving {
SaveInvoiceDocumentRequest({ SaveInvoiceDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.invoice, @required this.invoice,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final InvoiceEntity invoice; final InvoiceEntity invoice;
} }

View File

@ -432,7 +432,7 @@ Middleware<AppState> _saveDocument(InvoiceRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.invoice, action.filePath) store.state.credentials, action.invoice, action.multipartFile)
.then((invoice) { .then((invoice) {
store.dispatch(SaveInvoiceSuccess(invoice)); store.dispatch(SaveInvoiceSuccess(invoice));
action.completer.complete(null); action.completer.complete(null);

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -349,12 +350,12 @@ class ClearProductMultiselect {}
class SaveProductDocumentRequest implements StartSaving { class SaveProductDocumentRequest implements StartSaving {
SaveProductDocumentRequest({ SaveProductDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.product, @required this.product,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final ProductEntity product; final ProductEntity product;
} }

View File

@ -248,7 +248,7 @@ Middleware<AppState> _saveDocument(ProductRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.product, action.filePath) store.state.credentials, action.product, action.multipartFile)
.then((product) { .then((product) {
store.dispatch(SaveProductSuccess(product)); store.dispatch(SaveProductSuccess(product));
action.completer.complete(null); action.completer.complete(null);

View File

@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -367,12 +368,12 @@ class ClearProjectMultiselect {}
class SaveProjectDocumentRequest implements StartSaving { class SaveProjectDocumentRequest implements StartSaving {
SaveProjectDocumentRequest({ SaveProjectDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.project, @required this.project,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final ProjectEntity project; final ProjectEntity project;
} }

View File

@ -254,7 +254,7 @@ Middleware<AppState> _saveDocument(ProjectRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.project, action.filePath) store.state.credentials, action.project, action.multipartFile)
.then((project) { .then((project) {
store.dispatch(SaveProjectSuccess(project)); store.dispatch(SaveProjectSuccess(project));
action.completer.complete(null); action.completer.complete(null);

View File

@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -393,12 +394,12 @@ class ConvertQuoteFailure implements StopSaving {
class SaveQuoteDocumentRequest implements StartSaving { class SaveQuoteDocumentRequest implements StartSaving {
SaveQuoteDocumentRequest({ SaveQuoteDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.quote, @required this.quote,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final InvoiceEntity quote; final InvoiceEntity quote;
} }

View File

@ -367,7 +367,7 @@ Middleware<AppState> _saveDocument(QuoteRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.quote, action.filePath) store.state.credentials, action.quote, action.multipartFile)
.then((quote) { .then((quote) {
store.dispatch(SaveQuoteSuccess(quote)); store.dispatch(SaveQuoteSuccess(quote));
action.completer.complete(null); action.completer.complete(null);

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -347,12 +348,12 @@ class FilterRecurringInvoicesByCustom4 implements PersistUI {
class SaveRecurringInvoiceDocumentRequest implements StartSaving { class SaveRecurringInvoiceDocumentRequest implements StartSaving {
SaveRecurringInvoiceDocumentRequest({ SaveRecurringInvoiceDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.invoice, @required this.invoice,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final InvoiceEntity invoice; final InvoiceEntity invoice;
} }

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/client_model.dart'; import 'package:invoiceninja_flutter/data/models/client_model.dart';
import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/data/models/company_model.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart';
@ -52,10 +53,10 @@ class UpdateUserSettings implements PersistUI {
} }
class UploadLogoRequest implements StartSaving { class UploadLogoRequest implements StartSaving {
UploadLogoRequest({this.completer, this.path, this.type}); UploadLogoRequest({this.completer, this.multipartFile, this.type});
final Completer completer; final Completer completer;
final String path; final MultipartFile multipartFile;
final EntityType type; final EntityType type;
} }

View File

@ -144,7 +144,7 @@ Middleware<AppState> _uploadLogo(SettingsRepository settingsRepository) {
? settingsState.group.id ? settingsState.group.id
: settingsState.client.id; : settingsState.client.id;
settingsRepository settingsRepository
.uploadLogo(store.state.credentials, entityId, action.path, action.type) .uploadLogo(store.state.credentials, entityId, action.multipartFile, action.type)
.then((entity) { .then((entity) {
if (action.type == EntityType.client) { if (action.type == EntityType.client) {
store.dispatch(SaveClientSuccess(entity as ClientEntity)); store.dispatch(SaveClientSuccess(entity as ClientEntity));

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -414,12 +415,12 @@ class ClearTaskMultiselect {}
class SaveTaskDocumentRequest implements StartSaving { class SaveTaskDocumentRequest implements StartSaving {
SaveTaskDocumentRequest({ SaveTaskDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.task, @required this.task,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final TaskEntity task; final TaskEntity task;
} }

View File

@ -242,7 +242,7 @@ Middleware<AppState> _saveDocument(TaskRepository repository) {
final action = dynamicAction as SaveTaskDocumentRequest; final action = dynamicAction as SaveTaskDocumentRequest;
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument(store.state.credentials, action.task, action.filePath) .uploadDocument(store.state.credentials, action.task, action.multipartFile)
.then((task) { .then((task) {
store.dispatch(SaveTaskSuccess(task)); store.dispatch(SaveTaskSuccess(task));
action.completer.complete(null); action.completer.complete(null);

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -373,12 +374,12 @@ class ClearVendorMultiselect {}
class SaveVendorDocumentRequest implements StartSaving { class SaveVendorDocumentRequest implements StartSaving {
SaveVendorDocumentRequest({ SaveVendorDocumentRequest({
@required this.completer, @required this.completer,
@required this.filePath, @required this.multipartFile,
@required this.vendor, @required this.vendor,
}); });
final Completer completer; final Completer completer;
final String filePath; final MultipartFile multipartFile;
final VendorEntity vendor; final VendorEntity vendor;
} }

View File

@ -251,7 +251,7 @@ Middleware<AppState> _saveDocument(VendorRepository repository) {
if (store.state.isEnterprisePlan) { if (store.state.isEnterprisePlan) {
repository repository
.uploadDocument( .uploadDocument(
store.state.credentials, action.vendor, action.filePath) store.state.credentials, action.vendor, action.multipartFile)
.then((vendor) { .then((vendor) {
store.dispatch(SaveVendorSuccess(vendor)); store.dispatch(SaveVendorSuccess(vendor));
action.completer.complete(null); action.completer.complete(null);

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_share/flutter_share.dart'; import 'package:flutter_share/flutter_share.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:invoiceninja_flutter/data/web_client.dart';
@ -12,12 +13,11 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/files.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/icons.dart'; import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -31,7 +31,7 @@ class DocumentGrid extends StatelessWidget {
}); });
final List<DocumentEntity> documents; final List<DocumentEntity> documents;
final Function(String) onUploadDocument; final Function(MultipartFile) onUploadDocument;
final Function(DocumentEntity, String) onDeleteDocument; final Function(DocumentEntity, String) onDeleteDocument;
final Function(DocumentEntity) onViewExpense; final Function(DocumentEntity) onViewExpense;
@ -64,7 +64,11 @@ class DocumentGrid extends StatelessWidget {
final image = await ImagePicker() final image = await ImagePicker()
.getImage(source: ImageSource.camera); .getImage(source: ImageSource.camera);
if (image != null && image.path != null) { if (image != null && image.path != null) {
onUploadDocument(image.path); final bytes = await image.readAsBytes();
final multipartFile = MultipartFile.fromBytes(
'documents[]', bytes,
filename: image.path.split('/').last);
onUploadDocument(multipartFile);
} }
} else { } else {
openAppSettings(); openAppSettings();
@ -81,29 +85,9 @@ class DocumentGrid extends StatelessWidget {
iconData: Icons.insert_drive_file, iconData: Icons.insert_drive_file,
label: localization.uploadFile, label: localization.uploadFile,
onPressed: () async { onPressed: () async {
String path; final multipartFile =
if (kIsWeb) { await pickFile(fileIndex: 'documents[]');
path = await WebUtils.filePicker(); onUploadDocument(multipartFile);
} else {
final permissionStatus =
await [Permission.photos].request();
final permission =
permissionStatus[Permission.photos] ??
PermissionStatus.undetermined;
if (permission == PermissionStatus.granted) {
final image = await ImagePicker()
.getImage(source: ImageSource.gallery);
if (image != null) {
path = image.path;
}
} else {
openAppSettings();
}
}
if (path != null) {
onUploadDocument(path);
}
}, },
), ),
), ),

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -80,10 +81,10 @@ class ClientViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [client], action, autoPop: true), handleEntitiesActions(context, [client], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveClientDocumentRequest( store.dispatch(SaveClientDocumentRequest(
filePath: filePath, client: client, completer: completer)); multipartFile: multipartFile, client: client, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -114,7 +115,7 @@ class ClientViewVM {
final CompanyEntity company; final CompanyEntity company;
final Function(BuildContext, EntityAction) onEntityAction; final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -56,7 +57,7 @@ class CreditViewVM extends EntityViewVM {
Function(BuildContext) onPaymentsPressed, Function(BuildContext) onPaymentsPressed,
Function(BuildContext, PaymentEntity) onPaymentPressed, Function(BuildContext, PaymentEntity) onPaymentPressed,
Function(BuildContext) onRefreshed, Function(BuildContext) onRefreshed,
Function(BuildContext, String) onUploadDocument, Function(BuildContext, MultipartFile) onUploadDocument,
Function(BuildContext, DocumentEntity, String) onDeleteDocument, Function(BuildContext, DocumentEntity, String) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense, Function(BuildContext, DocumentEntity) onViewExpense,
}) : super( }) : super(
@ -107,10 +108,10 @@ class CreditViewVM extends EntityViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [credit], action, autoPop: true), handleEntitiesActions(context, [credit], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveCreditDocumentRequest( store.dispatch(SaveCreditDocumentRequest(
filePath: filePath, credit: credit, completer: completer)); multipartFile: multipartFile, credit: credit, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {

View File

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/bottom_buttons.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/bottom_buttons.dart';
@ -7,9 +8,9 @@ import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_documents.dart'; import 'package:invoiceninja_flutter/ui/expense/view/expense_view_documents.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart'; import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_overview.dart'; import 'package:invoiceninja_flutter/ui/expense/view/expense_view_overview.dart';
import 'package:invoiceninja_flutter/utils/files.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart' import 'package:permission_handler/permission_handler.dart';
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
class ExpenseView extends StatefulWidget { class ExpenseView extends StatefulWidget {
const ExpenseView({ const ExpenseView({
@ -101,18 +102,30 @@ class _ExpenseViewState extends State<ExpenseView>
heroTag: 'expense_fab', heroTag: 'expense_fab',
backgroundColor: Theme.of(context).primaryColorDark, backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () async { onPressed: () async {
String path; MultipartFile multipartFile;
if (kIsWeb) { if (kIsWeb) {
path = await WebUtils.filePicker(); multipartFile = await pickFile();
} else { } else {
final image = await ImagePicker() final permissionStatus =
.getImage(source: ImageSource.camera); await [Permission.camera].request();
if (image != null) { final permission = permissionStatus[Permission.camera] ??
path = image.path; PermissionStatus.undetermined;
if (permission == PermissionStatus.granted) {
final image = await ImagePicker()
.getImage(source: ImageSource.camera);
if (image != null && image.path != null) {
final bytes = await image.readAsBytes();
multipartFile = MultipartFile.fromBytes(
'file', bytes,
filename: image.path.split('/').last);
}
} else {
openAppSettings();
} }
} }
if (path != null) { if (multipartFile != null) {
viewModel.onUploadDocument(context, path); viewModel.onUploadDocument(context, multipartFile);
} }
}, },
child: Icon( child: Icon(

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/expense_model.dart'; import 'package:invoiceninja_flutter/data/models/expense_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -111,11 +112,11 @@ class ExpenseViewVM {
}, },
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [expense], action, autoPop: true), handleEntitiesActions(context, [expense], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = final Completer<DocumentEntity> completer =
Completer<DocumentEntity>(); Completer<DocumentEntity>();
store.dispatch(SaveDocumentRequest( store.dispatch(SaveDocumentRequest(
filePath: filePath, entity: expense, completer: completer)); multipartFile: multipartFile, entity: expense, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -143,7 +144,7 @@ class ExpenseViewVM {
final Function(BuildContext, EntityAction) onEntityAction; final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext, EntityType, [bool]) onEntityPressed; final Function(BuildContext, EntityType, [bool]) onEntityPressed;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -66,7 +67,7 @@ class EntityViewVM {
final Function(BuildContext, [int]) onEditPressed; final Function(BuildContext, [int]) onEditPressed;
final Function(BuildContext) onPaymentsPressed; final Function(BuildContext) onPaymentsPressed;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
final Function(BuildContext, DocumentEntity) onViewExpense; final Function(BuildContext, DocumentEntity) onViewExpense;
} }
@ -86,7 +87,7 @@ class InvoiceViewVM extends EntityViewVM {
Function(BuildContext, PaymentEntity, [bool]) onPaymentPressed, Function(BuildContext, PaymentEntity, [bool]) onPaymentPressed,
Function(BuildContext) onPaymentsPressed, Function(BuildContext) onPaymentsPressed,
Function(BuildContext) onRefreshed, Function(BuildContext) onRefreshed,
Function(BuildContext, String) onUploadDocument, Function(BuildContext, MultipartFile) onUploadDocument,
Function(BuildContext, DocumentEntity, String) onDeleteDocument, Function(BuildContext, DocumentEntity, String) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense, Function(BuildContext, DocumentEntity) onViewExpense,
}) : super( }) : super(
@ -140,10 +141,10 @@ class InvoiceViewVM extends EntityViewVM {
}, },
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [invoice], action, autoPop: true), handleEntitiesActions(context, [invoice], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveInvoiceDocumentRequest( store.dispatch(SaveInvoiceDocumentRequest(
filePath: filePath, invoice: invoice, completer: completer)); multipartFile: multipartFile, invoice: invoice, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -79,10 +80,10 @@ class ProductViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [product], action, autoPop: true), handleEntitiesActions(context, [product], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveProductDocumentRequest( store.dispatch(SaveProductDocumentRequest(
filePath: filePath, product: product, completer: completer)); multipartFile: multipartFile, product: product, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -113,7 +114,7 @@ class ProductViewVM {
final CompanyEntity company; final CompanyEntity company;
final Function(BuildContext, EntityAction) onEntityAction; final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/models/project_model.dart'; import 'package:invoiceninja_flutter/data/models/project_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -100,10 +101,10 @@ class ProjectViewVM {
}, },
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [project], action, autoPop: true), handleEntitiesActions(context, [project], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveProjectDocumentRequest( store.dispatch(SaveProjectDocumentRequest(
filePath: filePath, project: project, completer: completer)); multipartFile: multipartFile, project: project, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -140,6 +141,6 @@ class ProjectViewVM {
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;
final bool isDirty; final bool isDirty;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -54,7 +55,7 @@ class QuoteViewVM extends EntityViewVM {
Function(BuildContext) onPaymentsPressed, Function(BuildContext) onPaymentsPressed,
Function(BuildContext, PaymentEntity) onPaymentPressed, Function(BuildContext, PaymentEntity) onPaymentPressed,
Function(BuildContext) onRefreshed, Function(BuildContext) onRefreshed,
Function(BuildContext, String) onUploadDocument, Function(BuildContext, MultipartFile) onUploadDocument,
Function(BuildContext, DocumentEntity, String) onDeleteDocument, Function(BuildContext, DocumentEntity, String) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense, Function(BuildContext, DocumentEntity) onViewExpense,
}) : super( }) : super(
@ -105,10 +106,10 @@ class QuoteViewVM extends EntityViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [quote], action, autoPop: true), handleEntitiesActions(context, [quote], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveQuoteDocumentRequest( store.dispatch(SaveQuoteDocumentRequest(
filePath: filePath, quote: quote, completer: completer)); multipartFile: multipartFile, quote: quote, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -53,7 +54,7 @@ class RecurringInvoiceViewVM extends EntityViewVM {
Function(BuildContext) onPaymentsPressed, Function(BuildContext) onPaymentsPressed,
Function(BuildContext, PaymentEntity) onPaymentPressed, Function(BuildContext, PaymentEntity) onPaymentPressed,
Function(BuildContext) onRefreshed, Function(BuildContext) onRefreshed,
Function(BuildContext, String) onUploadDocument, Function(BuildContext, MultipartFile) onUploadDocument,
Function(BuildContext, DocumentEntity, String) onDeleteDocument, Function(BuildContext, DocumentEntity, String) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense, Function(BuildContext, DocumentEntity) onViewExpense,
}) : super( }) : super(
@ -106,10 +107,10 @@ class RecurringInvoiceViewVM extends EntityViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [invoice], action, autoPop: true), handleEntitiesActions(context, [invoice], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveRecurringInvoiceDocumentRequest( store.dispatch(SaveRecurringInvoiceDocumentRequest(
filePath: filePath, invoice: invoice, completer: completer)); multipartFile: multipartFile, invoice: invoice, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {

View File

@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:image_picker/image_picker.dart';
import 'package:invoiceninja_flutter/data/models/company_gateway_model.dart'; import 'package:invoiceninja_flutter/data/models/company_gateway_model.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -21,11 +20,10 @@ import 'package:invoiceninja_flutter/ui/settings/company_details_vm.dart';
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart'; import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/files.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
class CompanyDetails extends StatefulWidget { class CompanyDetails extends StatefulWidget {
const CompanyDetails({ const CompanyDetails({
@ -397,8 +395,9 @@ class _CompanyDetailsState extends State<CompanyDetails>
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(30), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: ListView(
shrinkWrap: true,
children: <Widget>[ children: <Widget>[
Builder( Builder(
builder: (context) { builder: (context) {
@ -427,20 +426,9 @@ class _CompanyDetailsState extends State<CompanyDetails>
label: localization.uploadLogo, label: localization.uploadLogo,
iconData: Icons.cloud_upload, iconData: Icons.cloud_upload,
onPressed: () async { onPressed: () async {
String path; final multipartFile = await pickFile(fileIndex: 'company_logo');
if (kIsWeb) { if (multipartFile != null) {
path = await WebUtils.filePicker(); viewModel.onUploadLogo(context, multipartFile);
} else {
final image = await ImagePicker().getImage(
source: kReleaseMode
? ImageSource.gallery
: ImageSource.camera);
if (image != null) {
path = image.path;
}
}
if (path != null) {
viewModel.onUploadLogo(context, path);
} }
}, },
), ),
@ -451,7 +439,7 @@ class _CompanyDetailsState extends State<CompanyDetails>
), ),
if ('${settings.companyLogo ?? ''}'.isNotEmpty) if ('${settings.companyLogo ?? ''}'.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 30), padding: const EdgeInsets.symmetric(vertical: 20),
child: CachedImage( child: CachedImage(
width: double.infinity, width: double.infinity,
url: settings.companyLogo, url: settings.companyLogo,

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/client_model.dart'; import 'package:invoiceninja_flutter/data/models/client_model.dart';
import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/data/models/company_model.dart';
@ -112,12 +113,12 @@ class CompanyDetailsVM {
break; break;
} }
}, },
onUploadLogo: (context, path) { onUploadLogo: (context, multipartFile) {
final type = state.uiState.settingsUIState.entityType; final type = state.uiState.settingsUIState.entityType;
final completer = snackBarCompleter<Null>( final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).uploadedLogo); context, AppLocalization.of(context).uploadedLogo);
store.dispatch( store.dispatch(
UploadLogoRequest(completer: completer, path: path, type: type)); UploadLogoRequest(completer: completer, multipartFile: multipartFile, type: type));
}, },
onConfigurePaymentTermsPressed: (context) { onConfigurePaymentTermsPressed: (context) {
if (state.paymentTermState.list.isEmpty) { if (state.paymentTermState.list.isEmpty) {
@ -139,7 +140,7 @@ class CompanyDetailsVM {
final Function(SettingsEntity) onSettingsChanged; final Function(SettingsEntity) onSettingsChanged;
final Function(CompanyEntity) onCompanyChanged; final Function(CompanyEntity) onCompanyChanged;
final Function(BuildContext) onSavePressed; final Function(BuildContext) onSavePressed;
final Function(BuildContext, String) onUploadLogo; final Function(BuildContext, MultipartFile) onUploadLogo;
final Function(BuildContext) onDeleteLogo; final Function(BuildContext) onDeleteLogo;
final Function(BuildContext) onConfigurePaymentTermsPressed; final Function(BuildContext) onConfigurePaymentTermsPressed;
} }

View File

@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/data/models/import_model.dart'; import 'package:invoiceninja_flutter/data/models/import_model.dart';
@ -97,8 +98,7 @@ class _FileImport extends StatefulWidget {
class _FileImportState extends State<_FileImport> { class _FileImportState extends State<_FileImport> {
var _entityType = EntityType.client; var _entityType = EntityType.client;
String _filePath; MultipartFile _multipartFile;
String _fileName;
bool _isLoading = false; bool _isLoading = false;
void uploadFile() { void uploadFile() {
@ -113,8 +113,7 @@ class _FileImportState extends State<_FileImport> {
webClient.post( webClient.post(
url, url,
credentials.token, credentials.token,
filePath: _filePath, multipartFile: _multipartFile,
fileIndex: 'file',
data: { data: {
'entity_type': toSnakeCase('$_entityType'), 'entity_type': toSnakeCase('$_entityType'),
}, },
@ -165,10 +164,10 @@ class _FileImportState extends State<_FileImport> {
), ),
), ),
DecoratedFormField( DecoratedFormField(
key: ValueKey(_fileName), key: ValueKey(_multipartFile?.filename),
enabled: false, enabled: false,
label: localization.csvFile, label: localization.csvFile,
initialValue: _fileName ?? localization.noFileSelected, initialValue: _multipartFile?.filename ?? localization.noFileSelected,
), ),
SizedBox(height: 20), SizedBox(height: 20),
if (_isLoading) if (_isLoading)
@ -189,11 +188,8 @@ class _FileImportState extends State<_FileImport> {
if (result != null) { if (result != null) {
setState(() { setState(() {
final file = result.files.single; final file = result.files.single;
_filePath = kIsWeb _multipartFile = MultipartFile.fromBytes('file', file.bytes,
? 'data:application/octet-stream;charset=utf-16le;base64,' + filename: file.name);
base64Encode(file.bytes)
: file.path;
_fileName = file.name;
}); });
} }
}, },
@ -205,7 +201,7 @@ class _FileImportState extends State<_FileImport> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)), borderRadius: BorderRadius.circular(5)),
child: Text(localization.uploadFile), child: Text(localization.uploadFile),
onPressed: _fileName == null ? null : () => uploadFile(), onPressed: _multipartFile == null ? null : () => uploadFile(),
//onPressed: () => uploadFile(), //onPressed: () => uploadFile(),
), ),
), ),

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/models/task_model.dart'; import 'package:invoiceninja_flutter/data/models/task_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -112,10 +113,10 @@ class TaskViewVM {
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [task], action, autoPop: true), handleEntitiesActions(context, [task], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveTaskDocumentRequest( store.dispatch(SaveTaskDocumentRequest(
filePath: filePath, task: task, completer: completer)); multipartFile: multipartFile, task: task, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -153,6 +154,6 @@ class TaskViewVM {
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;
final bool isDirty; final bool isDirty;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/models/vendor_model.dart'; import 'package:invoiceninja_flutter/data/models/vendor_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -100,10 +101,10 @@ class VendorViewVM {
}, },
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [vendor], action, autoPop: true), handleEntitiesActions(context, [vendor], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) { onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>(); final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveVendorDocumentRequest( store.dispatch(SaveVendorDocumentRequest(
filePath: filePath, vendor: vendor, completer: completer)); multipartFile: multipartFile, vendor: vendor, completer: completer));
completer.future.then((client) { completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument); showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) { }).catchError((Object error) {
@ -139,6 +140,6 @@ class VendorViewVM {
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;
final bool isDirty; final bool isDirty;
final Function(BuildContext, String) onUploadDocument; final Function(BuildContext, MultipartFile) onUploadDocument;
final Function(BuildContext, DocumentEntity, String) onDeleteDocument; final Function(BuildContext, DocumentEntity, String) onDeleteDocument;
} }

31
lib/utils/files.dart Normal file
View File

@ -0,0 +1,31 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:permission_handler/permission_handler.dart';
Future<MultipartFile> pickFile({String fileIndex = 'file'}) async {
if (kIsWeb) {
return _pickFile(fileIndex: fileIndex);
} else {
final permissionStatus = await [Permission.photos].request();
final permission =
permissionStatus[Permission.photos] ?? PermissionStatus.undetermined;
if (permission == PermissionStatus.granted) {
return _pickFile(fileIndex: fileIndex);
} else {
openAppSettings();
return null;
}
}
}
Future<MultipartFile> _pickFile({String fileIndex}) async {
final result = await FilePicker.platform.pickFiles();
if (result != null) {
final file = result.files.single;
return MultipartFile.fromBytes(fileIndex ?? 'file', file.bytes,
filename: file.name);
}
return null;
}

View File

@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:html'; import 'dart:html';
import 'dart:typed_data'; import 'dart:typed_data';
@ -14,24 +13,6 @@ class WebUtils {
static String getHtmlValue(String field) => static String getHtmlValue(String field) =>
window.document.documentElement.dataset[field]; window.document.documentElement.dataset[field];
static Future<String> filePicker() {
final completer = new Completer<String>();
final InputElement input = document.createElement('input');
input
..type = 'file'
..accept = 'image/*';
input.onChange.listen((e) async {
final List<File> files = input.files;
final reader = new FileReader();
reader.readAsDataUrl(files[0]);
reader.onError.listen((error) => completer.completeError(error));
await reader.onLoad.first;
completer.complete(reader.result as String);
});
input.click();
return completer.future;
}
static void downloadTextFile(String filename, String data) { static void downloadTextFile(String filename, String data) {
final encodedFileContents = Uri.encodeComponent(data); final encodedFileContents = Uri.encodeComponent(data);
AnchorElement(href: 'data:text/plain;charset=utf-8,$encodedFileContents') AnchorElement(href: 'data:text/plain;charset=utf-8,$encodedFileContents')

View File

@ -8,8 +8,6 @@ class WebUtils {
static String getHtmlValue(String field) => null; static String getHtmlValue(String field) => null;
static Future<String> filePicker() => null;
static void downloadTextFile(String filename, String data) {} static void downloadTextFile(String filename, String data) {}
static void downloadBinaryFile(String filename, Uint8List data) {} static void downloadBinaryFile(String filename, Uint8List data) {}