scroll_infinity 0.4.0 copy "scroll_infinity: ^0.4.0" to clipboard
scroll_infinity: ^0.4.0 copied to clipboard

Provides infinite scrolling functionality, simplifying its integration.

example/lib/main.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:scroll_infinity/scroll_infinity.dart';

typedef LoadDatatype = Future<List<Color>?> Function(
  int pageIndex, {
  Axis scrollDirection,
});

class LoadingStyleModel<T> {
  const LoadingStyleModel({
    required this.name,
    this.value,
  });

  final String name;
  final T? value;
}

const loadingColors = <LoadingStyleModel<Color>>[
  LoadingStyleModel(
    name: 'Default',
  ),
  LoadingStyleModel(
    name: 'Red',
    value: Colors.red,
  ),
  LoadingStyleModel(
    name: 'Blue',
    value: Colors.blue,
  ),
  LoadingStyleModel(
    name: 'Yellow',
    value: Colors.yellow,
  ),
  LoadingStyleModel(
    name: 'Green',
    value: Colors.green,
  ),
  LoadingStyleModel(
    name: 'Purple',
    value: Colors.purple,
  ),
];

const loadingStrokeAligns = <LoadingStyleModel<double>>[
  LoadingStyleModel(
    name: 'Center',
    value: BorderSide.strokeAlignCenter,
  ),
  LoadingStyleModel(
    name: 'Inside',
    value: BorderSide.strokeAlignInside,
  ),
  LoadingStyleModel(
    name: 'Outside',
    value: BorderSide.strokeAlignOutside,
  ),
];

const loadingStrokeWidths = <LoadingStyleModel<double>>[
  LoadingStyleModel(
    name: 'Default',
  ),
  LoadingStyleModel(
    name: '2',
    value: 2.0,
  ),
  LoadingStyleModel(
    name: '6',
    value: 6.0,
  ),
  LoadingStyleModel(
    name: '8',
    value: 8.0,
  ),
  LoadingStyleModel(
    name: '10',
    value: 10.0,
  ),
];

class LoadingStyleTypeModel<T> {
  const LoadingStyleTypeModel({
    required this.title,
    required this.value,
  });

  final String title;
  final List<LoadingStyleModel> value;
}

const loadingTypeStyles = <LoadingStyleTypeModel>[
  LoadingStyleTypeModel(
    title: 'Color',
    value: loadingColors,
  ),
  LoadingStyleTypeModel(
    title: 'Align',
    value: loadingStrokeAligns,
  ),
  LoadingStyleTypeModel(
    title: 'Width',
    value: loadingStrokeWidths,
  ),
];

final _random = Random();

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ScrollInfinityExample(),
    ),
  );
}

class _DefaultNotifier<T> extends ValueNotifier<List<T>> {
  _DefaultNotifier(super._value);

  void set(
    int index,
    T value,
  ) {
    super.value[index] = value;

    notifyListeners();
  }
}

class _EnablesNotifier<T> extends _DefaultNotifier<T> {
  _EnablesNotifier(super._value);
}

class _LoadingStylesNotifier<T> extends _DefaultNotifier<T> {
  _LoadingStylesNotifier(super._value);
}

class ScrollInfinityExample extends StatefulWidget {
  const ScrollInfinityExample({super.key});

  @override
  State<ScrollInfinityExample> createState() => _ScrollInfinityExampleState();
}

class _ScrollInfinityExampleState extends State<ScrollInfinityExample> {
  final _selectedScrollDirectionNotifier = ValueNotifier(Axis.vertical);
  final _maxItemsNotifier = ValueNotifier(10);
  final _intervalNotifier = ValueNotifier(2);
  static final _enableTitles = <String>[
    'Header',
    'Intervals',
    'Initial Items',
    'Custom Loader',
  ];
  final _enablesNotifier = _EnablesNotifier(
    List.filled(_enableTitles.length, false),
  );
  final _loadingStylesNotifier = _LoadingStylesNotifier(
    <LoadingStyleModel>[
      loadingColors.first,
      loadingStrokeAligns.first,
      loadingStrokeWidths.first,
    ],
  );

  LoadingStyle get _loadingStyle {
    final loadingStyles = _loadingStylesNotifier.value;

    return LoadingStyle(
      color: loadingStyles[0].value,
      strokeAlign: loadingStyles[1].value,
      strokeWidth: loadingStyles[2].value,
    );
  }

  void _navigateToExample() {
    final enables = _enablesNotifier.value;

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return InfiniteScrollExample(
            selectedScrollDirection: _selectedScrollDirectionNotifier.value,
            maxItems: _maxItemsNotifier.value,
            interval: _intervalNotifier.value,
            enableHeader: enables[0],
            enableInterval: enables[1],
            enableInitialItems: enables[2],
            enableCustomLoader: enables[3],
            loadingStyle: _loadingStyle,
          );
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              const FieldWidget(
                title: 'Scroll Direction',
              ),
              const Divider(),
              ValueListenableBuilder(
                valueListenable: _selectedScrollDirectionNotifier,
                builder: (context, value, child) {
                  return Column(
                    children: <Widget>[
                      RadioListTile(
                        title: const Text('Vertical'),
                        value: Axis.vertical,
                        groupValue: value,
                        onChanged: (Axis? value) {
                          setState(() {
                            _selectedScrollDirectionNotifier.value = value!;
                          });
                        },
                      ),
                      RadioListTile(
                        title: const Text('Horizontal'),
                        value: Axis.horizontal,
                        groupValue: value,
                        onChanged: (Axis? value) {
                          setState(() {
                            _selectedScrollDirectionNotifier.value = value!;
                          });
                        },
                      ),
                    ],
                  );
                },
              ),
              const SizedBox(height: 28.0),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  const FieldWidget(
                    title: 'Max Items',
                  ),
                  QuantitySelectorWidget(
                    notifier: _maxItemsNotifier,
                  ),
                ],
              ),
              const SizedBox(height: 40.0),
              const FieldWidget(
                title: 'Enable',
              ),
              const Divider(),
              ...List.generate(
                _enableTitles.length,
                (index) {
                  return ValueListenableBuilder(
                    valueListenable: _enablesNotifier,
                    builder: (context, value, child) {
                      return Column(
                        children: <Widget>[
                          SwitchListTile(
                            onChanged: (value) {
                              _enablesNotifier.set(index, value);
                            },
                            value: value[index],
                            title: Text(
                              _enableTitles[index],
                              style: const TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ),
                          if (index == 1 && value[index])
                            QuantitySelectorWidget(
                              notifier: _intervalNotifier,
                            ),
                        ],
                      );
                    },
                  );
                },
              ),
              const SizedBox(height: 40.0),
              const FieldWidget(
                title: 'Customize default loader',
              ),
              const Divider(),
              ...List.generate(loadingTypeStyles.length, (index) {
                final loadingTypeStyle = loadingTypeStyles[index];

                return Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 16.0,
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        '${loadingTypeStyle.title}:',
                        style: const TextStyle(
                          fontSize: 16.0,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                      const SizedBox(width: 12.0),
                      ValueListenableBuilder(
                        valueListenable: _loadingStylesNotifier,
                        builder: (context, value, child) {
                          return DropdownButtonHideUnderline(
                            child: DropdownButton(
                              value: value[index],
                              items: List.generate(
                                  loadingTypeStyle.value.length, (index) {
                                final style = loadingTypeStyle.value[index];

                                return DropdownMenuItem(
                                  value: style,
                                  child: Text(
                                    style.name,
                                  ),
                                );
                              }),
                              onChanged: (value) {
                                _loadingStylesNotifier.set(index, value!);
                              },
                            ),
                          );
                        },
                      ),
                    ],
                  ),
                );
              }),
              const Divider(),
              const SizedBox(height: 20.0),
            ],
          ),
        ),
      ),
      bottomNavigationBar: Container(
        color: Theme.of(context).scaffoldBackgroundColor,
        height: 80.0,
        alignment: Alignment.center,
        child: ElevatedButton(
          onPressed: _navigateToExample,
          child: const Text(
            'Access Example',
          ),
        ),
      ),
    );
  }
}

class FieldWidget extends StatelessWidget {
  const FieldWidget({
    super.key,
    required this.title,
  });

  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 16.0,
      ),
      child: Text(
        '$title:',
        style: const TextStyle(
          fontSize: 18.0,
          fontWeight: FontWeight.w500,
        ),
      ),
    );
  }
}

class QuantitySelectorWidget extends StatelessWidget {
  const QuantitySelectorWidget({
    super.key,
    required this.notifier,
  });

  final ValueNotifier<int> notifier;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: notifier,
      builder: (context, value, child) {
        return Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.remove),
              onPressed: value != 2
                  ? () {
                      notifier.value--;
                    }
                  : null,
            ),
            const SizedBox(width: 8.0),
            SizedBox(
              width: 24.0,
              child: Center(
                child: Text(
                  '$value',
                  style: const TextStyle(
                    fontSize: 18.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            const SizedBox(width: 8.0),
            IconButton(
              icon: const Icon(Icons.add),
              onPressed: value != 20
                  ? () {
                      notifier.value++;
                    }
                  : null,
            ),
          ],
        );
      },
    );
  }
}

class InfiniteScrollExample extends StatefulWidget {
  const InfiniteScrollExample({
    super.key,
    required this.selectedScrollDirection,
    required this.maxItems,
    required this.interval,
    required this.enableHeader,
    required this.enableInterval,
    required this.enableInitialItems,
    required this.enableCustomLoader,
    required this.loadingStyle,
  });

  final Axis selectedScrollDirection;
  final int maxItems;
  final int interval;
  final bool enableHeader;
  final bool enableInterval;
  final bool enableInitialItems;
  final bool enableCustomLoader;
  final LoadingStyle loadingStyle;

  @override
  State<InfiniteScrollExample> createState() => _InfiniteScrollExampleState();
}

class _InfiniteScrollExampleState extends State<InfiniteScrollExample> {
  late final int _maxItems;
  final _initialItemsNotifier = InitialItemsNotifier<Color>(null);

  Future<void> _initLoader() async {
    _initialItemsNotifier.update(
      items: null,
      hasError: false,
    );

    final items = await _loadData(0);

    _initialItemsNotifier.update(
      items: items,
      hasError: items == null,
    );
  }

  Future<List<Color>?> _loadData(
    int pageIndex,
  ) async {
    await Future.delayed(
      const Duration(
        seconds: 2,
      ),
    );

    if (_random.nextInt(4) == 0) {
      return null;
    }

    final isListEnd = _random.nextInt(5) == 0;
    final max = widget.maxItems - 1;

    return _generateColors(
      isListEnd
          ? max == 0
              ? 0
              : _random.nextInt(max)
          : _maxItems,
    );
  }

  List<Color> _generateColors(
    int amount,
  ) {
    return List.generate(
      amount,
      (index) {
        return Color.fromARGB(
          255,
          _random.nextInt(255),
          _random.nextInt(255),
          _random.nextInt(255),
        );
      },
    );
  }

  ScrollInfinity<Color?> _getScrollInfinity(
    List<Color>? initialItems,
  ) {
    final isScrollVertical = widget.selectedScrollDirection == Axis.vertical;

    return ScrollInfinity<Color?>(
      scrollDirection: widget.selectedScrollDirection,
      header: widget.enableHeader
          ? Container(
              height: isScrollVertical ? 40.0 : 0.0,
              width: isScrollVertical ? 0.0 : 160.0,
              color: Colors.red,
            )
          : null,
      maxItems: _maxItems,
      interval: widget.enableInterval ? widget.interval : null,
      loadData: _loadData,
      loadingStyle: widget.loadingStyle,
      initialPageIndex: widget.enableInitialItems ? 1 : 0,
      initialItems: widget.enableInitialItems ? initialItems : null,
      loading: widget.enableCustomLoader
          ? Container(
              padding: const EdgeInsets.all(20.0),
              child: Center(
                child: CircularProgressIndicator(
                  strokeWidth: 6,
                  valueColor: const AlwaysStoppedAnimation(Colors.blue),
                  backgroundColor: Colors.grey.shade800,
                ),
              ),
            )
          : null,
      itemBuilder: (value, index) {
        final width = isScrollVertical ? 0.0 : 200.0;
        final height = isScrollVertical ? 100.0 : 0.0;

        if (widget.enableInterval ? value == null : false) {
          return SizedBox(
            height: height,
            width: width,
            child: const Placeholder(),
          );
        }

        return Container(
          height: height,
          width: width,
          color: value,
        );
      },
    );
  }

  void _reset() {
    if (widget.enableInitialItems) {
      _initLoader();
    } else {
      setState(() {});
    }
  }

  @override
  void initState() {
    _maxItems = widget.maxItems;

    if (widget.enableInitialItems) {
      _initLoader();
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final scrollInfinity = widget.enableInitialItems
        ? ScrollInfinityLoader(
            notifier: _initialItemsNotifier,
            scrollInfinityBuilder: (items) {
              return _getScrollInfinity(items);
            },
          )
        : _getScrollInfinity(null);

    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          onPressed: () {
            Navigator.pop(context);
          },
          icon: const Icon(
            Icons.arrow_back_ios_new_rounded,
          ),
        ),
      ),
      body: Column(
        children: <Widget>[
          ElevatedButton(
            onPressed: _reset,
            child: const Text('Reset'),
          ),
          const SizedBox(height: 20.0),
          const Divider(
            height: 0.0,
          ),
          Expanded(
            child: widget.selectedScrollDirection == Axis.vertical
                ? scrollInfinity
                : Center(
                    child: SizedBox(
                      height: 100.0,
                      child: scrollInfinity,
                    ),
                  ),
          ),
        ],
      ),
    );
  }
}
2
likes
0
points
24
downloads

Publisher

verified publisherdariomatias-dev.com

Weekly Downloads

Provides infinite scrolling functionality, simplifying its integration.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on scroll_infinity