/flutter_smart_dialog

一种更优雅的Flutter Dialog解决方案

Primary LanguageDartMIT LicenseMIT

pub stars issues

Language: English | 中文简体

Introduction

An elegant Flutter Dialog solution.

dependencies:
  flutter_smart_dialog: ^3.0.0

Advantage

  • Do not need BuildContext
  • Can penetrate dark background, click on the page behind dialog
  • Support dialog stack,close the specified dialog
  • Easily implement toast,loading dialog,custome dialog

Quick start

Initialization

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: RouteConfig.main,
      getPages: RouteConfig.getPages,
      // here
      navigatorObservers: [FlutterSmartDialog.observer],
      // here
      builder: FlutterSmartDialog.init(),
    );
  }
}

Easy usage

  • toast usage💬
SmartDialog.showToast('test toast');

toastDefault

  • loading usage
SmartDialog.showLoading();
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss(); 

loadingDefault

  • dialog usage🎨
var custom = Container(
    height: 80,
    width: 180,
    decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: BorderRadius.circular(20),
    ),
    alignment: Alignment.center,
    child: Text('easy custom dialog', style: TextStyle(color: Colors.white)),
);
// here
SmartDialog.show(widget: custom, isLoadingTemp: false);

dialogEasy

You may have questions

About FlutterSmartDialog.init()

This method does not take up your builder parameters. The builder is called back in init

  • For example: continue to set Bloc global instance 😄
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: RouteConfig.main,
      getPages: RouteConfig.getPages,
      navigatorObservers: [FlutterSmartDialog.observer],
      builder: FlutterSmartDialog.init(builder: _builder),
    );
  }
}

Widget _builder(BuildContext context, Widget? child) {
  return MultiBlocProvider(
    providers: [
      BlocProvider.value(value: BlocSpanOneCubit()),
    ],
    child: child!,
  );
}

Entity Return Key

The monitoring of the back button is very important and can basically cover most situations

initBack

pop routing

Although the monitoring of the return button can cover most scenes, some manual pop scenes need to add parameter monitoring

  • Do not add FlutterSmartDialog.observer
    • If the penetration parameter is turned on (you can interact with the page after the dialog), then manually close the page
    • There will be such an embarrassing situation

initPopOne

  • Add FlutterSmartDialog.observer, it can be handled more reasonably

    initPopTwo

Super practical parameter: backDismiss

  • This parameter is set to true by default, and the dialog will be closed by default when returning; if it is set to false, the page will not be closed
    • In this way, an emergency dialog can be made very easily, prohibiting the user's next operation
  • Let’s look at a scenario: Suppose an open source author decides to abandon the software and does not allow users to use the software’s dialog

hardClose

Set Global Parameters

you can modify the global parameters that meet your own requirements

SmartDialog.config
    ..alignment = Alignment.center
    ..isPenetrate = false
    ..clickBgDismiss = true
    ..maskColor = Colors.black.withOpacity(0.3)
    ..maskWidget = null
    ..animationDuration = Duration(milliseconds: 260)
    ..isUseAnimation = true
    ..isLoading = true;

Toast Chapter

The particularity of toast

Strictly speaking, toast is a very special dialog, I think it should have the following characteristics

Toast messages should be displayed one by one, and subsequent messages should not top off the previous toast

  • This is a pit point. If the frame is not processed inside, it is easy to cause the back toast to directly top off the front toast.

toastOne

Displayed on the top level of the page, should not be blocked by some other dialog

  • You can find layouts such as loading and dialog masks, none of which obscures the toast information

toastTwo

Handle the occlusion of the keyboard

  • The keyboard is a bit tricky, it will directly obscure all layouts
    • when the keyboard is awakened, toast will dynamically adjust the distance between itself and the bottom of the screen
  • This will have the effect that the keyboard will not block the toast

toastSmart

Custom Toast

  • First, a custom toast
class CustomToast extends StatelessWidget {
  const CustomToast(this.msg, {Key? key}): super(key: key);

  final String msg;

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        margin: EdgeInsets.only(bottom: 30),
        padding: EdgeInsets.symmetric(horizontal: 20, vertical: 7),
        decoration: BoxDecoration(
          color: _randomColor(),
          borderRadius: BorderRadius.circular(100),
        ),
        child: Row(mainAxisSize: MainAxisSize.min, children: [
          //icon
          Container(
            margin: EdgeInsets.only(right: 15),
            child: Icon(Icons.add_moderator, color: _randomColor()),
          ),

          //msg
          Text('$msg', style: TextStyle(color: Colors.white)),
        ]),
      ),
    );
  }

  Color _randomColor() {
    return Color.fromRGBO(
      Random().nextInt(256),
      Random().nextInt(256),
      Random().nextInt(256),
      1,
    );
  }
}
  • use
SmartDialog.showToast('', widget: CustomToast('custom toast'));
  • Effect

toastCustom

Loading Chapter

Parameter Description

  • maskWidgetTemp: powerful mask customization function😆, use your brain. . .
var maskWidget = Container(
  width: double.infinity,
  height: double.infinity,
  child: Opacity(
    opacity: 0.6,
    child: Image.network(
      'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101103911.jpeg',
      fit: BoxFit.fill,
    ),
  ),
);
SmartDialog.showLoading(maskWidgetTemp: maskWidget);

loadingOne

  • maskColorTemp: support quick custom mask color
SmartDialog.showLoading(maskColorTemp: randomColor().withOpacity(0.3));

/// random color
Color randomColor() => Color.fromRGBO(
    Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);

loadingTwo

  • background: support loading background customization
SmartDialog.showLoading(background: randomColor());

/// random color
Color randomColor() => Color.fromRGBO(
    Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);

loadingThree

  • isLoadingTemp: Animation effect switch
SmartDialog.showLoading(isLoadingTemp: false);

loadingFour

  • isPenetrateTemp: Interaction events can penetrate the mask, which is a very useful function, which is very important for some special demand scenes
SmartDialog.showLoading(isPenetrateTemp: true);

loadingFive

Custom Loading

Use showLoading to easily customize the powerful loading dialog; I have limited brains, just demonstrate it briefly

Customize a loading layout

class CustomLoading extends StatefulWidget {
  const CustomLoading({Key? key, this.type = 0}): super(key: key);

  final int type;

  @override
  _CustomLoadingState createState() => _CustomLoadingState();
}

class _CustomLoadingState extends State<CustomLoading>
    with TickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(
      duration: const Duration(milliseconds: 800),
      vsync: this,
    );
    _controller.forward();
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reset();
        _controller.forward();
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      // smile
      Visibility(visible: widget.type == 0, child: _buildLoadingOne()),

      // icon
      Visibility(visible: widget.type == 1, child: _buildLoadingTwo()),

      // normal
      Visibility(visible: widget.type == 2, child: _buildLoadingThree()),
    ]);
  }

  Widget _buildLoadingOne() {
    return Stack(alignment: Alignment.center, children: [
      RotationTransition(
        alignment: Alignment.center,
        turns: _controller,
        child: Image.network(
          'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101174606.png',
          height: 110,
          width: 110,
        ),
      ),
      Image.network(
        'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101181404.png',
        height: 60,
        width: 60,
      ),
    ]);
  }

  Widget _buildLoadingTwo() {
    return Stack(alignment: Alignment.center, children: [
      Image.network(
        'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101162946.png',
        height: 50,
        width: 50,
      ),
      RotationTransition(
        alignment: Alignment.center,
        turns: _controller,
        child: Image.network(
          'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101173708.png',
          height: 80,
          width: 80,
        ),
      ),
    ]);
  }

  Widget _buildLoadingThree() {
    return Center(
      child: Container(
        height: 120,
        width: 180,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(15),
        ),
        alignment: Alignment.center,
        child: Column(mainAxisSize: MainAxisSize.min, children: [
          RotationTransition(
            alignment: Alignment.center,
            turns: _controller,
            child: Image.network(
              'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211101163010.png',
              height: 50,
              width: 50,
            ),
          ),
          Container(
            margin: EdgeInsets.only(top: 20),
            child: Text('loading...'),
          ),
        ]),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Let's see the effect

  • Effect one
SmartDialog.showLoading(isLoadingTemp: false, widget: CustomLoading());
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingSmile

  • Effect two
SmartDialog.showLoading(
    isLoadingTemp: false,
    widget: CustomLoading(type: 1),
);
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingIcon

  • Effect three
SmartDialog.showLoading(widget: CustomLoading(type: 2));
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();

loadingNormal

Dialog

Fancy

  • alignmentTemp: The animation effect will be different if the parameter setting is different
var location = ({
    double width = double.infinity,
    double height = double.infinity,
}) {
    return Container(width: width, height: height, color: randomColor());
};

//left
SmartDialog.show(
    widget: location(width: 50),
    alignmentTemp: Alignment.centerLeft,
);
await Future.delayed(Duration(milliseconds: 500));
//top
SmartDialog.show(
    widget: location(height: 50),
    alignmentTemp: Alignment.topCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//right
SmartDialog.show(
    widget: location(width: 50),
    alignmentTemp: Alignment.centerRight,
);
await Future.delayed(Duration(milliseconds: 500));
//bottom
SmartDialog.show(
    widget: location(height: 50),
    alignmentTemp: Alignment.bottomCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//center
SmartDialog.show(
    widget: location(height: 100, width: 100),
    alignmentTemp: Alignment.center,
    isLoadingTemp: false,
);

dialogLocation

  • isPenetrateTemp: Interaction event penetration mask
SmartDialog.show(
    alignmentTemp: Alignment.centerRight,
    isPenetrateTemp: true,
    clickBgDismissTemp: false,
    widget: Container(
        width: 80,
        height: double.infinity,
        color: randomColor(),
    ),
);

dialogPenetrate

dialog stack

  • This is a powerful and useful feature!
    • You can easily close a dialog at a fixed point
var stack = ({
  double width = double.infinity,
  double height = double.infinity,
  String? msg,
}) {
  return Container(
    width: width,
    height: height,
    color: randomColor(),
    alignment: Alignment.center,
    child: Text('pop window$msg', style: TextStyle(color: Colors.white)),
  );
};

//left
SmartDialog.show(
  tag:'A',
  widget: stack(msg:'A', width: 60),
  alignmentTemp: Alignment.centerLeft,
);
await Future.delayed(Duration(milliseconds: 500));
//top
SmartDialog.show(
  tag:'B',
  widget: stack(msg:'B', height: 60),
  alignmentTemp: Alignment.topCenter,
);
await Future.delayed(Duration(milliseconds: 500));
//right
SmartDialog.show(
  tag:'C',
  widget: stack(msg:'C', width: 60),
  alignmentTemp: Alignment.centerRight,
);
await Future.delayed(Duration(milliseconds: 500));
//bottom
SmartDialog.show(
  tag:'D',
  widget: stack(msg:'D', height: 60),
  alignmentTemp: Alignment.bottomCenter,
);
await Future.delayed(Duration(milliseconds: 500));

//center: the stack handler
SmartDialog.show(
  alignmentTemp: Alignment.center,
  isLoadingTemp: false,
  widget: Container(
    decoration: BoxDecoration(
        color: Colors.white, borderRadius: BorderRadius.circular(15)),
    padding: EdgeInsets.symmetric(horizontal: 30, vertical: 20),
    child: Wrap(spacing: 20, children: [
      ElevatedButton(
        child: Text('Close popup A'),
        onPressed: () => SmartDialog.dismiss(tag:'A'),
      ),
      ElevatedButton(
        child: Text('Close popup B'),
        onPressed: () => SmartDialog.dismiss(tag:'B'),
      ),
      ElevatedButton(
        child: Text('Close the pop- up window C'),
        onPressed: () => SmartDialog.dismiss(tag:'C'),
      ),
      ElevatedButton(
        child: Text('Close pop- up window D'),
        onPressed: () => SmartDialog.dismiss(tag:'D'),
      ),
    ]),
  ),
);

dialogStack

Little tricks of anger

There is a scene that compares the egg cone

  • We encapsulated a small component using StatefulWidget
  • In a special situation, we need to trigger a method inside this component outside the component
  • There are many implementation methods for this kind of scene, but it may be a little troublesome to make it

Here is a simple idea, which can be triggered very easily, a method inside the component

  • Create a widget
class OtherTrick extends StatefulWidget {
  const OtherTrick({Key? key, this.onUpdate}): super(key: key);

  final Function(VoidCallback onInvoke)? onUpdate;

  @override
  _OtherTrickState createState() => _OtherTrickState();
}

class _OtherTrickState extends State<OtherTrick> {
  int _count = 0;

  @override
  void initState() {
    // here
    widget.onUpdate?.call(() {
      _count++;
      setState(() {});
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(10),
        ),
        child: Text('Counter: $_count', style: TextStyle(fontSize: 30.0)),
      ),
    );
  }
}
  • Show this component and then trigger it externally
VoidCallback? callback;

// display
SmartDialog.show(
  alignmentTemp: Alignment.center,
  widget: OtherTrick(
    onUpdate: (VoidCallback onInvoke) => callback = onInvoke,
  ),
);

await Future.delayed(Duration(milliseconds: 500));

// handler
SmartDialog.show(
  alignmentTemp: Alignment.centerRight,
  maskColorTemp: Colors.transparent,
  widget: Container(
    height: double.infinity,
    width: 150,
    color: Colors.white,
    alignment: Alignment.center,
    child: ElevatedButton(
      child: Text('add'),
      onPressed: () => callback?.call(),
    ),
  ),
);
  • Let's see the effect

trick