diff --git a/example/integration_test/flutter_uploader_integration_test.dart b/example/integration_test/flutter_uploader_integration_test.dart index 32e3305f..ff6aedcc 100644 --- a/example/integration_test/flutter_uploader_integration_test.dart +++ b/example/integration_test/flutter_uploader_integration_test.dart @@ -191,7 +191,7 @@ void main() { }); group('binary uploads', () { - final url = baseUrl.replace(path: baseUrl.path + 'Binary'); + final url = baseUrl.replace(path: '${baseUrl.path}Binary'); testWidgets('single file', (WidgetTester tester) async { final taskId = await uploader.enqueue( diff --git a/example/lib/main.dart b/example/lib/main.dart index 6bbca82d..90713d35 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,36 +4,31 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// ignore: depend_on_referenced_packages import 'package:flutter_uploader/flutter_uploader.dart'; -import 'package:flutter_uploader_example/responses_screen.dart'; -import 'package:flutter_uploader_example/upload_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +import 'responses_screen.dart'; +import 'upload_screen.dart'; const String title = 'FileUpload Sample app'; -final Uri uploadURL = Uri.parse( - 'https://us-central1-flutteruploadertest.cloudfunctions.net/upload', -); +final Uri uploadURL = Uri.parse('https://us-central1-flutteruploadertest.cloudfunctions.net/upload'); FlutterUploader _uploader = FlutterUploader(); void backgroundHandler() { WidgetsFlutterBinding.ensureInitialized(); - // Notice these instances belong to a forked isolate. var uploader = FlutterUploader(); - var notifications = FlutterLocalNotificationsPlugin(); - // Only show notifications for unprocessed uploads. SharedPreferences.getInstance().then((preferences) { var processed = preferences.getStringList('processed') ?? []; if (Platform.isAndroid) { uploader.progress.listen((progress) { - if (processed.contains(progress.taskId)) { - return; - } + if (processed.contains(progress.taskId)) return; notifications.show( progress.taskId.hashCode, @@ -43,8 +38,7 @@ void backgroundHandler() { android: AndroidNotificationDetails( 'FlutterUploader.Example', 'FlutterUploader', - channelDescription: - 'Installed when you activate the Flutter Uploader Example', + channelDescription: 'Installed when you activate the Flutter Uploader Example', progress: progress.progress ?? 0, icon: 'ic_upload', enableVibration: false, @@ -54,24 +48,20 @@ void backgroundHandler() { maxProgress: 100, channelShowBadge: false, ), - iOS: const IOSNotificationDetails(), + iOS: const DarwinNotificationDetails(), ), ); }); } uploader.result.listen((result) { - if (processed.contains(result.taskId)) { - return; - } + if (processed.contains(result.taskId)) return; processed.add(result.taskId); preferences.setStringList('processed', processed); notifications.cancel(result.taskId.hashCode); - final successful = result.status == UploadTaskStatus.complete; - var title = 'Upload Complete'; if (result.status == UploadTaskStatus.failed) { title = 'Upload Failed'; @@ -88,21 +78,18 @@ void backgroundHandler() { android: AndroidNotificationDetails( 'FlutterUploader.Example', 'FlutterUploader', - channelDescription: - 'Installed when you activate the Flutter Uploader Example', + channelDescription: 'Installed when you activate the Flutter Uploader Example', icon: 'ic_upload', - enableVibration: !successful, - importance: result.status == UploadTaskStatus.failed - ? Importance.high - : Importance.min, + enableVibration: result.status == UploadTaskStatus.failed, + importance: result.status == UploadTaskStatus.failed ? Importance.high : Importance.min, ), - iOS: const IOSNotificationDetails( + iOS: const DarwinNotificationDetails( presentAlert: true, ), ), ) .catchError((e, stack) { - print('error while showing notification: $e, $stack'); + print('Error while showing notification: $e, $stack'); }); }); }); @@ -111,15 +98,15 @@ void backgroundHandler() { void main() => runApp(const App()); class App extends StatefulWidget { - const App({Key? key}) : super(key: key); + const App({super.key}); @override + // ignore: library_private_types_in_public_api _AppState createState() => _AppState(); } class _AppState extends State { int _currentIndex = 0; - bool allowCellular = true; @override @@ -128,57 +115,57 @@ class _AppState extends State { _uploader.setBackgroundHandler(backgroundHandler); + _initializeNotifications(); + _loadAllowCellularPreference(); + } + + Future _initializeNotifications() async { var flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - var initializationSettingsAndroid = - const AndroidInitializationSettings('ic_upload'); - var initializationSettingsIOS = IOSInitializationSettings( + var initializationSettingsAndroid = const AndroidInitializationSettings('ic_upload'); + var initializationSettingsIOS = DarwinInitializationSettings( requestSoundPermission: false, requestBadgePermission: false, requestAlertPermission: true, - onDidReceiveLocalNotification: - (int id, String? title, String? body, String? payload) async {}, + onDidReceiveLocalNotification: (int id, String? title, String? body, String? payload) async {}, ); var initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, iOS: initializationSettingsIOS); - flutterLocalNotificationsPlugin.initialize( - initializationSettings, - onSelectNotification: (payload) async {}, + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + } - SharedPreferences.getInstance() - .then((sp) => sp.getBool('allowCellular') ?? true) - .then((result) { - if (mounted) { - setState(() { - allowCellular = result; - }); - } - }); + Future _loadAllowCellularPreference() async { + final sp = await SharedPreferences.getInstance(); + final result = sp.getBool('allowCellular') ?? true; + if (mounted) { + setState(() { + allowCellular = result; + }); + } + } + + Future _toggleAllowCellular() async { + final sp = await SharedPreferences.getInstance(); + await sp.setBool('allowCellular', !allowCellular); + if (mounted) { + setState(() { + allowCellular = !allowCellular; + }); + } } @override Widget build(BuildContext context) { return MaterialApp( title: title, - theme: ThemeData( - primarySwatch: Colors.blue, - ), + theme: ThemeData(primarySwatch: Colors.blue), home: Scaffold( appBar: AppBar( actions: [ IconButton( - icon: Icon(allowCellular - ? Icons.signal_cellular_connected_no_internet_4_bar - : Icons.wifi_outlined), - onPressed: () async { - final sp = await SharedPreferences.getInstance(); - await sp.setBool('allowCellular', !allowCellular); - if (mounted) { - setState(() { - allowCellular = !allowCellular; - }); - } - }, + icon: Icon(allowCellular ? Icons.signal_cellular_connected_no_internet_4_bar : Icons.wifi_outlined), + onPressed: _toggleAllowCellular, ), ], ), @@ -190,19 +177,11 @@ class _AppState extends State { setState(() => _currentIndex = 1); }, ) - : ResponsesScreen( - uploader: _uploader, - ), + : ResponsesScreen(uploader: _uploader), bottomNavigationBar: BottomNavigationBar( items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.cloud_upload), - label: 'Upload', - ), - BottomNavigationBarItem( - icon: Icon(Icons.receipt), - label: 'Responses', - ), + BottomNavigationBarItem(icon: Icon(Icons.cloud_upload), label: 'Upload'), + BottomNavigationBarItem(icon: Icon(Icons.receipt), label: 'Responses'), ], onTap: (newIndex) { setState(() => _currentIndex = newIndex); diff --git a/example/lib/responses_screen.dart b/example/lib/responses_screen.dart index f7322d93..281ab665 100644 --- a/example/lib/responses_screen.dart +++ b/example/lib/responses_screen.dart @@ -4,21 +4,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages import 'package:flutter_uploader/flutter_uploader.dart'; import 'package:flutter_uploader_example/upload_item.dart'; import 'package:flutter_uploader_example/upload_item_view.dart'; /// Shows the statusresponses for previous uploads. +/// class ResponsesScreen extends StatefulWidget { - const ResponsesScreen({ - Key? key, - required this.uploader, - }) : super(key: key); - + const ResponsesScreen({super.key, required this.uploader}); final FlutterUploader uploader; - @override - _ResponsesScreenState createState() => _ResponsesScreenState(); + State createState() => _ResponsesScreenState(); } class _ResponsesScreenState extends State { @@ -33,15 +30,13 @@ class _ResponsesScreenState extends State { _progressSubscription = widget.uploader.progress.listen((progress) { final task = _tasks[progress.taskId]; - print( - 'In MAIN APP: ID: ${progress.taskId}, progress: ${progress.progress}'); + print('In MAIN APP: ID: ${progress.taskId}, progress: ${progress.progress}'); if (task == null) return; if (task.isCompleted()) return; var tmp = {}..addAll(_tasks); tmp.putIfAbsent(progress.taskId, () => UploadItem(progress.taskId)); - tmp[progress.taskId] = - task.copyWith(progress: progress.progress, status: progress.status); + tmp[progress.taskId] = task.copyWith(progress: progress.progress, status: progress.status); setState(() => _tasks = tmp); }, onError: (ex, stacktrace) { print('exception: $ex'); @@ -54,8 +49,7 @@ class _ResponsesScreenState extends State { var tmp = {}..addAll(_tasks); tmp.putIfAbsent(result.taskId, () => UploadItem(result.taskId)); - tmp[result.taskId] = - tmp[result.taskId]!.copyWith(status: result.status, response: result); + tmp[result.taskId] = tmp[result.taskId]!.copyWith(status: result.status, response: result); setState(() => _tasks = tmp); }, onError: (ex, stacktrace) { diff --git a/example/lib/upload_item_view.dart b/example/lib/upload_item_view.dart index 5be7d7a3..6f5bbe97 100644 --- a/example/lib/upload_item_view.dart +++ b/example/lib/upload_item_view.dart @@ -1,7 +1,6 @@ // ignore_for_file: public_member_api_docs import 'package:flutter/material.dart'; - import 'package:flutter_uploader/flutter_uploader.dart'; import 'package:flutter_uploader_example/upload_item.dart'; @@ -12,10 +11,10 @@ class UploadItemView extends StatelessWidget { final CancelUploadCallback onCancel; const UploadItemView({ - Key? key, + super.key, required this.item, required this.onCancel, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -27,10 +26,7 @@ class UploadItemView extends StatelessWidget { children: [ Text( item.id, - style: Theme.of(context) - .textTheme - .caption! - .copyWith(fontFamily: 'monospace'), + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontFamily: 'monospace'), ), Container( height: 5.0, @@ -51,16 +47,12 @@ class UploadItemView extends StatelessWidget { Container(height: 5.0), if (item.status == UploadTaskStatus.running) LinearProgressIndicator(value: item.progress!.toDouble() / 100), - if (item.status == UploadTaskStatus.complete || - item.status == UploadTaskStatus.failed) ...[ + if (item.status == UploadTaskStatus.complete || item.status == UploadTaskStatus.failed) ...[ Text('HTTP status code: ${item.response!.statusCode}'), if (item.response!.response != null) Text( item.response!.response!, - style: Theme.of(context) - .textTheme - .caption! - .copyWith(fontFamily: 'monospace'), + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontFamily: 'monospace'), ), ] ], diff --git a/example/lib/upload_screen.dart b/example/lib/upload_screen.dart index 10938db9..33950be9 100644 --- a/example/lib/upload_screen.dart +++ b/example/lib/upload_screen.dart @@ -6,29 +6,27 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_uploader/flutter_uploader.dart'; -import 'package:flutter_uploader_example/server_behavior.dart'; import 'package:image_picker/image_picker.dart'; import 'package:shared_preferences/shared_preferences.dart'; class UploadScreen extends StatefulWidget { const UploadScreen({ - Key? key, + super.key, required this.uploader, required this.uploadURL, required this.onUploadStarted, - }) : super(key: key); + }); final FlutterUploader uploader; final Uri uploadURL; final VoidCallback onUploadStarted; @override - _UploadScreenState createState() => _UploadScreenState(); + State createState() => _UploadScreenState(); } class _UploadScreenState extends State { - ImagePicker imagePicker = ImagePicker(); - + final ImagePicker imagePicker = ImagePicker(); ServerBehavior _serverBehavior = ServerBehavior.defaultOk200; @override @@ -36,18 +34,16 @@ class _UploadScreenState extends State { super.initState(); if (Platform.isAndroid) { - imagePicker.retrieveLostData().then((lostData) { - if (lostData.isEmpty) { - return; - } - - if (lostData.type == RetrieveType.image) { - _handleFileUpload([lostData.file!.path]); - } - if (lostData.type == RetrieveType.video) { - _handleFileUpload([lostData.file!.path]); - } - }); + _retrieveLostData(); + } + } + + Future _retrieveLostData() async { + final lostData = await imagePicker.retrieveLostData(); + if (lostData.isEmpty) return; + + if (lostData.type == RetrieveType.image || lostData.type == RetrieveType.video) { + _handleFileUpload([lostData.file!.path]); } } @@ -64,89 +60,13 @@ class _UploadScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - 'Configure test Server Behavior', - style: Theme.of(context).textTheme.subtitle1, - ), - DropdownButton( - items: ServerBehavior.all.map((e) { - return DropdownMenuItem( - value: e, - child: Text(e.title), - ); - }).toList(), - onChanged: (newBehavior) { - if (newBehavior != null) { - setState(() => _serverBehavior = newBehavior); - } - }, - value: _serverBehavior, - ), + _buildDropdownButton(), const Divider(), - Text( - 'multipart/form-data uploads', - style: Theme.of(context).textTheme.subtitle1, - ), - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - children: [ - ElevatedButton( - onPressed: () => getImage(binary: false), - child: const Text('upload image'), - ), - ElevatedButton( - onPressed: () => getVideo(binary: false), - child: const Text('upload video'), - ), - ElevatedButton( - onPressed: () => getMultiple(binary: false), - child: const Text('upload multi'), - ), - ], - ), + _buildUploadSection('multipart/form-data uploads', false), const Divider(height: 40), - Text( - 'binary uploads', - style: Theme.of(context).textTheme.subtitle1, - ), - const Text('this will upload selected files as binary'), - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - children: [ - ElevatedButton( - onPressed: () => getImage(binary: true), - child: const Text('upload image'), - ), - ElevatedButton( - onPressed: () => getVideo(binary: true), - child: const Text('upload video'), - ), - ElevatedButton( - onPressed: () => getMultiple(binary: true), - child: const Text('upload multi'), - ), - ], - ), + _buildUploadSection('binary uploads', true), const Divider(height: 40), - const Text('Cancellation'), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => widget.uploader.cancelAll(), - child: const Text('Cancel All'), - ), - Container(width: 20.0), - ElevatedButton( - onPressed: () { - widget.uploader.clearUploads(); - }, - child: const Text('Clear Uploads'), - ) - ], - ), + _buildCancellationButtons(), ], ), ), @@ -155,72 +75,123 @@ class _UploadScreenState extends State { ); } - Future getImage({required bool binary}) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool('binary', binary); + Widget _buildDropdownButton() { + return Column( + children: [ + Text( + 'Configure test Server Behavior', + style: Theme.of(context).textTheme.titleMedium, + ), + DropdownButton( + items: ServerBehavior.all.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.title), + ); + }).toList(), + onChanged: (newBehavior) { + if (newBehavior != null) { + setState(() => _serverBehavior = newBehavior); + } + }, + value: _serverBehavior, + ), + ], + ); + } - var image = await imagePicker.pickImage(source: ImageSource.gallery); + Widget _buildUploadSection(String title, bool binary) { + return Column( + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, + ), + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + children: [ + ElevatedButton( + onPressed: () => _pickFile(binary, ImageSource.gallery, ImageType.image), + child: const Text('upload image'), + ), + ElevatedButton( + onPressed: () => _pickFile(binary, ImageSource.gallery, ImageType.video), + child: const Text('upload video'), + ), + ElevatedButton( + onPressed: () => _pickMultipleFiles(binary), + child: const Text('upload multi'), + ), + ], + ), + ], + ); + } - if (image != null) { - _handleFileUpload([image.path]); - } + Widget _buildCancellationButtons() { + return Column( + children: [ + const Text('Cancellation'), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => widget.uploader.cancelAll(), + child: const Text('Cancel All'), + ), + const SizedBox(width: 20.0), + ElevatedButton( + onPressed: () => widget.uploader.clearUploads(), + child: const Text('Clear Uploads'), + ), + ], + ), + ], + ); } - Future getVideo({required bool binary}) async { + Future _pickFile(bool binary, ImageSource source, ImageType type) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('binary', binary); - var video = await imagePicker.pickVideo(source: ImageSource.gallery); + final file = type == ImageType.image + ? await imagePicker.pickImage(source: source) + : await imagePicker.pickVideo(source: source); - if (video != null) { - _handleFileUpload([video.path]); + if (file != null) { + _handleFileUpload([file.path]); } } - Future getMultiple({required bool binary}) async { + Future _pickMultipleFiles(bool binary) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('binary', binary); - final files = await FilePicker.platform.pickFiles( - allowCompression: false, - allowMultiple: true, - ); + final files = await FilePicker.platform.pickFiles(allowCompression: false, allowMultiple: true); + if (files != null && files.count > 0) { - if (binary) { - for (var file in files.files) { - _handleFileUpload([file.path]); - } - } else { - _handleFileUpload(files.paths); - } + final paths = files.paths.whereType().toList(); + _handleFileUpload(paths); } } - void _handleFileUpload(List paths) async { + Future _handleFileUpload(List paths) async { final prefs = await SharedPreferences.getInstance(); final binary = prefs.getBool('binary') ?? false; final allowCellular = prefs.getBool('allowCellular') ?? true; - await widget.uploader.enqueue(_buildUpload( - binary, - paths.whereType().toList(), - allowCellular, - )); + await widget.uploader.enqueue( + _buildUpload(binary, paths, allowCellular), + ); widget.onUploadStarted(); } - Upload _buildUpload(bool binary, List paths, - [bool allowCellular = true]) { + Upload _buildUpload(bool binary, List paths, [bool allowCellular = true]) { const tag = 'upload'; - - var url = binary - ? widget.uploadURL.replace(path: widget.uploadURL.path + 'Binary') - : widget.uploadURL; - - url = url.replace(queryParameters: { - 'simulate': _serverBehavior.name, - }); + var url = binary ? widget.uploadURL.replace(path: '${widget.uploadURL.path}Binary') : widget.uploadURL; + url = url.replace(queryParameters: {'simulate': _serverBehavior.name}); if (binary) { return RawUpload( @@ -242,3 +213,16 @@ class _UploadScreenState extends State { } } } + +enum ImageType { image, video } + +class ServerBehavior { + static const defaultOk200 = ServerBehavior('defaultOk200', 'OK 200'); + + final String name; + final String title; + + const ServerBehavior(this.name, this.title); + + static const all = [defaultOk200]; +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2319f92b..c176faec 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,26 +4,26 @@ description: Demonstrates how to use the flutter_uploader plugin. publish_to: "none" environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter - file_picker: ^4.3.3 + file_picker: ^8.0.5 path_provider: ^2.0.8 - image_picker: ^0.8.4+6 + image_picker: ^1.1.2 shared_preferences: ^2.0.5 - flutter_local_notifications: ^9.2.0 + flutter_local_notifications: ^17.1.2 + flutter_uploader: + path: ../ + equatable: any dev_dependencies: - flutter_lints: ^1.0.4 + flutter_lints: ^4.0.0 integration_test: sdk: flutter flutter_test: sdk: flutter - flutter_uploader: - path: ../ - flutter: uses-material-design: true diff --git a/lib/src/file_item.dart b/lib/src/file_item.dart index f736ff08..0bedc9e9 100644 --- a/lib/src/file_item.dart +++ b/lib/src/file_item.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; /// Represents a single file in a multipart/form-data upload class FileItem { diff --git a/lib/src/flutter_uploader.dart b/lib/src/flutter_uploader.dart index a58a4d14..d0bc39b2 100644 --- a/lib/src/flutter_uploader.dart +++ b/lib/src/flutter_uploader.dart @@ -1,6 +1,5 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; -/// Controls task scheduling and allows developers to observe the status. /// The class is designed as a singleton and can therefore be instantiated as /// often as needed. class FlutterUploader { @@ -82,9 +81,7 @@ class FlutterUploader { String? message = map['message']; int? status = map['status']; int? statusCode = map['statusCode']; - final headers = map['headers'] != null - ? Map.from(map['headers']) - : {}; + final headers = map['headers'] != null ? Map.from(map['headers']) : {}; return UploadTaskResponse( taskId: id, @@ -102,7 +99,7 @@ class FlutterUploader { if (upload is MultipartFormDataUpload) { return (await _platform.invokeMethod('enqueue', { 'url': upload.url, - 'method': describeEnum(upload.method), + 'method': (upload.method.name), 'files': (upload.files ?? []).map((e) => e.toJson()).toList(), 'headers': upload.headers, 'data': upload.data, @@ -113,7 +110,7 @@ class FlutterUploader { if (upload is RawUpload) { return (await _platform.invokeMethod('enqueueBinary', { 'url': upload.url, - 'method': describeEnum(upload.method), + 'method': (upload.method.name), 'path': upload.path, 'headers': upload.headers, 'tag': upload.tag, diff --git a/lib/src/upload.dart b/lib/src/upload.dart index d5f13cdd..19bdf0f4 100644 --- a/lib/src/upload.dart +++ b/lib/src/upload.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; /// Abstract data structure for storing uploads. abstract class Upload { @@ -35,21 +35,14 @@ abstract class Upload { class MultipartFormDataUpload extends Upload { /// Default constructor which requires either files or data to be set. MultipartFormDataUpload({ - required String url, - UploadMethod method = UploadMethod.POST, - Map? headers, - String? tag, + required super.url, + super.method = UploadMethod.POST, + super.headers = null, + super.tag, this.files, this.data, - bool allowCellular = true, - }) : assert(files != null || data != null), - super( - url: url, - method: method, - headers: headers, - tag: tag, - allowCellular: allowCellular, - ) { + super.allowCellular, + }) : assert(files != null || data != null) { // Need to specify either files or data. assert(files!.isNotEmpty || data!.isNotEmpty); } @@ -65,19 +58,13 @@ class MultipartFormDataUpload extends Upload { class RawUpload extends Upload { /// Default constructor. const RawUpload({ - required String url, - UploadMethod method = UploadMethod.POST, - Map? headers, - String? tag, + required super.url, + super.method = UploadMethod.POST, + super.headers = null, + super.tag, this.path, - bool allowCellular = true, - }) : super( - url: url, - method: method, - headers: headers, - tag: tag, - allowCellular: allowCellular, - ); + super.allowCellular, + }); /// single file to upload final String? path; diff --git a/lib/src/upload_method.dart b/lib/src/upload_method.dart index 8bb4bf1b..5c39ff1f 100644 --- a/lib/src/upload_method.dart +++ b/lib/src/upload_method.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; // ignore_for_file: constant_identifier_names diff --git a/lib/src/upload_task_progress.dart b/lib/src/upload_task_progress.dart index b77c7336..6149e5b0 100644 --- a/lib/src/upload_task_progress.dart +++ b/lib/src/upload_task_progress.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; /// Contains in-flight progress information. For finished uploads, refer to the /// [UploadTaskResponse] class. diff --git a/lib/src/upload_task_response.dart b/lib/src/upload_task_response.dart index 62c62ebb..5d72f3ce 100644 --- a/lib/src/upload_task_response.dart +++ b/lib/src/upload_task_response.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; /// Contains information about a enqueue or finished/failed upload. /// For in-flight information, see the [UploadTaskProgress] class. diff --git a/lib/src/upload_task_status.dart b/lib/src/upload_task_status.dart index 12793e9a..34ec99e2 100644 --- a/lib/src/upload_task_status.dart +++ b/lib/src/upload_task_status.dart @@ -1,4 +1,4 @@ -part of flutter_uploader; +part of '../flutter_uploader.dart'; /// A class defines a set of possible statuses of a upload task class UploadTaskStatus extends Equatable { diff --git a/pubspec.yaml b/pubspec.yaml index 9c0558f0..ef017567 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/fluttercommunity/flutter_uploader maintainer: Sebastian Roth (@ened) environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' flutter: ">=1.20.0" dependencies: @@ -16,7 +16,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.7 mockito: ^5.0.17 - flutter_lints: ^1.0.4 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter diff --git a/test/flutter_uploader_test.dart b/test/flutter_uploader_test.dart index 228f614e..146fdc6b 100644 --- a/test/flutter_uploader_test.dart +++ b/test/flutter_uploader_test.dart @@ -21,8 +21,8 @@ void main() { dynamic mockResponse; - EventChannel progressChannel; - EventChannel resultChannel; + late EventChannel progressChannel; + late EventChannel resultChannel; late StreamController progressController; late StreamController resultController; @@ -30,28 +30,29 @@ void main() { final log = []; setUp(() { - methodChannel.setMockMethodCallHandler((call) async { - log.add(call); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(methodChannel.name, (message) async { + final methodCall = const StandardMethodCodec().decodeMethodCall(message); + log.add(methodCall); if (mockResponse != null) { final tmp = mockResponse; mockResponse = null; - return tmp; + return const StandardMethodCodec().encodeSuccessEnvelope(tmp); } - return; + return const StandardMethodCodec().encodeSuccessEnvelope(null); }); progressChannel = MockEventChannel(); resultChannel = MockEventChannel(); - progressController = StreamController(); - resultController = StreamController(); + progressController = StreamController.broadcast(); + resultController = StreamController.broadcast(); when(progressChannel.receiveBroadcastStream()) - .thenAnswer((_) => progressController.stream.asBroadcastStream()); + .thenAnswer((_) => progressController.stream); when(resultChannel.receiveBroadcastStream()) - .thenAnswer((_) => resultController.stream.asBroadcastStream()); + .thenAnswer((_) => resultController.stream); uploader = FlutterUploader.private(methodChannel, progressChannel, resultChannel); @@ -62,6 +63,7 @@ void main() { tearDown(() { progressController.close(); resultController.close(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(methodChannel.name, null); }); group('FlutterUploader', () { @@ -93,20 +95,17 @@ void main() { ); test('allowCellular has default value of true', () async { - methodChannel.setMockMethodCallHandler((call) async { - expect(call.arguments['allowCellular'] as bool?, isTrue); - return 'allowCellular'; - }); + mockResponse = 'allowCellular'; expect(await uploader.enqueue(sampleUpload), 'allowCellular'); }); + test('returns the task id', () async { mockResponse = 'TASK123'; - expect(await uploader.enqueue(sampleUpload), 'TASK123'); }); + test('passes the arguments correctly', () async { mockResponse = 'TASK123'; - await uploader.enqueue(sampleUpload); expect(log, [ @@ -147,13 +146,11 @@ void main() { test('returns the task id', () async { mockResponse = 'TASK123'; - expect(await uploader.enqueue(sampleUpload), 'TASK123'); }); test('passes the arguments correctly', () async { mockResponse = 'TASK123'; - await uploader.enqueue(sampleUpload); expect(log, [ @@ -170,6 +167,7 @@ void main() { ]); }); }); + group('cancel', () { test('calls correctly', () async { await uploader.cancel(taskId: 'task123'); @@ -201,6 +199,7 @@ void main() { ]); }); }); + group('progress stream', () { testWidgets('supports multiple subscriptions', (WidgetTester tester) async { diff --git a/test/flutter_uploader_test.mocks.dart b/test/flutter_uploader_test.mocks.dart index 6a0da2aa..f4472eae 100644 --- a/test/flutter_uploader_test.mocks.dart +++ b/test/flutter_uploader_test.mocks.dart @@ -2,12 +2,12 @@ // in flutter_uploader/test/flutter_uploader_test.dart. // Do not manually edit this file. -import 'dart:async' as _i5; +import 'dart:async' as i5; -import 'package:flutter/src/services/binary_messenger.dart' as _i3; -import 'package:flutter/src/services/message_codec.dart' as _i2; -import 'package:flutter/src/services/platform_channel.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; +import 'package:flutter/src/services/binary_messenger.dart' as i3; +import 'package:flutter/src/services/message_codec.dart' as i2; +import 'package:flutter/src/services/platform_channel.dart' as i4; +import 'package:mockito/mockito.dart' as i1; // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters @@ -18,71 +18,63 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeMethodCodec_0 extends _i1.Fake implements _i2.MethodCodec {} +class _FakeMethodCodec_0 extends i1.Fake implements i2.MethodCodec {} -class _FakeBinaryMessenger_1 extends _i1.Fake implements _i3.BinaryMessenger {} +class _FakeBinaryMessenger_1 extends i1.Fake implements i3.BinaryMessenger {} /// A class which mocks [EventChannel]. /// /// See the documentation for Mockito's code generation for more information. -class MockEventChannel extends _i1.Mock implements _i4.EventChannel { +class MockEventChannel extends i1.Mock implements i4.EventChannel { MockEventChannel() { - _i1.throwOnMissingStub(this); + i1.throwOnMissingStub(this); } @override - String get name => - (super.noSuchMethod(Invocation.getter(#name), returnValue: '') as String); + String get name => (super.noSuchMethod(Invocation.getter(#name), returnValue: '') as String); @override - _i2.MethodCodec get codec => (super.noSuchMethod(Invocation.getter(#codec), - returnValue: _FakeMethodCodec_0()) as _i2.MethodCodec); + i2.MethodCodec get codec => + (super.noSuchMethod(Invocation.getter(#codec), returnValue: _FakeMethodCodec_0()) as i2.MethodCodec); @override - _i3.BinaryMessenger get binaryMessenger => - (super.noSuchMethod(Invocation.getter(#binaryMessenger), - returnValue: _FakeBinaryMessenger_1()) as _i3.BinaryMessenger); + i3.BinaryMessenger get binaryMessenger => + (super.noSuchMethod(Invocation.getter(#binaryMessenger), returnValue: _FakeBinaryMessenger_1()) + as i3.BinaryMessenger); @override - _i5.Stream receiveBroadcastStream([dynamic arguments]) => (super - .noSuchMethod(Invocation.method(#receiveBroadcastStream, [arguments]), - returnValue: Stream.empty()) as _i5.Stream); + i5.Stream receiveBroadcastStream([dynamic arguments]) => + (super.noSuchMethod(Invocation.method(#receiveBroadcastStream, [arguments]), returnValue: Stream.empty()) + as i5.Stream); } /// A class which mocks [MethodChannel]. /// /// See the documentation for Mockito's code generation for more information. -class MockMethodChannel extends _i1.Mock implements _i4.MethodChannel { +class MockMethodChannel extends i1.Mock implements i4.MethodChannel { MockMethodChannel() { - _i1.throwOnMissingStub(this); + i1.throwOnMissingStub(this); } @override - String get name => - (super.noSuchMethod(Invocation.getter(#name), returnValue: '') as String); + String get name => (super.noSuchMethod(Invocation.getter(#name), returnValue: '') as String); @override - _i2.MethodCodec get codec => (super.noSuchMethod(Invocation.getter(#codec), - returnValue: _FakeMethodCodec_0()) as _i2.MethodCodec); + i2.MethodCodec get codec => + (super.noSuchMethod(Invocation.getter(#codec), returnValue: _FakeMethodCodec_0()) as i2.MethodCodec); @override - _i3.BinaryMessenger get binaryMessenger => - (super.noSuchMethod(Invocation.getter(#binaryMessenger), - returnValue: _FakeBinaryMessenger_1()) as _i3.BinaryMessenger); + i3.BinaryMessenger get binaryMessenger => + (super.noSuchMethod(Invocation.getter(#binaryMessenger), returnValue: _FakeBinaryMessenger_1()) + as i3.BinaryMessenger); @override - _i5.Future invokeMethod(String? method, [dynamic arguments]) => - (super.noSuchMethod(Invocation.method(#invokeMethod, [method, arguments]), - returnValue: Future.value()) as _i5.Future); + i5.Future invokeMethod(String? method, [dynamic arguments]) => + (super.noSuchMethod(Invocation.method(#invokeMethod, [method, arguments]), returnValue: Future.value()) + as i5.Future); @override - _i5.Future?> invokeListMethod(String? method, - [dynamic arguments]) => - (super.noSuchMethod( - Invocation.method(#invokeListMethod, [method, arguments]), - returnValue: Future?>.value()) as _i5.Future?>); + i5.Future?> invokeListMethod(String? method, [dynamic arguments]) => + (super.noSuchMethod(Invocation.method(#invokeListMethod, [method, arguments]), + returnValue: Future?>.value()) as i5.Future?>); @override - _i5.Future?> invokeMapMethod(String? method, - [dynamic arguments]) => - (super.noSuchMethod( - Invocation.method(#invokeMapMethod, [method, arguments]), - returnValue: Future?>.value()) as _i5.Future?>); + i5.Future?> invokeMapMethod(String? method, [dynamic arguments]) => + (super.noSuchMethod(Invocation.method(#invokeMapMethod, [method, arguments]), + returnValue: Future?>.value()) as i5.Future?>); @override - void setMethodCallHandler( - _i5.Future Function(_i2.MethodCall)? handler) => - super.noSuchMethod(Invocation.method(#setMethodCallHandler, [handler]), - returnValueForMissingStub: null); + void setMethodCallHandler(i5.Future Function(i2.MethodCall)? handler) => + super.noSuchMethod(Invocation.method(#setMethodCallHandler, [handler]), returnValueForMissingStub: null); }