Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Attached Sheet to Playground #265

Merged
merged 11 commits into from
Jul 22, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

/// A [WoltModalType] that displays a floating bottom sheet which is dynamically positioned
/// relative to an anchor widget identified by [anchorKey]. This modal type allows for a
/// customizable presentation of content in relation to a specific UI element, enhancing
/// contextual awareness for the user.
///
/// ## Usage
/// This modal type is designed to be used when there is a need to display information or
/// actions related to a specific element on the screen. For example, it can be used to
/// show detailed information about an item in a list or to present actions related to
/// a specific UI component.
///
/// ## Positioning
/// The modal is positioned based on the location and size of the anchor widget. The
/// [alignment] parameter determines the modal's position relative to the anchor. If the
/// anchor widget cannot be found (e.g., if the key is not applied to any existing widget),
/// the modal will be centered on the screen.
///
/// The modal automatically adjusts its size and position to ensure it fits within the
/// available screen space while respecting the specified alignment relative to the anchor
/// widget. This behavior ensures that the modal remains accessible and visually connected
/// to the context it relates to.
///
/// ## Considerations
/// - The [anchorKey] must be applied to a widget that is already in the widget tree for the
/// modal to position itself correctly.
class AttachedFloatingBottomSheetType extends WoltModalType {
ulusoyca marked this conversation as resolved.
Show resolved Hide resolved
static const Duration _defaultEnterDuration = Duration(milliseconds: 350);
static const Duration _defaultExitDuration = Duration(milliseconds: 300);

/// Creates an AttachedFloatingBottomSheetType
///
/// [anchorKey] is the key of the widget that the bottom sheet will be attached to. Apply this key to the Widget where the Sheet should be attached to
/// [alignment] is the alignment of the bottom sheet to the anchor. Default is [Alignment.center]
///
/// If the anchor is not found, the bottom sheet will be centered on the screen
AttachedFloatingBottomSheetType({
required GlobalKey anchorKey,
this.alignment = Alignment.center,
}) : super(
shapeBorder: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(28.0)),
),
showDragHandle: false,
dismissDirection: WoltModalDismissDirection.down,
transitionDuration: _defaultEnterDuration,
reverseTransitionDuration: _defaultExitDuration,
) {
final renderBox =
anchorKey.currentContext?.findRenderObject() as RenderBox?;
_anchorPosition = renderBox?.localToGlobal(Offset.zero);
_anchorSize = renderBox?.size;
}

late final Offset? _anchorPosition;
late final Size? _anchorSize;
final Alignment alignment;

@override
Offset positionModal(
Size availableSize, Size modalContentSize, TextDirection textDirection) {
final anchorPosition = _anchorPosition;
final isOffscreen = anchorPosition == null ||
anchorPosition.dx < 0 ||
anchorPosition.dx > availableSize.width ||
anchorPosition.dy < 0 ||
anchorPosition.dy > availableSize.height;
if (isOffscreen) {
// Return the Center Offset by the size of the modal content
// If no position found
return availableSize.center(Offset.zero) -
Offset(modalContentSize.width, modalContentSize.height) / 2;
} else {
final modalOffset = Offset(
(alignment.x / 2 + 0.5) * modalContentSize.width,
(alignment.y / 2 + 0.5) * modalContentSize.height,
);
final anchorSize = _anchorSize ?? Size.zero;

final anchorOffset = Offset(
(alignment.x / 2 + 0.5) * anchorSize.width,
(alignment.y / 2 + 0.5) * anchorSize.height,
);

// Position the Modal based on Anchor Position plus the
return anchorPosition + anchorOffset - modalOffset;
}
}

@override
String routeLabel(BuildContext context) {
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
return localizations.bottomSheetLabel;
}

@override
BoxConstraints layoutModal(Size availableSize) {
const padding = 32.0;

// Calculate Available Space based on Anchor Position
final double availableWidth;
final double availableHeight;
final position = _anchorPosition;

if (position == null) {
availableWidth = availableSize.width;
availableHeight = availableSize.height;
} else {
if (alignment.x == -1 || alignment.x == 1) {
if (alignment.x == 1) {
// Modal is Left of the Anchor
availableWidth = position.dx;
} else {
// Modal is Right of the Anchor
availableWidth = availableSize.width - position.dx;
}
} else {
availableWidth = min(position.dx, availableSize.width - position.dx);
}

if (alignment.y == -1 || alignment.y == 1) {
if (alignment.y == 1) {
// Modal is Top of the Anchor
availableHeight = position.dy;
} else {
// Modal is Bottom of the Anchor
availableHeight = availableSize.height - position.dy;
}
} else {
availableHeight = min(position.dy, availableSize.height - position.dy);
}
}

double width = availableWidth > 523.0 ? 312.0 : availableWidth - padding;

if (availableWidth > 312) {
width = 312.0;
} else if (availableWidth > 240.0) {
width = 240.0;
} else {
width = availableWidth * 0.7;
}

return BoxConstraints(
minWidth: width,
maxWidth: width,
minHeight: 0,
maxHeight: min(availableHeight, availableSize.height * 0.8),
);
}

@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
final alphaAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: animation,
curve: const Interval(0.0, 100.0 / 300.0, curve: Curves.linear),
reverseCurve: const Interval(100.0 / 250.0, 1.0, curve: Curves.linear),
));

final slideAnimation = Tween<Offset>(
begin: Offset(alignment.x, alignment.y),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOutCubic,
));

final scaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOutCubic,
));

return FadeTransition(
opacity: alphaAnimation,
child: SlideTransition(
position: slideAnimation,
child: ScaleTransition(
scale: scaleAnimation,
child: child,
),
),
);
}
}
41 changes: 41 additions & 0 deletions playground/lib/home/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:playground/home/custom_sheets/attached_floating_bottom_sheet_type.dart';
import 'package:playground/home/custom_sheets/floating_bottom_sheet_type.dart';
import 'package:playground/home/custom_sheets/top_notification_sheet_type.dart';
import 'package:playground/home/pages/custom_sheet_pages/adjust_time_notification_page.dart';
ABausG marked this conversation as resolved.
Show resolved Hide resolved
import 'package:playground/home/pages/custom_sheet_pages/new_order_notification_page.dart';
import 'package:playground/home/pages/root_sheet_page.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';
Expand Down Expand Up @@ -30,11 +32,30 @@ class _HomeScreenState extends State<HomeScreen> {
_Responsiveness _selectedResponsiveness = _Responsiveness.auto;
TextDirection _selectedDirection = TextDirection.ltr;

final GlobalKey _attachedAppBarKey = GlobalKey();
final GlobalKey _attachedRandomKey = GlobalKey();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wolt Modal Sheet Playground'),
leading: IconButton(
key: _attachedAppBarKey,
icon: const Icon(Icons.anchor),
onPressed: () {
WoltModalSheet.show(
context: context,
modalTypeBuilder: (_) => AttachedFloatingBottomSheetType(
anchorKey: _attachedAppBarKey,
alignment: Directionality.of(context) == TextDirection.ltr
? Alignment.topLeft
: Alignment.topRight,
),
pageListBuilder: (_) => [NewOrderNotificationPage()],
);
},
),
actions: [
WoltCircularElevatedButton(
onPressed: () {
Expand Down Expand Up @@ -110,6 +131,7 @@ class _HomeScreenState extends State<HomeScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Expanded(child: SizedBox.shrink()),
const Text('LTR'),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
Expand All @@ -125,6 +147,25 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
const Text('RTL'),
Expanded(
child: Center(
child: IconButton(
key: _attachedRandomKey,
icon: const Icon(Icons.anchor),
onPressed: () {
WoltModalSheet.show(
context: context,
modalTypeBuilder: (_) =>
AttachedFloatingBottomSheetType(
anchorKey: _attachedRandomKey,
alignment: Alignment.bottomCenter,
),
pageListBuilder: (_) =>
[NewOrderNotificationPage()],
);
},
),
))
],
),
const SizedBox(height: 16),
Expand Down
Loading