From 5fa2cc3b85cd3e6bbcb1b9d8cce587e8918798d1 Mon Sep 17 00:00:00 2001 From: Ilya Nixan Date: Wed, 19 Aug 2026 17:45:40 +0300 Subject: [PATCH] Add Flutter Web support and update adaptive dialog behavior - Implemented web support by removing `dart:io` import. - Updated `showAdaptiveProgressDialog` to select style based on `Theme.of(context).platform`. - Enhanced README and CHANGELOG to reflect web support and behavior changes. - Added tests for adaptive dialog selection based on platform. --- .github/workflows/ci.yml | 26 +++++++++ CHANGELOG.md | 6 ++ README.md | 11 ++-- lib/src/show_progress_dialog.dart | 9 ++- test/progress_dialog_test.dart | 94 +++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c41f37..8dfa6eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,32 @@ jobs: - name: Run tests run: flutter test + test_web: + name: Run tests on web + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Chrome + id: setup-chrome + uses: browser-actions/setup-chrome@v1 + with: + install-dependencies: true + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Install dependencies + run: flutter pub get + + # Guards web support: a `dart:io` import in lib/ fails to compile here. + - name: Run tests + run: flutter test --platform chrome + env: + CHROME_EXECUTABLE: ${{ steps.setup-chrome.outputs.chrome-path }} + dry_run: name: Publish dry run runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 1884401..0a47d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ exhaustive `switch` statements over it need a `Cancelled` case added. `map()` and `flatMap()` pass cancellation through * Note: the task is not interrupted on cancellation — Dart futures cannot be cancelled, so it runs to completion and its result is discarded +* Added Flutter Web support — `flutter_future_progress_dialog` no longer imports + `dart:io`, so pub.dev now reports all six platforms +* **Behaviour change:** `showAdaptiveProgressDialog` now selects its style from + `Theme.of(context).platform` instead of the host operating system, matching + Flutter's own `showAdaptiveDialog`. An app that sets `ThemeData(platform: ...)` + now gets the style it asked for rather than the style of the machine it runs on ## 1.5.0 diff --git a/README.md b/README.md index 416cfb6..2f1f825 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Supported platforms, as reported on [pub.dev](https://pub.dev/packages/flutter_f | macOS | Yes | | Windows | Yes | | Linux | Yes | -| Web | No — `flutter_future_progress_dialog` imports `dart:io` for the platform check in `showAdaptiveProgressDialog` | +| Web | Yes | ## Installation @@ -160,6 +160,8 @@ Note that `future` takes a *callback* returning a `Future`, not a `Future` itsel Call `showCupertinoProgressDialog` for an iOS-styled dialog built around `CupertinoActivityIndicator`, or `showAdaptiveProgressDialog` to select the style automatically — Cupertino on iOS and macOS, Material everywhere else. +`showAdaptiveProgressDialog` reads `Theme.of(context).platform`, the same signal Flutter's own `showAdaptiveDialog` uses. An app that overrides `ThemeData.platform` gets the style it asked for, and the selection works on Flutter Web. + ```dart // Always iOS style final result = await showCupertinoProgressDialog( @@ -257,7 +259,7 @@ final result = await showProgressDialog( |---|---|---| | `showProgressDialog` | Material — `Dialog` with a `CircularProgressIndicator` | `Future>` | | `showCupertinoProgressDialog` | Cupertino — `CupertinoPopupSurface` with a `CupertinoActivityIndicator` | `Future>` | -| `showAdaptiveProgressDialog` | Cupertino on iOS and macOS, Material elsewhere | `Future>` | +| `showAdaptiveProgressDialog` | Cupertino on iOS and macOS, Material elsewhere, per `Theme.of(context).platform` | `Future>` | ### Parameters @@ -306,11 +308,11 @@ Yes. A `Future` task yields `Success`, and a task returning `null` y ### Does flutter_future_progress_dialog support Flutter Web? -No. `flutter_future_progress_dialog` imports `dart:io` to detect the platform in `showAdaptiveProgressDialog`, so pub.dev lists Android, iOS, macOS, Windows, and Linux, but not Web. +Yes. `flutter_future_progress_dialog` supports all six Flutter platforms — Android, iOS, macOS, Windows, Linux, and Web. Platform detection in `showAdaptiveProgressDialog` goes through `Theme.of(context).platform` rather than `dart:io`, so nothing is host-specific. ### What is the difference between the three functions? -`showProgressDialog` always renders a Material dialog, `showCupertinoProgressDialog` always renders an iOS-style dialog, and `showAdaptiveProgressDialog` picks between them based on the host platform. All three take the same task callback and return the same `ProgressDialogResult`. +`showProgressDialog` always renders a Material dialog, `showCupertinoProgressDialog` always renders an iOS-style dialog, and `showAdaptiveProgressDialog` picks between them based on `Theme.of(context).platform`. All three take the same task callback and return the same `ProgressDialogResult`. ### Do I need to check `context.mounted` after awaiting? @@ -319,7 +321,6 @@ Yes, if you use the `BuildContext` afterwards. `showProgressDialog` closes its o ## Known limitations - **The task cannot be aborted.** Cancellation dismisses the dialog, not the work. See [Can the user cancel the progress dialog?](#can-the-user-cancel-the-progress-dialog) -- **No Flutter Web support**, because of the `dart:io` import used for platform detection. - **No determinate progress.** The dialog shows an indeterminate spinner; there is no percentage or step reporting. - **If the hosting `Navigator` is disposed while the task is in flight** — for example a nested navigator removed from the widget tree — the dialog is torn down without error, but the returned future never completes, so the `await` waits forever. diff --git a/lib/src/show_progress_dialog.dart b/lib/src/show_progress_dialog.dart index d1694b1..dd890d8 100644 --- a/lib/src/show_progress_dialog.dart +++ b/lib/src/show_progress_dialog.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart' as c; import 'package:flutter/material.dart' as m; import 'package:flutter/widgets.dart' as w; @@ -191,6 +189,10 @@ Future> showCupertinoProgressDialog({ /// Displays a Cupertino-styled dialog on iOS and macOS, and a Material-styled /// dialog on all other platforms. /// +/// The style is chosen from `Theme.of(context).platform`, matching Flutter's own +/// `showAdaptiveDialog`. An app that overrides `ThemeData.platform` therefore +/// gets the dialog style it asked for, and the choice works on Flutter Web. +/// /// Parameters: /// * [context] - The build context used to show the dialog /// * [future] - The async task to execute while showing the progress dialog @@ -240,7 +242,8 @@ Future> showAdaptiveProgressDialog({ bool useSafeArea = true, bool? requestFocus, }) async { - if (Platform.isMacOS || Platform.isIOS) { + final platform = m.Theme.of(context).platform; + if (platform == m.TargetPlatform.iOS || platform == m.TargetPlatform.macOS) { return await showCupertinoProgressDialog( context: context, future: future, diff --git a/test/progress_dialog_test.dart b/test/progress_dialog_test.dart index 9b26706..42487d0 100644 --- a/test/progress_dialog_test.dart +++ b/test/progress_dialog_test.dart @@ -690,4 +690,98 @@ void main() { ); }); }); + + // showAdaptiveProgressDialog must follow the target platform reported by the + // theme, the way Flutter's own showAdaptiveDialog does — not the host OS the + // process happens to be running on. + group('adaptive platform selection', () { + Future> pumpAdaptive(WidgetTester tester, TargetPlatform platform) async { + final task = Completer(); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(platform: platform), + home: Material( + child: Builder( + builder: (context) { + SchedulerBinding.instance.addPostFrameCallback((_) { + showAdaptiveProgressDialog( + context: context, + future: () => task.future, + ).ignore(); + }); + return Container(); + }, + ), + ), + ), + ); + await tester.pump(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + return task; + } + + for (final platform in [TargetPlatform.iOS, TargetPlatform.macOS]) { + testWidgets('uses a Cupertino dialog on $platform', (WidgetTester tester) async { + final task = await pumpAdaptive(tester, platform); + + expect(find.byType(CupertinoActivityIndicator), findsOneWidget); + expect(find.byType(CircularProgressIndicator), findsNothing); + + task.complete(); + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + }); + } + + for (final platform in [ + TargetPlatform.android, + TargetPlatform.fuchsia, + TargetPlatform.linux, + TargetPlatform.windows, + ]) { + testWidgets('uses a Material dialog on $platform', (WidgetTester tester) async { + final task = await pumpAdaptive(tester, platform); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + expect(find.byType(CupertinoActivityIndicator), findsNothing); + + task.complete(); + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + }); + } + + testWidgets('returns a result like the non-adaptive variants', (WidgetTester tester) async { + final completer = Completer>(); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(platform: TargetPlatform.android), + home: Material( + child: Builder( + builder: (context) { + SchedulerBinding.instance.addPostFrameCallback((_) { + showAdaptiveProgressDialog( + context: context, + future: () async { + await Future.delayed(const Duration(milliseconds: 100)); + return 'ok'; + }, + ).then(completer.complete); + }); + return Container(); + }, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(await completer.future, const Success('ok')); + expect(tester.takeException(), isNull); + }); + }); }