diff --git a/.drone.yml b/.drone.yml index a3c4a7e..bcf30e5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,7 +4,7 @@ name: default steps: - name: build - image: cirrusci/flutter:3.3.9 + image: cirrusci/flutter:3.3.10 commands: - flutter doctor - flutter pub get diff --git a/CHANGELOG.md b/CHANGELOG.md index d93716a..6920a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG -## 1.5.2+18 +## 1.6.0+18 +* Fixed input colors in login view when using dark theme +* Added removal of individual files selected for upload +* Added size for individual files selected for upload +* Replaced intent sharing library with `flutter_sharing_intent` * Added proper linting to project ## 1.5.1+17 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 579db4d..2c6fc11 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -21,46 +21,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1faf8d4..8e2d676 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -60,7 +60,7 @@ - // TODO follow steps 2) on create share extension (https://pub.dev/packages/receive_sharing_intent) + // TODO follow steps on create share extension (https://pub.dev/packages/flutter_sharing_intent) NSPhotoLibraryUsageDescription Allow to select photos and upload them via the app LSApplicationQueriesSchemes diff --git a/lib/app.dart b/lib/app.dart index 76efa68..10c33bb 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -18,8 +18,10 @@ import 'ui/shared/app_colors.dart'; import 'ui/views/startup_view.dart'; class MyApp extends StatelessWidget { - static final _defaultLightColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.light); - static final _defaultDarkColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.dark); + static final _defaultLightColorScheme = ColorScheme.fromSwatch( + primarySwatch: myColor, brightness: Brightness.light); + static final _defaultDarkColorScheme = ColorScheme.fromSwatch( + primarySwatch: myColor, brightness: Brightness.dark); MyApp({super.key}) { initializeDateFormatting('en'); @@ -33,23 +35,30 @@ class MyApp extends StatelessWidget { state: LocalizationProvider.of(context).state, child: StreamProvider( initialData: null, - create: (context) => locator().refreshEventController.stream, + create: (context) => + locator().refreshEventController.stream, child: StreamProvider( initialData: Session.initial(), - create: (context) => locator().sessionController.stream, - child: LifeCycleManager(child: DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) { + create: (context) => + locator().sessionController.stream, + child: LifeCycleManager(child: DynamicColorBuilder( + builder: (lightColorScheme, darkColorScheme) { return MaterialApp( debugShowCheckedModeBanner: false, title: translate('app.title'), builder: (context, child) => Navigator( key: locator().dialogNavigationKey, - onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => DialogManager(child: child)), + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => DialogManager(child: child)), ), theme: ThemeData( useMaterial3: true, brightness: Brightness.light, - colorScheme: lightColorScheme ?? _defaultLightColorScheme), - darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme ?? _defaultDarkColorScheme), + colorScheme: + lightColorScheme ?? _defaultLightColorScheme), + darkTheme: ThemeData( + useMaterial3: true, + colorScheme: darkColorScheme ?? _defaultDarkColorScheme), onGenerateRoute: AppRouter.generateRoute, navigatorKey: locator().navigationKey, home: const StartUpView(), diff --git a/lib/core/manager/dialog_manager.dart b/lib/core/manager/dialog_manager.dart index b1c14bb..5eb75d0 100644 --- a/lib/core/manager/dialog_manager.dart +++ b/lib/core/manager/dialog_manager.dart @@ -31,7 +31,8 @@ class _DialogManagerState extends State { void _showDialog(DialogRequest request) { List actions = []; - if (request.buttonTitleDeny != null && request.buttonTitleDeny!.isNotEmpty) { + if (request.buttonTitleDeny != null && + request.buttonTitleDeny!.isNotEmpty) { Widget denyBtn = TextButton( child: Text(request.buttonTitleDeny!), onPressed: () { diff --git a/lib/core/manager/lifecycle_manager.dart b/lib/core/manager/lifecycle_manager.dart index b2da48a..09840ed 100644 --- a/lib/core/manager/lifecycle_manager.dart +++ b/lib/core/manager/lifecycle_manager.dart @@ -17,10 +17,14 @@ class LifeCycleManager extends StatefulWidget { _LifeCycleManagerState createState() => _LifeCycleManagerState(); } -class _LifeCycleManagerState extends State with WidgetsBindingObserver { +class _LifeCycleManagerState extends State + with WidgetsBindingObserver { final Logger logger = getLogger(); - List servicesToManage = [locator(), locator()]; + List servicesToManage = [ + locator(), + locator() + ]; @override Widget build(BuildContext context) { diff --git a/lib/core/models/rest/apikey.dart b/lib/core/models/rest/apikey.dart index 0e3d8bf..2386259 100644 --- a/lib/core/models/rest/apikey.dart +++ b/lib/core/models/rest/apikey.dart @@ -15,7 +15,11 @@ class ApiKey { final String? comment; - ApiKey({required this.key, required this.created, required this.accessLevel, this.comment}); + ApiKey( + {required this.key, + required this.created, + required this.accessLevel, + this.comment}); // JSON Init factory ApiKey.fromJson(Map json) => _$ApiKeyFromJson(json); diff --git a/lib/core/models/rest/apikeys.dart b/lib/core/models/rest/apikeys.dart index d8e6191..f6b30a5 100644 --- a/lib/core/models/rest/apikeys.dart +++ b/lib/core/models/rest/apikeys.dart @@ -12,7 +12,8 @@ class ApiKeys { ApiKeys({required this.apikeys}); // JSON Init - factory ApiKeys.fromJson(Map json) => _$ApiKeysFromJson(json); + factory ApiKeys.fromJson(Map json) => + _$ApiKeysFromJson(json); // JSON Export Map toJson() => _$ApiKeysToJson(this); diff --git a/lib/core/models/rest/apikeys_response.dart b/lib/core/models/rest/apikeys_response.dart index 138642a..a2bd8f8 100644 --- a/lib/core/models/rest/apikeys_response.dart +++ b/lib/core/models/rest/apikeys_response.dart @@ -15,7 +15,8 @@ class ApiKeysResponse { ApiKeysResponse({required this.status, required this.data}); // JSON Init - factory ApiKeysResponse.fromJson(Map json) => _$ApiKeysResponseFromJson(json); + factory ApiKeysResponse.fromJson(Map json) => + _$ApiKeysResponseFromJson(json); // JSON Export Map toJson() => _$ApiKeysResponseToJson(this); diff --git a/lib/core/models/rest/config_response.dart b/lib/core/models/rest/config_response.dart index 7e26ea0..d25ac7d 100644 --- a/lib/core/models/rest/config_response.dart +++ b/lib/core/models/rest/config_response.dart @@ -15,7 +15,8 @@ class ConfigResponse { ConfigResponse({required this.status, required this.data}); // JSON Init - factory ConfigResponse.fromJson(Map json) => _$ConfigResponseFromJson(json); + factory ConfigResponse.fromJson(Map json) => + _$ConfigResponseFromJson(json); // JSON Export Map toJson() => _$ConfigResponseToJson(this); diff --git a/lib/core/models/rest/create_apikey_response.dart b/lib/core/models/rest/create_apikey_response.dart index 9614676..55efe99 100644 --- a/lib/core/models/rest/create_apikey_response.dart +++ b/lib/core/models/rest/create_apikey_response.dart @@ -13,7 +13,8 @@ class CreateApiKeyResponse { CreateApiKeyResponse({required this.status, required this.data}); // JSON Init - factory CreateApiKeyResponse.fromJson(Map json) => _$CreateApiKeyResponseFromJson(json); + factory CreateApiKeyResponse.fromJson(Map json) => + _$CreateApiKeyResponseFromJson(json); // JSON Export Map toJson() => _$CreateApiKeyResponseToJson(this); diff --git a/lib/core/models/rest/history.dart b/lib/core/models/rest/history.dart index 602a290..94ec42d 100644 --- a/lib/core/models/rest/history.dart +++ b/lib/core/models/rest/history.dart @@ -19,7 +19,8 @@ class History { History({required this.items, required this.multipasteItems, this.totalSize}); // JSON Init - factory History.fromJson(Map json) => _$HistoryFromJson(json); + factory History.fromJson(Map json) => + _$HistoryFromJson(json); // JSON Export Map toJson() => _$HistoryToJson(this); diff --git a/lib/core/models/rest/history_item.dart b/lib/core/models/rest/history_item.dart index 17744ba..54364e1 100644 --- a/lib/core/models/rest/history_item.dart +++ b/lib/core/models/rest/history_item.dart @@ -23,7 +23,8 @@ class HistoryItem { this.thumbnail}); // JSON Init - factory HistoryItem.fromJson(Map json) => _$HistoryItemFromJson(json); + factory HistoryItem.fromJson(Map json) => + _$HistoryItemFromJson(json); // JSON Export Map toJson() => _$HistoryItemToJson(this); diff --git a/lib/core/models/rest/history_multipaste_item.dart b/lib/core/models/rest/history_multipaste_item.dart index cde11dc..1b56dcd 100644 --- a/lib/core/models/rest/history_multipaste_item.dart +++ b/lib/core/models/rest/history_multipaste_item.dart @@ -15,7 +15,8 @@ class HistoryMultipasteItem { HistoryMultipasteItem(this.items, {required this.date, required this.urlId}); // JSON Init - factory HistoryMultipasteItem.fromJson(Map json) => _$HistoryMultipasteItemFromJson(json); + factory HistoryMultipasteItem.fromJson(Map json) => + _$HistoryMultipasteItemFromJson(json); // JSON Export Map toJson() => _$HistoryMultipasteItemToJson(this); diff --git a/lib/core/models/rest/history_multipaste_item_entry.dart b/lib/core/models/rest/history_multipaste_item_entry.dart index 209fb2e..fb37fb2 100644 --- a/lib/core/models/rest/history_multipaste_item_entry.dart +++ b/lib/core/models/rest/history_multipaste_item_entry.dart @@ -9,7 +9,8 @@ class HistoryMultipasteItemEntry { HistoryMultipasteItemEntry({required this.id}); // JSON Init - factory HistoryMultipasteItemEntry.fromJson(Map json) => _$HistoryMultipasteItemEntryFromJson(json); + factory HistoryMultipasteItemEntry.fromJson(Map json) => + _$HistoryMultipasteItemEntryFromJson(json); // JSON Export Map toJson() => _$HistoryMultipasteItemEntryToJson(this); diff --git a/lib/core/models/rest/history_response.dart b/lib/core/models/rest/history_response.dart index bfc3548..fa8f904 100644 --- a/lib/core/models/rest/history_response.dart +++ b/lib/core/models/rest/history_response.dart @@ -15,7 +15,8 @@ class HistoryResponse { HistoryResponse({required this.status, required this.data}); // JSON Init - factory HistoryResponse.fromJson(Map json) => _$HistoryResponseFromJson(json); + factory HistoryResponse.fromJson(Map json) => + _$HistoryResponseFromJson(json); // JSON Export Map toJson() => _$HistoryResponseToJson(this); diff --git a/lib/core/models/rest/rest_error.dart b/lib/core/models/rest/rest_error.dart index 30b3d6f..6db731c 100644 --- a/lib/core/models/rest/rest_error.dart +++ b/lib/core/models/rest/rest_error.dart @@ -15,7 +15,8 @@ class RestError { required this.errorId, }); // JSON Init - factory RestError.fromJson(Map json) => _$RestErrorFromJson(json); + factory RestError.fromJson(Map json) => + _$RestErrorFromJson(json); // JSON Export Map toJson() => _$RestErrorToJson(this); diff --git a/lib/core/models/rest/uploaded.dart b/lib/core/models/rest/uploaded.dart index 7405970..28f0a6a 100644 --- a/lib/core/models/rest/uploaded.dart +++ b/lib/core/models/rest/uploaded.dart @@ -13,7 +13,8 @@ class Uploaded { Uploaded({required this.ids, required this.urls}); // JSON Init - factory Uploaded.fromJson(Map json) => _$UploadedFromJson(json); + factory Uploaded.fromJson(Map json) => + _$UploadedFromJson(json); // JSON Export Map toJson() => _$UploadedToJson(this); diff --git a/lib/core/models/rest/uploaded_multi.dart b/lib/core/models/rest/uploaded_multi.dart index 6339bb8..f404269 100644 --- a/lib/core/models/rest/uploaded_multi.dart +++ b/lib/core/models/rest/uploaded_multi.dart @@ -13,7 +13,8 @@ class UploadedMulti { UploadedMulti({required this.url, required this.urlId}); // JSON Init - factory UploadedMulti.fromJson(Map json) => _$UploadedMultiFromJson(json); + factory UploadedMulti.fromJson(Map json) => + _$UploadedMultiFromJson(json); // JSON Export Map toJson() => _$UploadedMultiToJson(this); diff --git a/lib/core/models/rest/uploaded_multi_response.dart b/lib/core/models/rest/uploaded_multi_response.dart index b6de81f..51a03a2 100644 --- a/lib/core/models/rest/uploaded_multi_response.dart +++ b/lib/core/models/rest/uploaded_multi_response.dart @@ -15,7 +15,8 @@ class UploadedMultiResponse { UploadedMultiResponse({required this.status, required this.data}); // JSON Init - factory UploadedMultiResponse.fromJson(Map json) => _$UploadedMultiResponseFromJson(json); + factory UploadedMultiResponse.fromJson(Map json) => + _$UploadedMultiResponseFromJson(json); // JSON Export Map toJson() => _$UploadedMultiResponseToJson(this); diff --git a/lib/core/models/rest/uploaded_response.dart b/lib/core/models/rest/uploaded_response.dart index 9ed84b1..707373f 100644 --- a/lib/core/models/rest/uploaded_response.dart +++ b/lib/core/models/rest/uploaded_response.dart @@ -15,7 +15,8 @@ class UploadedResponse { UploadedResponse({required this.status, required this.data}); // JSON Init - factory UploadedResponse.fromJson(Map json) => _$UploadedResponseFromJson(json); + factory UploadedResponse.fromJson(Map json) => + _$UploadedResponseFromJson(json); // JSON Export Map toJson() => _$UploadedResponseToJson(this); diff --git a/lib/core/models/session.dart b/lib/core/models/session.dart index a2725a8..4d07ac5 100644 --- a/lib/core/models/session.dart +++ b/lib/core/models/session.dart @@ -13,7 +13,8 @@ class Session { : url = '', apiKey = ''; - factory Session.fromJson(Map json) => _$SessionFromJson(json); + factory Session.fromJson(Map json) => + _$SessionFromJson(json); Map toJson() => _$SessionToJson(this); } diff --git a/lib/core/models/uploaded_paste.dart b/lib/core/models/uploaded_paste.dart index d209696..7db14b6 100644 --- a/lib/core/models/uploaded_paste.dart +++ b/lib/core/models/uploaded_paste.dart @@ -26,7 +26,8 @@ class UploadedPaste { this.items}); // JSON Init - factory UploadedPaste.fromJson(Map json) => _$UploadedPasteFromJson(json); + factory UploadedPaste.fromJson(Map json) => + _$UploadedPasteFromJson(json); // JSON Export Map toJson() => _$UploadedPasteToJson(this); diff --git a/lib/core/repositories/file_repository.dart b/lib/core/repositories/file_repository.dart index e93ae3a..dd49593 100644 --- a/lib/core/repositories/file_repository.dart +++ b/lib/core/repositories/file_repository.dart @@ -33,8 +33,10 @@ class FileRepository { return response; } - Future postUpload(List? files, Map? additionalFiles) async { - var response = await _api.post('/file/upload', files: files, additionalFiles: additionalFiles); + Future postUpload( + List? files, Map? additionalFiles) async { + var response = await _api.post('/file/upload', + files: files, additionalFiles: additionalFiles); return UploadedResponse.fromJson(json.decode(response.body)); } @@ -42,10 +44,12 @@ class FileRepository { Map multiPasteIds = {}; for (var element in ids) { - multiPasteIds.putIfAbsent("ids[${ids.indexOf(element) + 1}]", () => element); + multiPasteIds.putIfAbsent( + "ids[${ids.indexOf(element) + 1}]", () => element); } - var response = await _api.post('/file/create_multipaste', fields: multiPasteIds); + var response = + await _api.post('/file/create_multipaste', fields: multiPasteIds); return UploadedMultiResponse.fromJson(json.decode(response.body)); } } diff --git a/lib/core/repositories/user_repository.dart b/lib/core/repositories/user_repository.dart index f8d53e1..41397e5 100644 --- a/lib/core/repositories/user_repository.dart +++ b/lib/core/repositories/user_repository.dart @@ -8,8 +8,8 @@ import '../services/api.dart'; class UserRepository { final Api _api = locator(); - Future postApiKey( - String url, String username, String password, String accessLevel, String comment) async { + Future postApiKey(String url, String username, + String password, String accessLevel, String comment) async { _api.setUrl(url); var fields = Map.fromEntries([ diff --git a/lib/core/services/api.dart b/lib/core/services/api.dart index 2cf65b9..fd9168d 100644 --- a/lib/core/services/api.dart +++ b/lib/core/services/api.dart @@ -24,25 +24,34 @@ class Api implements ApiErrorConverter { String _url = ""; String _apiKey = ""; - final Map _headers = {"Content-Type": _applicationJson, "Accept": _applicationJson}; + final Map _headers = { + "Content-Type": _applicationJson, + "Accept": _applicationJson + }; Duration _timeout = const Duration(seconds: Constants.apiRequestTimeoutLimit); Future fetch(String route) async { try { - _logger - .d("Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'"); - var response = await http.get(Uri.parse(_url + route), headers: _headers).timeout(_timeout); + _logger.d( + "Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'"); + var response = await http + .get(Uri.parse(_url + route), headers: _headers) + .timeout(_timeout); handleRestErrors(response); return response; } on TimeoutException { - throw ServiceException(code: ErrorCode.socketTimeout, message: _errorTimeout); + throw ServiceException( + code: ErrorCode.socketTimeout, message: _errorTimeout); } on SocketException { - throw ServiceException(code: ErrorCode.socketError, message: _errorNoConnection); + throw ServiceException( + code: ErrorCode.socketError, message: _errorNoConnection); } } Future post(String route, - {Map? fields, List? files, Map? additionalFiles}) async { + {Map? fields, + List? files, + Map? additionalFiles}) async { try { var uri = Uri.parse(_url + route); var request = http.MultipartRequest('POST', uri) @@ -59,27 +68,34 @@ class Api implements ApiErrorConverter { if (files != null && files.isNotEmpty) { for (var element in files) { - request.files.add(await http.MultipartFile.fromPath('file[${files.indexOf(element) + 1}]', element.path)); + request.files.add(await http.MultipartFile.fromPath( + 'file[${files.indexOf(element) + 1}]', element.path)); } } if (additionalFiles != null && additionalFiles.isNotEmpty) { List keys = additionalFiles.keys.toList(); additionalFiles.forEach((key, value) { - var index = files != null ? files.length + keys.indexOf(key) + 1 : keys.indexOf(key) + 1; - request.files.add(http.MultipartFile.fromString('file[$index]', value, filename: key)); + var index = files != null + ? files.length + keys.indexOf(key) + 1 + : keys.indexOf(key) + 1; + request.files.add(http.MultipartFile.fromString('file[$index]', value, + filename: key)); }); } - _logger.d("Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files"); + _logger.d( + "Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files"); var multiResponse = await request.send(); var response = await http.Response.fromStream(multiResponse); handleRestErrors(response); return response; } on TimeoutException { - throw ServiceException(code: ErrorCode.socketTimeout, message: _errorTimeout); + throw ServiceException( + code: ErrorCode.socketTimeout, message: _errorTimeout); } on SocketException { - throw ServiceException(code: ErrorCode.socketError, message: _errorNoConnection); + throw ServiceException( + code: ErrorCode.socketError, message: _errorNoConnection); } } @@ -107,14 +123,17 @@ class Api implements ApiErrorConverter { /// have a json decoded object. Replace this with a custom /// conversion method by overwriting the interface if needed void handleRestErrors(http.Response response) { - if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.noContent) { + if (response.statusCode != HttpStatus.ok && + response.statusCode != HttpStatus.noContent) { if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) { - ContentType responseContentType = ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!); + ContentType responseContentType = + ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!); if (ContentType.json.primaryType == responseContentType.primaryType && ContentType.json.subType == responseContentType.subType) { var parsedBody = convert(response); - throw RestServiceException(response.statusCode, responseBody: parsedBody); + throw RestServiceException(response.statusCode, + responseBody: parsedBody); } } diff --git a/lib/core/services/dialog_service.dart b/lib/core/services/dialog_service.dart index ccf0d28..9a3e49e 100644 --- a/lib/core/services/dialog_service.dart +++ b/lib/core/services/dialog_service.dart @@ -7,7 +7,8 @@ import '../datamodels/dialog_request.dart'; import '../datamodels/dialog_response.dart'; class DialogService { - final GlobalKey _dialogNavigationKey = GlobalKey(); + final GlobalKey _dialogNavigationKey = + GlobalKey(); late Function(DialogRequest) _showDialogListener; Completer? _dialogCompleter; @@ -27,20 +28,28 @@ class DialogService { title: title, description: description, buttonTitleAccept: - buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept)); + buttonTitleAccept == null || buttonTitleAccept.isEmpty + ? translate('dialog.confirm') + : buttonTitleAccept)); return _dialogCompleter!.future; } Future showConfirmationDialog( - {String? title, String? description, String? buttonTitleAccept, String? buttonTitleDeny}) { + {String? title, + String? description, + String? buttonTitleAccept, + String? buttonTitleDeny}) { _dialogCompleter = Completer(); _showDialogListener(DialogRequest( title: title, description: description, buttonTitleAccept: - buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept, - buttonTitleDeny: - buttonTitleDeny == null || buttonTitleDeny.isEmpty ? translate('dialog.cancel') : buttonTitleDeny)); + buttonTitleAccept == null || buttonTitleAccept.isEmpty + ? translate('dialog.confirm') + : buttonTitleAccept, + buttonTitleDeny: buttonTitleDeny == null || buttonTitleDeny.isEmpty + ? translate('dialog.cancel') + : buttonTitleDeny)); return _dialogCompleter!.future; } diff --git a/lib/core/services/file_service.dart b/lib/core/services/file_service.dart index fd4a7e2..8ce822f 100644 --- a/lib/core/services/file_service.dart +++ b/lib/core/services/file_service.dart @@ -23,7 +23,8 @@ class FileService { return await _fileRepository.postDelete(id); } - Future uploadPaste(List? files, Map? additionalFiles) async { + Future uploadPaste( + List? files, Map? additionalFiles) async { return await _fileRepository.postUpload(files, additionalFiles); } diff --git a/lib/core/services/link_service.dart b/lib/core/services/link_service.dart index 7cfde11..47c4140 100644 --- a/lib/core/services/link_service.dart +++ b/lib/core/services/link_service.dart @@ -19,7 +19,8 @@ class LinkService { _logger.e('Could not launch link $link'); _dialogService.showDialog( title: translate('link.dialog.title'), - description: translate('link.dialog.description', args: {'link': link})); + description: + translate('link.dialog.description', args: {'link': link})); } } } diff --git a/lib/core/services/navigation_service.dart b/lib/core/services/navigation_service.dart index cd0b539..e8533bd 100644 --- a/lib/core/services/navigation_service.dart +++ b/lib/core/services/navigation_service.dart @@ -17,11 +17,13 @@ class NavigationService { Future navigateTo(String routeName, {dynamic arguments}) { logger.d('NavigationService: navigateTo $routeName'); - return _navigationKey.currentState!.pushNamed(routeName, arguments: arguments); + return _navigationKey.currentState! + .pushNamed(routeName, arguments: arguments); } Future navigateAndReplaceTo(String routeName, {dynamic arguments}) { logger.d('NavigationService: navigateAndReplaceTo $routeName'); - return _navigationKey.currentState!.pushReplacementNamed(routeName, arguments: arguments); + return _navigationKey.currentState! + .pushReplacementNamed(routeName, arguments: arguments); } } diff --git a/lib/core/services/permission_service.dart b/lib/core/services/permission_service.dart index 4000c54..4c5dcdc 100644 --- a/lib/core/services/permission_service.dart +++ b/lib/core/services/permission_service.dart @@ -57,7 +57,8 @@ class PermissionService extends StoppableService { return; } - var ignoredDialog = await _storageService.hasStoragePermissionDialogIgnored(); + var ignoredDialog = + await _storageService.hasStoragePermissionDialogIgnored(); if (ignoredDialog) { _logger.d('Permanently ignored permission request, skipping'); @@ -104,8 +105,9 @@ class PermissionService extends StoppableService { super.start(); await checkEnabledAndPermission(); - _serviceCheckTimer = - Timer.periodic(const Duration(milliseconds: Constants.mediaPermissionCheckInterval), (serviceTimer) async { + _serviceCheckTimer = Timer.periodic( + const Duration(milliseconds: Constants.mediaPermissionCheckInterval), + (serviceTimer) async { if (!super.serviceStopped) { await checkEnabledAndPermission(); } else { diff --git a/lib/core/services/refresh_service.dart b/lib/core/services/refresh_service.dart index 0e9e0e3..b92306e 100644 --- a/lib/core/services/refresh_service.dart +++ b/lib/core/services/refresh_service.dart @@ -3,7 +3,8 @@ import 'dart:async'; import '../enums/refresh_event.dart'; class RefreshService { - StreamController refreshEventController = StreamController.broadcast(); + StreamController refreshEventController = + StreamController.broadcast(); void addEvent(RefreshEvent event) { if (refreshEventController.hasListener) { diff --git a/lib/core/services/storage_service.dart b/lib/core/services/storage_service.dart index 322c607..95f08c4 100644 --- a/lib/core/services/storage_service.dart +++ b/lib/core/services/storage_service.dart @@ -7,7 +7,8 @@ import '../models/session.dart'; class StorageService { static const _sessionKey = 'session'; static const _lastUrlKey = 'last_url'; - static const _storagePermissionDialogIgnoredKey = 'storage_permission_ignored'; + static const _storagePermissionDialogIgnoredKey = + 'storage_permission_ignored'; Future storeLastUrl(String url) { return _store(_lastUrlKey, url); diff --git a/lib/core/services/user_service.dart b/lib/core/services/user_service.dart index ef10096..7637a1f 100644 --- a/lib/core/services/user_service.dart +++ b/lib/core/services/user_service.dart @@ -12,9 +12,10 @@ class UserService { final FileService _fileService = locator(); final UserRepository _userRepository = locator(); - Future createApiKey( - String url, String username, String password, String accessLevel, String comment) async { - return await _userRepository.postApiKey(url, username, password, accessLevel, comment); + Future createApiKey(String url, String username, + String password, String accessLevel, String comment) async { + return await _userRepository.postApiKey( + url, username, password, accessLevel, comment); } Future getApiKeys() async { diff --git a/lib/core/util/formatter_util.dart b/lib/core/util/formatter_util.dart index ba606f7..1f04f6e 100644 --- a/lib/core/util/formatter_util.dart +++ b/lib/core/util/formatter_util.dart @@ -6,7 +6,8 @@ class FormatterUtil { /// Format epoch timestamp static String formatEpoch(num millis) { DateFormat dateFormat = DateFormat().add_yMEd().add_Hm(); - return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis as int)); + return dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(millis as int)); } static String formatBytes(int bytes, int decimals) { diff --git a/lib/core/viewmodels/base_model.dart b/lib/core/viewmodels/base_model.dart index 9fc7570..c7c0687 100644 --- a/lib/core/viewmodels/base_model.dart +++ b/lib/core/viewmodels/base_model.dart @@ -12,7 +12,10 @@ class BaseModel extends ChangeNotifier { bool _isDisposed = false; - final Map _stateMap = {stateViewKey: ViewState.idle, stateMessageKey: null}; + final Map _stateMap = { + stateViewKey: ViewState.idle, + stateMessageKey: null + }; ViewState? get state => _stateMap[stateViewKey] as ViewState?; @@ -42,11 +45,14 @@ class BaseModel extends ChangeNotifier { return null; } - void setStateBoolValue(String key, bool stateValue) => _setStateValue(key, stateValue); + void setStateBoolValue(String key, bool stateValue) => + _setStateValue(key, stateValue); - void setStateIntValue(String key, int? stateValue) => _setStateValue(key, stateValue); + void setStateIntValue(String key, int? stateValue) => + _setStateValue(key, stateValue); - void setStateStringValue(String key, String? stateValue) => _setStateValue(key, stateValue); + void setStateStringValue(String key, String? stateValue) => + _setStateValue(key, stateValue); void _setStateValue(String key, Object? stateValue) { if (_stateMap.containsKey(key)) { @@ -57,7 +63,8 @@ class BaseModel extends ChangeNotifier { if (!_isDisposed) { notifyListeners(); - _logger.d("Notified state value update '($key, ${stateValue.toString()})'"); + _logger + .d("Notified state value update '($key, ${stateValue.toString()})'"); } } @@ -80,6 +87,7 @@ class BaseModel extends ChangeNotifier { @override void dispose() { + _logger.d("Calling dispose"); super.dispose(); _isDisposed = true; } diff --git a/lib/core/viewmodels/history_model.dart b/lib/core/viewmodels/history_model.dart index 4007ced..e506db3 100644 --- a/lib/core/viewmodels/history_model.dart +++ b/lib/core/viewmodels/history_model.dart @@ -34,7 +34,8 @@ class HistoryModel extends BaseModel { String? errorMessage; void init() { - _refreshTriggerSubscription = _refreshService.refreshEventController.stream.listen((event) { + _refreshTriggerSubscription = + _refreshService.refreshEventController.stream.listen((event) { if (event == RefreshEvent.refreshHistory) { _logger.d('History needs a refresh'); getHistory(); @@ -54,7 +55,8 @@ class HistoryModel extends BaseModel { pastes.add( UploadedPaste( id: key, - date: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch), + date: + DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch), filename: value.filename, filesize: int.parse(value.filesize), hash: value.hash, @@ -90,9 +92,11 @@ class HistoryModel extends BaseModel { e.responseBody is RestError && e.responseBody.message != null) { if (e.statusCode == HttpStatus.badRequest) { - errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message}); + errorMessage = translate('api.bad_request', + args: {'reason': e.responseBody.message}); } else { - errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message}); + errorMessage = translate('api.general_rest_error_payload', + args: {'message': e.responseBody.message}); } } else { errorMessage = translate('api.general_rest_error'); @@ -115,7 +119,8 @@ class HistoryModel extends BaseModel { Future deletePaste(String id) async { DialogResponse res = await _dialogService.showConfirmationDialog( title: translate('history.delete_dialog.title'), - description: translate('history.delete_dialog.description', args: {'id': id}), + description: + translate('history.delete_dialog.description', args: {'id': id}), buttonTitleAccept: translate('history.delete_dialog.accept'), buttonTitleDeny: translate('history.delete_dialog.deny')); @@ -139,7 +144,8 @@ class HistoryModel extends BaseModel { e.statusCode != HttpStatus.forbidden && e.responseBody is RestError && e.responseBody.message != null) { - errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message}); + errorMessage = translate('api.general_rest_error_payload', + args: {'message': e.responseBody.message}); } else { errorMessage = translate('api.general_rest_error'); } diff --git a/lib/core/viewmodels/login_model.dart b/lib/core/viewmodels/login_model.dart index afa9182..8d62d08 100644 --- a/lib/core/viewmodels/login_model.dart +++ b/lib/core/viewmodels/login_model.dart @@ -117,13 +117,19 @@ class LoginModel extends BaseModel { try { if (useCredentialsLogin) { CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey( - url, username, password, 'apikey', 'fbmobile-${DateTime.now().millisecondsSinceEpoch}'); + url, + username, + password, + 'apikey', + 'fbmobile-${DateTime.now().millisecondsSinceEpoch}'); var newKey = apiKeyResponse.data['new_key']; if (newKey != null) { success = await _sessionService.login(url, newKey); } else { - throw ServiceException(code: ErrorCode.invalidApiKey, message: translate('login.errors.invalid_api_key')); + throw ServiceException( + code: ErrorCode.invalidApiKey, + message: translate('login.errors.invalid_api_key')); } } else { _sessionService.setApiConfig(url, apiKey); @@ -135,13 +141,15 @@ class LoginModel extends BaseModel { if (e is RestServiceException) { if (e.statusCode == HttpStatus.unauthorized) { errorMessage = translate('login.errors.wrong_credentials'); - } else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) { + } else if (e.statusCode != HttpStatus.unauthorized && + e.statusCode == HttpStatus.forbidden) { errorMessage = translate('login.errors.forbidden'); } else if (e.statusCode == HttpStatus.notFound) { errorMessage = translate('api.incompatible_error_not_found'); } if (e.statusCode == HttpStatus.badRequest) { - errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message}); + errorMessage = translate('api.bad_request', + args: {'reason': e.responseBody.message}); } else { errorMessage = translate('api.general_rest_error'); } diff --git a/lib/core/viewmodels/profile_model.dart b/lib/core/viewmodels/profile_model.dart index edf835d..3d5362c 100644 --- a/lib/core/viewmodels/profile_model.dart +++ b/lib/core/viewmodels/profile_model.dart @@ -31,7 +31,8 @@ class ProfileModel extends BaseModel { Future logout() async { var dialogResult = await _dialogService.showConfirmationDialog( - title: translate('logout.title'), description: translate('logout.confirm')); + title: translate('logout.title'), + description: translate('logout.confirm')); if (dialogResult.confirmed!) { await _sessionService.logout(); @@ -41,7 +42,8 @@ class ProfileModel extends BaseModel { Future revealApiKey(String? apiKey) async { await _dialogService.showDialog( title: translate('profile.revealed_api_key.title'), - description: translate('profile.revealed_api_key.description', args: {'apiKey': apiKey})); + description: translate('profile.revealed_api_key.description', + args: {'apiKey': apiKey})); } Future showConfig(String url) async { @@ -54,13 +56,15 @@ class ProfileModel extends BaseModel { if (e is RestServiceException) { if (e.statusCode == HttpStatus.unauthorized) { errorMessage = translate('login.errors.wrong_credentials'); - } else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) { + } else if (e.statusCode != HttpStatus.unauthorized && + e.statusCode == HttpStatus.forbidden) { errorMessage = translate('login.errors.forbidden'); } else if (e.statusCode == HttpStatus.notFound) { errorMessage = translate('api.incompatible_error_not_found'); } if (e.statusCode == HttpStatus.badRequest) { - errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message}); + errorMessage = translate('api.bad_request', + args: {'reason': e.responseBody.message}); } else { errorMessage = translate('api.general_rest_error'); } @@ -84,15 +88,18 @@ class ProfileModel extends BaseModel { await _dialogService.showDialog( title: translate('profile.shown_config.title'), description: translate('profile.shown_config.description', args: { - 'uploadMaxSize': FormatterUtil.formatBytes(config.uploadMaxSize as int, 2), + 'uploadMaxSize': + FormatterUtil.formatBytes(config.uploadMaxSize as int, 2), 'maxFilesPerRequest': config.maxFilesPerRequest, 'maxInputVars': config.maxInputVars, - 'requestMaxSize': FormatterUtil.formatBytes(config.requestMaxSize as int, 2) + 'requestMaxSize': + FormatterUtil.formatBytes(config.requestMaxSize as int, 2) })); } else { await _dialogService.showDialog( title: translate('profile.shown_config.error.title'), - description: translate('profile.shown_config.error.description', args: {'message': errorMessage})); + description: translate('profile.shown_config.error.description', + args: {'message': errorMessage})); } } diff --git a/lib/core/viewmodels/upload_model.dart b/lib/core/viewmodels/upload_model.dart index 2be59b6..71e43ed 100644 --- a/lib/core/viewmodels/upload_model.dart +++ b/lib/core/viewmodels/upload_model.dart @@ -4,10 +4,11 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; +import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:logger/logger.dart'; import 'package:path/path.dart'; -import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import '../../locator.dart'; import '../enums/error_code.dart'; @@ -45,6 +46,41 @@ class UploadModel extends BaseModel { TextEditingController get pasteTextController => _pasteTextController; + void _parseIntentFiles(List 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); + } + void init() { _pasteTextController.addListener(() { pasteTextTouched = pasteTextController.text.isNotEmpty; @@ -52,61 +88,21 @@ class UploadModel extends BaseModel { }); // For sharing images coming from outside the app while the app is in the memory - _intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List value) { - if (value.isNotEmpty) { - setStateView(ViewState.busy); - paths = value.map((sharedFile) { - return PlatformFile.fromMap({ - 'path': sharedFile.path, - 'name': basename(sharedFile.path), - 'size': File(sharedFile.path).lengthSync(), - 'bytes': null - }); - }).toList(); - setStateView(ViewState.idle); - } + _intentDataStreamSubscription = FlutterSharingIntent.instance + .getMediaStream() + .listen((List value) { + _logger.d("Retrieved ${value.length} files from intent"); + _parseIntentFiles(value); }, onError: (err) { _errorIntentHandle(err); }); // For sharing images coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialMedia().then((List value) { - if (value.isNotEmpty) { - setStateView(ViewState.busy); - paths = value.map((sharedFile) { - return PlatformFile.fromMap({ - 'path': sharedFile.path, - 'name': basename(sharedFile.path), - 'size': File(sharedFile.path).lengthSync(), - 'bytes': null - }); - }).toList(); - setStateView(ViewState.idle); - } - }, onError: (err) { - _errorIntentHandle(err); - }); - - // For sharing or opening urls/text coming from outside the app while the app is in the memory - _intentDataStreamSubscription = ReceiveSharingIntent.getTextStream().listen((String value) { - if (value.isNotEmpty) { - setStateView(ViewState.busy); - pasteTextController.text = value; - setStateView(ViewState.idle); - } - }, onError: (err) { - _errorIntentHandle(err); - }); - - // For sharing or opening urls/text coming from outside the app while the app is closed - ReceiveSharingIntent.getInitialText().then((String? value) { - if (value != null && value.isNotEmpty) { - setStateView(ViewState.busy); - pasteTextController.text = value; - setStateView(ViewState.idle); - } - }, onError: (err) { - _errorIntentHandle(err); + FlutterSharingIntent.instance + .getInitialSharing() + .then((List value) { + _logger.d("Retrieved ${value.length} files from inactive intent"); + _parseIntentFiles(value); }); } @@ -150,7 +146,9 @@ class UploadModel extends BaseModel { allowMultiple: true, withData: false, withReadStream: true, - allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '').split(',') : null, + allowedExtensions: (_extension?.isNotEmpty ?? false) + ? _extension?.replaceAll(' ', '').split(',') + : null, )) ?.files; } on PlatformException catch (e) { @@ -185,21 +183,25 @@ class UploadModel extends BaseModel { Map? additionalFiles; if (pasteTextController.text.isNotEmpty) { - additionalFiles = - Map.from({'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': pasteTextController.text}); + additionalFiles = Map.from({ + 'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': + pasteTextController.text + }); } if (paths != null && paths!.isNotEmpty) { files = paths!.map((e) => File(e.path!)).toList(); } - UploadedResponse response = await _fileService.uploadPaste(files, additionalFiles); + UploadedResponse response = + await _fileService.uploadPaste(files, additionalFiles); for (var element in response.data.ids) { uploadedPasteIds.putIfAbsent(element, () => false); } if (createMulti && response.data.ids.length > 1) { - UploadedMultiResponse multiResponse = await _fileService.uploadMultiPaste(response.data.ids); + UploadedMultiResponse multiResponse = + await _fileService.uploadMultiPaste(response.data.ids); uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true); } @@ -219,9 +221,11 @@ class UploadModel extends BaseModel { e.responseBody is RestError && e.responseBody.message != null) { if (e.statusCode == HttpStatus.badRequest) { - errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message}); + errorMessage = translate('api.bad_request', + args: {'reason': e.responseBody.message}); } else { - errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message}); + errorMessage = translate('api.general_rest_error_payload', + args: {'message': e.responseBody.message}); } } else { errorMessage = translate('api.general_rest_error'); @@ -252,6 +256,8 @@ class UploadModel extends BaseModel { void dispose() { _pasteTextController.dispose(); _intentDataStreamSubscription.cancel(); + FlutterSharingIntent.instance.reset(); + paths = null; super.dispose(); } } diff --git a/lib/main.dart b/lib/main.dart index 1db3d2c..81b84f4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,7 +11,8 @@ void main() async { setupLogger(Level.info); setupLocator(); - var delegate = await LocalizationDelegate.create(fallbackLocale: 'en', supportedLocales: ['en', 'en_US']); + var delegate = await LocalizationDelegate.create( + fallbackLocale: 'en', supportedLocales: ['en', 'en_US']); WidgetsFlutterBinding.ensureInitialized(); runApp(LocalizedApp(delegate, MyApp())); diff --git a/lib/ui/app_router.dart b/lib/ui/app_router.dart index f43647e..5dc8b7c 100644 --- a/lib/ui/app_router.dart +++ b/lib/ui/app_router.dart @@ -27,7 +27,8 @@ class AppRouter { return MaterialPageRoute( builder: (_) => Scaffold( body: Center( - child: Text(translate('dev.no_route', args: {'route': settings.name})), + child: Text(translate('dev.no_route', + args: {'route': settings.name})), ), )); } diff --git a/lib/ui/views/about_view.dart b/lib/ui/views/about_view.dart index ddd4c45..d9ca5f2 100644 --- a/lib/ui/views/about_view.dart +++ b/lib/ui/views/about_view.dart @@ -38,7 +38,8 @@ class AboutView extends StatelessWidget { padding: const EdgeInsets.all(0), child: ListView( shrinkWrap: true, - padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10, top: 10), + padding: const EdgeInsets.only( + left: 10.0, right: 10.0, bottom: 10, top: 10), children: [ UIHelper.verticalSpaceMedium(), Center(child: logo), diff --git a/lib/ui/views/base_view.dart b/lib/ui/views/base_view.dart index 224a819..ff83c87 100644 --- a/lib/ui/views/base_view.dart +++ b/lib/ui/views/base_view.dart @@ -31,7 +31,9 @@ class _BaseViewState extends State> { @override Widget build(BuildContext context) { - return ChangeNotifierProvider(create: (context) => model, child: Consumer(builder: widget.builder!)); + return ChangeNotifierProvider( + create: (context) => model, + child: Consumer(builder: widget.builder!)); } @override diff --git a/lib/ui/views/history_view.dart b/lib/ui/views/history_view.dart index 53e659b..ff4847f 100644 --- a/lib/ui/views/history_view.dart +++ b/lib/ui/views/history_view.dart @@ -27,8 +27,9 @@ class HistoryView extends StatelessWidget { model.init(); return model.getHistory(); }, - builder: (context, model, child) => - Scaffold(appBar: MyAppBar(title: Text(translate('titles.history'))), body: _render(model, context)), + builder: (context, model, child) => Scaffold( + appBar: MyAppBar(title: Text(translate('titles.history'))), + body: _render(model, context)), ); } @@ -41,7 +42,8 @@ class HistoryView extends StatelessWidget { ? Container( padding: const EdgeInsets.all(0), child: RefreshIndicator( - onRefresh: () async => await model.getHistory(), child: _renderItems(model, url, context))) + onRefresh: () async => await model.getHistory(), + child: _renderItems(model, url, context))) : Container( padding: const EdgeInsets.all(25), child: CenteredErrorRow( @@ -61,7 +63,8 @@ class HistoryView extends StatelessWidget { var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl); var dateWidget = ListTile( - title: Text(FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)), + title: Text( + FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)), subtitle: Text(translate('history.date')), ); @@ -73,7 +76,8 @@ class HistoryView extends StatelessWidget { var copyWidget = ListTile( title: Text(translate('history.copy_link.description')), trailing: IconButton( - icon: const Icon(Icons.copy, color: blueColor, textDirection: TextDirection.ltr), + icon: const Icon(Icons.copy, + color: blueColor, textDirection: TextDirection.ltr), onPressed: () { FlutterClipboard.copy(fullPasteUrl).then((value) { final snackBar = SnackBar( @@ -164,12 +168,14 @@ class HistoryView extends StatelessWidget { trailing: Wrap(children: [ openInBrowserButton, IconButton( - icon: const Icon(Icons.share, color: blueColor, textDirection: TextDirection.ltr), + icon: const Icon(Icons.share, + color: blueColor, textDirection: TextDirection.ltr), onPressed: () async { await Share.share(fullPasteUrl); }) ]), - subtitle: Text(!paste.isMulti! ? paste.filename! : '', style: const TextStyle(fontStyle: FontStyle.italic)), + subtitle: Text(!paste.isMulti! ? paste.filename! : '', + style: const TextStyle(fontStyle: FontStyle.italic)), ), )); } @@ -190,7 +196,8 @@ class HistoryView extends StatelessWidget { Widget _renderOpenInBrowser(HistoryModel model, String url) { return IconButton( - icon: const Icon(Icons.open_in_new, color: blueColor, textDirection: TextDirection.ltr), + icon: const Icon(Icons.open_in_new, + color: blueColor, textDirection: TextDirection.ltr), onPressed: () { return model.openLink(url); }); diff --git a/lib/ui/views/home_view.dart b/lib/ui/views/home_view.dart index 3c9f3c3..2812e28 100644 --- a/lib/ui/views/home_view.dart +++ b/lib/ui/views/home_view.dart @@ -16,7 +16,9 @@ class HomeView extends StatelessWidget { return BaseView( builder: (context, model, child) => Scaffold( appBar: MyAppBar(title: Text(translate('app.title'))), - body: model.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) : Container()), + body: model.state == ViewState.busy + ? const Center(child: CircularProgressIndicator()) + : Container()), ); } } diff --git a/lib/ui/views/login_view.dart b/lib/ui/views/login_view.dart index 87125a6..96e1160 100644 --- a/lib/ui/views/login_view.dart +++ b/lib/ui/views/login_view.dart @@ -55,13 +55,18 @@ class LoginView extends StatelessWidget { child: const Icon(Icons.help), onTap: () { _dialogService.showDialog( - title: translate('login.compatibility_dialog.title'), - description: translate('login.compatibility_dialog.body')); + title: translate( + 'login.compatibility_dialog.title'), + description: translate( + 'login.compatibility_dialog.body')); }, ), InkWell( - child: - Icon(model.useCredentialsLogin ? Icons.person_outline : Icons.vpn_key, color: blueColor), + child: Icon( + model.useCredentialsLogin + ? Icons.person_outline + : Icons.vpn_key, + color: blueColor), onTap: () { model.toggleLoginMethod(); }, @@ -86,7 +91,8 @@ class LoginView extends StatelessWidget { onPressed: () async { var loginSuccess = await model.login(); if (loginSuccess) { - _navigationService.navigateAndReplaceTo(HomeView.routeName); + _navigationService + .navigateAndReplaceTo(HomeView.routeName); } }, ) diff --git a/lib/ui/views/navbar_authenticated.dart b/lib/ui/views/navbar_authenticated.dart index 18b95fa..70a0cc0 100644 --- a/lib/ui/views/navbar_authenticated.dart +++ b/lib/ui/views/navbar_authenticated.dart @@ -14,7 +14,8 @@ class AuthenticatedNavBarView extends StatefulWidget { AuthenticatedNavBarState createState() => AuthenticatedNavBarState(); } -class AuthenticatedNavBarState extends State with SingleTickerProviderStateMixin { +class AuthenticatedNavBarState extends State + with SingleTickerProviderStateMixin { final Logger _logger = getLogger(); int _currentTabIndex = 0; diff --git a/lib/ui/views/profile_view.dart b/lib/ui/views/profile_view.dart index e9e3e2c..6af69b0 100644 --- a/lib/ui/views/profile_view.dart +++ b/lib/ui/views/profile_view.dart @@ -20,8 +20,9 @@ class ProfileView extends StatelessWidget { @override Widget build(BuildContext context) { return BaseView( - builder: (context, model, child) => - Scaffold(appBar: MyAppBar(title: Text(translate('titles.profile'))), body: _render(model, context))); + builder: (context, model, child) => Scaffold( + appBar: MyAppBar(title: Text(translate('titles.profile'))), + body: _render(model, context))); } Widget _render(ProfileModel model, BuildContext context) { diff --git a/lib/ui/views/startup_view.dart b/lib/ui/views/startup_view.dart index e37f642..6808ed0 100644 --- a/lib/ui/views/startup_view.dart +++ b/lib/ui/views/startup_view.dart @@ -13,7 +13,7 @@ class StartUpView extends StatelessWidget { Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => StartUpViewModel(), - onModelReady: (model) => model.handleStartUpLogic(), + onViewModelReady: (model) => model.handleStartUpLogic(), builder: (context, model, child) => Scaffold( body: model.state == ViewState.busy ? Center( @@ -22,7 +22,9 @@ class StartUpView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ const CircularProgressIndicator(), - (model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container()) + (model.stateMessage!.isNotEmpty + ? Text(model.stateMessage!) + : Container()) ])) : Container())); } diff --git a/lib/ui/views/tabbar_container_view.dart b/lib/ui/views/tabbar_container_view.dart index 1228005..3fa21a5 100644 --- a/lib/ui/views/tabbar_container_view.dart +++ b/lib/ui/views/tabbar_container_view.dart @@ -11,7 +11,8 @@ class TabBarContainerView extends StatelessWidget { @override Widget build(BuildContext context) { Session? currentSession = Provider.of(context); - bool isAuthenticated = currentSession != null ? currentSession.apiKey.isNotEmpty : false; + bool isAuthenticated = + currentSession != null ? currentSession.apiKey.isNotEmpty : false; if (isAuthenticated) { return const AuthenticatedNavBarView(); diff --git a/lib/ui/views/upload_view.dart b/lib/ui/views/upload_view.dart index 21048b6..b0e4677 100644 --- a/lib/ui/views/upload_view.dart +++ b/lib/ui/views/upload_view.dart @@ -1,4 +1,5 @@ import 'package:clipboard/clipboard.dart'; +import 'package:fbmobile/core/util/formatter_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:provider/provider.dart'; @@ -20,12 +21,14 @@ class UploadView extends StatelessWidget { Widget build(BuildContext context) { return BaseView( onModelReady: (model) => model.init(), - builder: (context, model, child) => - Scaffold(appBar: MyAppBar(title: Text(translate('titles.upload'))), body: _render(model, context))); + builder: (context, model, child) => Scaffold( + appBar: MyAppBar(title: Text(translate('titles.upload'))), + body: _render(model, context))); } bool _isUploadButtonEnabled(UploadModel model) { - return model.pasteTextTouched || (model.paths != null && model.paths!.isNotEmpty); + return model.pasteTextTouched || + (model.paths != null && model.paths!.isNotEmpty); } Widget _render(UploadModel model, BuildContext context) { @@ -38,7 +41,9 @@ class UploadView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ const CircularProgressIndicator(), - (model.stateMessage != null && model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container()) + (model.stateMessage != null && model.stateMessage!.isNotEmpty + ? Text(model.stateMessage!) + : Container()) ])) : ListView(children: [ Padding( @@ -56,12 +61,15 @@ class UploadView extends StatelessWidget { Icons.text_snippet, ), suffixIcon: IconButton( - onPressed: () => model.pasteTextController.clear(), + onPressed: () => + model.pasteTextController.clear(), icon: const Icon(Icons.clear), ), hintText: translate('upload.text_to_be_pasted'), - contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)), + contentPadding: const EdgeInsets.fromLTRB( + 20.0, 10.0, 20.0, 10.0), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0)), ), controller: model.pasteTextController)), Padding( @@ -77,14 +85,17 @@ class UploadView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ ElevatedButton.icon( - icon: const Icon(Icons.file_copy_sharp, color: blueColor), + icon: const Icon(Icons.file_copy_sharp, + color: blueColor), onPressed: () => model.openFileExplorer(), label: Text( translate('upload.open_file_explorer'), )), ElevatedButton.icon( - icon: const Icon(Icons.cancel, color: orangeColor), - onPressed: model.paths != null && model.paths!.isNotEmpty + icon: const Icon(Icons.cancel, + color: orangeColor), + onPressed: model.paths != null && + model.paths!.isNotEmpty ? () => model.clearCachedFiles() : null, label: Text( @@ -114,34 +125,47 @@ class UploadView extends StatelessWidget { onPressed: !_isUploadButtonEnabled(model) ? null : () async { - Map? items = await model.upload(); - String? clipboardContent = model.generatePasteLinks(items, url); + Map? items = + await model.upload(); + String? clipboardContent = model + .generatePasteLinks(items, url); - if (clipboardContent != null && clipboardContent.isNotEmpty) { - FlutterClipboard.copy(clipboardContent).then((value) { + if (clipboardContent != null && + clipboardContent.isNotEmpty) { + FlutterClipboard.copy( + clipboardContent) + .then((value) { final snackBar = SnackBar( action: SnackBarAction( - label: translate('upload.dismiss'), + label: translate( + 'upload.dismiss'), textColor: blueColor, onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of( + context) + .hideCurrentSnackBar(); }, ), - content: Text(translate('upload.uploaded')), - duration: const Duration(seconds: 10), + content: Text(translate( + 'upload.uploaded')), + duration: + const Duration(seconds: 10), ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); }); } }, - icon: const Icon(Icons.upload_rounded, color: greenColor), + icon: const Icon(Icons.upload_rounded, + color: greenColor), label: Text( translate('upload.upload'), )), ])), model.errorMessage != null && model.errorMessage!.isNotEmpty ? (Padding( - padding: const EdgeInsets.only(top: 10.0, bottom: 10.0), + padding: + const EdgeInsets.only(top: 10.0, bottom: 10.0), child: CenteredErrorRow(model.errorMessage))) : Container(), Builder( @@ -152,28 +176,54 @@ class UploadView extends StatelessWidget { ) : model.paths != null ? Container( - padding: const EdgeInsets.only(bottom: 30.0), - height: MediaQuery.of(context).size.height * 0.50, + padding: const EdgeInsets.only(bottom: 20.0), + height: + MediaQuery.of(context).size.height * 0.50, child: ListView.separated( - itemCount: model.paths != null && model.paths!.isNotEmpty ? model.paths!.length : 1, - itemBuilder: (BuildContext context, int index) { - final bool isMultiPath = model.paths != null && model.paths!.isNotEmpty; + itemCount: model.paths != null && + model.paths!.isNotEmpty + ? model.paths!.length + : 1, + itemBuilder: + (BuildContext context, int index) { + final bool isMultiPath = + model.paths != null && + model.paths!.isNotEmpty; final String name = (isMultiPath - ? model.paths!.map((e) => e.name).toList()[index] + ? model.paths! + .map((e) => e.name) + .toList()[index] : model.fileName ?? '...'); + final size = model.paths!.isNotEmpty + ? model.paths! + .map((e) => e.size) + .toList()[index] + .toString() + : ''; final path = model.paths!.isNotEmpty - ? model.paths!.map((e) => e.path).toList()[index].toString() + ? model.paths! + .map((e) => e.path) + .toList()[index] + .toString() : ''; return Card( child: ListTile( + trailing: IconButton( + icon: const Icon(Icons.clear, + color: orangeColor), + onPressed: () { + model.deleteIntentFile(path); + }), title: Text( - name, + "$name (${FormatterUtil.formatBytes(int.parse(size), 2)})", ), subtitle: Text(path), )); }, - separatorBuilder: (BuildContext context, int index) => const Divider(), + separatorBuilder: + (BuildContext context, int index) => + const Divider(), ), ) : Container(), diff --git a/lib/ui/widgets/centered_error_row.dart b/lib/ui/widgets/centered_error_row.dart index b7fa563..c8d5be3 100644 --- a/lib/ui/widgets/centered_error_row.dart +++ b/lib/ui/widgets/centered_error_row.dart @@ -20,7 +20,10 @@ class CenteredErrorRow extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded(child: Center(child: Text(message!, style: const TextStyle(color: redColor)))), + Expanded( + child: Center( + child: Text(message!, + style: const TextStyle(color: redColor)))), ], ), (retryCallback != null diff --git a/lib/ui/widgets/login_header_apikey.dart b/lib/ui/widgets/login_header_apikey.dart index 90a90fe..56ed54f 100644 --- a/lib/ui/widgets/login_header_apikey.dart +++ b/lib/ui/widgets/login_header_apikey.dart @@ -11,13 +11,19 @@ class LoginApiKeyHeaders extends StatelessWidget { final String? validationMessage; const LoginApiKeyHeaders( - {super.key, required this.uriController, required this.apiKeyController, this.validationMessage}); + {super.key, + required this.uriController, + required this.apiKeyController, + this.validationMessage}); @override Widget build(BuildContext context) { return Column(children: [ - validationMessage != null ? Text(validationMessage!, style: const TextStyle(color: redColor)) : Container(), - LoginTextField(uriController, translate('login.url_placeholder'), const Icon(Icons.link), + validationMessage != null + ? Text(validationMessage!, style: const TextStyle(color: redColor)) + : Container(), + LoginTextField(uriController, translate('login.url_placeholder'), + const Icon(Icons.link), keyboardType: TextInputType.url), LoginTextField( apiKeyController, diff --git a/lib/ui/widgets/login_header_credentials.dart b/lib/ui/widgets/login_header_credentials.dart index 9657a66..d676008 100644 --- a/lib/ui/widgets/login_header_credentials.dart +++ b/lib/ui/widgets/login_header_credentials.dart @@ -21,12 +21,17 @@ class LoginCredentialsHeaders extends StatelessWidget { @override Widget build(BuildContext context) { return Column(children: [ - validationMessage != null ? Text(validationMessage!, style: const TextStyle(color: redColor)) : Container(), - LoginTextField(uriController, translate('login.url_placeholder'), const Icon(Icons.link), + validationMessage != null + ? Text(validationMessage!, style: const TextStyle(color: redColor)) + : Container(), + LoginTextField(uriController, translate('login.url_placeholder'), + const Icon(Icons.link), keyboardType: TextInputType.url), - LoginTextField(usernameController, translate('login.username_placeholder'), const Icon(Icons.person), + LoginTextField(usernameController, + translate('login.username_placeholder'), const Icon(Icons.person), keyboardType: TextInputType.name), - LoginTextField(passwordController, translate('login.password_placeholder'), const Icon(Icons.vpn_key), + LoginTextField(passwordController, + translate('login.password_placeholder'), const Icon(Icons.vpn_key), obscureText: true), ]); } diff --git a/lib/ui/widgets/login_text_field.dart b/lib/ui/widgets/login_text_field.dart index 56c0e85..1bf7ade 100644 --- a/lib/ui/widgets/login_text_field.dart +++ b/lib/ui/widgets/login_text_field.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import '../shared/app_colors.dart'; - class LoginTextField extends StatelessWidget { final TextEditingController controller; final String placeHolder; @@ -10,7 +8,9 @@ class LoginTextField extends StatelessWidget { final Widget prefixIcon; const LoginTextField(this.controller, this.placeHolder, this.prefixIcon, - {super.key, this.keyboardType = TextInputType.text, this.obscureText = false}); + {super.key, + this.keyboardType = TextInputType.text, + this.obscureText = false}); @override Widget build(BuildContext context) { @@ -19,7 +19,6 @@ class LoginTextField extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0), height: 50.0, alignment: Alignment.centerLeft, - decoration: BoxDecoration(color: whiteColor, borderRadius: BorderRadius.circular(10.0)), child: TextFormField( keyboardType: keyboardType, obscureText: obscureText, @@ -31,7 +30,8 @@ class LoginTextField extends StatelessWidget { prefixIcon: prefixIcon, hintText: placeHolder, contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)), + border: + OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)), ), controller: controller), ); diff --git a/lib/ui/widgets/my_appbar.dart b/lib/ui/widgets/my_appbar.dart index 9e802e4..67cd92b 100644 --- a/lib/ui/widgets/my_appbar.dart +++ b/lib/ui/widgets/my_appbar.dart @@ -6,10 +6,18 @@ class MyAppBar extends AppBar { static final List aboutEnabledWidgets = [AboutIconButton()]; static final List aboutDisabledWidgets = []; - MyAppBar({Key? key, required Widget title, List? actionWidgets, bool enableAbout = true}) - : super(key: key, title: Row(children: [title]), actions: _renderIconButtons(actionWidgets, enableAbout)); + MyAppBar( + {Key? key, + required Widget title, + List? actionWidgets, + bool enableAbout = true}) + : super( + key: key, + title: Row(children: [title]), + actions: _renderIconButtons(actionWidgets, enableAbout)); - static List _renderIconButtons(List? actionWidgets, bool aboutEnabled) { + static List _renderIconButtons( + List? actionWidgets, bool aboutEnabled) { actionWidgets ??= []; List widgets = [...actionWidgets]; diff --git a/pubspec.lock b/pubspec.lock index 7771c72..3d70bdf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -98,7 +98,7 @@ packages: name: built_value_generator url: "https://pub.dartlang.org" source: hosted - version: "8.4.2" + version: "8.4.3" characters: dependency: transitive description: @@ -217,7 +217,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "5.2.4" + version: "5.2.5" fixnum: dependency: transitive description: @@ -256,6 +256,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.7" + flutter_sharing_intent: + dependency: "direct main" + description: + name: flutter_sharing_intent + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -581,20 +588,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - receive_sharing_intent: - dependency: "direct main" - description: - name: receive_sharing_intent - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.5" share_plus: dependency: "direct main" description: @@ -615,7 +608,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.15" + version: "2.0.16" shared_preferences_android: dependency: transitive description: @@ -623,10 +616,10 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.14" - shared_preferences_ios: + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios + name: shared_preferences_foundation url: "https://pub.dartlang.org" source: hosted version: "2.1.1" @@ -637,13 +630,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: @@ -718,7 +704,7 @@ packages: name: stacked url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0+1" stacked_core: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fcd276a..892e0a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: A mobile client for FileBin. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.2+18 +version: 1.6.0+18 environment: sdk: '>=2.18.5 <3.0.0' @@ -24,31 +24,31 @@ dependencies: sdk: flutter flutter_translate: 4.0.3 provider: 6.0.5 - stacked: 3.0.1 + stacked: 3.1.0+1 get_it: 7.2.0 logger: 1.1.0 - shared_preferences: 2.0.15 + shared_preferences: 2.0.16 http: 0.13.5 validators: 3.0.0 flutter_linkify: 5.0.2 url_launcher: 6.1.7 expandable: 5.0.1 share_plus: 6.3.0 - file_picker: 5.2.4 + file_picker: 5.2.5 clipboard: 0.1.3 - receive_sharing_intent: 1.4.5 permission_handler: 10.2.0 package_info_plus: 3.0.2 json_annotation: 4.7.0 dynamic_color: 1.5.4 intl: 0.17.0 path: 1.8.2 + flutter_sharing_intent: 1.0.5 dev_dependencies: flutter_test: sdk: flutter build_runner: 2.3.3 - built_value_generator: 8.4.2 + built_value_generator: 8.4.3 json_serializable: 6.5.4 flutter_lints: 2.0.1