lorien_chat_list 0.0.3+1 copy "lorien_chat_list: ^0.0.3+1" to clipboard
lorien_chat_list: ^0.0.3+1 copied to clipboard

Flutter plugin for creating chat list page which will automatically scroll or maintain current scroll position.

example/lib/main.dart

import 'dart:math';

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

void main() {
  runApp(const LorienChatListExample());
}

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

  @override
  State<LorienChatListExample> createState() => _LorienChatListExampleState();
}

class _LorienChatListExampleState extends State<LorienChatListExample> {
  late final ChatListController<_MessageModel> _chatController =
      ChatListController<_MessageModel>(
    initialItems: List<int>.generate(10, (index) => -index)
        .map(
          (number) => _MessageModel(
            number: number,
            isMyMessage: _random.nextBool(),
            text: _tolkienQuotes[number % _tolkienQuotes.length],
          ),
        )
        .toList(),
  );

  int _nextTopNumber = -10;
  int _nextBottomNumber = 1;
  int _replaceNumber = 1;

  final _random = Random();
  final _scrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.grey[200],
        appBar: AppBar(
          backgroundColor: Colors.black,
          title: const Text(
            'LORIEN.DEV Chat list',
            style: TextStyle(
              color: Colors.white,
            ),
          ),
          actions: [
            IconButton(
              tooltip: 'Add item to top',
              icon: const Icon(
                Icons.add_comment_outlined,
                color: Colors.white,
              ),
              onPressed: () => _chatController.addToTop(
                _MessageModel(
                  number: _nextTopNumber--,
                  isMyMessage: _random.nextBool(),
                  text: _tolkienQuotes[_nextTopNumber % _tolkienQuotes.length],
                ),
              ),
            ),
            IconButton(
              tooltip: 'Add item to bottom',
              icon: const Icon(
                Icons.add_comment,
                color: Colors.white,
              ),
              onPressed: () => _chatController.addToBottom(
                _MessageModel(
                  number: _nextBottomNumber++,
                  isMyMessage: _random.nextBool(),
                  text:
                      _tolkienQuotes[_nextBottomNumber % _tolkienQuotes.length],
                ),
              ),
            ),
            IconButton(
              tooltip: 'Scroll to bottom',
              icon: const Icon(
                Icons.keyboard_arrow_down,
                color: Colors.white,
              ),
              onPressed: () => _chatController.scrollToBottom(),
            ),
            IconButton(
              tooltip: 'Jump to bottom',
              icon: const Icon(
                Icons.keyboard_double_arrow_down_outlined,
                color: Colors.white,
              ),
              onPressed: () => _chatController.jumpToBottom(),
            ),
            IconButton(
              tooltip: 'Clear list',
              icon: const Icon(
                Icons.refresh,
                color: Colors.white,
              ),
              onPressed: () {
                _chatController.clearAll();
                _nextTopNumber = 0;
                _nextBottomNumber = 1;
              },
            ),
            IconButton(
              tooltip: 'Replace item where',
              icon: const Icon(
                Icons.repeat_one,
                color: Colors.white,
              ),
              onPressed: () {
                if (_chatController.oldest == null) {
                  return;
                }
                _chatController.replaceFirstItemWhere(
                  test: (item) => item.id == _chatController.oldest!.id,
                  newItem: _chatController.oldest!
                      .copyWith(text: 'New item with replaced text'),
                );
              },
            ),
            IconButton(
              tooltip: 'Replace newest item',
              icon: const Icon(
                Icons.repeat_on,
                color: Colors.white,
              ),
              onPressed: () => _chatController.replaceItem(
                oldItem: _chatController.newest!,
                newItem: _chatController.newest!.copyWith(
                  text: '${_chatController.newest!.text} (${_replaceNumber++})',
                ),
              ),
            ),
          ],
        ),
        body: ChatList(
          controller: _chatController,
          itemBuilder: (item, properties) => _MessageCard(
            type: item.isMyMessage
                ? _MessageType.outgoing
                : _MessageType.incoming,
            text: '${item.number}. ${item.text}',
          ),
          loadingMoreWidget: const Padding(
            padding: EdgeInsets.only(top: 16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CircularProgressIndicator(),
              ],
            ),
          ),
          onLoadMoreCallback: _loadMoreOldMessages,
          scrollController: _scrollController,
          scrollPhysics: const ClampingScrollPhysics(),
          useJumpTo: false,
          animateToDuration: const Duration(milliseconds: 300),
          fadeInDuration: const Duration(milliseconds: 300),
          animateToCurve: Curves.easeInOut,
          fadeInCurve: Curves.easeInOut,
          bottomEdgeThreshold: 20.0,
          padding: const EdgeInsets.all(16.0),
          spacing: 4.0,
        ),
      ),
    );
  }

  /// Simulates loading more old messages.
  /// Returns *true* if there are more old messages to load
  /// otherwise returns *false* if everything is loaded.
  Future<bool> _loadMoreOldMessages() async {
    /// Simulate a delay of 3 seconds
    await Future.delayed(const Duration(seconds: 3));

    /// Fetch 10 more old messages
    final newOldItems = List<int>.generate(10, (index) => _nextTopNumber--)
        .map(
          (number) => _MessageModel(
            number: number,
            isMyMessage: _random.nextBool(),
            text: _tolkienQuotes[number % _tolkienQuotes.length],
          ),
        )
        .toList();

    /// Add the new old messages to the top of the chat list
    _chatController.addRangeToTop(newOldItems);

    /// Return true if there are more old messages to load
    return _nextTopNumber > -30;
  }
}

class _MessageCard extends StatelessWidget {
  const _MessageCard({
    required _MessageType type,
    required String text,
  })  : _type = type,
        _text = text;

  final _MessageType _type;
  final String _text;

  @override
  Widget build(BuildContext context) {
    final isIncoming = _type == _MessageType.incoming;
    return Row(
      mainAxisAlignment:
          isIncoming ? MainAxisAlignment.start : MainAxisAlignment.end,
      children: [
        Flexible(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: MediaQuery.of(context).size.width * 0.7,
            ),
            child: Card(
              color: isIncoming ? Colors.white : Colors.black,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  _text,
                  style: TextStyle(
                    color: isIncoming ? Colors.black : Colors.white,
                  ),
                  textAlign: isIncoming ? TextAlign.left : TextAlign.right,
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

class _MessageModel {
  _MessageModel({
    required this.number,
    required this.isMyMessage,
    required this.text,
  }) : id = UniqueKey().toString();

  final String id;
  final int number;
  final bool isMyMessage;
  final String text;

  _MessageModel copyWith({
    int? number,
    bool? isMyMessage,
    String? text,
  }) {
    return _MessageModel(
      number: number ?? this.number,
      isMyMessage: isMyMessage ?? this.isMyMessage,
      text: text ?? this.text,
    );
  }

  @override
  String toString() => 'MessageModel{'
      'id: $id,'
      'number: $number,'
      'isMyMessage: $isMyMessage,'
      'text: $text}';
}

enum _MessageType {
  incoming,
  outgoing,
}

const _tolkienQuotes = [
  "\"All we have to decide is what to do with the time that is given us.\" "
      "(The Fellowship of the Ring - J.R.R. Tolkien)",
  "\"Not all those who wander are lost.\" (The Fellowship of the Ring - J.R.R. Tolkien)",
  "\"Courage is found in unlikely places.\" (The Return of the King - J.R.R. Tolkien)",
  "\"Even the smallest person can change the course of the future.\" (The Fellowship of the Ring - J.R.R. Tolkien)",
  "\"It's a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, "
      "there's no knowing where you might be swept off to.\" (The Fellowship of the Ring - J.R.R. Tolkien)",
  "\"I will not say: do not weep; for not all tears are an evil.\" (The Return of the King - J.R.R. Tolkien)",
  "\"Far over the misty mountains cold, To dungeons deep and caverns old, We must away, ere break of day, To seek "
      "the pale enchanted gold.\" (The Hobbit - J.R.R. Tolkien)",
  "\"The world is indeed full of peril, and in it there are many dark places; but still there is much that is fair, "
      "and though in all lands love is now mingled with grief, it grows perhaps the greater.\" "
      "(The Fellowship of the Ring - J.R.R. Tolkien)",
  "\"War must be, while we defend our lives against a destroyer who would devour all; but I do not love the bright"
      " sword for its sharpness, nor the arrow for its swiftness, nor the warrior for his glory. I love only that"
      " which they defend.\" (The Two Towers - J.R.R. Tolkien)",
  "\"There are older and fouler things than Orcs in the deep places of the world.\" "
      "(The Fellowship of the Ring - J.R.R. Tolkien)"
];
2
likes
160
points
17
downloads

Documentation

API reference

Publisher

verified publisherlorien.dev

Weekly Downloads

Flutter plugin for creating chat list page which will automatically scroll or maintain current scroll position.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on lorien_chat_list