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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/data/models/group_model.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -67,7 +68,7 @@ class SettingsRepository {
}
Future<BaseEntity> uploadLogo(Credentials credentials, String entityId,
String path, EntityType type) async {
MultipartFile multipartFile, EntityType type) async {
final route = type == EntityType.company
? 'companies'
: type == EntityType.group
@ -76,7 +77,7 @@ class SettingsRepository {
final url = '${credentials.url}/$route/$entityId';
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) {
return serializers

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/.env.dart';
import 'package:http/http.dart' as http;
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/utils/strings.dart';
import 'package:path/path.dart';
import 'package:version/version.dart';
class WebClient {
@ -55,8 +54,7 @@ class WebClient {
String url,
String token, {
dynamic data,
String filePath,
String fileIndex,
MultipartFile multipartFile,
String secret,
String password,
bool rawResponse = false,
@ -75,9 +73,8 @@ class WebClient {
}
http.Response response;
if (filePath != null) {
response = await _uploadFile(url, token, filePath,
fileIndex: fileIndex, data: data);
if (multipartFile != null) {
response = await _uploadFile(url, token, multipartFile, data: data);
} else {
response = await http.Client()
.post(url,
@ -100,7 +97,7 @@ class WebClient {
String url,
String token, {
dynamic data,
String filePath,
MultipartFile multipartFile,
String fileIndex = 'file',
String password,
}) async {
@ -118,8 +115,8 @@ class WebClient {
}
http.Response response;
if (filePath != null) {
response = await _uploadFile(url, token, filePath,
if (multipartFile != null) {
response = await _uploadFile(url, token, multipartFile,
fileIndex: fileIndex, data: data, method: 'PUT');
} else {
response = await http.Client().put(
@ -238,27 +235,9 @@ String _parseError(int code, String response) {
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 {
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))
..fields.addAll(data ?? {})
..headers.addAll(_getHeaders(url, token))

View File

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

View File

@ -251,7 +251,7 @@ Middleware<AppState> _saveDocument(ClientRepository repository) {
if (store.state.isEnterprisePlan) {
repository
.uploadDocument(
store.state.credentials, action.client, action.filePath)
store.state.credentials, action.client, action.multipartFile)
.then((client) {
store.dispatch(SaveClientSuccess(client));
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/widgets.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -374,12 +375,12 @@ class FilterCreditsByCustom4 implements PersistUI {
class SaveCreditDocumentRequest implements StartSaving {
SaveCreditDocumentRequest({
@required this.completer,
@required this.filePath,
@required this.multipartFile,
@required this.credit,
});
final Completer completer;
final String filePath;
final MultipartFile multipartFile;
final InvoiceEntity credit;
}

View File

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

View File

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

View File

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

View File

@ -247,7 +247,7 @@ Middleware<AppState> _saveDocument(ExpenseRepository repository) {
if (store.state.isEnterprisePlan) {
repository
.uploadDocument(
store.state.credentials, action.expense, action.filePath)
store.state.credentials, action.expense, action.multipartFile)
.then((expense) {
store.dispatch(SaveExpenseSuccess(expense));
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/widgets.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -445,12 +446,12 @@ class ClearInvoiceMultiselect {}
class SaveInvoiceDocumentRequest implements StartSaving {
SaveInvoiceDocumentRequest({
@required this.completer,
@required this.filePath,
@required this.multipartFile,
@required this.invoice,
});
final Completer completer;
final String filePath;
final MultipartFile multipartFile;
final InvoiceEntity invoice;
}

View File

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

View File

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

View File

@ -248,7 +248,7 @@ Middleware<AppState> _saveDocument(ProductRepository repository) {
if (store.state.isEnterprisePlan) {
repository
.uploadDocument(
store.state.credentials, action.product, action.filePath)
store.state.credentials, action.product, action.multipartFile)
.then((product) {
store.dispatch(SaveProductSuccess(product));
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/widgets.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -367,12 +368,12 @@ class ClearProjectMultiselect {}
class SaveProjectDocumentRequest implements StartSaving {
SaveProjectDocumentRequest({
@required this.completer,
@required this.filePath,
@required this.multipartFile,
@required this.project,
});
final Completer completer;
final String filePath;
final MultipartFile multipartFile;
final ProjectEntity project;
}

View File

@ -254,7 +254,7 @@ Middleware<AppState> _saveDocument(ProjectRepository repository) {
if (store.state.isEnterprisePlan) {
repository
.uploadDocument(
store.state.credentials, action.project, action.filePath)
store.state.credentials, action.project, action.multipartFile)
.then((project) {
store.dispatch(SaveProjectSuccess(project));
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/widgets.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -393,12 +394,12 @@ class ConvertQuoteFailure implements StopSaving {
class SaveQuoteDocumentRequest implements StartSaving {
SaveQuoteDocumentRequest({
@required this.completer,
@required this.filePath,
@required this.multipartFile,
@required this.quote,
});
final Completer completer;
final String filePath;
final MultipartFile multipartFile;
final InvoiceEntity quote;
}

View File

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

View File

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

View File

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

View File

@ -144,7 +144,7 @@ Middleware<AppState> _uploadLogo(SettingsRepository settingsRepository) {
? settingsState.group.id
: settingsState.client.id;
settingsRepository
.uploadLogo(store.state.credentials, entityId, action.path, action.type)
.uploadLogo(store.state.credentials, entityId, action.multipartFile, action.type)
.then((entity) {
if (action.type == EntityType.client) {
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_redux/flutter_redux.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -414,12 +415,12 @@ class ClearTaskMultiselect {}
class SaveTaskDocumentRequest implements StartSaving {
SaveTaskDocumentRequest({
@required this.completer,
@required this.filePath,
@required this.multipartFile,
@required this.task,
});
final Completer completer;
final String filePath;
final MultipartFile multipartFile;
final TaskEntity task;
}

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_share/flutter_share.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:invoiceninja_flutter/data/models/models.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/lists/list_divider.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/icons.dart';
import 'package:http/http.dart' as http;
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:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
@ -31,7 +31,7 @@ class DocumentGrid extends StatelessWidget {
});
final List<DocumentEntity> documents;
final Function(String) onUploadDocument;
final Function(MultipartFile) onUploadDocument;
final Function(DocumentEntity, String) onDeleteDocument;
final Function(DocumentEntity) onViewExpense;
@ -64,7 +64,11 @@ class DocumentGrid extends StatelessWidget {
final image = await ImagePicker()
.getImage(source: ImageSource.camera);
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 {
openAppSettings();
@ -81,29 +85,9 @@ class DocumentGrid extends StatelessWidget {
iconData: Icons.insert_drive_file,
label: localization.uploadFile,
onPressed: () async {
String path;
if (kIsWeb) {
path = await WebUtils.filePicker();
} 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);
}
final multipartFile =
await pickFile(fileIndex: 'documents[]');
onUploadDocument(multipartFile);
},
),
),

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:invoiceninja_flutter/data/models/models.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_vm.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/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
import 'package:permission_handler/permission_handler.dart';
class ExpenseView extends StatefulWidget {
const ExpenseView({
@ -101,18 +102,30 @@ class _ExpenseViewState extends State<ExpenseView>
heroTag: 'expense_fab',
backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () async {
String path;
MultipartFile multipartFile;
if (kIsWeb) {
path = await WebUtils.filePicker();
multipartFile = await pickFile();
} else {
final permissionStatus =
await [Permission.camera].request();
final permission = permissionStatus[Permission.camera] ??
PermissionStatus.undetermined;
if (permission == PermissionStatus.granted) {
final image = await ImagePicker()
.getImage(source: ImageSource.camera);
if (image != null) {
path = image.path;
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) {
viewModel.onUploadDocument(context, path);
if (multipartFile != null) {
viewModel.onUploadDocument(context, multipartFile);
}
},
child: Icon(

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.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/project_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -100,10 +101,10 @@ class ProjectViewVM {
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [project], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) {
onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveProjectDocumentRequest(
filePath: filePath, project: project, completer: completer));
multipartFile: multipartFile, project: project, completer: completer));
completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) {
@ -140,6 +141,6 @@ class ProjectViewVM {
final bool isSaving;
final bool isLoading;
final bool isDirty;
final Function(BuildContext, String) onUploadDocument;
final Function(BuildContext, MultipartFile) onUploadDocument;
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_redux/flutter_redux.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/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
@ -54,7 +55,7 @@ class QuoteViewVM extends EntityViewVM {
Function(BuildContext) onPaymentsPressed,
Function(BuildContext, PaymentEntity) onPaymentPressed,
Function(BuildContext) onRefreshed,
Function(BuildContext, String) onUploadDocument,
Function(BuildContext, MultipartFile) onUploadDocument,
Function(BuildContext, DocumentEntity, String) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense,
}) : super(
@ -105,10 +106,10 @@ class QuoteViewVM extends EntityViewVM {
onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [quote], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) {
onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveQuoteDocumentRequest(
filePath: filePath, quote: quote, completer: completer));
multipartFile: multipartFile, quote: quote, completer: completer));
completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) {

View File

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

View File

@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.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/entities.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/utils/completers.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/localization.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 {
const CompanyDetails({
@ -397,8 +395,9 @@ class _CompanyDetailsState extends State<CompanyDetails>
),
),
Padding(
padding: const EdgeInsets.all(30),
child: Column(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ListView(
shrinkWrap: true,
children: <Widget>[
Builder(
builder: (context) {
@ -427,20 +426,9 @@ class _CompanyDetailsState extends State<CompanyDetails>
label: localization.uploadLogo,
iconData: Icons.cloud_upload,
onPressed: () async {
String path;
if (kIsWeb) {
path = await WebUtils.filePicker();
} 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);
final multipartFile = await pickFile(fileIndex: 'company_logo');
if (multipartFile != null) {
viewModel.onUploadLogo(context, multipartFile);
}
},
),
@ -451,7 +439,7 @@ class _CompanyDetailsState extends State<CompanyDetails>
),
if ('${settings.companyLogo ?? ''}'.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 30),
padding: const EdgeInsets.symmetric(vertical: 20),
child: CachedImage(
width: double.infinity,
url: settings.companyLogo,

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/client_model.dart';
import 'package:invoiceninja_flutter/data/models/company_model.dart';
@ -112,12 +113,12 @@ class CompanyDetailsVM {
break;
}
},
onUploadLogo: (context, path) {
onUploadLogo: (context, multipartFile) {
final type = state.uiState.settingsUIState.entityType;
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).uploadedLogo);
store.dispatch(
UploadLogoRequest(completer: completer, path: path, type: type));
UploadLogoRequest(completer: completer, multipartFile: multipartFile, type: type));
},
onConfigurePaymentTermsPressed: (context) {
if (state.paymentTermState.list.isEmpty) {
@ -139,7 +140,7 @@ class CompanyDetailsVM {
final Function(SettingsEntity) onSettingsChanged;
final Function(CompanyEntity) onCompanyChanged;
final Function(BuildContext) onSavePressed;
final Function(BuildContext, String) onUploadLogo;
final Function(BuildContext, MultipartFile) onUploadLogo;
final Function(BuildContext) onDeleteLogo;
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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/data/models/import_model.dart';
@ -97,8 +98,7 @@ class _FileImport extends StatefulWidget {
class _FileImportState extends State<_FileImport> {
var _entityType = EntityType.client;
String _filePath;
String _fileName;
MultipartFile _multipartFile;
bool _isLoading = false;
void uploadFile() {
@ -113,8 +113,7 @@ class _FileImportState extends State<_FileImport> {
webClient.post(
url,
credentials.token,
filePath: _filePath,
fileIndex: 'file',
multipartFile: _multipartFile,
data: {
'entity_type': toSnakeCase('$_entityType'),
},
@ -165,10 +164,10 @@ class _FileImportState extends State<_FileImport> {
),
),
DecoratedFormField(
key: ValueKey(_fileName),
key: ValueKey(_multipartFile?.filename),
enabled: false,
label: localization.csvFile,
initialValue: _fileName ?? localization.noFileSelected,
initialValue: _multipartFile?.filename ?? localization.noFileSelected,
),
SizedBox(height: 20),
if (_isLoading)
@ -189,11 +188,8 @@ class _FileImportState extends State<_FileImport> {
if (result != null) {
setState(() {
final file = result.files.single;
_filePath = kIsWeb
? 'data:application/octet-stream;charset=utf-16le;base64,' +
base64Encode(file.bytes)
: file.path;
_fileName = file.name;
_multipartFile = MultipartFile.fromBytes('file', file.bytes,
filename: file.name);
});
}
},
@ -205,7 +201,7 @@ class _FileImportState extends State<_FileImport> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
child: Text(localization.uploadFile),
onPressed: _fileName == null ? null : () => uploadFile(),
onPressed: _multipartFile == null ? null : () => uploadFile(),
//onPressed: () => uploadFile(),
),
),

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.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/task_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -112,10 +113,10 @@ class TaskViewVM {
onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [task], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) {
onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveTaskDocumentRequest(
filePath: filePath, task: task, completer: completer));
multipartFile: multipartFile, task: task, completer: completer));
completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) {
@ -153,6 +154,6 @@ class TaskViewVM {
final bool isSaving;
final bool isLoading;
final bool isDirty;
final Function(BuildContext, String) onUploadDocument;
final Function(BuildContext, MultipartFile) onUploadDocument;
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_redux/flutter_redux.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/vendor_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -100,10 +101,10 @@ class VendorViewVM {
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [vendor], action, autoPop: true),
onUploadDocument: (BuildContext context, String filePath) {
onUploadDocument: (BuildContext context, MultipartFile multipartFile) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
store.dispatch(SaveVendorDocumentRequest(
filePath: filePath, vendor: vendor, completer: completer));
multipartFile: multipartFile, vendor: vendor, completer: completer));
completer.future.then((client) {
showToast(AppLocalization.of(context).uploadedDocument);
}).catchError((Object error) {
@ -139,6 +140,6 @@ class VendorViewVM {
final bool isSaving;
final bool isLoading;
final bool isDirty;
final Function(BuildContext, String) onUploadDocument;
final Function(BuildContext, MultipartFile) onUploadDocument;
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:html';
import 'dart:typed_data';
@ -14,24 +13,6 @@ class WebUtils {
static String getHtmlValue(String 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) {
final encodedFileContents = Uri.encodeComponent(data);
AnchorElement(href: 'data:text/plain;charset=utf-8,$encodedFileContents')

View File

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