This package adds CustomRefreshIndicator widget that allows you to create whatever indicator you want.

Overview

Cover image

Custom Refresh Indicator

Tests

A flutter package that allows you to easily create a custom refresh indicator widget.

TLDR; ONLINE DEMO!


QUICK START

CustomRefreshIndicator(
  /// Scrollable widget
  child: ListView.separated(
    itemBuilder: (BuildContext context, int index) => const SizedBox(
      height: 100,
    ),
    separatorBuilder: (BuildContext context, int index) =>
        const SizedBox(height: 20),
  ),
  /// Custom indicator builder function
  builder: (
    BuildContext context,
    Widget child,
    IndicatorController controller,
    ) {
      /// TODO: Implement your own refresh indicator
      return Stack(
        children: <Widget>[
          AnimatedBuilder(
            animation: controller,
            builder: (BuildContext context, _) {
              /// This part will be rebuild on every controller change
              return MyIndicator();
            },
          ),
          /// Scrollable widget that was provided as [child] argument
          ///
          /// TIP:
          /// You can also wrap [child] with [Transform] widget to also a animate list transform (see example app)
          child,
        ],
      );
    }
  /// A function that's called when the user has dragged the refresh indicator
  /// far enough to demonstrate that they want the app to refresh.
  /// Should return [Future].
  onRefresh: myAsyncRefreshMethod,
)

Examples

Almost all of these examples are available in the example application.

Plane indicator [SOURCE][DEMO] Ice cream [SOURCE][DEMO] Warp [SOURCE][DEMO]
plane_indicator ice_cream_indicator warp_indicator
With complete state [SOURCE][DEMO] Pull to fetch more [SOURCE][DEMO] Envelope [SOURCE][DEMO]
indicator_with_complete_state fetch_more Envelope indicator
Programmatically controlled [SOURCE][DEMO] Your indicator Your indicator
programmatically_controlled Have you created a fancy refresh indicator? This place is for you. Open PR. Have you created a fancy refresh indicator? This place is for you. Open PR.

Documentation

CustomRefreshIndicator widget

The CustomRefreshIndicator widget provides an absolute minimum functionality that allows you to create and set your own custom indicators.

onStateChanged

The onStateChanged callback is called everytime IndicatorState has been changed.
This is a convenient place for tracking indicator state changes. For a reference take a look at the example check mark indicator widget.

Example usage:

CustomRefreshIndicator(
  onRefresh: onRefresh,
  // You can track state changes here.
  onStateChanged: (IndicatorStateChange change) {
    if (change.didChange(from: IndicatorState.dragging, to: IndicatorState.armed)) {
      // Do something...
    } else if(change.didChange(to: IndicatorState.idle)) {
      // Do something...
    }
    // And so on...
  }
  // ...
)

IndicatorController

Controller state and value changes.

The best way to understand how the CustomRefreshIndicator widget changes its controller data is to see the example ๐Ÿ˜‰ . An example is available in the example application.

Controller data example

Online example

state value value description Description
idle ==0.0 Value eqals 0.0. No user action.
dragging =<0.0 Value is eqal 0.0 or larger but lower than 1.0. User is dragging the indicator.
armed >=1.0 Value is larger than 1.0. User dragged the indicator further than the distance declared by extentPercentageToArmed or offsetToArmed. User still keeps the finger on the screen.
loading >=1.0 Value decreses from last armed state value in duration of armedToLoadingDuration argument to 1.0. User finished dragging (took his finger off the screen), when state was equal to armed. onRefresh function is called.
hiding <=1.0 Value decreses in duration of draggingToIdleDuration or loadingToIdleDuration arguments to 0.0. Indicator is hiding after:
- User ended dragging when indicator was in dragging state.
- Future returned from onRefresh function is resolved.
- Complete state ended.
- User started scrolling through the list.
complete ==1.0 Value equals 1.0 for duration of completeStateDuration argument. This state is OPTIONAL, provide completeStateDuration argument with non null value to enable it.
Loading is completed.

Support

If you like this package, you have learned something from it, or you just don't know what to do with your money ๐Ÿ˜… just buy me a cup of coffee โ˜•๏ธ and this dose of caffeine will put a smile on my face which in turn will help me improve this package. Also as a thank you, you will be mentioned in this readme as a sponsor.

Buy Me A Coffee

Have a nice day! ๐Ÿ‘‹

Comments
  • [feature_request] add IndicatorState.completed

    [feature_request] add IndicatorState.completed

    It would be nice to add an optional complete state which is shown after onRefresh has completed but before the indicator is hidden again. One could show for example a checkmark.

    enhancement 
    opened by The-Redhat 6
  • RefreshIndicator not shown because of nested CustomScrollView inside  bodyContent()

    RefreshIndicator not shown because of nested CustomScrollView inside bodyContent()

    Swiping down fails to activate CustomRefreshIndicator. Issue is with CustomScrollView inside the bodyContent(), when it's removed (return Container()) the problem goes away . Any ideas on how to get this to work? SimpleIndicatorContent is a copy/paste from sample project.

    import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
    import 'package:flutter/material.dart';
    import 'package:tester/indicator/simple_indicator.dart';
    
    class Home extends StatefulWidget {
      Home({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _HomeState createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.grey[300],
          body: SafeArea(
            child: CustomRefreshIndicator(
              leadingGlowVisible: false,
              offsetToArmed: 200.0,
              trailingGlowVisible: false,
              builder: customRefreshBuilder(),
              onRefresh: () => Future.delayed(const Duration(seconds: 2)),
              child: NestedScrollView(
                headerSliverBuilder:
                    (BuildContext context, bool innerBoxIsScrolled) {
                  return <Widget>[
                    SliverAppBar(
                      title: Text("Hello World"),
                    ),
                    sliverContent()
                  ];
                },
                body:bodyContent()
    //            body: Container(), <-- Works
    //            ),
              ),
            ),
          ),
        );
      }
    }
    
    customRefreshBuilder() => (context, child, controller) {
      return Stack(
        children: <Widget>[
          child,
          PositionedIndicatorContainer(
            controller: controller,
            child: SimpleIndicatorContent(
              controller: controller,
            ),
          ),
        ],
      );
    };
    
    bodyContent() {
      final List<Widget> entries = <Widget>[
        Text('1'),
        Text('2'),
        Text('3'),
    
      ];
    
      SliverList sliverList = createSliverList(entries);
      return Tooltip(
          message: "Swipe to refresh.",
          child: CustomScrollView(
              scrollDirection: Axis.vertical,
              physics: const AlwaysScrollableScrollPhysics(
                  parent: BouncingScrollPhysics()),
              slivers: <Widget>[SliverToBoxAdapter(), sliverList]));
    }
    
    createSliverList(entries) {
      List<Widget> list = List.from(entries);
      return SliverList(delegate: SliverChildListDelegate(list));
    }
    
    sliverContent() {
      return SliverAppBar(
          automaticallyImplyLeading: false,
          backgroundColor: Colors.white,
          elevation: 0.0,
          expandedHeight: 300.0,
          floating: true,
          snap: true,
          flexibleSpace: FlexibleSpaceBar(
            background: Container(
              child: Column(
                children: <Widget>[
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: <Widget>[
                          textContainer(),
                        ],
                      )
                    ],
                  ),
                  Expanded(
                    child: Container(child: Text("Content")),
                  ),
                  Container(
                    child: Text("Content"),
                  )
                ],
              ),
            ),
          ));
    }
    
    textContainer() {
      return Expanded(
          child: Container(
              margin: EdgeInsets.only(left: 5.0),
              padding: EdgeInsets.only(left: 5.0, top: 5.0, bottom: 5.0),
              child: Text("Content")));
    }
    
    _refresh() async {}
    
    
    bug question 
    opened by willladislaw 5
  • didStateChange never catch IndicatorState.hiding

    didStateChange never catch IndicatorState.hiding

    With current didStateChange implementaion next call controller.didStateChange(from: IndicatorState.loading, to: IndicatorState.hiding) and {controller.didStateChange(to: IndicatorState.hiding) never return true.

    It seams then next bloc:

        controller._setIndicatorState(IndicatorState.hiding);
        await _animationController.animateTo(0.0,
            duration: widget.loadingToIdleDuration);
    

    execute notifyListeners of IndicatorController twice before builder of AnimatedBuilder called.

    bug 
    opened by ycherniavskyi 4
  • Error When rebuilding child in CustomRefresher

    Error When rebuilding child in CustomRefresher

    Hi, first of all, great package.

    Im getting and error when I rebuild the child in CustomRefresher. I'm using a StaggeredGridView(or a ListView) inside the child, but I attach a controller to it as I need to listen to the scroll to know when it reaches the end and add more items to the list. To create an infinite scroll.

    The problem is that when I add those items this error appears:

    โ•โ•โ•โ•โ•โ•โ• Exception caught by foundation library โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    The following assertion was thrown while dispatching notifications for IndicatorController:
    Build scheduled during frame.
    
    While the widget tree was being built, laid out, and painted, a new frame was scheduled to rebuild the widget tree.
    
    This might be because setState() was called from a layout or paint callback. If a change is needed to the widget tree, it should be applied as the tree is being built. Scheduling a change for the subsequent frame instead results in an interface that lags behind by one frame. If this was done to make your build dependent on a size measured at layout time, consider using a LayoutBuilder, CustomSingleChildLayout, or CustomMultiChildLayout. If, on the other hand, the one frame delay is the desired effect, for example because this is an animation, consider scheduling the frame in a post-frame callback using SchedulerBinding.addPostFrameCallback or using an AnimationController to trigger the animation.
    
    When the exception was thrown, this was the stack
    #0      WidgetsBinding._handleBuildScheduled.<anonymous closure>
    package:flutter/โ€ฆ/widgets/binding.dart:783
    #1      WidgetsBinding._handleBuildScheduled
    package:flutter/โ€ฆ/widgets/binding.dart:806
    #2      BuildOwner.scheduleBuildFor
    package:flutter/โ€ฆ/widgets/framework.dart:2587
    #3      Element.markNeedsBuild
    package:flutter/โ€ฆ/widgets/framework.dart:4311
    #4      State.setState
    package:flutter/โ€ฆ/widgets/framework.dart:1264
    ...
    The IndicatorController sending notification was: Instance of 'IndicatorController'
    โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    

    The app doesn't break, but it would be nice to solve it.

    Hope you can shine some light into it. Thanks! ;)

    bug 
    opened by Flucadetena 4
  • Nested ListViews trigger parent refresh indicator

    Nested ListViews trigger parent refresh indicator

    When you have add vertical scrolling list view with nested horizontal scrolling, if you overflow scroll one of the nested ListViews the parent refresh indicator is triggered.

    It would nice to lock the pull to an axis or direction.

    bug enhancement 
    opened by kennethj 3
  • Error with version 1.2.0 and flutter 2.10.5

    Error with version 1.2.0 and flutter 2.10.5

    On Xcode build: : Error: Method 'addPostFrameCallback' cannot be called on 'WidgetsBinding?' because it is potentially null. ../โ€ฆ/src/custom_refresh_indicator.dart:209

    • 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('../../flutter/packages/flutter/lib/src/widgets/binding.dart'). package:flutter/โ€ฆ/widgets/binding.dart:1 Try calling using ?. instead.
    bug 
    opened by veloso11 2
  • often stop unexpectedly [PlaneIndicator + NestedScrollView]

    often stop unexpectedly [PlaneIndicator + NestedScrollView]

    โ•โ•โ•ก EXCEPTION CAUGHT BY FOUNDATION LIBRARYโ•žโ•โ•โ•โ•
    The following assertion was thrown while dispatching notifications for IndicatorController:
    Build scheduled during frame.
    While the widget tree was being built, laid out, and painted, a new frame was scheduled to rebuild
    the widget tree.
    This might be because setState() was called from a layout or paint callback. If a change is needed
    to the widget tree, it should be applied as the tree is being built. Scheduling a change for the
    subsequent frame instead results in an interface that lags behind by one frame. If this was done to
    make your build dependent on a size measured at layout time, consider using a LayoutBuilder,
    CustomSingleChildLayout, or CustomMultiChildLayout. If, on the other hand, the one frame delay is
    the desired effect, for example because this is an animation, consider scheduling the frame in a
    post-frame callback using SchedulerBinding.addPostFrameCallback or using an AnimationController to
    trigger the animation.
    
    When the exception was thrown, this was the stack:
    #0      WidgetsBinding._handleBuildScheduled.<anonymous closure>
    (package:flutter/src/widgets/binding.dart:747:9)
    #1      WidgetsBinding._handleBuildScheduled (package:flutter/src/widgets/binding.dart:770:6)
    #2      BuildOwner.scheduleBuildFor (package:flutter/src/widgets/framework.dart:2434:24)
    #3      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4280:12)
    #4      State.setState (package:flutter/src/widgets/framework.dart:1108:15)
    #5      _AnimatedState._handleChange (package:flutter/src/widgets/transitions.dart:128:5)
    #6      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:308:24)
    #7      IndicatorController.setIndicatorState
    (package:custom_refresh_indicator/src/controller.dart:150:5)
    #8      _CustomRefreshIndicatorState._start
    (package:custom_refresh_indicator/src/custom_refresh_indicator.dart:293:16)
    #9      _CustomRefreshIndicatorState._handleScrollEndNotification
    (package:custom_refresh_indicator/src/custom_refresh_indicator.dart:212:9)
    #10     _CustomRefreshIndicatorState._handleScrollNotification
    (package:custom_refresh_indicator/src/custom_refresh_indicator.dart:283:14)
    #11     Element.visitAncestorElements (package:flutter/src/widgets/framework.dart:4091:39)
    #12     Notification.dispatch (package:flutter/src/widgets/notification_listener.dart:83:13)
    #13     ScrollActivity.dispatchScrollEndNotification
    (package:flutter/src/widgets/scroll_activity.dart:104:63)
    #14     ScrollPosition.didEndScroll (package:flutter/src/widgets/scroll_position.dart:907:15)
    #15     ScrollPosition.beginActivity (package:flutter/src/widgets/scroll_position.dart:876:9)
    #16     _NestedInnerBallisticScrollActivity.applyNewDimensions
    (package:extended_nested_scroll_view/src/extended_nested_scroll_view.dart:1676:14)
    #17     ScrollPosition.applyNewDimensions (package:flutter/src/widgets/scroll_position.dart:623:15)
    #18     _NestedScrollPosition.applyNewDimensions
    (package:extended_nested_scroll_view/src/extended_nested_scroll_view.dart:1631:11)
    #19     _ExtendedNestedScrollPosition.applyNewDimensions
    (package:extended_nested_scroll_view/src/extended_nested_scroll_view_part.dart:261:11)
    #20     ScrollPosition.applyContentDimensions
    (package:flutter/src/widgets/scroll_position.dart:553:7)
    #21     _ExtendedNestedScrollPosition.applyContentDimensions
    (package:extended_nested_scroll_view/src/extended_nested_scroll_view_part.dart:273:18)
    #22     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1493:20)
    #23     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #24     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #25     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #26     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #27     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #28     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #29     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #30     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #31     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #32     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #33     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #34     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #35     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #36     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #37     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #38     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #39     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #40     RenderSliverFixedExtentBoxAdaptor.performLayout
    (package:flutter/src/rendering/sliver_fixed_extent_list.dart:240:19)
    #41     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #42     RenderSliverEdgeInsetsPadding.performLayout
    (package:flutter/src/rendering/sliver_padding.dart:137:12)
    #43     _RenderSliverFractionalPadding.performLayout
    (package:flutter/src/widgets/sliver_fill.dart:167:11)
    #44     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #45     RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:510:13)
    #46     RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1580:12)
    #47     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1489:20)
    #48     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #49     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #50     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #51     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #52     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #53     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #54     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #55     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #56     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #57     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #58     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #59     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
    #60     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #61     ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:56:11)
    #62     RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:896:45)
    #63     RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:931:32)
    #64     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #65     RenderSliverFillRemainingWithScrollable.performLayout
    (package:flutter/src/rendering/sliver_fill.dart:92:14)
    #66     RenderObject.layout (package:flutter/src/rendering/object.dart:1852:7)
    #67     RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:510:13)
    #68     RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1580:12)
    #69     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1489:20)
    #70     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1707:7)
    #71     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:879:18)
    #72     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:497:19)
    #73     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:883:13)
    #74     RendererBinding._handlePersistentFrameCallback
    (package:flutter/src/rendering/binding.dart:363:5)
    #75     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1145:15)
    #76     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082:9)
    #77     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:996:5)
    #81     _invoke (dart:ui/hooks.dart:150:10)
    #82     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:270:5)
    #83     _drawFrame (dart:ui/hooks.dart:114:31)
    (elided 3 frames from dart:async)
    
    The IndicatorController sending notification was:
      Instance of 'IndicatorController'
    โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    

    image

    image

    bug 
    opened by mzl1988 2
  • Push onRefresh out to the top as a parameter on the examples. Accepts a Future function.

    Push onRefresh out to the top as a parameter on the examples. Accepts a Future function.

    Here's an example of bringing the onRefresh parameter to the top for warp-indicator.dart so that you can just replace the default refresh widget with this one and you still have access to the onRefresh from whichever widget you were set up in. Might be a good idea to do this for all the examples as then people can just swap out the indicators easily in their code and try the different ones. BRILLIANT project by the way - Thanks for this!

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
    import 'package:flutter/rendering.dart';
    import 'package:flutter/scheduler.dart';
    
    enum WarpAnimationState {
      stopped,
      playing,
    }
    
    typedef StarColorGetter = Color Function(int index);
    
    class WarpIndicator extends StatefulWidget {
      final Widget child;
      final int starsCount;
      final Color skyColor;
      final StarColorGetter starColorGetter;
      final Future Function() onRefresh; // Step 1
    
      const WarpIndicator(
          {Key? key,
          required this.child,
          this.starsCount = 30,
          this.skyColor = Colors.black,
          this.starColorGetter = _defaultStarColorGetter,
          required this.onRefresh}) // Step 2
          : super(key: key);
    
      static Color _defaultStarColorGetter(int index) =>
          HSLColor.fromAHSL(1, Random().nextDouble() * 360, 1, 0.98).toColor();
    
      @override
      _WarpIndicatorState createState() => _WarpIndicatorState();
    }
    
    class _WarpIndicatorState extends State<WarpIndicator>
        with SingleTickerProviderStateMixin {
      static const _indicatorSize = 150.0;
      final _random = Random();
      final _helper = IndicatorStateHelper();
      WarpAnimationState _state = WarpAnimationState.stopped;
    
      List<Star> stars = [];
      final _offsetTween = Tween<Offset>(
        begin: Offset.zero,
        end: Offset.zero,
      );
      final _angleTween = Tween<double>(
        begin: 0,
        end: 0,
      );
    
      late AnimationController shakeController;
    
      static final _scaleTween = Tween(begin: 1.0, end: 0.75);
      static final _radiusTween = Tween(begin: 0.0, end: 16.0);
    
      @override
      void initState() {
        shakeController = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 100),
        );
        super.initState();
      }
    
      Offset _getRandomOffset() => Offset(
            _random.nextInt(10) - 5,
            _random.nextInt(10) - 5,
          );
    
      double _getRandomAngle() {
        final degrees = ((_random.nextDouble() * 2) - 1);
        final radians = degrees == 0 ? 0.0 : degrees / 360.0;
        return radians;
      }
    
      void _shiftAndGenerateRandomShakeTransform() {
        _offsetTween.begin = _offsetTween.end;
        _offsetTween.end = _getRandomOffset();
    
        _angleTween.begin = _angleTween.end;
        _angleTween.end = _getRandomAngle();
      }
    
      void _startShakeAnimation() {
        _shiftAndGenerateRandomShakeTransform();
        shakeController.animateTo(1.0);
        _state = WarpAnimationState.playing;
        stars = List.generate(
          widget.starsCount,
          (index) => Star(initialColor: widget.starColorGetter(index)),
        );
      }
    
      void _resetShakeAnimation() {
        _shiftAndGenerateRandomShakeTransform();
        shakeController.value = 0.0;
        shakeController.animateTo(1.0);
      }
    
      void _stopShakeAnimation() {
        _offsetTween.end = Offset.zero;
        _angleTween.end = 0.0;
        _state = WarpAnimationState.stopped;
        _shiftAndGenerateRandomShakeTransform();
        shakeController.stop();
        shakeController.value = 0.0;
        stars = [];
      }
    
      @override
      Widget build(BuildContext context) {
        return CustomRefreshIndicator(
          offsetToArmed: _indicatorSize,
          leadingGlowVisible: false,
          trailingGlowVisible: false,
          onRefresh: widget.onRefresh, // Step 3
          child: widget.child,
          builder: (
            BuildContext context,
            Widget child,
            IndicatorController controller,
          ) {
            final animation = Listenable.merge([controller, shakeController]);
            return Stack(
              children: <Widget>[
                AnimatedBuilder(
                    animation: shakeController,
                    builder: (_, __) {
                      return LayoutBuilder(
                        builder:
                            (BuildContext context, BoxConstraints constraints) {
                          return CustomPaint(
                            painter: Sky(
                              stars: stars,
                              color: widget.skyColor,
                            ),
                            child: const SizedBox.expand(),
                          );
                        },
                      );
                    }),
                AnimatedBuilder(
                  animation: animation,
                  builder: (context, _) {
                    _helper.update(controller.state);
                    if (_helper.didStateChange(
                      to: IndicatorState.loading,
                    )) {
                      SchedulerBinding.instance
                          ?.addPostFrameCallback((_) => _startShakeAnimation());
                    } else if (_helper.didStateChange(
                      to: IndicatorState.idle,
                    )) {
                      SchedulerBinding.instance
                          ?.addPostFrameCallback((_) => _stopShakeAnimation());
                    }
                    return Transform.scale(
                      scale: _scaleTween.transform(controller.value),
                      child: Builder(builder: (context) {
                        if (shakeController.value == 1.0 &&
                            _state == WarpAnimationState.playing) {
                          SchedulerBinding.instance
                              ?.addPostFrameCallback((_) => _resetShakeAnimation());
                        }
                        return Transform.rotate(
                          angle: _angleTween.transform(shakeController.value),
                          child: Transform.translate(
                            offset: _offsetTween.transform(shakeController.value),
                            child: ClipRRect(
                              child: child,
                              borderRadius: BorderRadius.circular(
                                _radiusTween.transform(controller.value),
                              ),
                            ),
                          ),
                        );
                      }),
                    );
                  },
                ),
              ],
            );
          },
        );
      }
    
      @override
      void dispose() {
        shakeController.dispose();
        super.dispose();
      }
    }
    
    class Star {
      Offset? position;
      Color? color;
      double value;
      late Offset speed;
      final Color initialColor;
      late double angle;
    
      Star({
        required this.initialColor,
      }) : value = 0.0;
    
      static const _minOpacity = 0.1;
      static const _maxOpacity = 1.0;
    
      void _init(Rect rect) {
        position = rect.center;
        value = 0.0;
        final random = Random();
        angle = random.nextDouble() * pi * 3;
        speed = Offset(cos(angle), sin(angle));
        const minSpeedScale = 20;
        const maxSpeedScale = 35;
        final speedScale = minSpeedScale +
            random.nextInt(maxSpeedScale - minSpeedScale).toDouble();
        speed = speed.scale(
          speedScale,
          speedScale,
        );
        final t = speedScale / maxSpeedScale;
        final opacity = _minOpacity + (_maxOpacity - _minOpacity) * t;
        color = initialColor.withOpacity(opacity);
      }
    
      draw(Canvas canvas, Rect rect) {
        if (position == null) {
          _init(rect);
        }
    
        value++;
        final startPosition = Offset(position!.dx, position!.dy);
        final endPosition = position! + (speed * (value * 0.3));
        position = speed + position!;
        final paint = Paint()..color = color!;
    
        final startShiftAngle = angle + (pi / 2);
        final startShift = Offset(cos(startShiftAngle), sin(startShiftAngle));
        final shiftedStartPosition =
            startPosition + (startShift * (0.75 + value * 0.01));
    
        final endShiftAngle = angle + (pi / 2);
        final endShift = Offset(cos(endShiftAngle), sin(endShiftAngle));
        final shiftedEndPosition = endPosition + (endShift * (1.5 + value * 0.01));
    
        final path = Path()
          ..moveTo(startPosition.dx, startPosition.dy)
          ..lineTo(startPosition.dx, startPosition.dy)
          ..lineTo(shiftedStartPosition.dx, shiftedStartPosition.dy)
          ..lineTo(shiftedEndPosition.dx, shiftedEndPosition.dy)
          ..lineTo(endPosition.dx, endPosition.dy);
    
        if (!rect.contains(startPosition)) {
          _init(rect);
        }
    
        canvas.drawPath(path, paint);
      }
    }
    
    class Sky extends CustomPainter {
      final List<Star> stars;
      final Color color;
    
      Sky({
        required this.stars,
        required this.color,
      });
    
      @override
      void paint(Canvas canvas, Size size) {
        var rect = Offset.zero & size;
    
        canvas.drawRect(rect, Paint()..color = color);
    
        for (final star in stars) {
          star.draw(canvas, rect);
        }
      }
    
      @override
      SemanticsBuilderCallback get semanticsBuilder {
        return (Size size) {
          var rect = Offset.zero & size;
    
          return [
            CustomPainterSemantics(
              rect: rect,
              properties: const SemanticsProperties(
                label: 'Lightspeed animation.',
                textDirection: TextDirection.ltr,
              ),
            ),
          ];
        };
      }
    
      @override
      bool shouldRepaint(Sky oldDelegate) => true;
      @override
      bool shouldRebuildSemantics(Sky oldDelegate) => false;
    }
    
    enhancement 
    opened by alienfrenZyNo1 2
  • CheckMarkIdicatorExample - indicator visible without DecoratedBox behind list

    CheckMarkIdicatorExample - indicator visible without DecoratedBox behind list

    Hi, First of all, thanks for sharing with that fancy code. When i use check mark indicator example without DecoratedBox on my ListView, the incidator is allways visible . I saw that it can be hidden based on Indicator state, like in the plane indicator:

                return Stack(
                  overflow: Overflow.clip,
                  children: <Widget>[
                    **if (_prevState != IndicatorState.idle)**
                      Container(
    

    Is that the desired way to hide the CircularProgressIndicator for CheckMarkIdicator when using list without DecoratedBox?

    To reproduce simply remove DecoratedBox from ExampleList

    class ExampleList extends StatelessWidget {
      final Color backgroundColor;
      const ExampleList([this.backgroundColor = appBackgroundColor]);
      @override
      Widget build(BuildContext context) {
        return ListView.separated(
            itemBuilder: (BuildContext context, int index) => const Element(),
            itemCount: 4,
            separatorBuilder: (BuildContext context, int index) => const Divider(
              height: 0,
              color: Color(0xFFe2d6ce),
              thickness: 1,
            ),
          );
      }
    }
    
    question 
    opened by skaletaPL 2
  • [question]Is there a way to listen for the drag from bottom?

    [question]Is there a way to listen for the drag from bottom?

    I believe we all have encountered scenario in developing a long list of data when we want to keep requesting more data from the server while user scroll at the end of the list...

    I believe that's how we can scroll indefinitely on Twitter with their seemingly infinite data list.

    So, this package provide us ways to pull down the list from the top to refresh the whole list, but from the api documentation, I don't see there's way to listen to the 'drag from bottom' event.

    My question is, is it available to do that feature with this package?

    Thanks in advance!

    question 
    opened by renntbenrennt 2
  • [feature_request] Parameter to disable snapping back to 1.0 when armed

    [feature_request] Parameter to disable snapping back to 1.0 when armed

    Hey, would it be possible to add a parameter to CustomRefreshIndicator to disable this line essentially -> await _animationController.animateTo(1.0, duration: widget.armedToLoadingDuration);

    For my use case, I want the Widgets dependent on the IndicatorController.value to stay the same until the onRefresh function completes.

    enhancement question 
    opened by kmaxion 2
Releases(v2.0.1)
  • v2.0.1(Nov 4, 2022)

    Changes:

    • Added missing isCanceling and isSettling getters for IndicatorState and IndicatorController enum.

    Full Changelog: https://github.com/gonuit/flutter-custom-refresh-indicator/compare/v2.0.0...v2.0.1

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Oct 25, 2022)

    Breaking changes

    • Added autoRebuild flag, which is by default set to true. There is no need to wrap widgets in the builder function with the AnimatedBuilder widget, as it will be automatically rebuilt. For optimization purposes, you can use the old behavior by setting the autoRebuild argument to false.
    • Remove IndicatorState.hiding state. Instead introduced IndicatorState.finalizing and IndicatorState.canceling.
    • Splited IndicatorState.loading state into two phases: IndicatorState.settling and IndicatorState.loading.
    • Renamed extentPercentageToArmed argument to containerExtentPercentageToArmed which better describes what it exactly does.
    • Changed the default value of the defaultContainerExtentPercentageToArmed from 0.20 to 0.1(6) to match the behavior of the built-in indicator widget.
    • Removed deprecated IndicatorStateHelper class. Instead use CustomRefreshIndicator.onStateChanged method.
    • Removed deprecated leadingGlowVisible and trailingGlowVisible arguments. Instead use leadingScrollIndicatorVisible and trailingScrollIndicatorVisible accoringly.
    • Allow setting the edge of the list that will trigger the pull to refresh action.
      • Introduced IndicatorEdge, IndicatorTrigger, IndicatorSide and IndicatorTriggerMode classes.
      • Replaced reversed argument of the CustomRefreshIndicator class with trigger.
      • Added edge and side properties to the IndicatorController class.
    • Added extension with utility getters for IndicatorState class.
    • Trigger mode support added. Equivalent to the trigger mode of the built-in RefreshIndicator widget.
    • Now the onRefresh function will be triggered immediately when the indicator is released in the armed state. Previously, the onRefresh function was triggered when the indicator reached a target value in the loading state of 1.0.
    • Fixed a bug causing the onRefresh method not to be triggered on the iOS platform due to bounce physics.
    • Implemented equality operator for IndicatorStateChange class.
    • Improved code coverage with tests
    • Multiple minor fixes, improvements and optimizations.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-dev.7(Oct 24, 2022)

  • v2.0.0-beta.1(Oct 24, 2022)

  • v2.0.0-dev.4(Oct 22, 2022)

  • v2.0.0-dev.3(Oct 21, 2022)

    What's Changed

    • Now, the onRefresh function will be triggered immediately when the indicator is released in the armed state. Previously, the onRefresh function was triggered when the indicator reached a target value in the loading state of 1.0.
    • Fixed a bug causing the onRefresh method not to be triggered on the iOS platform due to bounce physics.
    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Jun 3, 2022)

  • v1.1.2(Jun 3, 2022)

  • v1.1.0(Apr 2, 2022)

    Changes

    Fixes:

    • Handle errors thrown from the onRefresh method.

    Improvements:

    • Updated example app
      • Added support for the Android embedding v2
      • Added web support
      • Added windows support.
    • Added a web-based demo app (URL in the readme file).
    • Replaced the deprecated disallowGlow method calls with disallowIndicator.
    • Added onStateChanged function argument that allows tracking indicator state changes.
    • The IndicatorStateHelper class is now deprecated in favor of onStateChange function and IndicatorStateChange class.
    • Initial support for programmatically-controlled indicators has been added. Added the show,hide and refresh methods to theCustomRefreshIndicatorState class. It can be accessed via GlobalKey. Take a look at an programmatically-controlled screen example.
    • Use the flutter_lints package for analysis.
    • Deprecate leadingGlowVisible and trailingGlowVisible in favor of leadingScrollIndicatorVisible and trailingScrollIndicatorVisible arguments.
    • Added reversed argument that allows you to trigger a refresh indicator from the end of the list.
    • Added envelope example.
    • Added pull to fetch more example.
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0-dev.3(Apr 2, 2022)

    Changes:

    Fixes:

    • Handle errors thrown from the onRefresh method.

    Improvements:

    • Updated example app
      • Added support for the Android embedding v2
      • Added web support
      • Added windows support.
    • Added a web-based demo app (url in the readme file).
    • Replaced the deprecated disallowGlow method calls with disallowIndicator.
    • Added onStateChanged function argument that allows tracking indicator state changes.
    • The IndicatorStateHelper class is now deprecated in favor of onStateChange function and IndicatorStateChange class.
    • Initial support for programmatically-controlled indicators has been added. Added the show,hide and refresh methods to theCustomRefreshIndicatorState class. It can be accessed via GlobalKey. Take a look at an programmatically-controlled screen example.
    • Use the flutter_lints package for analysis.
    • Deprecate leadingGlowVisible and trailingGlowVisible in favor of leadingScrollIndicatorVisible and trailingScrollIndicatorVisible arguments.
    • Added reversed argument that allows you to trigger a refresh indicator from the end of the list.
    • Added envelope example.
    • Added pull to fetch more example.
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0-dev.2(Apr 2, 2022)

    Changes

    Fixes:

    • Handle errors thrown from the onRefresh method.

    Improvements:

    • Updated example app
      • Added support for the Android embedding v2
      • Added web support
      • Added windows support.
    • Added a web based demo app (url in the readme file).
    • Replaced the deprecated disallowGlow method calls with disallowIndicator.
    • Added onStateChanged function argument that allows tracking indicator state changes.
    • The IndicatorStateHelper class is now deprecated in favor of onStateChange function and IndicatorStateChange class.
    • Initial support for programmatically-controlled indicators has been added. Added the show,hide and refresh methods to theCustomRefreshIndicatorState class. It can be accessed via GlobalKey. Take a look at an programmatically-controlled screen example.
    • Use the flutter_lints package for analysis.
    • Deprecate leadingGlowVisible and trailingGlowVisible in favor of leadingScrollIndicatorVisible and trailingScrollIndicatorVisible arguments.
    • Added reversed argument that allows you to trigger a refresh indicator from the end of the list.
    • Added envelope example.
    • Added pull to fetch more example.
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0-dev.1(Mar 31, 2022)

    Changes:

    • Fixes:
      • Handle errors thrown from the onRefresh method.
    • Improvements:
      • Updated example app
        • Added support for the Android embedding v2
        • Added web support
        • Added windows support.
      • Added a web-based demo app (URL in the readme file).
      • Replaced the deprecated disallowGlow method calls with disallowIndicator.
      • Added onStateChanged function argument that allows tracking indicator state changes.
      • The IndicatorStateHelper class is now deprecated in favor of onStateChange function and IndicatorStateChange class.
      • Initial support for programmatically-controlled indicators has been added. Added the show,hide and refresh methods to theCustomRefreshIndicatorState class. It can be accessed via GlobalKey. Take a look at an programmatically-controlled screen example.
      • Use the flutter_lints package for analysis.
      • Deprecate leadingGlowVisible and trailingGlowVisible in favor of leadingScrollIndicatorVisible and trailingScrollIndicatorVisible arguments.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Mar 31, 2022)

    • Stable nullsafety release.
    • BREAKING: opt into null safety
      • Dart SDK constraints: >=2.12.0-0 <3.0.0
    • BREAKING: Removed prevState from IndicatorController class. Because flutter only marks the widget that it is ready for rebuild, it is possible that the controller state will change more than once during a single frame what causes one or more steps to be skipped. To still use prevState and didChangeState method, you can use IndicatorStateHelper. Take a look at check_mark_indicator.dart or warp_indicator.dart for example usage.
    • Added IndicatorStateHelper class.
    • Added IndicatorController unit tests.
    • Added warp indicator example.
    • Added stopDrag method to the IndicatorController class. It allows you to stop current user drag.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-nullsafety.1(Apr 22, 2021)

    Changes:

    • Stable nullsafety release.
    • BREAKING: Removed prevState from IndicatorController class. Because flutter only marks the widget that it is ready for rebuild, it is possible that the controller state will change more than once during a single frame what causes one or more steps to be skipped. To still use prevState and didChangeState method, you can use IndicatorStateHelper. Take a look at check_mark_indicator.dart or warp_indicator.dart for example usage.
    • Added IndicatorStateHelper class.
    • Added IndicatorController unit tests.
    • Added warp indicator example.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-nullsafety.0(Feb 23, 2021)

  • v0.9.0(Oct 1, 2020)

    Changes:

    • Improved readme documentation.
    • Removed material package import.
    • Added isComplete and wasComplete controller getters.
    • Removed axis property as it can be handled by notificationPredicate.
    • Added optional complete indicator state together with completeStateDuration parameter.
    • IndicatorControler changes:
      • Added prevoiusState property.
      • Added didStateChange helper method.
      • Added wasArmed, wasDragging, wasLoading, wasHiding and wasIdle properties.
    • Added notificationPredicate property to the CustomRefreshIndicator widget.
    • Example app:
      • Added initial version of check_mark_indicator. Example that shows how to make use of complete state.
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Apr 11, 2020)

    BREAKING API CHANGES

    • Feedback improvements (thank you for your emails!):
      • Changed long identifier names:
        • CustomRefreshIndicatorData => IndicatorController
        • CustomRefreshIndicatorState => IndicatorState
    • Update example app
    • indicatorBuilder argument is no longer present. Instead use builder argument which has some significant changes.

    To animate indicator based on IndicatorControler you can use AnimationBuilder widget and pass IndicatorData object as animation argument. Because of that you can implement your own widget rebuild system what can improve your custom indicator performance (instead of building indicator eg. 300 times you can decide when you want to do it). Example:

    return CustomRefreshIndicator(
        child: ListView(children: <Widget>[/* ... */]),
        builder: (
          BuildContext context,
          /// Subtree that contains scrollable widget and was passed
          /// to child argument
          Widget child,
          /// Now all your data will be stored in controller.
          /// To get controller outside of this function
          /// 1. Create controller in parent widget and pass it to CustomRefreshIndicator
          /// 2. Assign [GlobalKey] to CustomRefreshIndicator and access `key.currentState.controller`.
          IndicatorController controller
        ) {
          return AnimatedBuilder(
            // IndicatorData extends ChangeNotifier class so it is possible to
            // assign it to an AnimationBuilder widget and take advantage of subtree rebuild
            animation: controller,
            child: MyPrebuildWidget(),
            child: child,
            builder: (BuildContext context, Widget child) {
              /// TODO: Implement your custom refresh indicator
            },
          );
        },
        onRefresh: myAsyncMethod,
      );
    
    Source code(tar.gz)
    Source code(zip)
Owner
Kamil Klyta
Dart (Flutter), TypeScript/JavaScript (React, Node.js) full stack developer.
Kamil Klyta
Smooth-Page-Indicator-Example-Flutter - A simple example about smooth page indicator in Flutter

Smooth Page Indicator Example - Flutter Screenshots โš ๏ธ Essential Packages smooth

AmirHossein Bayat 6 Dec 7, 2022
Ever want to create a quick form without wiring everything up? This might be the library for you.

Ever want to create a quick form without wiring everything up? This might be the library for you.

Adam Hammer 15 Sep 13, 2022
A Flutter slidable widget that provides an easy to use configuration. Highly customisable. Just as you want it!

A Flutter slidable widget that provides an easy to use configuration. Highly customisable. Just as you want it!

Ravi Kavaiya 89 Dec 8, 2022
Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.

English | Portuguรชs The brightest, hippest, coolest router for Flutter. Features Simple route navigation Function handlers (map to a function instead

Luke Pighetti 3.5k Jan 4, 2023
A list component that refreshes and adds more data for Flutter.

Language: English | ไธญๆ–‡็ฎ€ไฝ“ Dynamic List View A list component that can refreshes and adds more data for Flutter App. ?? github Installation Add this to

Sword 18 Sep 27, 2022
Adds a side menu in all screens with debug information

Adds a side menu in all screens with debug information. You can decide which information to show and create new modules to include more information.

Sergi Martรญnez 27 Oct 7, 2022
Wrapper for flutter_test that adds a tester argument to setUp and tearDown functions

flutter_test_ui Wrapper of testWidgets, setUp, and tearDown that provide the WidgetTesterCallback argument to the set-up and tear-down functions: test

Sander Kersten 12 Apr 10, 2022
Scouter is a package which was made following the goal to provide a NestJS-like experience to Dart Developers that want to develop Rest APIS

Scouter is a package which was made following the goal to provide a NestJS-like experience to Dart Developers that want to develop Rest APIS Features

Vinicius Amรฉlio 3 Sep 12, 2022
It's a universal app template to have a great animated splash screen and liquid slider. Just change the animation if you want (rive) and change the images or colours according to your app.

liquid A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this i

Zikyan Rasheed 28 Oct 7, 2022
This is a Flutter Food Recipe App this shows food recipes of any food which you want.

food_recipe_app A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started

Saksham gupta 3 Oct 31, 2022
Flutter plugin to display a simple steps indicator line widget

steps_indicator A simple steps indicator widget Installation Add steps_indicator: ^1.3.0 in your pubspec.yaml dependencies. And import it: import 'pac

Hugo EXTRAT 49 Oct 18, 2022
A tab bar widget for Flutter ๐Ÿ’™ with point indicator.

flutter_point_tab_bar A tab bar widget with point indicator. Demo Usage TabBar( controller: _tabController, indicator: PointTabIndicator(

Hiแปƒn Lรช 5 Sep 16, 2022
A package that offers various page indicators inlcuding a Flutter implementation of the Ink Page Indicator

A package that offers various page indicators inlcuding a Flutter implementation of the Ink Page Indicator. See below for more examples.

null 50 Jan 6, 2022
Open source Flutter package, bar indicator made of a series of selected and unselected steps

Step Progress Indicator Open source Flutter package, bar indicator made of a series of selected and unselected steps. Made by Sandro Maglione, check o

Sandro Maglione 138 Dec 15, 2022
A simple button that gives you the possibility to transform into a circular one and shows a progress indicator

Progress Button A simple button that gives you the possibility to transform into

Daniel B Schneider 0 Dec 22, 2021
This library allows you to create editable tables and spreadsheets with ease, either by providing initial row and column count to display an empty table or use it with predefined rows and column data sets.

Editable โšก๏ธ A highly customizable, editable table package for Flutter projects. Specs This package allows you to create editable tables and spreadshee

Godwin Asuquo 94 Dec 7, 2022
A lightweight and customizable http client that allows you to create offline-first dart app easily.

Enjoyable & customizable offline-first REST API client Unruffled is lightweight and customizable http client that allows you to create offline-first e

T. Milian 3 May 20, 2022
Created application for team to help each other with providing food they want

Food wishes app When you login or create your account, you can write what do you wish right now as a separate card, using "Edit" button. If you no lon

Pavlo Osadchuk 0 Nov 29, 2021
M.Yavuz Yagis 3 Feb 21, 2022