This time, solve the various pain points of fluent dialog!

Posted by noiseusse on Wed, 03 Nov 2021 03:33:33 +0100

preface

Q: What's the most smelly thing you've ever smelled in your life?

A: My rotten dream.

Brother Meng!!! I'm here again!

This time, I can confidently say to you: I finally brought you a pub package that can really help you solve many pit ratio scenes!

Put the previous shuttle_ smart_ Dialog, on the basis of maintaining the stability of the api, has carried out various scratch head reconfigurations and solved a series of problems

Now, I can finally say: it is now a simple, powerful and low intrusive pub package!

On invasive issues

  • Before, in order to solve the problem of closing the pop-up window, a very inelegant solution was used, resulting in a little high invasiveness
  • This really makes me on pins and needles, like a thorn in my back, like a lump in my throat. This problem has finally been solved!

At the same time, I designed a pop-up stack inside the pub package, which can automatically remove the pop-up window at the top of the stack or remove the marked pop-up window in the stack at a fixed point.

problem

Using the system pop-up window, there are a series of pits to discuss with you

  • BuildContext must be passed

    • In some scenes, we must do more communication and reference work, which is a painful but not difficult problem
  • loading Popup

    • Using the system pop-up window as the loading pop-up window, I must have encountered this pit ratio problem

      • Loading is encapsulated in the network library: when loading is requested for the network, press the return button and close loading
      • Then after the request, I found: why is my page closed!!!
    • The system pop-up window is a routing page. The pop method is used to close the system, which is easy to close the normal page by mistake

      • Of course, there must be a solution. There is no detailed table here
  • Multiple system dialogs pop up on a page. It is difficult to close a non stack pop-up window at a fixed point

    • Eggs, which are caused by routing stack, make complaints about them.
  • In the system Dialog, the click event cannot penetrate the dark background

    • I really can't do anything about this pit ratio problem

reflection

The above lists some common problems. The most serious problem should be the problem of loading

  • Loading is a pop-up window for UHF use. The method of closing the loading pop-up window can also close the normally used pages, which is a hidden danger in itself
  • This food dog doesn't have the ability of big factory bosses to change flutter. The food wants to change. I can only cut in from other directions and seek solutions

The Page of the system is implemented based on Overlay. We should also start with Overlay

This time, I want to help you solve it at one time: toast message, loading pop-up window, and more powerful custom dialog!

Get started quickly

initialization

dependencies:
  flutter_smart_dialog: ^3.0.0

Initialization method 1: strongly recommended 😃

  • More concise configuration
void main() => runApp(MyApp());

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

Initialization mode 2: compatible with the old version ❤

  • The old version initialization is still valid, the difference is: you need to call FlutterSmartDialog.monitor() before loading MaterialApp.
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // here
    FlutterSmartDialog.monitor();
    return MaterialApp(
      home: SmartDialogPage(),
      // here
      navigatorObservers: [FlutterSmartDialog.observer],
      /// here
      builder: (BuildContext context, Widget? child) {
        return FlutterSmartDialog(child: child);
      },
    );
  }
}

be accomplished 🚀

Select one of the above two initialization methods; Then, you can fully use all the functions of this library

I highly recommend the first initialization method because it is concise enough; Simple and clear things will make people feel happy when used 🌞

Minimalist use

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

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

  • 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);

OK, as shown above, you can call the corresponding functions with very little code

Of course, there are many special optimizations in the interior. Next, I will describe them in detail

You may have questions

When initializing the framework, I felt very guilty for letting everyone write one more parameter than before 😩

Closing a page is essentially a complex situation involving

  • Physical return key
  • back button of AppBar
  • Manual pop

In order to monitor these conditions, a route monitoring parameter has to be added

About FlutterSmartDialog.init()

This method will not occupy your builder parameters. The init internal callback comes out of the builder. You can continue to set it boldly and safely

  • For example: continue to set the global instance of Bloc 😄
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      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

Monitoring the return button is very important and can basically cover most situations

pop routing

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

  • Without FlutterSmartDialog.observer

    • If the penetration parameter is turned on (you can interact with the page after the pop-up window), then close the page manually
    • There will be such an embarrassing situation

  • With FlutterSmartDialog.observer, it can be handled reasonably

    • Of course, the transition animation here also provides parameters to control whether it is turned on ❤

Super practical parameter: backdisassiss

  • This parameter is set to true by default, and the pop-up window will be closed by default when returning; If set to false, the page will not be closed

    • In this way, you can easily make an emergency pop-up window to prohibit 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 pop-up window of the software
SmartDialog.show(
  // here
  backDismiss: false,
  clickBgDismissTemp: false,
  isLoadingTemp: false,
  widget: Container(
    height: 480,
    width: 500,
    padding: EdgeInsets.all(20),
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(20),
      color: Colors.white,
    ),
    alignment: Alignment.topCenter,
    child: SingleChildScrollView(
      child: Wrap(
        direction: Axis.vertical,
        crossAxisAlignment: WrapCrossAlignment.center,
        spacing: 10,
        children: [
          // title
          Text(
            'Extraordinary announcement',
            style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
          ),
          // content
          Text('I studied the following secret script day and night and finally succeeded in catching a rich woman'),
          Image.network(
            'https://cdn.jsdelivr.net/gh/xdd666t/MyData@master/pic/flutter/blog/20211102213746.jpeg',
            height: 200,
            width: 400,
          ),
          Text('I thought for three seconds, with\'heavy\'I decided to abandon the open source software'),
          Text('My future life is a rich woman and far away, no longer\'energy\' Maintain this open source software'),
          Text('I'll see you in the Jianghu!'),
          // button (only method of close the dialog)
          ElevatedButton(
            onPressed: () => SmartDialog.dismiss(),
            child: Text('Goodbye!'),
          )
        ],
      ),
    ),
  ),
);

As can be seen from the effect drawing above

  • Click the mask to close the pop-up window
  • Click the back button to close the pop-up window
  • We can only click our own button to close the pop-up window. The logic of clicking the button can be directly written as closing app and so on

It only needs two simple parameter settings to realize such a great emergency pop-up window

Set global parameters

The global parameters of SmartDialog have a reasonable default value

In order to cope with changeable scenarios, you can modify the global parameters to meet your own requirements

  • Set the data that meets your requirements, put it in the app entry, and initialize it

    • Note: if there are no special requirements, you can completely avoid initializing global parameters
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;
  • The code comments are well written. If you don't understand a parameter, just click in and have a look

Toast

Particularity of toast

Strictly speaking, toast is a very special pop-up window. 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. If the interior of the frame is not handled, it is easy to see that the rear toast will directly top the front toast

It is displayed at the top of the page and should not be obscured by some pop-up windows

  • It can be found that the masks of loading and dialog do not obscure the toast information

Handle the occlusion of the keyboard

  • The keyboard is a bit of a pit. It will directly block all the layouts and can only save the country by curving

    • A special treatment is made here. When the keyboard is aroused, toast will dynamically adjust the distance between itself and the bottom of the screen
    • In this way, the keyboard will not block the toast effect

Customize Toast

Parameter description

toast does not expose many parameters, only four parameters are provided

  • For example: Toast font size, font color, toast background color, etc. I didn't provide parameters

    • First, I feel that providing these parameters will make the whole parameter input very much, and the confusion will gradually become charming
    • Second, I feel that even if I provide many parameters, it will not necessarily meet those strange aesthetic and needs
  • Based on the above considerations, I directly provide the underlying parameters and directly provide the widget parameters

    • You can customize the toast as you like
    • Note: if widget parameters are used, msg and alignment parameters will become invalid

Adjust the position of toast display

SmartDialog.showToast('the toast at the bottom');
SmartDialog.showToast('the toast at the center', alignment: Alignment.center);
SmartDialog.showToast('the toast at the top', alignment: Alignment.topCenter);

More powerful custom toast

  • First, the whole 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

Loading article

Pit avoidance Guide

  • After loading is enabled, you can close it in the following ways

    • Smartdialog. Disass(): you can close loading and dialog
    • status is set to SmartStatus.loading: just turn off loading
// easy close
SmartDialog.dismiss();
// exact close
SmartDialog.dismiss(status: SmartStatus.loading);
  • Generally speaking, the loading pop-up window is encapsulated in the network library and automatically opens and closes with the request status

    • Based on this scenario, I suggest that when using dismis, add the status parameter and set it to SmartStatus.loading
  • Pit ratio scene

    • When the network requests to load, loading is also opened. At this time, it is easy to touch the return button by mistake to close loading
    • When the network request ends, the dismiss method is automatically called
    • Because loading has been closed, assuming that there is a SmartDialog pop-up window on the page at this time, disass without status will close the SmartDialog pop-up window
    • Of course, this situation is easy to solve. For loading encapsulated in the network library, use: smartdialog. Dismis (status: smartstatus. Loading); Just close it
  • The status parameter is designed to accurately close the pop-up window of the corresponding type. It can play a great role in some special scenarios

    • If you understand the meaning of this parameter, you will know when to add the status parameter

Parameter description

The parameters are written in the notes in great detail, so I won't repeat them. Let's see the effect

  • maskWidgetTemp: powerful mask customization 😆, Play your brain hole...
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);

  • maskColorTemp: supports quick custom mask colors
SmartDialog.showLoading(maskColorTemp: randomColor().withOpacity(0.3));

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

  • Background: supports loading custom background
SmartDialog.showLoading(background: randomColor());

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

  • isLoadingTemp: animation effect Toggle
SmartDialog.showLoading(isLoadingTemp: false);

  • isPenetrateTemp: interactive events can penetrate the mask, which is a very useful function and is key to some special demand scenarios
SmartDialog.showLoading(isPenetrateTemp: true);

Custom Loading

Using showLoading, you can easily customize a powerful loading pop-up window; My brain hole is limited. Let's simply demonstrate it

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();

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

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

Dialog

garish

The pop-up window pops up from different positions, and the animation is different

  • alignmentTemp: the animation effect will be different with different parameter settings
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,
);

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

dialog stack

  • This is a powerful and practical function!

    • You can easily close a pop-up window 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('Popup $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 pop-up window A'),
        onPressed: () => SmartDialog.dismiss(tag: 'A'),
      ),
      ElevatedButton(
        child: Text('Close pop-up window B'),
        onPressed: () => SmartDialog.dismiss(tag: 'B'),
      ),
      ElevatedButton(
        child: Text('Close pop-up window C'),
        onPressed: () => SmartDialog.dismiss(tag: 'C'),
      ),
      ElevatedButton(
        child: Text('Close pop-up window D'),
        onPressed: () => SmartDialog.dismiss(tag: 'D'),
      ),
    ]),
  ),
);

Coquettish tips

There is a scene that compares egg cones

  • We use stateful widget to encapsulate a small component
  • In a special case, we need to trigger a method inside the component outside the component
  • There are many implementation methods for this scenario, but it may be a little troublesome

Here is a simple idea, which can easily trigger 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 the component and 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

last

Relevant address

Alas, people always move forward in constant confusion...

Topics: Front-end iOS Android Flutter dialog