easy_chatview 0.2.7 copy "easy_chatview: ^0.2.7" to clipboard
easy_chatview: ^0.2.7 copied to clipboard

PlatformAndroidiOS
unlisted

A package that lets you implement chatting the way you like it.

example/lib/main.dart

import 'dart:async';
import 'dart:math';

import 'package:easy_chatview/easy_chatview.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:utc_date_time/utc_date_time.dart';

void main() {
  runApp(MyApp());
}

// I declare it here in the example, in your app you should not do that
// but use a different way of keeping only one single-source-of-truth
// datasource at a time.
final datasource = TestChatviewDataSource();
final chatController = TestChatviewController(
  myUuid: '1',
  datasource: datasource,
  onSendTap: (
    final context,
    final message, {
    required final chat,
  }) async {
    if (kDebugMode) {
      print('sending ${message.content} in ${chat.uuid}');
      print('marking as sent in 3 seconds');
    }
    await Future<void>.delayed(const Duration(seconds: 3));
    if (kDebugMode) {
      print('marking ${message.content} as sent');
    }
    message.uuid = '123-${message.sentAt.millisecondsSinceEpoch}';
  },
);

final ValueNotifier<ThemeMode> themeModeChanger =
    ValueNotifier<ThemeMode>(ThemeMode.light);

void changeTheme() {
  themeModeChanger.value = themeModeChanger.value == ThemeMode.light
      ? ThemeMode.dark
      : ThemeMode.light;
}

final defaultAppBarActions = <Widget>[
  ValueListenableBuilder(
    valueListenable: themeModeChanger,
    builder: (final context, final themeMode, final _) => IconButton(
      onPressed: changeTheme,
      icon: Icon(
        themeMode == ThemeMode.light
            ? Icons.dark_mode_outlined
            : Icons.light_mode,
      ),
    ),
  ),
];

class MyApp extends StatelessWidget {
  MyApp({super.key}) {
    // unawaited(_startChangingTheme());
  }

  // ignore: unused_element
  Future<void> _startChangingTheme() async {
    while (true) {
      await Future<void>.delayed(const Duration(seconds: 5));
      changeTheme();
    }
  }

  @override
  Widget build(final BuildContext context) {
    const colorSchemeSeedColor = Colors.blue;
    return ValueListenableBuilder(
      valueListenable: themeModeChanger,
      builder: (final context, final themeMode, final _) => MaterialApp(
        themeMode: themeMode,
        theme: ThemeData(
          colorSchemeSeed: colorSchemeSeedColor,
          brightness: Brightness.light,
        ),
        darkTheme: ThemeData(
          colorSchemeSeed: colorSchemeSeedColor,
          brightness: Brightness.dark,
        ),
        title: 'My First App',
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Chat list'),
            actions: defaultAppBarActions,
          ),
          body: Center(
            child: TestChatviewChatListView(
              chatviewController: chatController,
            ),
          ),
        ),
      ),
    );
  }
}

class TestChatviewChatListView extends EasyChatviewChatListView<
    TestChatviewMessage, TestChatviewChat, TestChatviewUser> {
  TestChatviewChatListView({
    required super.chatviewController,
    super.key,
  });

  @override
  Widget chatListItemBuilder(
    final BuildContext context,
    final TestChatviewChat chat,
  ) =>
      ValueListenableBuilder(
        valueListenable: chat,
        builder: (final context, final _, final __) => ListTile(
          onTap: () => _onTapChat(
            context,
            chat,
          ),
          title: Text(chat.title),
          subtitle: Text(chat.messagePreview),
        ),
      );

  void _onTapChat(final BuildContext context, final TestChatviewChat chat) {
    unawaited(
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (final context) => Scaffold(
            appBar: AppBar(
              title: Text('Chat: ${chat.title}'),
              actions: defaultAppBarActions,
            ),
            body: Center(
              child: TestChatviewChatView(
                chat: chat,
                chatviewController: chatviewController,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class TestChatviewChatView extends EasyChatviewChatView<TestChatviewMessage,
    TestChatviewChat, TestChatviewUser> {
  TestChatviewChatView({
    required super.chatviewController,
    required super.chat,
    super.key,
  });

  @override
  List<Color>? getTheirColorGradient(final BuildContext context) => [
        Colors.teal,
        Colors.purpleAccent,
      ];

  @override
  List<Color>? getMyColorGradient(final BuildContext context) => [
        Colors.orange,
      ];

  Widget _replyToHeadingBuilder(final TestChatviewMessage inReplyToMessage) =>
      Text('in reply to: ${inReplyToMessage.content}');

  @override
  Widget chatMessageBuilder(
    final BuildContext context,
    final EasyChatviewMessageBuilderInfo<TestChatviewMessage> info,
  ) {
    if (info.message.isSystemMessage) {
      return ListTile(
        title: Text('system: ${info.message.sentAt}'),
        subtitle: Text(info.message.content),
      );
    }
    Widget replyToWidget = const SizedBox.shrink();
    if (info.message.inReplyTo != null) {
      final replyToTuple = chatviewController.getMessageByUuid(
        info.message.inReplyTo!,
        chat: chat,
      );
      if (replyToTuple.isLoading) {
        replyToWidget = FutureBuilder(
          future: replyToTuple.reloader,
          builder: (final context, final snapshot) {
            if (snapshot.hasData) {
              return _replyToHeadingBuilder(snapshot.data!);
            }

            if (snapshot.hasError) {
              return Text('error: ${snapshot.error}');
            }
            return const CircularProgressIndicator();
          },
        );
      } else {
        replyToWidget = _replyToHeadingBuilder(replyToTuple.item!);
      }
    }

    return Card.filled(
      margin: const EdgeInsets.all(8),
      color: Colors.transparent,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          replyToWidget,
          Text(
            'šŸ”¼ ${info.prevMessage?.content}'
            '\n text: ${info.message.content}'
            '\nšŸ”½ ${info.nextMessage?.content}',
            textAlign: TextAlign.start,
          ),
        ],
      ),
      // subtitle: Text(
      //   info.message.sentAt.todmYHM(),
      //   style: const TextStyle(
      //     color: Colors.black,
      //   ),
      // ),
    );
  }
}

class TestChatviewController extends EasyChatviewController<TestChatviewMessage,
    TestChatviewChat, TestChatviewUser> {
  TestChatviewController({
    required super.myUuid,
    required this.datasource,
    required this.onSendTap,
  }) {
    // unawaited(_startChangingUserUuid());
    // unawaited(_startChangingReplyTo());
  }

  // ignore: unused_element
  Future<void> _startChangingReplyTo() async {
    while (true) {
      await Future<void>.delayed(const Duration(milliseconds: 5000));
      _setRandomReplyTo();
    }
  }

  void _setRandomReplyTo() {
    if (hasReplyInitiated) {
      cancelReply();
      return;
    }

    initiateReply(
      TestChatviewMessage(
        uuid: 'aab',
        senderUuid: '1',
        sentAt: UtcDateTime.now(),
        content: '132132132123',
      ),
    );
  }

  // ignore: unused_element
  Future<void> _startChangingUserUuid() async {
    while (true) {
      await Future<void>.delayed(const Duration(milliseconds: 5000));
      _setRandomUuid();
    }
  }

  void _setRandomUuid() {
    final allUsers = datasource
        .getUsers(
          chat: datasource.getChats().item.first,
        )
        .item;
    final newUser = allUsers.elementAt(Random().nextInt(allUsers.length));

    if (kDebugMode) {
      print('setting myUuid to ${newUser.uuid}');
    }
    myUuid = newUser.uuid;
  }

  @override
  final TestChatviewDataSource datasource;

  final Future<void> Function(
    BuildContext context,
    TestChatviewMessage message, {
    required TestChatviewChat chat,
  }) onSendTap;

  @override
  TestChatviewMessage newUserMessageFromText(
    final String text, {
    required final TestChatviewMessage? inReplyToMessage,
  }) =>
      TestChatviewMessage(
        uuid: null,
        sentAt: UtcDateTime.now(),
        content: text,
        senderUuid: myUuid,
        inReplyTo: inReplyToMessage?.uuid,
      );

  @override
  Future<void> userSentTextMessage(
    final BuildContext context,
    final TestChatviewMessage message, {
    required final String? inReplyTo,
    required final TestChatviewChat chat,
  }) =>
      onSendTap(context, message, chat: chat);
}

class TestChatviewDataSource extends EasyChatviewDataSource<TestChatviewMessage,
    TestChatviewChat, TestChatviewUser> {
  TestChatviewDataSource() : super() {
    while (_addCounter < 10) {
      _addRandomChangingMessage();
    }
    unawaited(_startAddingRandomMessage());
  }

  int _addCounter = 0;
  int _dayAdder = 0;
  int _monthAdder = 0;
  final List<String> _possibleSenders = [
    '1',
    '2',
    '3',
    '4',
  ];

  final int messageDeltaMillis = 200;
  // final int messageDeltaMillis = 0;

  Future<void> _startAddingRandomMessage() async {
    while (_addCounter < 1000) {
      await Future<void>.delayed(
        Duration(
          milliseconds: messageDeltaMillis,
        ),
      );
      _addRandomChangingMessage();
    }
  }

  void _addRandomChangingMessage() {
    if (Random().nextInt(100) > 50) {
      _dayAdder += 1;
    }

    if (Random().nextInt(100) > 90) {
      _monthAdder += 1;
    }

    final realSentAt = UtcDateTime.now().subtract(
      _addCounter.isEven
          ? Duration(
              milliseconds: (messageDeltaMillis * 1.5) ~/ 1,
            )
          : Duration.zero,
    );

    final sentAt = realSentAt.add(Duration(days: _dayAdder)).copyWith(
          month: 1 + (realSentAt.month + _monthAdder) % 12,
          year: 1 + realSentAt.year + ((realSentAt.month + _monthAdder) ~/ 12),
        );

    final newMessage = TestChatviewMessage(
      uuid: Random().nextBool()
          ? UtcDateTime.now().millisecondsSinceEpoch.toString()
          : null,
      allHaveReceived: Random().nextBool(),
      allHaveRead: Random().nextBool(),
      sentAt: sentAt,
      content: '$_addCounter: $sentAt',
      senderUuid: _possibleSenders[Random().nextInt(_possibleSenders.length)],
    );

    // final chatUuid =
    //     _testMessages.keys.elementAt(_addCounter % _testMessages.length);
    final chatUuid = _testMessages.keys.first;

    if (kDebugMode) {
      print(
        'adding message ${newMessage.sentAt.todmYHMS()} '
        '-> ${_testMessages[chatUuid]!.messages.length}',
      );
    }
    _testMessages[chatUuid]!.addMessage(newMessage);

    unawaited(
      Future<void>.delayed(const Duration(milliseconds: 1000)).then(
        (final _) => newMessage.content =
            '${newMessage.content}: ${UtcDateTime.now().todmYHMS()}',
      ),
    );
    _addCounter++;
  }

  final _testMessages = {
    'abc': TestChatviewChat(
      uuid: 'abc',
      title: 'Testchat abc :)',
      messages: [
        TestChatviewMessage.system(
          uuid: 'system1',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 5),
          ),
          content: 'Testmessage 1 in abc',
          visibleToAll: true,
        ),
        TestChatviewMessage(
          uuid: 'userabc1',
          senderUuid: 'x123x',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 3),
          ),
          content: 'Usermessage 1 in abc',
        ),
        TestChatviewMessage(
          uuid: 'userabc2',
          senderUuid: 'x123x',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 1),
          ),
          content: 'Testmessage 2 in abc',
        ),
      ],
    ),
    'def': TestChatviewChat(
      uuid: 'def',
      title: 'Testchat def :o',
      messages: [
        TestChatviewMessage.video(
          uuid: 'userdef1',
          senderUuid: 'x123x',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 5),
          ),
          videoName: 'MyVideo',
        ),
        TestChatviewMessage(
          uuid: 'userdef2',
          senderUuid: 'x123x',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 3),
          ),
          content: 'Testmessage 2 in def',
        ),
        TestChatviewMessage(
          uuid: 'userdef2',
          senderUuid: '1',
          sentAt: UtcDateTime.now().subtract(
            const Duration(minutes: 3),
          ),
          content: 'Testmessage 3 in def',
        ),
      ],
    ),
  };

  TestChatviewUser _makeUserFromUuid(
    final String uuid,
  ) =>
      TestChatviewUser(
        uuid: uuid,
        displayName: 'user $uuid',
      );

  @override
  FutureOr<Iterable<TestChatviewUser>> loadUsers({
    required final TestChatviewChat chat,
  }) =>
      _possibleSenders.map(_makeUserFromUuid);

  @override
  FutureOr<TestChatviewUser> loadUser({
    required final String uuid,
    required final TestChatviewChat chat,
  }) =>
      Future<void>.delayed(const Duration(seconds: 2))
          .then((final _) => _makeUserFromUuid(uuid));

  @override
  FutureOr<Iterable<TestChatviewChat>> loadChats() => _testMessages.values;

  @override
  FutureOr<Iterable<TestChatviewMessage>> loadMessages({
    required final TestChatviewChat chat,
  }) {
    if (!_testMessages.containsKey(chat.uuid)) {
      return [];
    }

    return _testMessages[chat.uuid]!.messageList;
  }

  @override
  FutureOr<TestChatviewMessage> loadMessage({
    required final String uuid,
    required final TestChatviewChat chat,
  }) async {
    await Future<void>.delayed(const Duration(seconds: 5));
    if (!_testMessages.containsKey(chat.uuid)) {
      return Future.error(Exception('not found'));
    }

    return _testMessages[chat.uuid]!
        .messageList
        .firstWhere((final msg) => msg.uuid == uuid);
  }
}

class TestChatviewMessage extends EasyChatviewMessage<String> {
  TestChatviewMessage({
    required super.uuid,
    required super.sentAt,
    required super.content,
    required super.senderUuid,
    super.allHaveReceived,
    super.allHaveRead,
    super.inReplyTo,
    super.error,
  }) : super.user();

  TestChatviewMessage.system({
    required super.uuid,
    required super.sentAt,
    required super.content,
    required super.visibleToAll,
  }) : super.system();

  TestChatviewMessage.video({
    required super.uuid,
    required super.sentAt,
    required super.senderUuid,
    required final String videoName,
    super.allHaveReceived,
    super.allHaveRead,
    super.inReplyTo,
    super.error,
  }) : super.user(content: 'video: $videoName');
}

class TestChatviewChat
    extends EasyChatviewChat<TestChatviewMessage, TestChatviewUser> {
  TestChatviewChat({
    required super.uuid,
    required super.title,
    super.messages,
  });
}

class TestChatviewUser extends EasyChatviewUser {
  TestChatviewUser({
    required super.uuid,
    required super.displayName,
  });
}
0
likes
140
points
46
downloads

Documentation

API reference

Publisher

verified publisherjb.software

Weekly Downloads

A package that lets you implement chatting the way you like it.

Repository (GitLab)

Topics

#chat

License

BSD-3-Clause (license)

Dependencies

diffutil_dart, easy_tuple, flutter, intl, super_sliver_list, utc_date_time

More

Packages that depend on easy_chatview