2021-02-03 00:57:42 +00:00
|
|
|
import 'dart:async';
|
2021-02-02 14:33:23 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
2023-01-16 00:43:37 +00:00
|
|
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
|
|
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
import 'package:flutter_translate/flutter_translate.dart';
|
|
|
|
import 'package:logger/logger.dart';
|
2021-02-03 00:57:42 +00:00
|
|
|
import 'package:path/path.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
|
|
|
|
import '../../locator.dart';
|
|
|
|
import '../enums/error_code.dart';
|
2021-02-04 00:07:03 +00:00
|
|
|
import '../enums/refresh_event.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
import '../enums/viewstate.dart';
|
|
|
|
import '../error/rest_service_exception.dart';
|
|
|
|
import '../error/service_exception.dart';
|
|
|
|
import '../models/rest/rest_error.dart';
|
2021-02-02 17:17:48 +00:00
|
|
|
import '../models/rest/uploaded_multi_response.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
import '../models/rest/uploaded_response.dart';
|
|
|
|
import '../services/file_service.dart';
|
2021-02-03 00:57:42 +00:00
|
|
|
import '../services/link_service.dart';
|
2021-02-04 00:07:03 +00:00
|
|
|
import '../services/refresh_service.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
import '../util/logger.dart';
|
2021-04-16 17:25:16 +00:00
|
|
|
import '../util/paste_util.dart';
|
2021-02-02 14:33:23 +00:00
|
|
|
import 'base_model.dart';
|
|
|
|
|
|
|
|
class UploadModel extends BaseModel {
|
|
|
|
final Logger _logger = getLogger();
|
|
|
|
final FileService _fileService = locator<FileService>();
|
2021-02-03 00:57:42 +00:00
|
|
|
final LinkService _linkService = locator<LinkService>();
|
2021-02-04 00:07:03 +00:00
|
|
|
final RefreshService _refreshService = locator<RefreshService>();
|
2021-02-03 00:57:42 +00:00
|
|
|
|
2023-01-04 20:17:54 +00:00
|
|
|
final TextEditingController _pasteTextController = TextEditingController();
|
2021-04-06 12:00:00 +00:00
|
|
|
bool pasteTextTouched = false;
|
|
|
|
|
2021-11-29 23:44:22 +00:00
|
|
|
late StreamSubscription _intentDataStreamSubscription;
|
2021-02-02 14:33:23 +00:00
|
|
|
|
2021-02-03 00:57:42 +00:00
|
|
|
bool createMulti = false;
|
2021-11-29 23:44:22 +00:00
|
|
|
String? fileName;
|
|
|
|
List<PlatformFile>? paths;
|
|
|
|
String? _extension;
|
2021-02-02 14:33:23 +00:00
|
|
|
bool loadingPath = false;
|
2021-11-29 23:44:22 +00:00
|
|
|
String? errorMessage;
|
2021-02-02 14:33:23 +00:00
|
|
|
|
|
|
|
TextEditingController get pasteTextController => _pasteTextController;
|
|
|
|
|
2023-01-16 00:43:37 +00:00
|
|
|
void _parseIntentFiles(List<SharedFile> files) {
|
|
|
|
if (files.isNotEmpty) {
|
|
|
|
setStateView(ViewState.busy);
|
|
|
|
|
|
|
|
paths = files.map((sharedFile) {
|
|
|
|
_logger.d("Shared file name: ${basename(sharedFile.value ?? '')}");
|
|
|
|
_logger.d("Shared file path: ${sharedFile.value}");
|
|
|
|
_logger.d(
|
|
|
|
"Shared file size: ${File(sharedFile.value ?? '').lengthSync()}");
|
|
|
|
_logger.d("Shared file type: ${sharedFile.type}");
|
|
|
|
return PlatformFile.fromMap({
|
|
|
|
'path': sharedFile.value,
|
|
|
|
'name': basename(sharedFile.value!),
|
|
|
|
'size': File(sharedFile.value!).lengthSync(),
|
|
|
|
'bytes': null
|
|
|
|
});
|
|
|
|
}).toList();
|
|
|
|
|
|
|
|
setStateView(ViewState.idle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void deleteIntentFile(String path) {
|
|
|
|
setStateView(ViewState.busy);
|
|
|
|
_logger.d("Removing path '$path' from $paths");
|
|
|
|
|
|
|
|
paths?.removeWhere((element) => element.path == path);
|
|
|
|
|
|
|
|
int length = paths!.length;
|
|
|
|
if (length == 0) {
|
|
|
|
paths = null;
|
|
|
|
}
|
|
|
|
setStateView(ViewState.idle);
|
|
|
|
}
|
|
|
|
|
2021-02-03 00:57:42 +00:00
|
|
|
void init() {
|
2021-04-06 12:00:00 +00:00
|
|
|
_pasteTextController.addListener(() {
|
|
|
|
pasteTextTouched = pasteTextController.text.isNotEmpty;
|
2022-06-22 22:42:37 +00:00
|
|
|
setStateBoolValue("PASTE_TEXT_TOUCHED", pasteTextTouched);
|
2021-04-06 12:00:00 +00:00
|
|
|
});
|
|
|
|
|
2021-02-03 00:57:42 +00:00
|
|
|
// For sharing images coming from outside the app while the app is in the memory
|
2023-01-16 00:43:37 +00:00
|
|
|
_intentDataStreamSubscription = FlutterSharingIntent.instance
|
|
|
|
.getMediaStream()
|
|
|
|
.listen((List<SharedFile> value) {
|
|
|
|
_logger.d("Retrieved ${value.length} files from intent");
|
|
|
|
_parseIntentFiles(value);
|
2021-02-03 00:57:42 +00:00
|
|
|
}, onError: (err) {
|
2021-04-19 22:50:46 +00:00
|
|
|
_errorIntentHandle(err);
|
2021-02-03 00:57:42 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// For sharing images coming from outside the app while the app is closed
|
2023-01-16 00:43:37 +00:00
|
|
|
FlutterSharingIntent.instance
|
|
|
|
.getInitialSharing()
|
|
|
|
.then((List<SharedFile> value) {
|
|
|
|
_logger.d("Retrieved ${value.length} files from inactive intent");
|
|
|
|
_parseIntentFiles(value);
|
2021-02-03 00:57:42 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-19 22:50:46 +00:00
|
|
|
void _errorIntentHandle(err) {
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.busy);
|
2021-04-19 22:50:46 +00:00
|
|
|
errorMessage = translate('upload.retrieval_intent');
|
|
|
|
_logger.e('Error while retrieving shared data: $err');
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-04-19 22:50:46 +00:00
|
|
|
}
|
|
|
|
|
2021-11-29 23:44:22 +00:00
|
|
|
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
2023-01-04 20:17:54 +00:00
|
|
|
if (uploads != null && uploads.isNotEmpty) {
|
2021-04-16 17:25:16 +00:00
|
|
|
var links = '';
|
|
|
|
|
|
|
|
uploads.forEach((id, isMulti) {
|
|
|
|
if (isMulti && createMulti || !isMulti && !createMulti) {
|
|
|
|
links += '${PasteUtil.generateLink(url, id)}\n';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return links;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-02 14:33:23 +00:00
|
|
|
void toggleCreateMulti() {
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.busy);
|
2021-02-02 14:33:23 +00:00
|
|
|
createMulti = !createMulti;
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void openFileExplorer() async {
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.busy);
|
2021-02-02 14:33:23 +00:00
|
|
|
setStateMessage(translate('upload.file_explorer_open'));
|
|
|
|
loadingPath = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
paths = (await FilePicker.platform.pickFiles(
|
|
|
|
type: FileType.any,
|
|
|
|
allowMultiple: true,
|
|
|
|
withData: false,
|
|
|
|
withReadStream: true,
|
2023-01-16 00:43:37 +00:00
|
|
|
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
|
|
|
? _extension?.replaceAll(' ', '').split(',')
|
|
|
|
: null,
|
2021-02-02 14:33:23 +00:00
|
|
|
))
|
|
|
|
?.files;
|
|
|
|
} on PlatformException catch (e) {
|
|
|
|
_logger.e('Unsupported operation', e);
|
|
|
|
} catch (ex) {
|
|
|
|
_logger.e('An unknown error occurred', ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
loadingPath = false;
|
2021-11-29 23:44:22 +00:00
|
|
|
fileName = paths != null ? paths!.map((e) => e.name).toString() : '...';
|
2021-02-02 14:33:23 +00:00
|
|
|
|
|
|
|
setStateMessage(null);
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void clearCachedFiles() async {
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.busy);
|
2021-02-02 14:33:23 +00:00
|
|
|
await FilePicker.platform.clearTemporaryFiles();
|
|
|
|
paths = null;
|
|
|
|
fileName = null;
|
|
|
|
errorMessage = null;
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-29 23:44:22 +00:00
|
|
|
Future<Map<String, bool>?> upload() async {
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.busy);
|
2021-02-02 14:33:23 +00:00
|
|
|
setStateMessage(translate('upload.uploading_now'));
|
|
|
|
|
2023-01-04 20:17:54 +00:00
|
|
|
Map<String, bool> uploadedPasteIds = {};
|
2021-02-02 14:33:23 +00:00
|
|
|
try {
|
2021-11-29 23:44:22 +00:00
|
|
|
List<File>? files;
|
|
|
|
Map<String, String>? additionalFiles;
|
|
|
|
|
|
|
|
if (pasteTextController.text.isNotEmpty) {
|
2023-01-16 00:43:37 +00:00
|
|
|
additionalFiles = Map.from({
|
|
|
|
'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
|
|
|
pasteTextController.text
|
|
|
|
});
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-04 20:17:54 +00:00
|
|
|
if (paths != null && paths!.isNotEmpty) {
|
|
|
|
files = paths!.map((e) => File(e.path!)).toList();
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-16 00:43:37 +00:00
|
|
|
UploadedResponse response =
|
|
|
|
await _fileService.uploadPaste(files, additionalFiles);
|
2023-01-04 20:17:54 +00:00
|
|
|
for (var element in response.data.ids) {
|
2021-02-13 22:50:38 +00:00
|
|
|
uploadedPasteIds.putIfAbsent(element, () => false);
|
2023-01-04 20:17:54 +00:00
|
|
|
}
|
2021-02-02 14:33:23 +00:00
|
|
|
|
|
|
|
if (createMulti && response.data.ids.length > 1) {
|
2023-01-16 00:43:37 +00:00
|
|
|
UploadedMultiResponse multiResponse =
|
|
|
|
await _fileService.uploadMultiPaste(response.data.ids);
|
2021-02-13 22:50:38 +00:00
|
|
|
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
clearCachedFiles();
|
|
|
|
_pasteTextController.clear();
|
2023-01-04 20:17:54 +00:00
|
|
|
_refreshService.addEvent(RefreshEvent.refreshHistory);
|
2021-02-02 14:33:23 +00:00
|
|
|
errorMessage = null;
|
2021-02-02 17:17:48 +00:00
|
|
|
return uploadedPasteIds;
|
2021-02-02 14:33:23 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (e is RestServiceException) {
|
|
|
|
if (e.statusCode == HttpStatus.notFound) {
|
|
|
|
errorMessage = translate('upload.errors.not_found');
|
|
|
|
} else if (e.statusCode == HttpStatus.forbidden) {
|
|
|
|
errorMessage = translate('api.forbidden');
|
|
|
|
} else if (e.statusCode != HttpStatus.notFound &&
|
|
|
|
e.statusCode != HttpStatus.forbidden &&
|
|
|
|
e.responseBody is RestError &&
|
|
|
|
e.responseBody.message != null) {
|
|
|
|
if (e.statusCode == HttpStatus.badRequest) {
|
2023-01-16 00:43:37 +00:00
|
|
|
errorMessage = translate('api.bad_request',
|
|
|
|
args: {'reason': e.responseBody.message});
|
2021-02-02 14:33:23 +00:00
|
|
|
} else {
|
2023-01-16 00:43:37 +00:00
|
|
|
errorMessage = translate('api.general_rest_error_payload',
|
|
|
|
args: {'message': e.responseBody.message});
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
errorMessage = translate('api.general_rest_error');
|
|
|
|
}
|
2023-01-04 20:17:54 +00:00
|
|
|
} else if (e is ServiceException && e.code == ErrorCode.socketError) {
|
2021-02-02 14:33:23 +00:00
|
|
|
errorMessage = translate('api.socket_error');
|
2023-01-04 20:17:54 +00:00
|
|
|
} else if (e is ServiceException && e.code == ErrorCode.socketTimeout) {
|
2021-02-02 14:33:23 +00:00
|
|
|
errorMessage = translate('api.socket_timeout');
|
|
|
|
} else {
|
|
|
|
errorMessage = translate('app.unknown_error');
|
|
|
|
setStateMessage(null);
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-02-02 14:33:23 +00:00
|
|
|
_logger.e('An unknown error occurred', e);
|
2023-01-04 20:17:54 +00:00
|
|
|
rethrow;
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setStateMessage(null);
|
2023-01-04 20:17:54 +00:00
|
|
|
setStateView(ViewState.idle);
|
2021-02-02 17:17:48 +00:00
|
|
|
return null;
|
2021-02-02 14:33:23 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 00:57:42 +00:00
|
|
|
void openLink(String link) {
|
|
|
|
_linkService.open(link);
|
|
|
|
}
|
|
|
|
|
2021-02-02 14:33:23 +00:00
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_pasteTextController.dispose();
|
2021-02-03 00:57:42 +00:00
|
|
|
_intentDataStreamSubscription.cancel();
|
2023-01-16 00:43:37 +00:00
|
|
|
FlutterSharingIntent.instance.reset();
|
|
|
|
paths = null;
|
2021-02-02 14:33:23 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
}
|