From a5dab51765042d44d6bf02c2c22c5593d1b6babc Mon Sep 17 00:00:00 2001 From: Varakh Date: Mon, 16 Jan 2023 01:43:37 +0100 Subject: [PATCH] 10: 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 --- CHANGELOG.md | 4 + android/app/src/main/AndroidManifest.xml | 40 -------- ios/Runner/Info.plist | 2 +- lib/core/viewmodels/base_model.dart | 1 + lib/core/viewmodels/upload_model.dart | 122 ++++++++++++----------- lib/ui/views/upload_view.dart | 110 ++++++++++++++------ lib/ui/widgets/login_text_field.dart | 3 - pubspec.lock | 14 +-- pubspec.yaml | 2 +- 9 files changed, 158 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d93716a..482baa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG ## 1.5.2+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/core/viewmodels/base_model.dart b/lib/core/viewmodels/base_model.dart index 9fc7570..953fd51 100644 --- a/lib/core/viewmodels/base_model.dart +++ b/lib/core/viewmodels/base_model.dart @@ -80,6 +80,7 @@ class BaseModel extends ChangeNotifier { @override void dispose() { + _logger.d("Calling dispose"); super.dispose(); _isDisposed = true; } 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/ui/views/upload_view.dart b/lib/ui/views/upload_view.dart index 21048b6..4815fa2 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/login_text_field.dart b/lib/ui/widgets/login_text_field.dart index 56c0e85..6d04cf7 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; @@ -19,7 +17,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, diff --git a/pubspec.lock b/pubspec.lock index 7771c72..a80f2af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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 @@ -588,13 +595,6 @@ packages: 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: diff --git a/pubspec.yaml b/pubspec.yaml index fcd276a..e996478 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,13 +36,13 @@ dependencies: share_plus: 6.3.0 file_picker: 5.2.4 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: