-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add iOS 15 style modal sheet transition (#21)
This PR introduces `CupertinoModalSheetRoute` and `CupertinoModalSheetPage`, which emulate the iOS 15 style modal sheet transition mentioned in #5. ## Changes - Add new components for iOS-style modal sheets - `CupertinoModalSheetRoute` - `CupertinoModalSheetPage` - `CupertinoStackedTransition` - `CupertinoStackedModalTransition` - Add a flag `fireImmediately` to `SheetController.addListener` - This enables the listeners to handle sheet metrics changes even if they are fired during a layout phase - Add `MaybeSheetMetrics.viewPixels` getter - Which represents the current visual height of a sheet (extent + keyboard height) - Make `SheetExtent` to notify its listeners whenever the `pixels` or the `viewPixels` are changed - Make `SheetExtent.animateTo` not to run an animation if it is already at the destination - Make `ModalSheetRoute` always create a `SheetController` and expose it to the descendant widgets ## Documentation - Add a tutorial code for the new stuff - Add a Safari clone app to the showcases - Add descriptions of `CupertinoModalSheetRoute` and `CupertinoModalSheetPage` to the README - Add a description of the Safari clone app to the README
- Loading branch information
Showing
24 changed files
with
1,508 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import 'package:cookbook/showcase/safari/common.dart'; | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:smooth_sheets/smooth_sheets.dart'; | ||
|
||
void showEditActionsSheet(BuildContext context) { | ||
Navigator.push( | ||
context, | ||
CupertinoModalSheetRoute( | ||
builder: (context) => const EditActionsSheet(), | ||
), | ||
); | ||
} | ||
|
||
class EditActionsSheet extends StatelessWidget { | ||
const EditActionsSheet({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ScrollableSheet( | ||
child: ClipRRect( | ||
borderRadius: BorderRadius.circular(16), | ||
child: SheetContentScaffold( | ||
backgroundColor: CupertinoColors.systemGroupedBackground, | ||
appBar: CupertinoAppBar( | ||
title: const Text('Edit Actions'), | ||
trailing: CupertinoButton( | ||
onPressed: () => Navigator.pop(context), | ||
child: const Text('Done'), | ||
), | ||
), | ||
body: const _ActionList(), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class _ActionList extends StatelessWidget { | ||
const _ActionList(); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ListView( | ||
children: const [ | ||
SizedBox(height: 16), | ||
_ActionListSection( | ||
header: Text('Favorites'), | ||
children: [ | ||
_ActionListItem(title: 'Copy', isFavorite: true), | ||
_ActionListItem(title: 'Save in Keep', isFavorite: true), | ||
], | ||
), | ||
SizedBox(height: 16), | ||
_ActionListSection( | ||
header: Text('Safari'), | ||
children: [ | ||
_ActionListItem(title: 'Add to Reading List'), | ||
_ActionListItem(title: 'Add Bookmark'), | ||
_ActionListItem(title: 'Add to Favorites'), | ||
_ActionListItem(title: 'Add to Quick Note'), | ||
_ActionListItem(title: 'Find on Page'), | ||
_ActionListItem(title: 'Add to Home Screen'), | ||
], | ||
), | ||
SizedBox(height: 16), | ||
_ActionListSection( | ||
header: Text('Other actions'), | ||
children: [ | ||
_ActionListItem(title: 'Markup'), | ||
_ActionListItem(title: 'Print'), | ||
_ActionListItem(title: 'Analyze with Bing Chat'), | ||
_ActionListItem(title: 'Open in Chrome'), | ||
_ActionListItem(title: 'Open using Mastodon'), | ||
_ActionListItem(title: 'Save to Pinterest'), | ||
_ActionListItem(title: 'Save to Dropbox'), | ||
], | ||
), | ||
], | ||
); | ||
} | ||
} | ||
|
||
class _ActionListSection extends StatelessWidget { | ||
const _ActionListSection({ | ||
this.header, | ||
required this.children, | ||
}); | ||
|
||
final Widget? header; | ||
final List<Widget> children; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return CupertinoListSection.insetGrouped( | ||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), | ||
header: header, | ||
children: children, | ||
); | ||
} | ||
} | ||
|
||
class _ActionListItem extends StatelessWidget { | ||
const _ActionListItem({ | ||
required this.title, | ||
this.isFavorite = false, | ||
}); | ||
|
||
final String title; | ||
final bool isFavorite; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return CupertinoListTile.notched( | ||
title: Text(title), | ||
leading: isFavorite | ||
? const Icon( | ||
CupertinoIcons.minus_circle_fill, | ||
color: CupertinoColors.systemRed, | ||
) | ||
: const Icon( | ||
CupertinoIcons.plus_circle_fill, | ||
color: CupertinoColors.systemGreen, | ||
), | ||
trailing: isFavorite | ||
? const Icon( | ||
CupertinoIcons.line_horizontal_3, | ||
color: CupertinoColors.systemGrey4, | ||
) | ||
: null, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import 'package:cookbook/showcase/safari/common.dart'; | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:smooth_sheets/smooth_sheets.dart'; | ||
|
||
void showEditBookmarkSheet(BuildContext context) { | ||
Navigator.push( | ||
context, | ||
CupertinoModalSheetRoute( | ||
builder: (context) => const EditBookmarkSheet( | ||
pageUrl: 'https://www.apple.com', | ||
faviconUrl: 'https://www.apple.com/favicon.ico', | ||
), | ||
), | ||
); | ||
} | ||
|
||
class EditBookmarkSheet extends StatelessWidget { | ||
const EditBookmarkSheet({ | ||
super.key, | ||
required this.pageUrl, | ||
required this.faviconUrl, | ||
}); | ||
|
||
final String pageUrl; | ||
final String faviconUrl; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return DraggableSheet( | ||
keyboardDismissBehavior: const SheetKeyboardDismissBehavior.onDragDown(), | ||
child: ClipRRect( | ||
borderRadius: BorderRadius.circular(16), | ||
child: SheetContentScaffold( | ||
backgroundColor: CupertinoColors.systemGroupedBackground, | ||
appBar: CupertinoAppBar( | ||
title: const Text('Add Bookmark'), | ||
leading: CupertinoButton( | ||
onPressed: () => Navigator.pop(context), | ||
child: const Text('Cancel'), | ||
), | ||
trailing: CupertinoButton( | ||
onPressed: () => | ||
Navigator.popUntil(context, (route) => route.isFirst), | ||
child: const Text('Save'), | ||
), | ||
), | ||
body: SizedBox.expand( | ||
child: CupertinoListSection.insetGrouped( | ||
children: [ | ||
_BookmarkEditor( | ||
pageUrl: pageUrl, | ||
faviconUrl: faviconUrl, | ||
), | ||
], | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
class _BookmarkEditor extends StatelessWidget { | ||
const _BookmarkEditor({ | ||
required this.pageUrl, | ||
required this.faviconUrl, | ||
}); | ||
|
||
final String pageUrl; | ||
final String faviconUrl; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Row( | ||
children: [ | ||
Padding( | ||
padding: const EdgeInsets.all(16), | ||
child: SiteIcon(url: faviconUrl), | ||
), | ||
Expanded( | ||
child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
const CupertinoTextField.borderless( | ||
padding: EdgeInsets.zero, | ||
autofocus: true, | ||
), | ||
const Divider(color: CupertinoColors.systemGrey5), | ||
Text( | ||
pageUrl, | ||
style: Theme.of(context) | ||
.textTheme | ||
.bodyMedium | ||
?.copyWith(color: CupertinoColors.secondaryLabel), | ||
maxLines: 1, | ||
overflow: TextOverflow.ellipsis, | ||
), | ||
], | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
class CupertinoAppBar extends StatelessWidget implements PreferredSizeWidget { | ||
const CupertinoAppBar({ | ||
super.key, | ||
required this.title, | ||
this.leading, | ||
this.trailing, | ||
}); | ||
|
||
final Widget title; | ||
final Widget? leading; | ||
final Widget? trailing; | ||
|
||
@override | ||
Size get preferredSize => const Size.fromHeight(56); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Container( | ||
height: preferredSize.height, | ||
decoration: const BoxDecoration( | ||
color: CupertinoColors.systemGroupedBackground, | ||
border: Border(bottom: BorderSide(color: CupertinoColors.systemGrey5)), | ||
), | ||
child: Stack( | ||
alignment: Alignment.center, | ||
children: [ | ||
if (leading case final leading?) | ||
Positioned( | ||
left: 1, | ||
child: leading, | ||
), | ||
DefaultTextStyle( | ||
style: Theme.of(context).textTheme.titleMedium!, | ||
child: title, | ||
), | ||
if (trailing case final trailing?) | ||
Positioned( | ||
right: 1, | ||
child: trailing, | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} | ||
|
||
class SiteIcon extends StatelessWidget { | ||
const SiteIcon({ | ||
super.key, | ||
required this.url, | ||
}); | ||
|
||
final String url; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return SizedBox.square( | ||
dimension: 48, | ||
child: Image.network(url), | ||
); | ||
} | ||
} |
Oops, something went wrong.