Skip to content

Commit

Permalink
feat: allow barrier dismiss in dialogs (#3)
Browse files Browse the repository at this point in the history
This add the builder option and possibility to allow
dialog dismissal via backdrop press. Be aware that
Scaffold and Material App are full screen by default.
So to use a "small modal" of some sort, you need to
provide theme data or use "Material" widgets to provide
defaults. (e.g. in the example app)
  • Loading branch information
buehler authored Feb 23, 2024
1 parent 3a31510 commit efe034f
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 48 deletions.
10 changes: 10 additions & 0 deletions packages/fluorflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ when used with the fluorflow generator.
Dialogs work exactly the same way as bottom sheets, but are shown via the
`DialogService` and have another base class.

**Important:** Bottom sheets are always wrapped in a `Scaffold` widget. Thus,
they inherit your styles. `Dialogs` do not have this behavior (by design).
So you may create a full screen dialog and wrap it in a `Scaffold`, or
if you want a small "modal dialog" that has a backdrop and is dismissible
on click of the backdrop, it is also possible. However, you need
to wrap some parts of the content into a `Material` (or Theme provider)
widget to provide some decent default styles. Otherwise, some styles
are weird (e.g. Text Styles are big, red, and underlined).
You can see this in the examples (`SmallDialog`).

## CLI

FluorFlow comes with a CLI that can be used to generate views and other things.
Expand Down
35 changes: 35 additions & 0 deletions packages/fluorflow/example/lib/dialogs/small_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:fluorflow/annotations.dart';
import 'package:fluorflow/fluorflow.dart';
import 'package:flutter/material.dart';

@DialogConfig(
routeBuilder: RouteBuilder.topToBottomFade, defaultBarrierDismissible: true)
final class SmallDialog extends FluorFlowSimpleDialog<void> {
const SmallDialog({super.key, required super.completer});

@override
Widget build(BuildContext context) => Center(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Material(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Dialog Page'),
const Text('Close via button or click into background'),
const SizedBox(height: 36),
ElevatedButton(
onPressed: completer.confirm,
child: const Text('Close'),
),
],
),
),
),
);
}
26 changes: 12 additions & 14 deletions packages/fluorflow/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:example/app.locator.dart';
import 'package:fluorflow/fluorflow.dart';
import 'package:flutter/material.dart';

import 'app.locator.dart';
import 'app.router.dart';

void main() async {
Expand All @@ -13,17 +13,15 @@ class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FluorFlow Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: AppRoute.homeView.path,
onGenerateRoute: onGenerateRoute,
navigatorKey: NavigationService.navigatorKey,
navigatorObservers: [NavigationService.observer()],
);
}
Widget build(BuildContext context) => MaterialApp(
title: 'FluorFlow Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: AppRoute.homeView.path,
onGenerateRoute: onGenerateRoute,
navigatorKey: NavigationService.navigatorKey,
navigatorObservers: [NavigationService.observer()],
);
}
4 changes: 4 additions & 0 deletions packages/fluorflow/example/lib/views/home/home_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ final class HomeView extends FluorFlowView<HomeViewModel> {
onPressed: viewModel.showTestDialog,
child: const Text('Show Dialog'),
),
ElevatedButton(
onPressed: viewModel.showSmallDialog,
child: const Text('Show Small Dialog'),
),
],
),
),
Expand Down
7 changes: 5 additions & 2 deletions packages/fluorflow/example/lib/views/home/home_viewmodel.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'package:example/app.dialogs.dart';
import 'package:example/app.router.dart';
import 'package:fluorflow/fluorflow.dart';

import '../../app.dialogs.dart';
import '../../app.router.dart';

final class HomeViewModel extends BaseViewModel {
final _dialogService = locator<DialogService>();
final _navService = locator<NavigationService>();
Expand All @@ -17,5 +18,7 @@ final class HomeViewModel extends BaseViewModel {

void showTestDialog() => _dialogService.showRedDialog(elements: []);

void showSmallDialog() => _dialogService.showSmallDialog();

void goToDetail() => _navService.navigateToDetailView();
}
5 changes: 5 additions & 0 deletions packages/fluorflow/lib/src/annotations/dialog_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class DialogConfig {
/// The default barrier (background) color for the dialog.
final int defaultBarrierColor;

/// The default value for "barrierDismissible" for the dialog.
/// Defines whether the dialog can be dismissed by tapping the barrier.
final bool defaultBarrierDismissible;

/// The page route builder for the dialog.
final Type? pageRouteBuilder;

Expand All @@ -18,5 +22,6 @@ class DialogConfig {
this.pageRouteBuilder,
this.routeBuilder = RouteBuilder.noTransition,
this.defaultBarrierColor = 0x80000000,
this.defaultBarrierDismissible = false,
});
}
13 changes: 12 additions & 1 deletion packages/fluorflow/lib/src/dialogs/dialog_service.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:get/get.dart';

/// A service for showing and closing dialogs.
/// Works with route builders. However, it is recommended to use the
/// convenience methods generated by the generator to show a dialog.
class DialogService {
final _random = Random();

/// Returns whether a dialog is currently open.
bool get isDialogOpen => Get.isDialogOpen ?? false;

/// Shows a dialog and returns a future with the (possible) result.
/// The [barrierColor] parameter can be used to specify a custom barrier color for the dialog.
///
/// - The [barrierColor] parameter can be used to specify a custom barrier color for the dialog.
/// - The [barrierDismissible] parameter specifies whether the dialog can be dismissed by
/// tapping the barrier (if it is visible).
Future<TResult?> showDialog<TResult>({
required PageRouteBuilder dialogBuilder,
Color barrierColor = const Color(0x80000000),
bool barrierDismissible = false,
}) =>
Get.generalDialog<TResult>(
barrierDismissible: barrierDismissible,
barrierLabel:
barrierDismissible ? 'dialog_${_random.nextInt(1000000)}' : null,
pageBuilder: dialogBuilder.pageBuilder,
barrierColor: barrierColor,
transitionDuration: dialogBuilder.transitionDuration,
Expand Down
10 changes: 10 additions & 0 deletions packages/fluorflow_generator/lib/src/builder/dialog_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ class DialogBuilder implements Builder {
CodeExpression(Code(
'0x${(configAnnotation?.read('defaultBarrierColor').intValue ?? 0x80000000).toRadixString(16).padLeft(8, '0')}'))
]).code))
..optionalParameters.add(Parameter((b) => b
..name = 'barrierDismissible'
..type = refer('bool')
..named = true
..defaultTo = literalBool(configAnnotation
?.read('defaultBarrierDismissible')
.boolValue ??
false)
.code))
..optionalParameters.addAll(params.map((p) => Parameter((b) => b
..name = p.name
..type = recursiveTypeReference(lib, p.type)
Expand All @@ -120,6 +129,7 @@ class DialogBuilder implements Builder {
..body = refer('showDialog')
.call([], {
'barrierColor': refer('barrierColor'),
'barrierDismissible': refer('barrierDismissible'),
'dialogBuilder': dialogBuilder.newInstance([], {
'pageBuilder': Method((b) => b
..requiredParameters.add(Parameter((b) => b.name = '_'))
Expand Down
Loading

0 comments on commit efe034f

Please sign in to comment.