♻️ Basic things to move to events system
This commit is contained in:
		
							
								
								
									
										119
									
								
								lib/controllers/chat_events_controller.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								lib/controllers/chat_events_controller.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/platform.dart'; | ||||
| import 'package:solian/providers/message/helper.dart'; | ||||
| import 'package:solian/providers/message/events.dart'; | ||||
|  | ||||
| class ChatEventController { | ||||
|   late final MessageHistoryDb database; | ||||
|  | ||||
|   final RxList<LocalEvent> currentEvents = RxList.empty(growable: true); | ||||
|   final RxInt totalEvents = 0.obs; | ||||
|  | ||||
|   final RxBool isLoading = false.obs; | ||||
|  | ||||
|   Channel? channel; | ||||
|  | ||||
|   initialize() async { | ||||
|     database = await createHistoryDb(); | ||||
|     currentEvents.clear(); | ||||
|   } | ||||
|  | ||||
|   Future<void> getEvents(Channel channel, String scope) async { | ||||
|     syncLocal(channel); | ||||
|  | ||||
|     isLoading.value = true; | ||||
|     final result = await database.syncEvents( | ||||
|       channel, | ||||
|       scope: scope, | ||||
|     ); | ||||
|     totalEvents.value = result?.$2 ?? 0; | ||||
|     if (!await syncLocal(channel) && result != null) { | ||||
|       currentEvents.addAll(result.$1.map( | ||||
|         (x) => LocalEvent( | ||||
|           x.id, | ||||
|           x, | ||||
|           x.channelId, | ||||
|           x.createdAt, | ||||
|         ), | ||||
|       )); | ||||
|     } | ||||
|     isLoading.value = false; | ||||
|   } | ||||
|  | ||||
|   Future<void> loadEvents(Channel channel, String scope) async { | ||||
|     isLoading.value = true; | ||||
|     final result = await database.syncEvents( | ||||
|       channel, | ||||
|       depth: 3, | ||||
|       scope: scope, | ||||
|       offset: currentEvents.length, | ||||
|     ); | ||||
|     totalEvents.value = result?.$2 ?? 0; | ||||
|     if (!await syncLocal(channel) && result != null) { | ||||
|       currentEvents.addAll(result.$1.map( | ||||
|         (x) => LocalEvent( | ||||
|           x.id, | ||||
|           x, | ||||
|           x.channelId, | ||||
|           x.createdAt, | ||||
|         ), | ||||
|       )); | ||||
|     } | ||||
|     isLoading.value = false; | ||||
|   } | ||||
|  | ||||
|   Future<bool> syncLocal(Channel channel) async { | ||||
|     if (PlatformInfo.isWeb) return false; | ||||
|     currentEvents.replaceRange( | ||||
|       0, | ||||
|       currentEvents.length, | ||||
|       await database.localEvents.findAllByChannel(channel.id), | ||||
|     ); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   receiveEvent(Event remote) async { | ||||
|     final entry = await database.receiveEvent(remote); | ||||
|  | ||||
|     if (remote.channelId != channel?.id) return; | ||||
|  | ||||
|     final idx = currentEvents.indexWhere((x) => x.data.uuid == remote.uuid); | ||||
|     if (idx != -1) { | ||||
|       currentEvents[idx] = entry; | ||||
|     } else { | ||||
|       currentEvents.insert(0, entry); | ||||
|     } | ||||
|  | ||||
|     switch (remote.type) { | ||||
|       case 'messages.edit': | ||||
|         final body = EventMessageBody.fromJson(remote.body); | ||||
|         if (body.relatedEvent != null) { | ||||
|           final idx = | ||||
|               currentEvents.indexWhere((x) => x.data.id == body.relatedEvent); | ||||
|           if (idx != -1) { | ||||
|             currentEvents[idx].data.body = remote.body; | ||||
|             currentEvents[idx].data.updatedAt = remote.updatedAt; | ||||
|           } | ||||
|         } | ||||
|       case 'messages.delete': | ||||
|         final body = EventMessageBody.fromJson(remote.body); | ||||
|         if (body.relatedEvent != null) { | ||||
|           currentEvents.removeWhere((x) => x.id == body.relatedEvent); | ||||
|         } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   addPendingEvent(Event info) async { | ||||
|     currentEvents.insert( | ||||
|       0, | ||||
|       LocalEvent( | ||||
|         info.id, | ||||
|         info, | ||||
|         info.channelId, | ||||
|         DateTime.now(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,83 +0,0 @@ | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/providers/message/helper.dart'; | ||||
| import 'package:solian/providers/message/history.dart'; | ||||
|  | ||||
| class ChatHistoryController { | ||||
|   late final MessageHistoryDb database; | ||||
|  | ||||
|   final RxList<LocalMessage> currentHistory = RxList.empty(growable: true); | ||||
|   final RxInt totalHistoryCount = 0.obs; | ||||
|  | ||||
|   final RxBool isLoading = false.obs; | ||||
|  | ||||
|   initialize() async { | ||||
|     database = await createHistoryDb(); | ||||
|     currentHistory.clear(); | ||||
|   } | ||||
|  | ||||
|   Future<void> getMessages(Channel channel, String scope) async { | ||||
|     syncHistory(channel); | ||||
|  | ||||
|     isLoading.value = true; | ||||
|     totalHistoryCount.value = await database.syncMessages( | ||||
|       channel, | ||||
|       scope: scope, | ||||
|     ); | ||||
|     await syncHistory(channel); | ||||
|     isLoading.value = false; | ||||
|   } | ||||
|  | ||||
|   Future<void> getMoreMessages(Channel channel, String scope) async { | ||||
|     isLoading.value = true; | ||||
|     totalHistoryCount.value = await database.syncMessages( | ||||
|       channel, | ||||
|       breath: 3, | ||||
|       scope: scope, | ||||
|       offset: currentHistory.length, | ||||
|     ); | ||||
|     await syncHistory(channel); | ||||
|     isLoading.value = false; | ||||
|   } | ||||
|  | ||||
|   Future<void> syncHistory(Channel channel) async { | ||||
|     currentHistory.replaceRange(0, currentHistory.length, | ||||
|         await database.localMessages.findAllByChannel(channel.id)); | ||||
|   } | ||||
|  | ||||
|   receiveMessage(Message remote) async { | ||||
|     final entry = await database.receiveMessage(remote); | ||||
|     final idx = currentHistory.indexWhere((x) => x.data.uuid == remote.uuid); | ||||
|     if (idx != -1) { | ||||
|       currentHistory[idx] = entry; | ||||
|     } else { | ||||
|       currentHistory.insert(0, entry); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   addTemporaryMessage(Message info) async { | ||||
|     currentHistory.insert( | ||||
|       0, | ||||
|       LocalMessage( | ||||
|         info.id, | ||||
|         info, | ||||
|         info.channelId, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void replaceMessage(Message remote) async { | ||||
|     final entry = await database.replaceMessage(remote); | ||||
|     currentHistory.replaceRange( | ||||
|       0, | ||||
|       currentHistory.length, | ||||
|       currentHistory.map((x) => x.id == entry.id ? entry : x), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void burnMessage(int id) async { | ||||
|     await database.burnMessage(id); | ||||
|     currentHistory.removeWhere((x) => x.id == id); | ||||
|   } | ||||
| } | ||||
| @@ -1,31 +1,31 @@ | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| 
 | ||||
| class Message { | ||||
| class Event { | ||||
|   int id; | ||||
|   String uuid; | ||||
|   DateTime createdAt; | ||||
|   DateTime updatedAt; | ||||
|   DateTime? deletedAt; | ||||
|   Map<String, dynamic> content; | ||||
|   Map<String, dynamic> body; | ||||
|   String type; | ||||
|   List<int>? attachments; | ||||
|   Channel? channel; | ||||
|   Sender sender; | ||||
|   int? replyId; | ||||
|   Message? replyTo; | ||||
|   Event? replyTo; | ||||
|   int channelId; | ||||
|   int senderId; | ||||
| 
 | ||||
|   bool isSending = false; | ||||
| 
 | ||||
|   Message({ | ||||
|   Event({ | ||||
|     required this.id, | ||||
|     required this.uuid, | ||||
|     required this.createdAt, | ||||
|     required this.updatedAt, | ||||
|     this.deletedAt, | ||||
|     required this.content, | ||||
|     required this.body, | ||||
|     required this.type, | ||||
|     this.attachments, | ||||
|     this.channel, | ||||
| @@ -36,13 +36,13 @@ class Message { | ||||
|     required this.senderId, | ||||
|   }); | ||||
| 
 | ||||
|   factory Message.fromJson(Map<String, dynamic> json) => Message( | ||||
|   factory Event.fromJson(Map<String, dynamic> json) => Event( | ||||
|         id: json['id'], | ||||
|         uuid: json['uuid'], | ||||
|         createdAt: DateTime.parse(json['created_at']), | ||||
|         updatedAt: DateTime.parse(json['updated_at']), | ||||
|         deletedAt: json['deleted_at'], | ||||
|         content: json['content'], | ||||
|         body: json['body'], | ||||
|         type: json['type'], | ||||
|         attachments: json['attachments'] != null | ||||
|             ? List<int>.from(json['attachments']) | ||||
| @@ -51,9 +51,8 @@ class Message { | ||||
|             json['channel'] != null ? Channel.fromJson(json['channel']) : null, | ||||
|         sender: Sender.fromJson(json['sender']), | ||||
|         replyId: json['reply_id'], | ||||
|         replyTo: json['reply_to'] != null | ||||
|             ? Message.fromJson(json['reply_to']) | ||||
|             : null, | ||||
|         replyTo: | ||||
|             json['reply_to'] != null ? Event.fromJson(json['reply_to']) : null, | ||||
|         channelId: json['channel_id'], | ||||
|         senderId: json['sender_id'], | ||||
|       ); | ||||
| @@ -64,7 +63,7 @@ class Message { | ||||
|         'created_at': createdAt.toIso8601String(), | ||||
|         'updated_at': updatedAt.toIso8601String(), | ||||
|         'deleted_at': deletedAt, | ||||
|         'content': content, | ||||
|         'body': body, | ||||
|         'type': type, | ||||
|         'attachments': attachments, | ||||
|         'channel': channel?.toJson(), | ||||
| @@ -76,6 +75,47 @@ class Message { | ||||
|       }; | ||||
| } | ||||
| 
 | ||||
| class EventMessageBody { | ||||
|   String text; | ||||
|   String algorithm; | ||||
|   List<int>? attachments; | ||||
|   int? quoteEvent; | ||||
|   int? relatedEvent; | ||||
|   List<int>? relatedUsers; | ||||
| 
 | ||||
|   EventMessageBody({ | ||||
|     required this.text, | ||||
|     required this.algorithm, | ||||
|     required this.attachments, | ||||
|     required this.quoteEvent, | ||||
|     required this.relatedEvent, | ||||
|     required this.relatedUsers, | ||||
|   }); | ||||
| 
 | ||||
|   factory EventMessageBody.fromJson(Map<String, dynamic> json) => | ||||
|       EventMessageBody( | ||||
|         text: json['text'], | ||||
|         algorithm: json['algorithm'], | ||||
|         attachments: json['attachments'] != null | ||||
|             ? List<int>.from(json['attachments'].map((x) => x)) | ||||
|             : null, | ||||
|         quoteEvent: json['quote_event'], | ||||
|         relatedEvent: json['related_event'], | ||||
|         relatedUsers: json['related_users'] != null | ||||
|             ? List<int>.from(json['related_users'].map((x) => x)) | ||||
|             : null, | ||||
|       ); | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => { | ||||
|         'text': text, | ||||
|         'algorithm': algorithm, | ||||
|         'attachments': attachments?.cast<dynamic>(), | ||||
|         'quote_event': quoteEvent, | ||||
|         'related_event': relatedEvent, | ||||
|         'related_users': relatedUsers?.cast<dynamic>(), | ||||
|       }; | ||||
| } | ||||
| 
 | ||||
| class Sender { | ||||
|   int id; | ||||
|   DateTime createdAt; | ||||
| @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_secure_storage/flutter_secure_storage.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:get/get_connect/http/src/request/request.dart'; | ||||
| import 'package:solian/controllers/chat_history_controller.dart'; | ||||
| import 'package:solian/controllers/chat_events_controller.dart'; | ||||
| import 'package:solian/providers/account.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| @@ -136,9 +136,9 @@ class AuthProvider extends GetConnect { | ||||
|     Get.find<AccountProvider>().notifications.clear(); | ||||
|     Get.find<AccountProvider>().notificationUnread.value = 0; | ||||
|  | ||||
|     final chatHistory = ChatHistoryController(); | ||||
|     final chatHistory = ChatEventController(); | ||||
|     chatHistory.initialize().then((_) async { | ||||
|       await chatHistory.database.localMessages.wipeLocalMessages(); | ||||
|       await chatHistory.database.localEvents.wipeLocalEvents(); | ||||
|     }); | ||||
|  | ||||
|     storage.deleteAll(); | ||||
|   | ||||
							
								
								
									
										83
									
								
								lib/providers/message/events.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								lib/providers/message/events.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'package:floor/floor.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:sqflite/sqflite.dart' as sqflite; | ||||
|  | ||||
| part 'events.g.dart'; | ||||
|  | ||||
| @entity | ||||
| class LocalEvent { | ||||
|   @primaryKey | ||||
|   final int id; | ||||
|  | ||||
|   final Event data; | ||||
|   final int channelId; | ||||
|  | ||||
|   final DateTime createdAt; | ||||
|  | ||||
|   LocalEvent(this.id, this.data, this.channelId, this.createdAt); | ||||
| } | ||||
|  | ||||
| class DateTimeConverter extends TypeConverter<DateTime, int> { | ||||
|   @override | ||||
|   DateTime decode(int databaseValue) { | ||||
|     return DateTime.fromMillisecondsSinceEpoch(databaseValue); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int encode(DateTime value) { | ||||
|     return value.millisecondsSinceEpoch; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class RemoteEventConverter extends TypeConverter<Event, String> { | ||||
|   @override | ||||
|   Event decode(String databaseValue) { | ||||
|     return Event.fromJson(jsonDecode(databaseValue)); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String encode(Event value) { | ||||
|     return jsonEncode(value.toJson()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @dao | ||||
| abstract class LocalEventDao { | ||||
|   @Query('SELECT COUNT(id) FROM LocalEvent WHERE channelId = :channelId') | ||||
|   Future<int?> countByChannel(int channelId); | ||||
|  | ||||
|   @Query('SELECT * FROM LocalEvent WHERE id = :id') | ||||
|   Future<LocalEvent?> findById(int id); | ||||
|  | ||||
|   @Query('SELECT * FROM LocalEvent WHERE channelId = :channelId ORDER BY createdAt DESC') | ||||
|   Future<List<LocalEvent>> findAllByChannel(int channelId); | ||||
|  | ||||
|   @Query('SELECT * FROM LocalEvent WHERE channelId = :channelId ORDER BY createdAt DESC LIMIT 1') | ||||
|   Future<LocalEvent?> findLastByChannel(int channelId); | ||||
|  | ||||
|   @Insert(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> insert(LocalEvent m); | ||||
|  | ||||
|   @Insert(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> insertBulk(List<LocalEvent> m); | ||||
|  | ||||
|   @Update(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> update(LocalEvent m); | ||||
|  | ||||
|   @Query('DELETE FROM LocalEvent WHERE id = :id') | ||||
|   Future<void> delete(int id); | ||||
|  | ||||
|   @Query('DELETE FROM LocalEvent WHERE channelId = :channelId') | ||||
|   Future<List<LocalEvent>> deleteByChannel(int channelId); | ||||
|  | ||||
|   @Query('DELETE FROM LocalEvent') | ||||
|   Future<void> wipeLocalEvents(); | ||||
| } | ||||
|  | ||||
| @TypeConverters([DateTimeConverter, RemoteEventConverter]) | ||||
| @Database(version: 2, entities: [LocalEvent]) | ||||
| abstract class MessageHistoryDb extends FloorDatabase { | ||||
|   LocalEventDao get localEvents; | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| 
 | ||||
| part of 'history.dart'; | ||||
| part of 'events.dart'; | ||||
| 
 | ||||
| // ************************************************************************** | ||||
| // FloorGenerator | ||||
| @@ -72,7 +72,7 @@ class _$MessageHistoryDb extends MessageHistoryDb { | ||||
|     changeListener = listener ?? StreamController<String>.broadcast(); | ||||
|   } | ||||
| 
 | ||||
|   LocalMessageDao? _localMessagesInstance; | ||||
|   LocalEventDao? _localEventsInstance; | ||||
| 
 | ||||
|   Future<sqflite.Database> open( | ||||
|     String path, | ||||
| @@ -80,7 +80,7 @@ class _$MessageHistoryDb extends MessageHistoryDb { | ||||
|     Callback? callback, | ||||
|   ]) async { | ||||
|     final databaseOptions = sqflite.OpenDatabaseOptions( | ||||
|       version: 1, | ||||
|       version: 2, | ||||
|       onConfigure: (database) async { | ||||
|         await database.execute('PRAGMA foreign_keys = ON'); | ||||
|         await callback?.onConfigure?.call(database); | ||||
| @@ -96,7 +96,7 @@ class _$MessageHistoryDb extends MessageHistoryDb { | ||||
|       }, | ||||
|       onCreate: (database, version) async { | ||||
|         await database.execute( | ||||
|             'CREATE TABLE IF NOT EXISTS `LocalMessage` (`id` INTEGER NOT NULL, `data` TEXT NOT NULL, `channelId` INTEGER NOT NULL, PRIMARY KEY (`id`))'); | ||||
|             'CREATE TABLE IF NOT EXISTS `LocalEvent` (`id` INTEGER NOT NULL, `data` TEXT NOT NULL, `channelId` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, PRIMARY KEY (`id`))'); | ||||
| 
 | ||||
|         await callback?.onCreate?.call(database, version); | ||||
|       }, | ||||
| @@ -105,33 +105,34 @@ class _$MessageHistoryDb extends MessageHistoryDb { | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   LocalMessageDao get localMessages { | ||||
|     return _localMessagesInstance ??= | ||||
|         _$LocalMessageDao(database, changeListener); | ||||
|   LocalEventDao get localEvents { | ||||
|     return _localEventsInstance ??= _$LocalEventDao(database, changeListener); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _$LocalMessageDao extends LocalMessageDao { | ||||
|   _$LocalMessageDao( | ||||
| class _$LocalEventDao extends LocalEventDao { | ||||
|   _$LocalEventDao( | ||||
|     this.database, | ||||
|     this.changeListener, | ||||
|   )   : _queryAdapter = QueryAdapter(database), | ||||
|         _localMessageInsertionAdapter = InsertionAdapter( | ||||
|         _localEventInsertionAdapter = InsertionAdapter( | ||||
|             database, | ||||
|             'LocalMessage', | ||||
|             (LocalMessage item) => <String, Object?>{ | ||||
|             'LocalEvent', | ||||
|             (LocalEvent item) => <String, Object?>{ | ||||
|                   'id': item.id, | ||||
|                   'data': _remoteMessageConverter.encode(item.data), | ||||
|                   'channelId': item.channelId | ||||
|                   'data': _remoteEventConverter.encode(item.data), | ||||
|                   'channelId': item.channelId, | ||||
|                   'createdAt': _dateTimeConverter.encode(item.createdAt) | ||||
|                 }), | ||||
|         _localMessageUpdateAdapter = UpdateAdapter( | ||||
|         _localEventUpdateAdapter = UpdateAdapter( | ||||
|             database, | ||||
|             'LocalMessage', | ||||
|             'LocalEvent', | ||||
|             ['id'], | ||||
|             (LocalMessage item) => <String, Object?>{ | ||||
|             (LocalEvent item) => <String, Object?>{ | ||||
|                   'id': item.id, | ||||
|                   'data': _remoteMessageConverter.encode(item.data), | ||||
|                   'channelId': item.channelId | ||||
|                   'data': _remoteEventConverter.encode(item.data), | ||||
|                   'channelId': item.channelId, | ||||
|                   'createdAt': _dateTimeConverter.encode(item.createdAt) | ||||
|                 }); | ||||
| 
 | ||||
|   final sqflite.DatabaseExecutor database; | ||||
| @@ -140,75 +141,88 @@ class _$LocalMessageDao extends LocalMessageDao { | ||||
| 
 | ||||
|   final QueryAdapter _queryAdapter; | ||||
| 
 | ||||
|   final InsertionAdapter<LocalMessage> _localMessageInsertionAdapter; | ||||
|   final InsertionAdapter<LocalEvent> _localEventInsertionAdapter; | ||||
| 
 | ||||
|   final UpdateAdapter<LocalMessage> _localMessageUpdateAdapter; | ||||
|   final UpdateAdapter<LocalEvent> _localEventUpdateAdapter; | ||||
| 
 | ||||
|   @override | ||||
|   Future<int?> countByChannel(int channelId) async { | ||||
|     return _queryAdapter.query( | ||||
|         'SELECT COUNT(id) FROM LocalMessage WHERE channelId = ?1', | ||||
|         'SELECT COUNT(id) FROM LocalEvent WHERE channelId = ?1', | ||||
|         mapper: (Map<String, Object?> row) => row.values.first as int, | ||||
|         arguments: [channelId]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<LocalMessage>> findAllByChannel(int channelId) async { | ||||
|     return _queryAdapter.queryList( | ||||
|         'SELECT * FROM LocalMessage WHERE channelId = ?1 ORDER BY id DESC', | ||||
|         mapper: (Map<String, Object?> row) => LocalMessage( | ||||
|   Future<LocalEvent?> findById(int id) async { | ||||
|     return _queryAdapter.query('SELECT * FROM LocalEvent WHERE id = ?1', | ||||
|         mapper: (Map<String, Object?> row) => LocalEvent( | ||||
|             row['id'] as int, | ||||
|             _remoteMessageConverter.decode(row['data'] as String), | ||||
|             row['channelId'] as int), | ||||
|             _remoteEventConverter.decode(row['data'] as String), | ||||
|             row['channelId'] as int, | ||||
|             _dateTimeConverter.decode(row['createdAt'] as int)), | ||||
|         arguments: [id]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<LocalEvent>> findAllByChannel(int channelId) async { | ||||
|     return _queryAdapter.queryList( | ||||
|         'SELECT * FROM LocalEvent WHERE channelId = ?1 ORDER BY createdAt DESC', | ||||
|         mapper: (Map<String, Object?> row) => LocalEvent( | ||||
|             row['id'] as int, | ||||
|             _remoteEventConverter.decode(row['data'] as String), | ||||
|             row['channelId'] as int, | ||||
|             _dateTimeConverter.decode(row['createdAt'] as int)), | ||||
|         arguments: [channelId]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<LocalMessage?> findLastByChannel(int channelId) async { | ||||
|   Future<LocalEvent?> findLastByChannel(int channelId) async { | ||||
|     return _queryAdapter.query( | ||||
|         'SELECT * FROM LocalMessage WHERE channelId = ?1 ORDER BY id DESC LIMIT 1', | ||||
|         mapper: (Map<String, Object?> row) => LocalMessage(row['id'] as int, _remoteMessageConverter.decode(row['data'] as String), row['channelId'] as int), | ||||
|         'SELECT * FROM LocalEvent WHERE channelId = ?1 ORDER BY createdAt DESC LIMIT 1', | ||||
|         mapper: (Map<String, Object?> row) => LocalEvent(row['id'] as int, _remoteEventConverter.decode(row['data'] as String), row['channelId'] as int, _dateTimeConverter.decode(row['createdAt'] as int)), | ||||
|         arguments: [channelId]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> delete(int id) async { | ||||
|     await _queryAdapter.queryNoReturn('DELETE FROM LocalMessage WHERE id = ?1', | ||||
|         arguments: [id]); | ||||
|     await _queryAdapter | ||||
|         .queryNoReturn('DELETE FROM LocalEvent WHERE id = ?1', arguments: [id]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<LocalMessage>> deleteByChannel(int channelId) async { | ||||
|   Future<List<LocalEvent>> deleteByChannel(int channelId) async { | ||||
|     return _queryAdapter.queryList( | ||||
|         'DELETE FROM LocalMessage WHERE channelId = ?1', | ||||
|         mapper: (Map<String, Object?> row) => LocalMessage( | ||||
|         'DELETE FROM LocalEvent WHERE channelId = ?1', | ||||
|         mapper: (Map<String, Object?> row) => LocalEvent( | ||||
|             row['id'] as int, | ||||
|             _remoteMessageConverter.decode(row['data'] as String), | ||||
|             row['channelId'] as int), | ||||
|             _remoteEventConverter.decode(row['data'] as String), | ||||
|             row['channelId'] as int, | ||||
|             _dateTimeConverter.decode(row['createdAt'] as int)), | ||||
|         arguments: [channelId]); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> wipeLocalMessages() async { | ||||
|     await _queryAdapter.queryNoReturn('DELETE FROM LocalMessage'); | ||||
|   Future<void> wipeLocalEvents() async { | ||||
|     await _queryAdapter.queryNoReturn('DELETE FROM LocalEvent'); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> insert(LocalMessage m) async { | ||||
|     await _localMessageInsertionAdapter.insert(m, OnConflictStrategy.replace); | ||||
|   Future<void> insert(LocalEvent m) async { | ||||
|     await _localEventInsertionAdapter.insert(m, OnConflictStrategy.replace); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> insertBulk(List<LocalMessage> m) async { | ||||
|     await _localMessageInsertionAdapter.insertList( | ||||
|         m, OnConflictStrategy.replace); | ||||
|   Future<void> insertBulk(List<LocalEvent> m) async { | ||||
|     await _localEventInsertionAdapter.insertList(m, OnConflictStrategy.replace); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> update(LocalMessage person) async { | ||||
|     await _localMessageUpdateAdapter.update(person, OnConflictStrategy.replace); | ||||
|   Future<void> update(LocalEvent m) async { | ||||
|     await _localEventUpdateAdapter.update(m, OnConflictStrategy.replace); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // ignore_for_file: unused_element | ||||
| final _remoteMessageConverter = RemoteMessageConverter(); | ||||
| final _dateTimeConverter = DateTimeConverter(); | ||||
| final _remoteEventConverter = RemoteEventConverter(); | ||||
| @@ -1,71 +1,84 @@ | ||||
| import 'package:floor/floor.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/platform.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/message/history.dart'; | ||||
| import 'package:solian/providers/message/events.dart'; | ||||
|  | ||||
| Future<MessageHistoryDb> createHistoryDb() async { | ||||
|   final migration1to2 = Migration(1, 2, (database) async { | ||||
|     await database.execute('DROP TABLE IF EXISTS LocalMessage'); | ||||
|   }); | ||||
|  | ||||
|   return await $FloorMessageHistoryDb | ||||
|       .databaseBuilder('messaging_data.dart') | ||||
|       .build(); | ||||
|       .addMigrations([migration1to2]).build(); | ||||
| } | ||||
|  | ||||
| extension MessageHistoryHelper on MessageHistoryDb { | ||||
|   receiveMessage(Message remote) async { | ||||
|     final entry = LocalMessage( | ||||
|   receiveEvent(Event remote) async { | ||||
|     final entry = LocalEvent( | ||||
|       remote.id, | ||||
|       remote, | ||||
|       remote.channelId, | ||||
|       remote.createdAt, | ||||
|     ); | ||||
|     await localMessages.insert(entry); | ||||
|     await localEvents.insert(entry); | ||||
|     switch (remote.type) { | ||||
|       case 'messages.edit': | ||||
|         final body = EventMessageBody.fromJson(remote.body); | ||||
|         if (body.relatedEvent != null) { | ||||
|           final target = await localEvents.findById(body.relatedEvent!); | ||||
|           if (target != null) { | ||||
|             target.data.body = remote.body; | ||||
|             target.data.updatedAt = remote.updatedAt; | ||||
|             await localEvents.update(target); | ||||
|           } | ||||
|         } | ||||
|       case 'messages.delete': | ||||
|         final body = EventMessageBody.fromJson(remote.body); | ||||
|         if (body.relatedEvent != null) { | ||||
|           await localEvents.delete(body.relatedEvent!); | ||||
|         } | ||||
|     } | ||||
|     return entry; | ||||
|   } | ||||
|  | ||||
|   replaceMessage(Message remote) async { | ||||
|     final entry = LocalMessage( | ||||
|       remote.id, | ||||
|       remote, | ||||
|       remote.channelId, | ||||
|     ); | ||||
|     await localMessages.update(entry); | ||||
|     return entry; | ||||
|   } | ||||
|   Future<(List<Event>, int)?> syncEvents(Channel channel, | ||||
|       {String scope = 'global', depth = 10, offset = 0}) async { | ||||
|     final lastOne = await localEvents.findLastByChannel(channel.id); | ||||
|  | ||||
|   burnMessage(int id) async { | ||||
|     await localMessages.delete(id); | ||||
|   } | ||||
|  | ||||
|   syncMessages(Channel channel, {String scope = 'global', breath = 10, offset = 0}) async { | ||||
|     final lastOne = await localMessages.findLastByChannel(channel.id); | ||||
|  | ||||
|     final data = await _getRemoteMessages( | ||||
|     final data = await _getRemoteEvents( | ||||
|       channel, | ||||
|       scope, | ||||
|       remainBreath: breath, | ||||
|       remainDepth: depth, | ||||
|       offset: offset, | ||||
|       onBrake: (items) { | ||||
|         return items.any((x) => x.id == lastOne?.id); | ||||
|       }, | ||||
|     ); | ||||
|     if (data != null) { | ||||
|       await localMessages.insertBulk( | ||||
|         data.$1.map((x) => LocalMessage(x.id, x, x.channelId)).toList(), | ||||
|     if (data != null && !PlatformInfo.isWeb) { | ||||
|       await localEvents.insertBulk( | ||||
|         data.$1 | ||||
|             .map((x) => LocalEvent(x.id, x, x.channelId, x.createdAt)) | ||||
|             .toList(), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return data?.$2 ?? 0; | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   Future<(List<Message>, int)?> _getRemoteMessages( | ||||
|   Future<(List<Event>, int)?> _getRemoteEvents( | ||||
|     Channel channel, | ||||
|     String scope, { | ||||
|     required int remainBreath, | ||||
|     bool Function(List<Message> items)? onBrake, | ||||
|     required int remainDepth, | ||||
|     bool Function(List<Event> items)? onBrake, | ||||
|     take = 10, | ||||
|     offset = 0, | ||||
|   }) async { | ||||
|     if (remainBreath <= 0) { | ||||
|     if (remainDepth <= 0) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
| @@ -75,7 +88,8 @@ extension MessageHistoryHelper on MessageHistoryDb { | ||||
|     final client = auth.configureClient('messaging'); | ||||
|  | ||||
|     final resp = await client.get( | ||||
|         '/api/channels/$scope/${channel.alias}/messages?take=$take&offset=$offset'); | ||||
|       '/api/channels/$scope/${channel.alias}/events?take=$take&offset=$offset', | ||||
|     ); | ||||
|  | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw Exception(resp.bodyString); | ||||
| @@ -83,16 +97,16 @@ extension MessageHistoryHelper on MessageHistoryDb { | ||||
|  | ||||
|     final PaginationResult response = PaginationResult.fromJson(resp.body); | ||||
|     final result = | ||||
|         response.data?.map((e) => Message.fromJson(e)).toList() ?? List.empty(); | ||||
|         response.data?.map((e) => Event.fromJson(e)).toList() ?? List.empty(); | ||||
|  | ||||
|     if (onBrake != null && onBrake(result)) { | ||||
|       return (result, response.count); | ||||
|     } | ||||
|  | ||||
|     final expandResult = (await _getRemoteMessages( | ||||
|     final expandResult = (await _getRemoteEvents( | ||||
|           channel, | ||||
|           scope, | ||||
|           remainBreath: remainBreath - 1, | ||||
|           remainDepth: remainDepth - 1, | ||||
|           take: take, | ||||
|           offset: offset + result.length, | ||||
|         )) | ||||
| @@ -102,7 +116,7 @@ extension MessageHistoryHelper on MessageHistoryDb { | ||||
|     return ([...result, ...expandResult], response.count); | ||||
|   } | ||||
|  | ||||
|   Future<List<LocalMessage>> listMessages(Channel channel) async { | ||||
|     return await localMessages.findAllByChannel(channel.id); | ||||
|   Future<List<LocalEvent>> listMessages(Channel channel) async { | ||||
|     return await localEvents.findAllByChannel(channel.id); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,66 +0,0 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'package:floor/floor.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:sqflite/sqflite.dart' as sqflite; | ||||
|  | ||||
| part 'history.g.dart'; | ||||
|  | ||||
| @entity | ||||
| class LocalMessage { | ||||
|   @primaryKey | ||||
|   final int id; | ||||
|  | ||||
|   final Message data; | ||||
|   final int channelId; | ||||
|  | ||||
|   LocalMessage(this.id, this.data, this.channelId); | ||||
| } | ||||
|  | ||||
| class RemoteMessageConverter extends TypeConverter<Message, String> { | ||||
|   @override | ||||
|   Message decode(String databaseValue) { | ||||
|     return Message.fromJson(jsonDecode(databaseValue)); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String encode(Message value) { | ||||
|     return jsonEncode(value.toJson()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @dao | ||||
| abstract class LocalMessageDao { | ||||
|   @Query('SELECT COUNT(id) FROM LocalMessage WHERE channelId = :channelId') | ||||
|   Future<int?> countByChannel(int channelId); | ||||
|  | ||||
|   @Query('SELECT * FROM LocalMessage WHERE channelId = :channelId ORDER BY id DESC') | ||||
|   Future<List<LocalMessage>> findAllByChannel(int channelId); | ||||
|  | ||||
|   @Query('SELECT * FROM LocalMessage WHERE channelId = :channelId ORDER BY id DESC LIMIT 1') | ||||
|   Future<LocalMessage?> findLastByChannel(int channelId); | ||||
|  | ||||
|   @Insert(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> insert(LocalMessage m); | ||||
|  | ||||
|   @Insert(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> insertBulk(List<LocalMessage> m); | ||||
|  | ||||
|   @Update(onConflict: OnConflictStrategy.replace) | ||||
|   Future<void> update(LocalMessage person); | ||||
|  | ||||
|   @Query('DELETE FROM LocalMessage WHERE id = :id') | ||||
|   Future<void> delete(int id); | ||||
|  | ||||
|   @Query('DELETE FROM LocalMessage WHERE channelId = :channelId') | ||||
|   Future<List<LocalMessage>> deleteByChannel(int channelId); | ||||
|  | ||||
|   @Query('DELETE FROM LocalMessage') | ||||
|   Future<void> wipeLocalMessages(); | ||||
| } | ||||
|  | ||||
| @TypeConverters([RemoteMessageConverter]) | ||||
| @Database(version: 1, entities: [LocalMessage]) | ||||
| abstract class MessageHistoryDb extends FloorDatabase { | ||||
|   LocalMessageDao get localMessages; | ||||
| } | ||||
| @@ -4,11 +4,11 @@ import 'dart:ui'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/controllers/chat_history_controller.dart'; | ||||
| import 'package:solian/controllers/chat_events_controller.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/call.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/models/packet.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| @@ -50,7 +50,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|   ChannelMember? _channelProfile; | ||||
|   StreamSubscription<NetworkPackage>? _subscription; | ||||
|  | ||||
|   late final ChatHistoryController _chatController; | ||||
|   late final ChatEventController _chatController; | ||||
|  | ||||
|   getProfile() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
| @@ -109,36 +109,22 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|     final ChatProvider provider = Get.find(); | ||||
|     _subscription = provider.stream.stream.listen((event) { | ||||
|       switch (event.method) { | ||||
|         case 'messages.new': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             _chatController.receiveMessage(payload); | ||||
|           } | ||||
|           break; | ||||
|         case 'messages.update': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             _chatController.replaceMessage(payload); | ||||
|           } | ||||
|           break; | ||||
|         case 'messages.burnt': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             _chatController.burnMessage(payload.id); | ||||
|           } | ||||
|         case 'events.new': | ||||
|           final payload = Event.fromJson(event.payload!); | ||||
|             _chatController.receiveEvent(payload); | ||||
|           break; | ||||
|         case 'calls.new': | ||||
|           final payload = Call.fromJson(event.payload!); | ||||
|           _ongoingCall = payload; | ||||
|           setState(() => _ongoingCall = payload); | ||||
|           break; | ||||
|         case 'calls.end': | ||||
|           _ongoingCall = null; | ||||
|           setState(() => _ongoingCall = null); | ||||
|           break; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   bool checkMessageMergeable(Message? a, Message? b) { | ||||
|   bool checkMessageMergeable(Event? a, Event? b) { | ||||
|     if (a?.replyTo != null) return false; | ||||
|     if (a == null || b == null) return false; | ||||
|     if (a.sender.account.id != b.sender.account.id) return false; | ||||
| @@ -156,10 +142,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Message? _messageToReplying; | ||||
|   Message? _messageToEditing; | ||||
|   Event? _messageToReplying; | ||||
|   Event? _messageToEditing; | ||||
|  | ||||
|   Widget buildHistoryBody(Message item, {bool isMerged = false}) { | ||||
|   Widget buildHistoryBody(Event item, {bool isMerged = false}) { | ||||
|     if (item.replyTo != null) { | ||||
|       return Column( | ||||
|         children: [ | ||||
| @@ -188,18 +174,18 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|     bool isMerged = false, hasMerged = false; | ||||
|     if (index > 0) { | ||||
|       hasMerged = checkMessageMergeable( | ||||
|         _chatController.currentHistory[index - 1].data, | ||||
|         _chatController.currentHistory[index].data, | ||||
|         _chatController.currentEvents[index - 1].data, | ||||
|         _chatController.currentEvents[index].data, | ||||
|       ); | ||||
|     } | ||||
|     if (index + 1 < _chatController.currentHistory.length) { | ||||
|     if (index + 1 < _chatController.currentEvents.length) { | ||||
|       isMerged = checkMessageMergeable( | ||||
|         _chatController.currentHistory[index].data, | ||||
|         _chatController.currentHistory[index + 1].data, | ||||
|         _chatController.currentEvents[index].data, | ||||
|         _chatController.currentEvents[index + 1].data, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     final item = _chatController.currentHistory[index].data; | ||||
|     final item = _chatController.currentEvents[index].data; | ||||
|  | ||||
|     return InkWell( | ||||
|       child: Container( | ||||
| @@ -230,18 +216,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     _chatController = ChatHistoryController(); | ||||
|     _chatController = ChatEventController(); | ||||
|     _chatController.initialize(); | ||||
|  | ||||
|     getChannel().then((_) { | ||||
|       _chatController.getMessages(_channel!, widget.realm); | ||||
|       _chatController.channel = _channel!; | ||||
|       _chatController.getEvents(_channel!, widget.realm); | ||||
|  | ||||
|       listenMessages(); | ||||
|     }); | ||||
|  | ||||
|     getProfile(); | ||||
|     getOngoingCall(); | ||||
|  | ||||
|     listenMessages(); | ||||
|  | ||||
|     super.initState(); | ||||
|   } | ||||
|  | ||||
| @@ -325,13 +312,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|                     Obx(() { | ||||
|                       return SliverList.builder( | ||||
|                         key: Key('chat-history#${_channel!.id}'), | ||||
|                         itemCount: _chatController.currentHistory.length, | ||||
|                         itemCount: _chatController.currentEvents.length, | ||||
|                         itemBuilder: buildHistory, | ||||
|                       ); | ||||
|                     }), | ||||
|                     Obx(() { | ||||
|                       final amount = _chatController.totalHistoryCount - | ||||
|                           _chatController.currentHistory.length; | ||||
|                       final amount = _chatController.totalEvents - | ||||
|                           _chatController.currentEvents.length; | ||||
|  | ||||
|                       if (amount.value <= 0 || | ||||
|                           _chatController.isLoading.isTrue) { | ||||
| @@ -348,7 +335,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|                             'count': amount.string, | ||||
|                           })), | ||||
|                           onTap: () { | ||||
|                             _chatController.getMoreMessages( | ||||
|                             _chatController.loadEvents( | ||||
|                               _channel!, | ||||
|                               widget.realm, | ||||
|                             ); | ||||
| @@ -378,9 +365,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|                       realm: widget.realm, | ||||
|                       placeholder: placeholder, | ||||
|                       channel: _channel!, | ||||
|                       onSent: (Message item) { | ||||
|                       onSent: (Event item) { | ||||
|                         setState(() { | ||||
|                           _chatController.addTemporaryMessage(item); | ||||
|                           _chatController.addPendingEvent(item); | ||||
|                         }); | ||||
|                       }, | ||||
|                       onReset: () { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:get/get.dart'; | ||||
|  | ||||
| abstract class ServiceFinder { | ||||
|   static const bool devFlag = false; | ||||
|   static const bool devFlag = true; | ||||
|  | ||||
|   static Map<String, String> services = { | ||||
|     'paperclip': | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:flutter_markdown/flutter_markdown.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/account/account_profile_popup.dart'; | ||||
| import 'package:solian/widgets/attachments/attachment_list.dart'; | ||||
| @@ -13,7 +13,7 @@ import 'package:timeago/timeago.dart' show format; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| class ChatMessage extends StatelessWidget { | ||||
|   final Message item; | ||||
|   final Event item; | ||||
|   final bool isContentPreviewing; | ||||
|   final bool isReply; | ||||
|   final bool isMerged; | ||||
| @@ -46,7 +46,7 @@ class ChatMessage extends StatelessWidget { | ||||
|     final hasAttachment = item.attachments?.isNotEmpty ?? false; | ||||
|  | ||||
|     return FutureBuilder( | ||||
|       future: decodeContent(item.content), | ||||
|       future: decodeContent(item.body), | ||||
|       builder: (context, snapshot) { | ||||
|         if (!snapshot.hasData) { | ||||
|           return Opacity( | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/widgets/chat/chat_message_deletion.dart'; | ||||
| @@ -11,7 +11,7 @@ import 'package:solian/widgets/chat/chat_message_deletion.dart'; | ||||
| class ChatMessageAction extends StatefulWidget { | ||||
|   final Channel channel; | ||||
|   final Realm? realm; | ||||
|   final Message item; | ||||
|   final Event item; | ||||
|   final Function? onEdit; | ||||
|   final Function? onReply; | ||||
|  | ||||
|   | ||||
| @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
|  | ||||
| class ChatMessageDeletionDialog extends StatefulWidget { | ||||
|   final Channel channel; | ||||
|   final Realm? realm; | ||||
|   final Message item; | ||||
|   final Event item; | ||||
|  | ||||
|   const ChatMessageDeletionDialog({ | ||||
|     super.key, | ||||
|   | ||||
| @@ -4,19 +4,19 @@ import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/account.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/event.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/widgets/attachments/attachment_publish.dart'; | ||||
| import 'package:solian/widgets/chat/chat_message.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| class ChatMessageInput extends StatefulWidget { | ||||
|   final Message? edit; | ||||
|   final Message? reply; | ||||
|   final Event? edit; | ||||
|   final Event? reply; | ||||
|   final String? placeholder; | ||||
|   final Channel channel; | ||||
|   final String realm; | ||||
|   final Function(Message) onSent; | ||||
|   final Function(Event) onSent; | ||||
|   final Function()? onReset; | ||||
|  | ||||
|   const ChatMessageInput({ | ||||
| @@ -40,8 +40,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> { | ||||
|  | ||||
|   List<int> _attachments = List.empty(growable: true); | ||||
|  | ||||
|   Message? _editTo; | ||||
|   Message? _replyTo; | ||||
|   Event? _editTo; | ||||
|   Event? _replyTo; | ||||
|  | ||||
|   void showAttachments() { | ||||
|     showModalBottomSheet( | ||||
| @@ -89,12 +89,12 @@ class _ChatMessageInputState extends State<ChatMessageInput> { | ||||
|       accountId: prof.body['id'], | ||||
|       notify: 0, | ||||
|     ); | ||||
|     final message = Message( | ||||
|     final message = Event( | ||||
|       id: 0, | ||||
|       uuid: payload['uuid'] as String, | ||||
|       createdAt: DateTime.now(), | ||||
|       updatedAt: DateTime.now(), | ||||
|       content: payload['content'] as Map<String, dynamic>, | ||||
|       body: payload['content'] as Map<String, dynamic>, | ||||
|       type: payload['type'] as String, | ||||
|       attachments: _attachments, | ||||
|       sender: sender, | ||||
| @@ -141,7 +141,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> { | ||||
|   void syncWidget() { | ||||
|     if (widget.edit != null) { | ||||
|       _editTo = widget.edit!; | ||||
|       _textController.text = widget.edit!.content['value']; | ||||
|       _textController.text = widget.edit!.body['value']; | ||||
|     } | ||||
|     if (widget.reply != null) { | ||||
|       _replyTo = widget.reply!; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user