Skip to main content

adaptive_smooth_sheets 0.2.0 copy "adaptive_smooth_sheets: ^0.2.0" to clipboard
adaptive_smooth_sheets: ^0.2.0 copied to clipboard

Adaptive bottom-sheet and dialog presentation for Flutter, built on smooth_sheets with live window-size adaptation.

Adaptive Smooth Sheets #

Adaptive Smooth Sheets extends smooth_sheets with the responsive presentation behavior familiar from wolt_modal_sheet. The same modal appears as a draggable bottom sheet on compact screens and as a centered dialog on larger screens.

smooth_sheets provides the underlying sheet, scrolling, and navigation behavior. This package adds the screen-size adaptation that smooth_sheets does not provide on its own—combining its flexible foundation with a responsive bottom-sheet-to-dialog experience.

An already-open modal switches presentation live when the window crosses its configured breakpoint, without recreating its page, form, or scroll state.

An open modal switches live between dialog and bottom-sheet presentation while preserving its state.

Install #

Version 0.2.0 requires Flutter 3.47.0 or later and Dart 3.13.0 or later. It uses the standalone material_ui library for Material widgets, themes, and localizations.

flutter pub add adaptive_smooth_sheets
flutter pub add material_ui

Then import the package's single public entry point:

import 'package:adaptive_smooth_sheets/adaptive_smooth_sheets.dart';

Smooth Sheets version policy #

This package supports smooth_sheets: '>=1.3.0 <1.4.0'. Patch releases in the 1.3.x series are allowed; 1.4.0 and later require a reviewed dependency update and a new release of this package. This avoids automatically accepting another minor release that changes Material types, as happened in Smooth Sheets 1.3.0. Patch releases still need compatibility checks; the version constraint does not guarantee that every future release is bug-free.

Migrating from 0.1.x to 0.2.0 #

This is a breaking change for applications using package:flutter/material.dart.

  1. Update Flutter and Dart to the minimum versions above.
  2. Update adaptive_smooth_sheets to ^0.2.0 and add material_ui: ^1.6.0 to the application's dependencies.
  3. Replace Material imports with package:material_ui/material_ui.dart in the application root, theme definitions, sheet content, and related tests. Keep package:flutter/widgets.dart for design-independent Flutter widgets.
  4. Register AdaptiveSheetThemeData in material_ui's ThemeData.extensions. Its new ThemeExtension type cannot be registered in the legacy Flutter theme.
  5. If the app uses GlobalMaterialLocalizations or GlobalCupertinoLocalizations from flutter_localizations, use GlobalMaterialLocalizations.delegates from material_ui instead.

Flutter's automated import migration is available with dart fix --apply --code=migrate_design_widgets. Check the resulting imports and dependencies, then run analysis and tests.

Older third-party widget subtrees can use MaterialUiCompatibilityBridge inside the new MaterialApp.builder. This bridges themes and localizations, but does not convert incompatible public API types such as ThemeExtension. See Flutter's migration guide for the bridge setup and limitations. The package and example app use material_ui throughout and do not require a compatibility bridge.

First adaptive modal #

Register optional application-wide defaults in ThemeData.extensions, then open a modal with showAdaptiveSheet.

import 'package:adaptive_smooth_sheets/adaptive_smooth_sheets.dart';
import 'package:material_ui/material_ui.dart';

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        extensions: const [
          AdaptiveSheetThemeData(
            dialogBreakpoint: 720,
            dialogWidth: 640,
          ),
        ],
      ),
      home: Scaffold(
        body: Center(
          child: FilledButton(
            onPressed: () => showAdaptiveSheet<void>(
              context: context,
              page: const AdaptiveSheetPage<void>(
                child: _AccountSheet(),
              ),
            ),
            child: const Text('Edit account'),
          ),
        ),
      ),
    );
  }
}

class _AccountSheet extends StatelessWidget {
  const _AccountSheet();

  @override
  Widget build(BuildContext context) {
    final presentation = AdaptiveSheetScope.of(context).presentation;

    return AdaptiveSheetScaffold(
      topBar: const Padding(
        padding: EdgeInsets.fromLTRB(24, 20, 24, 12),
        child: Text('Edit account'),
      ),
      body: SingleChildScrollView(
        primary: true,
        padding: const EdgeInsets.fromLTRB(24, 12, 24, 24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              presentation == AdaptiveSheetPresentation.bottomSheet
                  ? 'Bottom-sheet presentation'
                  : 'Dialog presentation',
            ),
            const SizedBox(height: 16),
            const TextField(
              decoration: InputDecoration(labelText: 'Display name'),
            ),
          ],
        ),
      ),
      bottomBar: Padding(
        padding: const EdgeInsets.all(16),
        child: FilledButton(
          onPressed: () => AdaptiveSheetNavigator.of(context).close(),
          child: const Text('Save'),
        ),
      ),
    );
  }
}

Resize the app window while the modal is open. The presentation label changes, but the TextField and the modal page remain mounted.

What the package owns #

  • Responsive bottom-sheet and dialog presentation, including live resizing.
  • Route geometry, barriers, safe areas, keyboard avoidance, dragging, and mouse-drag support.
  • A primary AdaptiveSheetScrollController that bridges scrolling and sheet dragging in bottom-sheet presentation.
  • An internal, typed page stack through AdaptiveSheetPage and AdaptiveSheetNavigator.
  • Presentation-aware configuration through AdaptiveSheetScope, global AdaptiveSheetThemeData, and per-route AdaptiveSheetConfig.

Headers, footers, buttons, forms, tabs, and application-specific styling are deliberately consumer concerns. Compose them with AdaptiveSheetScaffold or with your own widgets.

Configuration #

Use AdaptiveSheetThemeData for application defaults. A route can override only the values it needs with AdaptiveSheetConfig.

await showAdaptiveSheet<void>(
  context: context,
  config: const AdaptiveSheetConfig(
    dialogWidth: 560,
    dialogMaxHeight: 720,
    enableMouseDrag: false,
    bottomSheetPhysics: BouncingSheetPhysics(),
  ),
  page: AdaptiveSheetPage<void>(child: SettingsSheet()),
);

The default bottom-sheet physics are ClampingSheetPhysics, so a sheet cannot be dragged beyond its bounds. Use BouncingSheetPhysics when elastic overdrag fits the application.

AdaptiveSheetPresentationPolicy can override the breakpoint for one route or use a custom resolver when width alone is not enough. For example, this policy uses a dialog only for sufficiently wide landscape windows:

final config = AdaptiveSheetConfig(
  presentationPolicy: AdaptiveSheetPresentationPolicy(
    resolver: (context) {
      final mediaQuery = MediaQuery.of(context);
      final useDialog = mediaQuery.orientation == Orientation.landscape &&
          mediaQuery.size.width > 840;

      return useDialog
          ? AdaptiveSheetPresentation.dialog
          : AdaptiveSheetPresentation.bottomSheet;
    },
  ),
);

A resolver takes precedence over dialogBreakpoint. Reading MediaQuery from its context also makes an already-open route react to subsequent window-size or orientation changes.

AdaptiveSheetNavigator keeps page navigation separate from closing the outer modal:

final sheetNavigator = AdaptiveSheetNavigator.of(context);

await sheetNavigator.push<void>(
  const AdaptiveSheetPage<void>(child: DetailsSheet()),
);

sheetNavigator.pop(); // Pops one internal page when possible.

// Replaces the current page while retaining earlier pages.
await sheetNavigator.replace<void, void>(
  const AdaptiveSheetPage<void>(child: ReviewSheet()),
);

// Replaces the complete stack and creates a new root page.
await sheetNavigator.replaceAll<void>(
  const AdaptiveSheetPage<void>(child: SuccessSheet()),
);

sheetNavigator.close(); // Closes the complete adaptive modal.

Page, route, and theme transitions are independently configurable with AdaptiveSheetPageTransition.

On native platforms, the system Back action—such as Android's hardware or software Back button, or the equivalent platform back gesture—pops the current internal page first. When the first page is already visible, it closes the modal by default. Override that policy with AdaptiveSheetNativeBackBehavior when needed.

Scrolling and dismissal guards #

Every AdaptiveSheetPage supplies an AdaptiveSheetScrollController as its primary vertical controller. Ordinary primary ListView and SingleChildScrollView widgets inherit it automatically. Retrieve it only when an action needs programmatic scrolling:

await AdaptiveSheetScrollController.of(context).animateTo(
  600,
  duration: const Duration(milliseconds: 250),
  curve: Curves.easeOut,
);

Use AdaptiveSheetPopScope to guard outer-modal dismissal, including a barrier tap, Escape, and a swipe-to-dismiss gesture:

AdaptiveSheetPopScope<void>(
  canPop: formIsSaved,
  onPopInvokedWithResult: (didPop, result) {
    if (!didPop) {
      showDiscardChangesMessage();
    }
  },
  child: const EditProfileSheet(),
)

Example app #

Run the included example to see the public API in focused demos:

  • a minimal adaptive modal;
  • primary scrolling and sheet-drag handoff;
  • live resize while local widget state is preserved;
  • internal page navigation, replace, and replaceAll;
  • guarded dismissal; and
  • a route-specific breakpoint and geometry overrides.
cd example
flutter run

Web browser Back #

Adaptive-sheet pages use a nested Flutter Navigator; they are not browser history entries. Browser Back cannot reliably unwind those pages in an imperative Flutter web app. Provide visible Back and Close controls inside the modal, or model browser-history-aware steps in the root Router.

2
likes
160
points
263
downloads
screenshot

Documentation

API reference

Publisher

verified publisherwinkk.com

Weekly Downloads

Adaptive bottom-sheet and dialog presentation for Flutter, built on smooth_sheets with live window-size adaptation.

Repository (GitHub)
View/report issues

Topics

#modal #bottom-sheet #dialog #responsive #adaptive

License

BSD-3-Clause (license)

Dependencies

flutter, material_ui, smooth_sheets

More

Packages that depend on adaptive_smooth_sheets