From 435cd337198895d5fca1319dceb721819886ba18 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 3 Aug 2023 14:48:44 +0200 Subject: [PATCH] chore: prepare v3.1.0 --- .gitignore | 2 + CHANGELOG.md | 6 ++ analysis_options.yaml | 64 ++---------------- .../big_video_upload_view.dart | 2 +- .../multi_video_upload.dart | 2 +- example/pubspec.yaml | 4 +- lib/image_picker_web.dart | 67 ++++++++++--------- lib/src/extensions/file_extensions.dart | 13 +++- .../Types.dart => models/media_info.dart} | 37 +++++++--- lib/src/web_image_picker.dart | 54 ++++++++++----- pubspec.yaml | 6 +- test/image_picker_web_test.dart | 18 ----- 12 files changed, 129 insertions(+), 146 deletions(-) rename lib/src/{Models/Types.dart => models/media_info.dart} (54%) delete mode 100644 test/image_picker_web_test.dart diff --git a/.gitignore b/.gitignore index 3069f82..55e16cd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ example/ios/Flutter/ example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java example/android/local.properties + +custom_lint.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f5d2ec..1f02219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.1.0 + +* Updated Dart SDK constraint to `>=3.0.0 <4.0.0` +* Updated linting rules +* Changed license to MIT + ## 3.0.0+1 * Fixed README.md diff --git a/analysis_options.yaml b/analysis_options.yaml index 2c1167e..6c52c99 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,61 +1,5 @@ -# Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. -# -# Google internally enforced rules. See README.md for more information, -# including a list of lints that are intentionally _not_ enforced. +include: package:fd_lints/dart.yaml -linter: - rules: - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_relative_lib_imports - - avoid_return_types_on_setters - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - await_only_futures - - camel_case_extensions - - curly_braces_in_flow_control_structures - - empty_catches - - empty_constructor_bodies - - library_names - - library_prefixes - - no_duplicate_case_values - - null_closures - - omit_local_variable_types - - prefer_adjacent_string_concatenation - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_contains - - prefer_equal_for_default_values - - prefer_final_fields - - prefer_for_elements_to_map_fromIterable - - prefer_generic_function_type_aliases - - prefer_if_null_operators - - prefer_inlined_adds - - prefer_is_empty - - prefer_is_not_empty - - prefer_iterable_whereType - - prefer_single_quotes - - prefer_spread_collections - - recursive_getters - - slash_for_doc_comments - - sort_child_properties_last - - type_init_formals - - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_new - - unnecessary_null_in_if_null_operators - - unnecessary_this - - unrelated_type_equality_checks - - unsafe_html - - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - - use_rethrow_when_possible - - valid_regexps \ No newline at end of file +analyzer: + plugins: + - custom_lint diff --git a/example/lib/big_video_upload/big_video_upload_view.dart b/example/lib/big_video_upload/big_video_upload_view.dart index 48ce637..37040b8 100644 --- a/example/lib/big_video_upload/big_video_upload_view.dart +++ b/example/lib/big_video_upload/big_video_upload_view.dart @@ -18,7 +18,7 @@ class _BigVideoUploadViewState extends State { Future _createVideo(Uint8List bytes) async { final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); - _controller = VideoPlayerController.network(url); + _controller = VideoPlayerController.networkUrl(Uri.parse(url)); await _controller?.initialize(); setState(() {}); } diff --git a/example/lib/multi_video_upload/multi_video_upload.dart b/example/lib/multi_video_upload/multi_video_upload.dart index d4cc80c..81e9737 100644 --- a/example/lib/multi_video_upload/multi_video_upload.dart +++ b/example/lib/multi_video_upload/multi_video_upload.dart @@ -19,7 +19,7 @@ class _MultiVideoUploadViewState extends State { for (final bytes in bytesList) { final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); - final controller = VideoPlayerController.network(url); + final controller = VideoPlayerController.networkUrl(Uri.parse(url)); await controller.initialize(); _controllers.add(controller); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index cf33c43..fe87bf9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,12 +3,12 @@ description: Demonstrates how to use the image_picker_web plugin. publish_to: "none" environment: - sdk: ">=2.18.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter - video_player: ^2.4.7 + video_player: ^2.7.0 dev_dependencies: flutter_test: diff --git a/lib/image_picker_web.dart b/lib/image_picker_web.dart index 4e5b39a..5b618c2 100644 --- a/lib/image_picker_web.dart +++ b/lib/image_picker_web.dart @@ -7,17 +7,20 @@ import 'dart:html' as html; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:image_picker_web/src/extensions/file_extensions.dart' + show FileModifier; +import 'package:image_picker_web/src/models/media_info.dart'; +import 'package:image_picker_web/src/web_image_picker.dart'; -import 'src/Models/Types.dart'; -import 'src/extensions/file_extensions.dart' show FileModifier; -import 'src/web_image_picker.dart'; - -export 'src/Models/Types.dart'; +export 'src/models/media_info.dart'; class ImagePickerWeb { static void registerWith(Registrar registrar) { final channel = MethodChannel( - 'image_picker_web', const StandardMethodCodec(), registrar); + 'image_picker_web', + const StandardMethodCodec(), + registrar, + ); final instance = WebImagePicker(); channel.setMethodCallHandler((call) async { switch (call.method) { @@ -35,22 +38,21 @@ class ImagePickerWeb { static Future _pickFile(String type) async { final completer = Completer?>(); - final input = html.FileUploadInputElement() as html.InputElement; - input.accept = '$type/*'; + final input = html.FileUploadInputElement()..accept = '$type/*'; - var changeEventTriggered = false; + bool changeEventTriggered = false; void changeEventListener(html.Event e) { if (changeEventTriggered) return; changeEventTriggered = true; - final files = input.files!; + final files = input.files ?? []; final resultFuture = files.map>((file) async { - final reader = html.FileReader(); - reader.readAsDataUrl(file); + final reader = html.FileReader()..readAsDataUrl(file); reader.onError.listen(completer.completeError); return file; }); - Future.wait(resultFuture).then((results) => completer.complete(results)); + + Future.wait(resultFuture).then(completer.complete); } // Cancel event management inspired by: @@ -61,7 +63,7 @@ class ImagePickerWeb { // This listener is called before the input changed event, // and the `uploadInput.files` value is still null // Wait for results from js to dart - Future.delayed(Duration(milliseconds: 500)).then((value) { + Future.delayed(const Duration(milliseconds: 500)).whenComplete(() { if (!changeEventTriggered) { changeEventTriggered = true; completer.complete(null); @@ -78,7 +80,7 @@ class ImagePickerWeb { input.click(); // Need to append on mobile Safari. - html.document.body!.append(input); + html.document.body?.append(input); final results = await completer.future; if (results == null || results.isEmpty) return null; @@ -88,24 +90,22 @@ class ImagePickerWeb { /// source: https://stackoverflow.com/a/59420655/9942346 Future?> _pickMultiFiles(String type) async { final completer = Completer?>(); - final input = html.FileUploadInputElement(); - input.multiple = true; - input.accept = '$type/*'; + final input = html.FileUploadInputElement() + ..multiple = true + ..accept = '$type/*'; var changeEventTriggered = false; void changeEventListener(html.Event e) { if (changeEventTriggered) return; changeEventTriggered = true; - final files = input.files!; + final files = input.files ?? []; final resultsFutures = files.map>((file) async { - final reader = html.FileReader(); - reader.readAsDataUrl(file); + final reader = html.FileReader()..readAsDataUrl(file); reader.onError.listen(completer.completeError); return file; }); - Future.wait(resultsFutures) - .then((results) => completer.complete(results)); + Future.wait(resultsFutures).then(completer.complete); } // Cancel event management inspired by: @@ -116,7 +116,7 @@ class ImagePickerWeb { // This listener is called before the input changed event, // and the `uploadInput.files` value is still null // Wait for results from js to dart - Future.delayed(Duration(milliseconds: 500)).then((value) { + Future.delayed(const Duration(milliseconds: 500)).whenComplete(() { if (!changeEventTriggered) { changeEventTriggered = true; completer.complete(null); @@ -133,7 +133,7 @@ class ImagePickerWeb { input.click(); // Need to append on mobile Safari. - html.document.body!.append(input); + html.document.body?.append(input); final results = await completer.future; if (results == null || results.isEmpty) return null; return results; @@ -166,7 +166,7 @@ class ImagePickerWeb { /// Return an object [MediaInfo] containing image's informations. static Future get getImageInfo async { final data = - await (_methodChannel.invokeMapMethod('pickImage')); + await _methodChannel.invokeMapMethod('pickImage'); if (data == null) return null; return MediaInfo.fromJson(data); } @@ -176,7 +176,7 @@ class ImagePickerWeb { static Future?> getMultiImagesAsBytes() async { final images = await ImagePickerWeb()._pickMultiFiles('image'); if (images == null) return null; - var files = []; + final files = []; for (final img in images) { files.add(await img.asBytes()); } @@ -188,12 +188,12 @@ class ImagePickerWeb { static Future?> getMultiImagesAsWidget() async { final images = await ImagePickerWeb()._pickMultiFiles('image'); if (images == null) return null; - var files = []; + final files = []; for (final img in images) { files.add(await img.asBytes()); } if (files.isEmpty) return null; - return files.map((e) => Image.memory(e)).toList(); + return files.map(Image.memory).toList(); } /// Picker that allows multi-image selection and return a [html.File] list of @@ -205,10 +205,11 @@ class ImagePickerWeb { /// Picker that close after selecting 1 video and return a [Uint8List] of the /// selected video. static Future getVideoAsBytes() async { - final data = + final dataMap = await _methodChannel.invokeMapMethod('pickVideo'); - if (data == null) return null; - final imageData = base64.decode(data['data']); + final data = dataMap?['data']; + if (data == null || data is! String) return null; + final imageData = base64.decode(data); return imageData; } @@ -233,7 +234,7 @@ class ImagePickerWeb { static Future?> getMultiVideosAsBytes() async { final videos = await ImagePickerWeb()._pickMultiFiles('video'); if (videos == null) return null; - var files = []; + final files = []; for (final video in videos) { files.add(await video.asBytes()); } diff --git a/lib/src/extensions/file_extensions.dart b/lib/src/extensions/file_extensions.dart index b241374..b5ad422 100644 --- a/lib/src/extensions/file_extensions.dart +++ b/lib/src/extensions/file_extensions.dart @@ -3,12 +3,23 @@ import 'dart:html' as html; import 'dart:typed_data'; +typedef _ByteResult = FutureOr>; + extension FileModifier on html.File { Future asBytes() async { final bytesFile = Completer>(); final reader = html.FileReader(); reader.onLoad.listen( - (event) => bytesFile.complete(reader.result as FutureOr>?)); + (_) { + final result = reader.result; + if (result is! _ByteResult?) { + bytesFile.completeError('Result is not a byte result'); + return; + } + + bytesFile.complete(result); + }, + ); reader.readAsArrayBuffer(this); return Uint8List.fromList(await bytesFile.future); } diff --git a/lib/src/Models/Types.dart b/lib/src/models/media_info.dart similarity index 54% rename from lib/src/Models/Types.dart rename to lib/src/models/media_info.dart index d42f29d..db1f08e 100644 --- a/lib/src/Models/Types.dart +++ b/lib/src/models/media_info.dart @@ -4,6 +4,33 @@ import 'dart:typed_data'; /// Class used to return informations retrieved from an image or video. class MediaInfo { + MediaInfo({ + this.fileName, + this.base64, + this.base64WithScheme, + this.data, + }); + + /// Factory constructor to generate [MediaInfo] from [Map]. + factory MediaInfo.fromJson(Map json) { + switch (json) { + case { + 'name': final String? name, + 'base64': final String? base64, + 'data_scheme': final String? dataScheme, + 'data': final String data, + }: + return MediaInfo( + fileName: name, + base64: base64, + base64WithScheme: dataScheme, + data: base64Decode(data), + ); + default: + throw const FormatException('Invalid media info format'); + } + } + /// Name of the file. final String? fileName; @@ -16,16 +43,6 @@ class MediaInfo { /// File's bytes data. final Uint8List? data; - MediaInfo({this.fileName, this.base64, this.base64WithScheme, this.data}); - - /// Factory constructor to generate [MediaInfo] from [Map]. - factory MediaInfo.fromJson(Map json) => MediaInfo( - fileName: json['name'], - base64: json['data'], - base64WithScheme: json['data_scheme'], - data: base64Decode(json['data']), - ); - /// Convert [MediaInfo] to [Map] format Map toJson() => { 'name': fileName, diff --git a/lib/src/web_image_picker.dart b/lib/src/web_image_picker.dart index 253474c..33af41d 100644 --- a/lib/src/web_image_picker.dart +++ b/lib/src/web_image_picker.dart @@ -4,41 +4,59 @@ import 'dart:html' as html; class WebImagePicker { Future?> pickImage() async { final data = {}; - final input = html.FileUploadInputElement(); - input.accept = 'image/*'; - input.click(); - html.document.body!.append(input); + final input = html.FileUploadInputElement() + ..accept = 'image/*' + ..click(); + + html.document.body?.append(input); + await input.onChange.first; - if (input.files!.isEmpty) return null; - final reader = html.FileReader(); - reader.readAsDataUrl(input.files![0]); + + final files = input.files; + if (files == null || files.isEmpty) return null; + + final reader = html.FileReader()..readAsDataUrl(files[0]); await reader.onLoad.first; - final encoded = reader.result as String; + + final encoded = reader.result; + if (encoded is! String) return null; + final stripped = - encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), ''); + encoded.replaceFirst(RegExp('data:image/[^;]+;base64,'), ''); final imageName = input.files?.first.name; + data.addAll({'name': imageName, 'data': stripped, 'data_scheme': encoded}); input.remove(); + return data; } Future?> pickVideo() async { final data = {}; - final input = html.FileUploadInputElement(); - input.accept = 'video/*'; - input.click(); - html.document.body!.append(input); + final input = html.FileUploadInputElement() + ..accept = 'video/*' + ..click(); + + html.document.body?.append(input); + await input.onChange.first; - if (input.files!.isEmpty) return null; - final reader = html.FileReader(); - reader.readAsDataUrl(input.files![0]); + + final files = input.files; + if (files == null || files.isEmpty) return null; + + final reader = html.FileReader()..readAsDataUrl(files[0]); await reader.onLoad.first; - final encoded = reader.result as String; + + final encoded = reader.result; + if (encoded is! String) return null; + final stripped = - encoded.replaceFirst(RegExp(r'data:video/[^;]+;base64,'), ''); + encoded.replaceFirst(RegExp('data:video/[^;]+;base64,'), ''); final videoName = input.files?.first.name; + data.addAll({'name': videoName, 'data': stripped, 'data_scheme': encoded}); input.remove(); + return data; } } diff --git a/pubspec.yaml b/pubspec.yaml index e31a4a8..6cd67cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: image_picker_web description: Flutter Web Plugin to pick Images (as Widget, File or Uint8List) and Videos (as File or Uint8List) -version: 3.0.0+1 +version: 3.1.0 repository: https://github.com/Ahmadre/image_picker_web environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=1.20.0" dependencies: @@ -15,6 +15,8 @@ dependencies: sdk: flutter dev_dependencies: + custom_lint: ^0.5.0 + fd_lints: ^2.1.0 flutter_test: sdk: flutter diff --git a/test/image_picker_web_test.dart b/test/image_picker_web_test.dart deleted file mode 100644 index 9eef64e..0000000 --- a/test/image_picker_web_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - const channel = MethodChannel('image_picker_web'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); -}