diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3c2c291..4bf382b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,7 +14,7 @@ FlutterApplication and put your custom class here. --> createState() => _MainDrawerState(); +} + +class _MainDrawerState extends State { + ChatStore _chatStore = getIt(); @override Widget build(BuildContext context) { return Drawer( @@ -24,123 +33,30 @@ class MainDrawer extends StatelessWidget { title: Text('New chat'), minLeadingWidth: 10, onTap: () { - setChatThreadId(null); + widget.setChatThreadId(null); }, ), Expanded( - child: ListView( - padding: EdgeInsets.zero, - children: [ - ChatThreadWidget( - name: 'GraphQL in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'REST API Explanation', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Jitsi Meet in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Websockets in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'ChatGPT Flutter App', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'GraphQL in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'REST API Explanation', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Jitsi Meet in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Websockets in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'GraphQL in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'REST API Explanation', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Jitsi Meet in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Websockets in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'ChatGPT Flutter App', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'GraphQL in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'REST API Explanation', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Jitsi Meet in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ChatThreadWidget( - name: 'Websockets in Flutter', - onTap: () { - setChatThreadId('1'); - }, - ), - ], - ), + child: Observer(builder: (context) { + if (_chatStore.isLoadingChatThreads) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return ListView( + padding: EdgeInsets.zero, + children: _chatStore.chatThreads + .map( + (e) => ChatThreadWidget( + name: e.subject, + onTap: () { + _chatStore.setChatThreadId(e.id); + }, + ), + ) + .toList(), + ); + }), ), ], ), diff --git a/lib/data/local/datasources/message/message_datasource.dart b/lib/data/local/datasources/message/message_datasource.dart index bc92eb2..10d4f4a 100644 --- a/lib/data/local/datasources/message/message_datasource.dart +++ b/lib/data/local/datasources/message/message_datasource.dart @@ -57,7 +57,7 @@ class MessageDataSource { print('Loading from database'); // post list - var chatThreadList; + List chatThreadList = []; // fetching data final recordSnapshots = await _messageStore.find( diff --git a/lib/domain/usecase/message/get_all_chat_threads_usecase.dart b/lib/domain/usecase/message/get_all_chat_threads_usecase.dart index 1479564..e337989 100644 --- a/lib/domain/usecase/message/get_all_chat_threads_usecase.dart +++ b/lib/domain/usecase/message/get_all_chat_threads_usecase.dart @@ -8,7 +8,7 @@ class GetAllChatThreadsUseCase extends UseCase, void> { GetAllChatThreadsUseCase(this._messageRepository); @override - Future> call({required void params}) { + Future> call({void params}) { return _messageRepository.getAllChatThreads(); } } diff --git a/lib/presentation/chat_screen/store/chat_store.dart b/lib/presentation/chat_screen/store/chat_store.dart new file mode 100644 index 0000000..cebc911 --- /dev/null +++ b/lib/presentation/chat_screen/store/chat_store.dart @@ -0,0 +1,134 @@ +import 'package:boilerplate/core/stores/error/error_store.dart'; +import 'package:boilerplate/domain/entity/message/chat_thread.dart'; +import 'package:boilerplate/domain/entity/message/message.dart'; +import 'package:boilerplate/domain/entity/message/message_with_time.dart'; +import 'package:boilerplate/domain/usecase/message/get_all_chat_threads_usecase.dart'; +import 'package:boilerplate/domain/usecase/message/send_message_usecase.dart'; + +import 'package:mobx/mobx.dart'; + +part 'chat_store.g.dart'; + +class ChatStore = _ChatStore with _$ChatStore; + +abstract class _ChatStore with Store { + // constructor:--------------------------------------------------------------- + _ChatStore( + this.errorStore, + this._sendMessageUseCase, + this._getAllChatThreadsUseCase, + ) { + // load all chat threads + getAllChatThreads(); + // setting up disposers + _setupDisposers(); + } + + // use cases:----------------------------------------------------------------- + final SendMessageUseCase _sendMessageUseCase; + final GetAllChatThreadsUseCase _getAllChatThreadsUseCase; + + // store for handling error messages + final ErrorStore errorStore; + + // disposers:----------------------------------------------------------------- + late List _disposers; + + void _setupDisposers() { + _disposers = [ + reaction((_) => success, (_) => success = false, delay: 200), + ]; + } + + // store variables:----------------------------------------------------------- + + @observable + bool success = false; + + @observable + int id = 0; + + @observable + List chatThreads = []; + + @observable + ObservableFuture?> chatThreadsFuture = + ObservableFuture.value(null); + + @observable + ObservableFuture sendMessageFuture = ObservableFuture.value(null); + + @computed + bool get isLoading => sendMessageFuture.status == FutureStatus.pending; + + @computed + bool get isLoadingChatThreads => + chatThreadsFuture.status == FutureStatus.pending; + + // actions:------------------------------------------------------------------- + @action + Future setChatThreadId(int newId) async { + id = newId; + } + + @action + Future setChatThreads(List newChatThreads) async { + chatThreads = newChatThreads; + } + + @action + Future getAllChatThreads() async { + final future = _getAllChatThreadsUseCase.call(); + chatThreadsFuture = ObservableFuture(future); + + await future.then((value) async { + print('response message ${value}'); + this.success = true; + this.chatThreads = value; + }).catchError((e) { + print(e); + this.success = false; + throw e; + }); + } + + @action + Future sendMessage(String content) async { + final Message message = Message(role: Role.user, content: content); + final time = DateTime.now(); + final messages = chatThreads + .firstWhere((element) => element.id == id) + .messages + .map((e) => e.message) + .toList(); + final future = _sendMessageUseCase.call(params: [...messages, message]); + sendMessageFuture = ObservableFuture(future); + + await future.then((value) async { + print('response message ${value.toMap()}'); + // TODO: update chat thread in db + // await _saveChatThreadUseCase.call( + // params: ChatThread( + // id: time.millisecondsSinceEpoch, + // subject: message.content, + // messages: [ + // MessageWithTime(message, time), + // MessageWithTime(value, time), + // ], + // ), + // ); + this.success = true; + }).catchError((e) { + print(e); + this.success = false; + throw e; + }); + } + + // general methods:----------------------------------------------------------- + void dispose() { + for (final d in _disposers) { + d(); + } + } +} diff --git a/lib/presentation/chat_screen/store/chat_store.g.dart b/lib/presentation/chat_screen/store/chat_store.g.dart new file mode 100644 index 0000000..80c9ccf --- /dev/null +++ b/lib/presentation/chat_screen/store/chat_store.g.dart @@ -0,0 +1,149 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_store.dart'; + +// ************************************************************************** +// StoreGenerator +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers + +mixin _$ChatStore on _ChatStore, Store { + Computed? _$isLoadingComputed; + + @override + bool get isLoading => (_$isLoadingComputed ??= + Computed(() => super.isLoading, name: '_ChatStore.isLoading')) + .value; + Computed? _$isLoadingChatThreadsComputed; + + @override + bool get isLoadingChatThreads => (_$isLoadingChatThreadsComputed ??= + Computed(() => super.isLoadingChatThreads, + name: '_ChatStore.isLoadingChatThreads')) + .value; + + late final _$successAtom = Atom(name: '_ChatStore.success', context: context); + + @override + bool get success { + _$successAtom.reportRead(); + return super.success; + } + + @override + set success(bool value) { + _$successAtom.reportWrite(value, super.success, () { + super.success = value; + }); + } + + late final _$idAtom = Atom(name: '_ChatStore.id', context: context); + + @override + int get id { + _$idAtom.reportRead(); + return super.id; + } + + @override + set id(int value) { + _$idAtom.reportWrite(value, super.id, () { + super.id = value; + }); + } + + late final _$chatThreadsAtom = + Atom(name: '_ChatStore.chatThreads', context: context); + + @override + List get chatThreads { + _$chatThreadsAtom.reportRead(); + return super.chatThreads; + } + + @override + set chatThreads(List value) { + _$chatThreadsAtom.reportWrite(value, super.chatThreads, () { + super.chatThreads = value; + }); + } + + late final _$chatThreadsFutureAtom = + Atom(name: '_ChatStore.chatThreadsFuture', context: context); + + @override + ObservableFuture?> get chatThreadsFuture { + _$chatThreadsFutureAtom.reportRead(); + return super.chatThreadsFuture; + } + + @override + set chatThreadsFuture(ObservableFuture?> value) { + _$chatThreadsFutureAtom.reportWrite(value, super.chatThreadsFuture, () { + super.chatThreadsFuture = value; + }); + } + + late final _$sendMessageFutureAtom = + Atom(name: '_ChatStore.sendMessageFuture', context: context); + + @override + ObservableFuture get sendMessageFuture { + _$sendMessageFutureAtom.reportRead(); + return super.sendMessageFuture; + } + + @override + set sendMessageFuture(ObservableFuture value) { + _$sendMessageFutureAtom.reportWrite(value, super.sendMessageFuture, () { + super.sendMessageFuture = value; + }); + } + + late final _$setChatThreadIdAsyncAction = + AsyncAction('_ChatStore.setChatThreadId', context: context); + + @override + Future setChatThreadId(int newId) { + return _$setChatThreadIdAsyncAction.run(() => super.setChatThreadId(newId)); + } + + late final _$setChatThreadsAsyncAction = + AsyncAction('_ChatStore.setChatThreads', context: context); + + @override + Future setChatThreads(List newChatThreads) { + return _$setChatThreadsAsyncAction + .run(() => super.setChatThreads(newChatThreads)); + } + + late final _$getAllChatThreadsAsyncAction = + AsyncAction('_ChatStore.getAllChatThreads', context: context); + + @override + Future getAllChatThreads() { + return _$getAllChatThreadsAsyncAction.run(() => super.getAllChatThreads()); + } + + late final _$sendMessageAsyncAction = + AsyncAction('_ChatStore.sendMessage', context: context); + + @override + Future sendMessage(String content) { + return _$sendMessageAsyncAction.run(() => super.sendMessage(content)); + } + + @override + String toString() { + return ''' +success: ${success}, +id: ${id}, +chatThreads: ${chatThreads}, +chatThreadsFuture: ${chatThreadsFuture}, +sendMessageFuture: ${sendMessageFuture}, +isLoading: ${isLoading}, +isLoadingChatThreads: ${isLoadingChatThreads} + '''; + } +} diff --git a/lib/presentation/di/module/store_module.dart b/lib/presentation/di/module/store_module.dart index 471fc56..5330db9 100644 --- a/lib/presentation/di/module/store_module.dart +++ b/lib/presentation/di/module/store_module.dart @@ -2,14 +2,15 @@ import 'dart:async'; import 'package:boilerplate/core/stores/error/error_store.dart'; import 'package:boilerplate/core/stores/form/form_store.dart'; -import 'package:boilerplate/domain/repository/message/message_repository.dart'; import 'package:boilerplate/domain/repository/setting/setting_repository.dart'; +import 'package:boilerplate/domain/usecase/message/get_all_chat_threads_usecase.dart'; import 'package:boilerplate/domain/usecase/message/save_chat_thread_usecase.dart'; import 'package:boilerplate/domain/usecase/message/send_message_usecase.dart'; import 'package:boilerplate/domain/usecase/post/get_post_usecase.dart'; import 'package:boilerplate/domain/usecase/user/is_logged_in_usecase.dart'; import 'package:boilerplate/domain/usecase/user/login_usecase.dart'; import 'package:boilerplate/domain/usecase/user/save_login_in_status_usecase.dart'; +import 'package:boilerplate/presentation/chat_screen/store/chat_store.dart'; import 'package:boilerplate/presentation/home_demo/store/language/language_store.dart'; import 'package:boilerplate/presentation/home_demo/store/theme/theme_store.dart'; import 'package:boilerplate/presentation/login/store/login_store.dart'; @@ -66,5 +67,13 @@ mixin StoreModule { getIt(), ), ); + + getIt.registerSingleton( + ChatStore( + getIt(), + getIt(), + getIt(), + ), + ); } } diff --git a/lib/presentation/new_chat/store/new_chat_store.dart b/lib/presentation/new_chat/store/new_chat_store.dart index d5938c2..f387950 100644 --- a/lib/presentation/new_chat/store/new_chat_store.dart +++ b/lib/presentation/new_chat/store/new_chat_store.dart @@ -1,9 +1,11 @@ import 'package:boilerplate/core/stores/error/error_store.dart'; +import 'package:boilerplate/di/service_locator.dart'; import 'package:boilerplate/domain/entity/message/chat_thread.dart'; import 'package:boilerplate/domain/entity/message/message.dart'; import 'package:boilerplate/domain/entity/message/message_with_time.dart'; import 'package:boilerplate/domain/usecase/message/save_chat_thread_usecase.dart'; import 'package:boilerplate/domain/usecase/message/send_message_usecase.dart'; +import 'package:boilerplate/presentation/chat_screen/store/chat_store.dart'; import 'package:mobx/mobx.dart'; @@ -59,17 +61,20 @@ abstract class _NewChatStore with Store { await future.then((value) async { print('response message ${value.toMap()}'); + final chatThread = ChatThread( + id: time.millisecondsSinceEpoch, + subject: message.content, + messages: [ + MessageWithTime(message, time), + MessageWithTime(value, time), + ], + ); await _saveChatThreadUseCase.call( - params: ChatThread( - id: time.millisecondsSinceEpoch, - subject: 'Chat ${time.millisecondsSinceEpoch}}', - messages: [ - MessageWithTime(message, time), - MessageWithTime(value, time), - ], - ), + params: chatThread, ); this.success = true; + ChatStore _chatStore = getIt(); + _chatStore.setChatThreads([..._chatStore.chatThreads, chatThread]); }).catchError((e) { print(e); this.success = false;