diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index f8527d6bc1..52268b8d45 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.5.1+5 +* Fixes the behavior of no type groups to allow selecting any file. * Migrates `styleFrom` usage in examples off of deprecated `primary` and `onPrimary` parameters. ## 0.5.1+4 diff --git a/packages/file_selector/file_selector_ios/example/lib/home_page.dart b/packages/file_selector/file_selector_ios/example/lib/home_page.dart index 29837b4eb8..22d55f0762 100644 --- a/packages/file_selector/file_selector_ios/example/lib/home_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/home_page.dart @@ -35,6 +35,12 @@ class HomePage extends StatelessWidget { onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Open any file'), + onPressed: () => Navigator.pushNamed(context, '/open/any'), + ), + const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), diff --git a/packages/file_selector/file_selector_ios/example/lib/main.dart b/packages/file_selector/file_selector_ios/example/lib/main.dart index 00641de167..1f3508a149 100644 --- a/packages/file_selector/file_selector_ios/example/lib/main.dart +++ b/packages/file_selector/file_selector_ios/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'home_page.dart'; +import 'open_any_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; import 'open_text_page.dart'; @@ -32,6 +33,7 @@ class MyApp extends StatelessWidget { '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), + '/open/any': (BuildContext context) => const OpenAnyPage(), }, ); } diff --git a/packages/file_selector/file_selector_ios/example/lib/open_any_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_any_page.dart new file mode 100644 index 0000000000..7b67217918 --- /dev/null +++ b/packages/file_selector/file_selector_ios/example/lib/open_any_page.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:flutter/material.dart'; + +/// Screen that allows the user to select any file file using `openFile`, then +/// displays its path in a dialog. +class OpenAnyPage extends StatelessWidget { + /// Default Constructor + const OpenAnyPage({super.key}); + + Future _openTextFile(BuildContext context) async { + final XFile? file = await FileSelectorPlatform.instance.openFile(); + if (file == null) { + // Operation was canceled by the user. + return; + } + + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => PathDisplay(file.name, file.path), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Open a file'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + child: const Text('Press to open a file of any type'), + onPressed: () => _openTextFile(context), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class PathDisplay extends StatelessWidget { + /// Default Constructor. + const PathDisplay(this.fileName, this.filePath, {super.key}); + + /// The name of the selected file. + final String fileName; + + /// The contents of the text file. + final String filePath; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(fileName), + content: Text(filePath), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart b/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart index 22349b6232..3c2e4a2b8a 100644 --- a/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart +++ b/packages/file_selector/file_selector_ios/lib/file_selector_ios.dart @@ -44,14 +44,18 @@ class FileSelectorIOS extends FileSelectorPlatform { // Converts the type group list into a list of all allowed UTIs, since // iOS doesn't support filter groups. List _allowedUtiListFromTypeGroups(List? typeGroups) { + // iOS requires a list of allowed types, so allowing all is expressed via + // a root type rather than an empty list. + const List allowAny = ['public.data']; + if (typeGroups == null || typeGroups.isEmpty) { - return []; + return allowAny; } final List allowedUTIs = []; for (final XTypeGroup typeGroup in typeGroups) { // If any group allows everything, no filtering should be done. if (typeGroup.allowsAny) { - return []; + return allowAny; } if (typeGroup.uniformTypeIdentifiers?.isEmpty ?? true) { throw ArgumentError('The provided type group $typeGroup should either ' diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index dd3f7dfca5..9470e75678 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.1+4 +version: 0.5.1+5 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart index 6d3c3c684c..9c065f3bd1 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart @@ -72,13 +72,25 @@ void main() { throwsArgumentError); }); - test('allows a wildcard group', () async { + test('correctly handles no type groups', () async { + await expectLater(plugin.openFile(), completes); + final VerificationResult result = verify(mockApi.openFile(captureAny)); + final FileSelectorConfig config = + result.captured[0] as FileSelectorConfig; + expect(listEquals(config.utis, ['public.data']), isTrue); + }); + + test('correctly handles a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), completes); + final VerificationResult result = verify(mockApi.openFile(captureAny)); + final FileSelectorConfig config = + result.captured[0] as FileSelectorConfig; + expect(listEquals(config.utis, ['public.data']), isTrue); }); }); @@ -113,6 +125,7 @@ void main() { isTrue); expect(config.allowMultiSelection, isTrue); }); + test('throws for a type group that does not support iOS', () async { const XTypeGroup group = XTypeGroup( label: 'images', @@ -124,13 +137,25 @@ void main() { throwsArgumentError); }); - test('allows a wildcard group', () async { + test('correctly handles no type groups', () async { + await expectLater(plugin.openFiles(), completes); + final VerificationResult result = verify(mockApi.openFile(captureAny)); + final FileSelectorConfig config = + result.captured[0] as FileSelectorConfig; + expect(listEquals(config.utis, ['public.data']), isTrue); + }); + + test('correctly handles a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), completes); + final VerificationResult result = verify(mockApi.openFile(captureAny)); + final FileSelectorConfig config = + result.captured[0] as FileSelectorConfig; + expect(listEquals(config.utis, ['public.data']), isTrue); }); }); }