Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating Resizable View that resizes when pinch or drag from corners and sides in FLUTTER

enter image description here

I am currently working on a ScreenView with features like draggable and resizable views with corners and sides like in the image above. The problem I have now is that I want to resize the view by touch gestures in the corners. Therefore, I thought of a Point which I add to a view on selection, which can be dragged to resize the selected view. Answer updated!!

Resizable-Widget ReactNative Demo: React Native PLUGIN example

Modified Workable Example:

  import 'package:flutter/material.dart';

  class ResizeWidget extends StatefulWidget {
    @override
    _ResizeWidgetState createState() => _ResizeWidgetState();
  }

  class _ResizeWidgetState extends State<ResizeWidget> {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        home: Scaffold(
          backgroundColor: Colors.black,
          body: Container(
            // padding: EdgeInsets.only(top: 50),
            child: ResizebleWidget(
              child: Container(
                padding: EdgeInsets.all(10),
                child: Text(
                  'Waao!! you can really dance.',
                  style: TextStyle(
                      color: Colors.white,
                      fontStyle: FontStyle.italic,
                      fontSize: 18),
                ),
              ),
            ),
          ),
        ),
      );
    }
  }

  class ResizebleWidget extends StatefulWidget {
    ResizebleWidget({this.child});

    final Widget child;
    @override
    _ResizebleWidgetState createState() => _ResizebleWidgetState();
  }

  const ballDiameter = 10.0;

  class _ResizebleWidgetState extends State<ResizebleWidget> {
    double height = 100;
    double width = 200;
    bool isCorner = false;

    double top = 0;
    double left = 0;

    @override
    Widget build(BuildContext context) {
      return Stack(
        children: <Widget>[
          Positioned(
            top: top,
            left: left,
            child: Container(
              height: height,
              width: width,

              decoration: BoxDecoration(
                color: Colors.blueGrey,
                border: Border.all(
                  width: 2,
                  color: Colors.white70,
                ),
                borderRadius: BorderRadius.circular(0.0),
              ),

              // need tp check if draggable is done from corner or sides
              child: isCorner
                  ? FittedBox(
                      child: widget.child,
                    )
                  : Center(
                      child: widget.child,
                    ),
            ),
          ),
          // top left
          Positioned(
            top: top - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + dy) / 2;
                var newHeight = height - 2 * mid;
                var newWidth = width - 2 * mid;

                setState(() {
                  isCorner = true;
                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top + mid;
                  left = left + mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // top middle
          Positioned(
            top: top - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newHeight = height - dy;

                setState(() {
                  isCorner = false;

                  height = newHeight > 0 ? newHeight : 0;
                  top = top + dy;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // top right
          Positioned(
            top: top - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + (dy * -1)) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;
                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // center right
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newWidth = width + dx;

                setState(() {
                  isCorner = false;

                  width = newWidth > 0 ? newWidth : 0;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // bottom right
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left + width - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = (dx + dy) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;

                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          // bottom center
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newHeight = height + dy;

                setState(() {
                  isCorner = false;

                  height = newHeight > 0 ? newHeight : 0;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // bottom left
          Positioned(
            top: top + height - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var mid = ((dx * -1) + dy) / 2;

                var newHeight = height + 2 * mid;
                var newWidth = width + 2 * mid;

                setState(() {
                  isCorner = true;

                  height = newHeight > 0 ? newHeight : 0;
                  width = newWidth > 0 ? newWidth : 0;
                  top = top - mid;
                  left = left - mid;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
          //left center
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                var newWidth = width - dx;

                setState(() {
                  isCorner = false;

                  width = newWidth > 0 ? newWidth : 0;
                  left = left + dx;
                });
              },
              handlerWidget: HandlerWidget.HORIZONTAL,
            ),
          ),
          // center center
          Positioned(
            top: top + height / 2 - ballDiameter / 2,
            left: left + width / 2 - ballDiameter / 2,
            child: ManipulatingBall(
              onDrag: (dx, dy) {
                setState(() {
                  isCorner = false;

                  top = top + dy;
                  left = left + dx;
                });
              },
              handlerWidget: HandlerWidget.VERTICAL,
            ),
          ),
        ],
      );
    }
  }

  class ManipulatingBall extends StatefulWidget {
    ManipulatingBall({Key key, this.onDrag, this.handlerWidget});

    final Function onDrag;
    final HandlerWidget handlerWidget;

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

  enum HandlerWidget { HORIZONTAL, VERTICAL }

  class _ManipulatingBallState extends State<ManipulatingBall> {
    double initX;
    double initY;

    _handleDrag(details) {
      setState(() {
        initX = details.globalPosition.dx;
        initY = details.globalPosition.dy;
      });
    }

    _handleUpdate(details) {
      var dx = details.globalPosition.dx - initX;
      var dy = details.globalPosition.dy - initY;
      initX = details.globalPosition.dx;
      initY = details.globalPosition.dy;
      widget.onDrag(dx, dy);
    }

    @override
    Widget build(BuildContext context) {
      return GestureDetector(
        onPanStart: _handleDrag,
        onPanUpdate: _handleUpdate,
        child: Container(
          width: ballDiameter,
          height: ballDiameter,
          decoration: BoxDecoration(
            color: Colors.white,
            shape: this.widget.handlerWidget == HandlerWidget.VERTICAL
                ? BoxShape.circle
                : BoxShape.rectangle,
          ),
        ),
      );
    }
  }

Output:

enter image description here

like image 464
jazzbpn Avatar asked Mar 30 '20 05:03

jazzbpn


People also ask

How do you resize a flutter widget depending on screen size?

You can use LayoutBuilder to fetch the parent widget's dimension and adjust the children widgets' dimensions to fit. From your sample, I've added height and width parameter requirement to create calendarCarousel widgets. The height and width parameters used on the calendar widgets came from LayoutBuilder .

How do you use draggable in flutter?

Draggable is a Flutter widget that you can drag or move around. As soon as the user click and starts dragging the Draggable widget, a new feedback widget appears and follows the user's finger or mouse pointer. When the user lifts the finger or mouse pointer, the feedback widget disappears.


2 Answers

Updated

I've made a simple prototype to show the idea.

  1. Draw size handlers and a container;
  2. Use GestureDetector to detect dragging;
  3. Refresh the main container size and coordinates.

enter image description here

    import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Overflow Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Demo(),
      ),
    );
  }
}

class Demo extends StatefulWidget {
  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(60),
      child: ResizebleWidget(
        child: Text(
'''I've just did simple prototype to show main idea.
  1. Draw size handlers with container;
  2. Use GestureDetector to get new variables of sizes
  3. Refresh the main container size.''',
        ),
      ),
    );
  }
}

class ResizebleWidget extends StatefulWidget {
  ResizebleWidget({this.child});

  final Widget child;
  @override
  _ResizebleWidgetState createState() => _ResizebleWidgetState();
}

const ballDiameter = 30.0;

class _ResizebleWidgetState extends State<ResizebleWidget> {
  double height = 400;
  double width = 200;

  double top = 0;
  double left = 0;

  void onDrag(double dx, double dy) {
    var newHeight = height + dy;
    var newWidth = width + dx;

    setState(() {
      height = newHeight > 0 ? newHeight : 0;
      width = newWidth > 0 ? newWidth : 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: top,
          left: left,
          child: Container(
            height: height,
            width: width,
            color: Colors.red[100],
            child: widget.child,
          ),
        ),
        // top left
        Positioned(
          top: top - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;
              var newHeight = height - 2 * mid;
              var newWidth = width - 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top + mid;
                left = left + mid;
              });
            },
          ),
        ),
        // top middle
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height - dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                top = top + dy;
              });
            },
          ),
        ),
        // top right
        Positioned(
          top: top - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + (dy * -1)) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // center right
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width + dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
              });
            },
          ),
        ),
        // bottom right
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = (dx + dy) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        // bottom center
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newHeight = height + dy;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
              });
            },
          ),
        ),
        // bottom left
        Positioned(
          top: top + height - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var mid = ((dx * -1) + dy) / 2;

              var newHeight = height + 2 * mid;
              var newWidth = width + 2 * mid;

              setState(() {
                height = newHeight > 0 ? newHeight : 0;
                width = newWidth > 0 ? newWidth : 0;
                top = top - mid;
                left = left - mid;
              });
            },
          ),
        ),
        //left center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              var newWidth = width - dx;

              setState(() {
                width = newWidth > 0 ? newWidth : 0;
                left = left + dx;
              });
            },
          ),
        ),
        // center center
        Positioned(
          top: top + height / 2 - ballDiameter / 2,
          left: left + width / 2 - ballDiameter / 2,
          child: ManipulatingBall(
            onDrag: (dx, dy) {
              setState(() {
                top = top + dy;
                left = left + dx;
              });
            },
          ),
        ),
      ],
    );
  }
}

class ManipulatingBall extends StatefulWidget {
  ManipulatingBall({Key key, this.onDrag});

  final Function onDrag;

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

class _ManipulatingBallState extends State<ManipulatingBall> {
  double initX;
  double initY;

  _handleDrag(details) {
    setState(() {
      initX = details.globalPosition.dx;
      initY = details.globalPosition.dy;
    });
  }

  _handleUpdate(details) {
    var dx = details.globalPosition.dx - initX;
    var dy = details.globalPosition.dy - initY;
    initX = details.globalPosition.dx;
    initY = details.globalPosition.dy;
    widget.onDrag(dx, dy);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _handleDrag,
      onPanUpdate: _handleUpdate,
      child: Container(
        width: ballDiameter,
        height: ballDiameter,
        decoration: BoxDecoration(
          color: Colors.blue.withOpacity(0.5),
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}
like image 103
Kherel Avatar answered Oct 22 '22 04:10

Kherel


I did a discrete version (ie snaps every 50 units) of the code above if it helps anyone:

    import 'package:flutter/material.dart';
    
    class DiscreteResizableComponent extends StatefulWidget {
      const DiscreteResizableComponent({Key key, this.child}):super(key:key);
    
      final Widget child;
      @override
      _ResizebleWidgetState createState() => _ResizebleWidgetState();
    }
    
    const ballDiameter = 30.0;
    const discreteStepSize = 50;
    
    class _ResizebleWidgetState extends State<DiscreteResizableComponent> {
      double height = 400;
      double width = 200;
    
      double top = 0;
      double left = 0;
    
      double cumulativeDy=0;
      double cumulativeDx=0;
      double cumulativeMid = 0;
    
      void onDrag(double dx, double dy) {
        var newHeight = height + dy;
        var newWidth = width + dx;
    
        setState(() {
          height = newHeight > 0 ? newHeight : 0;
          width = newWidth > 0 ? newWidth : 0;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Positioned(
              top: top,
              left: left,
              child: Container(
                height: height,
                width: width,
                color: Colors.red[100],
                child: widget.child,
              ),
            ),
            // top left
            Positioned(
              top: top - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + dy) / 2;
                  cumulativeMid -= 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // top middle
            Positioned(
              top: top - ballDiameter / 2,
              left: left + width / 2 - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDy -= dy;
                  if(cumulativeDy>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      cumulativeDy=0;
                    });
                  }
                  else if(cumulativeDy<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      cumulativeDy=0;
                    });
                  }
                },
              ),
            ),
            // top right
            Positioned(
              top: top - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + (dy * -1)) / 2;
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // center right
            Positioned(
              top: top + height / 2 - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDx += dx;
    
                  if(cumulativeDx>=discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width+discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                  else if(cumulativeDx<=-discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width-discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                },
              ),
            ),
            // bottom right
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left + width - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = (dx + dy) / 2;
    
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            // bottom center
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left + width / 2 - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDy += dy;
    
                  if(cumulativeDy>=discreteStepSize)
                    {
                      setState(() {
                        var newHeight = height+discreteStepSize;
                        height = newHeight > 0 ? newHeight : 0;
                        cumulativeDy=0;
                      });
                    }
                  else if(cumulativeDy<=-discreteStepSize)
                    {
                      setState(() {
                        var newHeight = height-discreteStepSize;
                        height = newHeight > 0 ? newHeight : 0;
                        cumulativeDy=0;
                      });
                    }
                },
              ),
            ),
            // bottom left
            Positioned(
              top: top + height - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  var mid = ((dx * -1) + dy) / 2;
    
                  cumulativeMid += 2*mid;
                  if(cumulativeMid>=discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height+discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width +discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                  else if(cumulativeMid<=-discreteStepSize)
                  {
                    setState(() {
                      var newHeight = height - discreteStepSize;
                      height = newHeight > 0 ? newHeight : 0;
                      var newWidth = width - discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeMid=0;
                    });
                  }
                },
              ),
            ),
            //left center
            Positioned(
              top: top + height / 2 - ballDiameter / 2,
              left: left - ballDiameter / 2,
              child: ManipulatingBall(
                onDrag: (dx, dy) {
                  cumulativeDx -= dx;
    
                  if(cumulativeDx>=discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width+discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                  else if(cumulativeDx<=-discreteStepSize)
                  {
                    setState(() {
                      var newWidth = width-discreteStepSize;
                      width = newWidth > 0 ? newWidth : 0;
                      cumulativeDx=0;
                    });
                  }
                },
              ),
            ),
          ],
        );
      }
    }
    
    class ManipulatingBall extends StatefulWidget {
      ManipulatingBall({Key key, this.onDrag});
    
      final Function onDrag;
    
      @override
      _ManipulatingBallState createState() => _ManipulatingBallState();
    }
    
    class _ManipulatingBallState extends State<ManipulatingBall> {
      double initX;
      double initY;
    
      _handleDrag(details) {
        setState(() {
          initX = details.globalPosition.dx;
          initY = details.globalPosition.dy;
        });
      }
    
      _handleUpdate(details) {
        var dx = details.globalPosition.dx - initX;
        var dy = details.globalPosition.dy - initY;
        initX = details.globalPosition.dx;
        initY = details.globalPosition.dy;
        widget.onDrag(dx, dy);
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onPanStart: _handleDrag,
          onPanUpdate: _handleUpdate,
          child: Container(
            width: ballDiameter,
            height: ballDiameter,
            decoration: BoxDecoration(
              color: Colors.blue.withOpacity(0.5),
              shape: BoxShape.circle,
            ),
          ),
        );
      }
    }
like image 32
Seth Kitchen Avatar answered Oct 22 '22 05:10

Seth Kitchen