diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 8ced7815..240c0310 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -1,4 +1,3 @@ - name: stream_flutter_workflow env: @@ -70,7 +69,23 @@ jobs: find . -maxdepth 12 -name "*.java" -print0 \| xargs -0 java -jar $HOME/google-java-format.jar --replace ./.github/workflows/scripts/validate-formatting.sh - test: + test_dart: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: 'Install Flutter' + run: ./.github/workflows/scripts/install-flutter.sh stable + - name: 'Install Tools' + run: ./.github/workflows/scripts/install-tools.sh + - name: 'Bootstrap Workspace' + run: melos bootstrap + - name: 'Flutter Test' + run: cd packages/dart_client && flutter pub run test + + test_flutter: runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -85,5 +100,5 @@ jobs: run: melos bootstrap - name: 'Flutter Test' run: | - melos exec -c 3 --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ + melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ flutter test \ No newline at end of file diff --git a/melos.yaml b/melos.yaml index 7e02909f..6a6c74dc 100644 --- a/melos.yaml +++ b/melos.yaml @@ -33,8 +33,12 @@ scripts: flutter build macos - test: > - melos exec -c 1 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ + test:dart: > + melos exec -c 1 --fail-fast --no-flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ + flutter pub run test + + test:flutter: > + melos exec -c 1 --fail-fast --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \ flutter test test:web: > diff --git a/packages/dart_client/CHANGELOG.md b/packages/dart_client/CHANGELOG.md index fdaee098..98730359 100644 --- a/packages/dart_client/CHANGELOG.md +++ b/packages/dart_client/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.0.0-beta + +- 🛑 **BREAKING** Renamed `Client` to less generic `StreamChatClient` +- 🛑 **BREAKING** Segregated the persistence layer into separate package (stream_chat_persistence)[https://pub.dev/packages/stream_chat_persistence] +- 🛑 **BREAKING** Moved `Client.backgroundKeepAlive` to (core package)[https://pub.dev/packages/stream_chat_core] +- 🛑 **BREAKING** Moved `Client.showLocalNotification` to (core package)[https://pub.dev/packages/stream_chat_core] and renamed it to `StreamChatCore.onBackgroundEventReceived` +- Removed `flutter` dependency. This is now a pure Dart package 🥳 +- Minor improvements and bugfixes + ## 0.2.24+2 - Fix reconnection bug while using tokenProvider diff --git a/packages/dart_client/README.md b/packages/dart_client/README.md index 241c2f4e..e0ed18bf 100644 --- a/packages/dart_client/README.md +++ b/packages/dart_client/README.md @@ -14,7 +14,7 @@ You can sign up for a Stream account at https://getstream.io/chat/ ```yaml dependencies: - stream_chat: ^0.2.0 + stream_chat: ^1.0.0-beta ``` You should then run `flutter packages get` @@ -28,7 +28,7 @@ There is a detailed Flutter example project in the `example` folder. You can dir First you need to instantiate a chat client. The Chat client will manage API call, event handling and manage the websocket connection to Stream Chat servers. You should only create the client once and re-use it across your application. ```dart -final client = Client("stream-chat-api-key"); +final client = StreamChatClient("stream-chat-api-key"); ``` ### Logging @@ -40,7 +40,7 @@ By default the Chat Client will write all messages with level Warn or Error to s During development you might want to enable more logging information, you can change the default log level when constructing the client. ```dart -final client = Client("stream-chat-api-key", logLevel: Level.INFO); +final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO); ``` #### Custom Logger @@ -52,32 +52,39 @@ myLogHandlerFunction = (LogRecord record) { // do something with the record (ie. send it to Sentry or Fabric) } -final client = Client("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction); +final client = StreamChatClient("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction); ``` ### Offline storage -By default the library saves information about channels and messages in a SQLite DB. +To add data persistance you can extend the class `ChatPersistenceClient` and pass an instance to the `StreamChatClient`. -Set the property `persistenceEnabled` to false if you don't want to use the offline storage. +```dart +class CustomChatPersistentClient extends ChatPersistenceClient { +... +} -## Flutter Web - -Due to Moor web (for offline storage) you need to include the sql.js library: - -```html - - - - - - - - - +final client = StreamChatClient( + apiKey ?? kDefaultStreamApiKey, + logLevel: Level.INFO, +)..chatPersistenceClient = CustomChatPersistentClient(); ``` -You can grab the latest version of sql-wasm.js and sql-wasm.wasm [here](https://github.com/sql-js/sql.js/releases) and copy them into your `/web` folder. +We provide an official persistent client in the (stream_chat_persistence)[https://pub.dev/packages/stream_chat_persistence] package. + +```dart +import 'package:stream_chat_persistence/stream_chat_persistence.dart'; + +final chatPersistentClient = StreamChatPersistenceClient( + logLevel: Level.INFO, + connectionMode: ConnectionMode.background, +); + +final client = StreamChatClient( + apiKey ?? kDefaultStreamApiKey, + logLevel: Level.INFO, +)..chatPersistenceClient = chatPersistentClient; +``` ## Contributing diff --git a/packages/dart_client/build.yaml b/packages/dart_client/build.yaml index ea58f519..ddbd70dd 100644 --- a/packages/dart_client/build.yaml +++ b/packages/dart_client/build.yaml @@ -1,9 +1,6 @@ targets: $default: builders: - moor_generator: - options: - generate_connect_constructor: true json_serializable: options: explicit_to_json: true diff --git a/packages/dart_client/example/lib/main.dart b/packages/dart_client/example/lib/main.dart index ed21df76..769e6d44 100644 --- a/packages/dart_client/example/lib/main.dart +++ b/packages/dart_client/example/lib/main.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; Future main() async { - /// Create a new instance of [Client] passing the apikey obtained from your + /// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// project dashboard. - final client = Client('b67pax5b2wdq'); + final client = StreamChatClient('b67pax5b2wdq'); /// Set the current user. In a production scenario, this should be done using /// a backend to generate a user token using our server SDK. @@ -48,9 +48,9 @@ class StreamExample extends StatelessWidget { @required this.channel, }) : super(key: key); - /// Instance of [Client] we created earlier. This contains information about + /// Instance of [StreamChatClient] we created earlier. This contains information about /// our application and connection state. - final Client client; + final StreamChatClient client; /// The channel we'd like to observe and participate. final Channel channel; @@ -242,7 +242,7 @@ class _MessageViewState extends State { } } -/// Helper extension for quickly retrieving the current user id from a [Client]. -extension on Client { +/// Helper extension for quickly retrieving the current user id from a [StreamChatClient]. +extension on StreamChatClient { String get uid => state.user.id; } diff --git a/packages/dart_client/lib/src/api/channel.dart b/packages/dart_client/lib/src/api/channel.dart index d8ef1a03..449a5267 100644 --- a/packages/dart_client/lib/src/api/channel.dart +++ b/packages/dart_client/lib/src/api/channel.dart @@ -160,8 +160,8 @@ class Channel { state?.channelStateStream?.map((cs) => cs.channel?.extraData); /// The main Stream chat client - Client get client => _client; - final Client _client; + StreamChatClient get client => _client; + final StreamChatClient _client; String get _channelURL => '/channels/$type/$id'; @@ -183,7 +183,7 @@ class Channel { user: _client.state.user, id: messageId, quotedMessage: quotedMessage, - status: MessageSendingStatus.SENDING, + status: MessageSendingStatus.sending, ); if (message.parentId != null && message.id == null) { @@ -419,7 +419,7 @@ class Channel { } } - await _client.offlineStorage?.deleteMessages([messageId]); + await _client.chatPersistenceClient?.deleteMessageById(messageId); } return res; @@ -487,9 +487,9 @@ class Channel { PaginationParams options, { bool preferOffline = false, }) async { - final cachedReplies = await _client.offlineStorage?.getReplies( + final cachedReplies = await _client.chatPersistenceClient?.getReplies( parentId, - lessThan: options?.lessThan, + options: options, ); if (cachedReplies != null && cachedReplies.isNotEmpty) { state?.updateThreadInfo(parentId, cachedReplies); @@ -603,11 +603,10 @@ class Channel { } if (preferOffline && cid != null) { - final updatedState = await _client.offlineStorage?.getChannel( + final updatedState = + await _client.chatPersistenceClient?.getChannelStateByCid( cid, - limit: messagesPagination?.limit, - messageLessThan: messagesPagination?.lessThan, - messageGreaterThan: messagesPagination?.greaterThan, + messagePagination: messagesPagination, ); if (updatedState != null && updatedState.messages.isNotEmpty) { if (state == null) { @@ -723,7 +722,7 @@ class Channel { }); } - /// Hides the channel from [Client.queryChannels] for the user until a message is added + /// Hides the channel from [StreamChatClient.queryChannels] for the user until a message is added /// If [clearHistory] is set to true - all messages will be removed for the user Future hide({bool clearHistory = false}) async { _checkInitialized(); @@ -732,7 +731,7 @@ class Channel { if (clearHistory == true) { state.truncate(); - await _client.offlineStorage?.deleteChannelsMessages([_cid]); + await _client.chatPersistenceClient?.deleteMessageByCid(_cid); } return _client.decode(response.data, EmptyResponse.fromJson); @@ -871,7 +870,7 @@ class ChannelClientState { _computeInitialUnread(); - _channel._client.offlineStorage + _channel._client.chatPersistenceClient ?.getChannelThreads(_channel.cid) ?.then((threads) { _threads = threads; @@ -949,8 +948,8 @@ class ChannelClientState { .on(EventType.channelTruncated, EventType.notificationChannelTruncated) .listen((event) async { final channel = event.channel; - await _channel._client.offlineStorage - ?.deleteChannelsMessages([channel.cid]); + await _channel._client.chatPersistenceClient + ?.deleteMessageByCid(channel.cid); truncate(); })); } @@ -978,7 +977,7 @@ class ChannelClientState { [...messages, ...threads.values.expand((v) => v)] .where((message) => message.status != null && - message.status != MessageSendingStatus.SENT && + message.status != MessageSendingStatus.sent && message.createdAt.isBefore(DateTime.now().subtract(Duration( seconds: 1, )))) @@ -1404,7 +1403,7 @@ class ChannelClientState { set _channelState(ChannelState v) { _channelStateController.add(v); - _channel._client.offlineStorage?.updateChannelState(v); + _channel._client.chatPersistenceClient?.updateChannelState(v); } /// The channel threads related to this channel @@ -1417,9 +1416,9 @@ class ChannelClientState { BehaviorSubject.seeded({}); set _threads(Map> v) { - _channel._client.offlineStorage?.updateMessages( - v.values.expand((v) => v).toList(), + _channel._client.chatPersistenceClient?.updateMessages( _channel.cid, + v.values.expand((v) => v).toList(), ); _threadsController.add(v); } diff --git a/packages/dart_client/lib/src/api/responses.dart b/packages/dart_client/lib/src/api/responses.dart index 919a875b..e27ac2c9 100644 --- a/packages/dart_client/lib/src/api/responses.dart +++ b/packages/dart_client/lib/src/api/responses.dart @@ -17,7 +17,7 @@ class _BaseResponse { String duration; } -/// Model response for [Client.resync] api call +/// Model response for [StreamChatClient.resync] api call @JsonSerializable(createToJson: false) class SyncResponse extends _BaseResponse { /// The list of events @@ -28,7 +28,7 @@ class SyncResponse extends _BaseResponse { _$SyncResponseFromJson(json); } -/// Model response for [Client.queryChannels] api call +/// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryChannelsResponse extends _BaseResponse { /// List of channels state returned by the query @@ -39,7 +39,7 @@ class QueryChannelsResponse extends _BaseResponse { _$QueryChannelsResponseFromJson(json); } -/// Model response for [Client.queryChannels] api call +/// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class TranslateMessageResponse extends _BaseResponse { /// List of channels state returned by the query @@ -50,7 +50,7 @@ class TranslateMessageResponse extends _BaseResponse { _$TranslateMessageResponseFromJson(json); } -/// Model response for [Client.queryChannels] api call +/// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class QueryMembersResponse extends _BaseResponse { /// List of channels state returned by the query @@ -61,7 +61,7 @@ class QueryMembersResponse extends _BaseResponse { _$QueryMembersResponseFromJson(json); } -/// Model response for [Client.queryUsers] api call +/// Model response for [StreamChatClient.queryUsers] api call @JsonSerializable(createToJson: false) class QueryUsersResponse extends _BaseResponse { /// List of users returned by the query @@ -94,7 +94,7 @@ class QueryRepliesResponse extends _BaseResponse { _$QueryRepliesResponseFromJson(json); } -/// Model response for [Client.getDevices] api call +/// Model response for [StreamChatClient.getDevices] api call @JsonSerializable(createToJson: false) class ListDevicesResponse extends _BaseResponse { /// List of user devices @@ -141,7 +141,7 @@ class SendReactionResponse extends _BaseResponse { _$SendReactionResponseFromJson(json); } -/// Model response for [Client.setGuestUser] api call +/// Model response for [StreamChatClient.setGuestUser] api call @JsonSerializable(createToJson: false) class SetGuestUserResponse extends _BaseResponse { /// Guest user access token @@ -155,7 +155,7 @@ class SetGuestUserResponse extends _BaseResponse { _$SetGuestUserResponseFromJson(json); } -/// Model response for [Client.updateUser] api call +/// Model response for [StreamChatClient.updateUser] api call @JsonSerializable(createToJson: false) class UpdateUsersResponse extends _BaseResponse { /// Updated users @@ -166,7 +166,7 @@ class UpdateUsersResponse extends _BaseResponse { _$UpdateUsersResponseFromJson(json); } -/// Model response for [Client.updateMessage] api call +/// Model response for [StreamChatClient.updateMessage] api call @JsonSerializable(createToJson: false) class UpdateMessageResponse extends _BaseResponse { /// Message returned by the api call @@ -188,7 +188,7 @@ class SendMessageResponse extends _BaseResponse { _$SendMessageResponseFromJson(json); } -/// Model response for [Client.getMessage] api call +/// Model response for [StreamChatClient.getMessage] api call @JsonSerializable(createToJson: false) class GetMessageResponse extends _BaseResponse { /// Message returned by the api call @@ -208,7 +208,7 @@ class GetMessageResponse extends _BaseResponse { } } -/// Model response for [Client.search] api call +/// Model response for [StreamChatClient.search] api call @JsonSerializable(createToJson: false) class SearchMessagesResponse extends _BaseResponse { /// List of messages returned by the api call diff --git a/packages/dart_client/lib/src/api/retry_policy.dart b/packages/dart_client/lib/src/api/retry_policy.dart index 43af3b8e..2c59ec48 100644 --- a/packages/dart_client/lib/src/api/retry_policy.dart +++ b/packages/dart_client/lib/src/api/retry_policy.dart @@ -1,4 +1,4 @@ -import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; @@ -15,17 +15,18 @@ class RetryPolicy { int attempt = 0; /// This function evaluates if we should retry the failure - final bool Function(Client client, int attempt, ApiError apiError) + final bool Function(StreamChatClient client, int attempt, ApiError apiError) shouldRetry; /// In the case that we want to retry a failed request the retryTimeout method is called to determine the timeout - final Duration Function(Client client, int attempt, ApiError apiError) - retryTimeout; + final Duration Function( + StreamChatClient client, int attempt, ApiError apiError) retryTimeout; /// Creates a copy of [RetryPolicy] with specified attributes overridden. RetryPolicy copyWith({ - bool Function(Client client, int attempt, ApiError apiError) shouldRetry, - Duration Function(Client client, int attempt, ApiError apiError) + bool Function(StreamChatClient client, int attempt, ApiError apiError) + shouldRetry, + Duration Function(StreamChatClient client, int attempt, ApiError apiError) retryTimeout, int attempt, }) => diff --git a/packages/dart_client/lib/src/api/retry_queue.dart b/packages/dart_client/lib/src/api/retry_queue.dart index 12e8377a..534d9c08 100644 --- a/packages/dart_client/lib/src/api/retry_queue.dart +++ b/packages/dart_client/lib/src/api/retry_queue.dart @@ -1,8 +1,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart'; import 'package:logging/logging.dart'; import 'package:stream_chat/src/api/channel.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; @@ -113,30 +112,30 @@ class RetryQueue { } void _sendFailedEvent(Message message) { - final newStatus = message.status == MessageSendingStatus.SENDING - ? MessageSendingStatus.FAILED - : (message.status == MessageSendingStatus.UPDATING - ? MessageSendingStatus.FAILED_UPDATE - : MessageSendingStatus.FAILED_DELETE); + final newStatus = message.status == MessageSendingStatus.sending + ? MessageSendingStatus.failed + : (message.status == MessageSendingStatus.updating + ? MessageSendingStatus.failed_update + : MessageSendingStatus.failed_delete); channel.state.addMessage(message.copyWith( status: newStatus, )); } Future _sendMessage(Message message) async { - if (message.status == MessageSendingStatus.FAILED_UPDATE || - message.status == MessageSendingStatus.UPDATING) { + if (message.status == MessageSendingStatus.failed_update || + message.status == MessageSendingStatus.updating) { await channel.client.updateMessage( message, channel.cid, ); - } else if (message.status == MessageSendingStatus.FAILED || - message.status == MessageSendingStatus.SENDING) { + } else if (message.status == MessageSendingStatus.failed || + message.status == MessageSendingStatus.sending) { await channel.sendMessage( message, ); - } else if (message.status == MessageSendingStatus.FAILED_DELETE || - message.status == MessageSendingStatus.DELETING) { + } else if (message.status == MessageSendingStatus.failed_delete || + message.status == MessageSendingStatus.deleting) { await channel.client.deleteMessage( message, channel.cid, @@ -152,15 +151,15 @@ class RetryQueue { messageList.indexWhere((m) => m.id == event.message.id); if (messageIndex == -1 && [ - MessageSendingStatus.FAILED_UPDATE, - MessageSendingStatus.FAILED, - MessageSendingStatus.FAILED_DELETE, + MessageSendingStatus.failed_update, + MessageSendingStatus.failed, + MessageSendingStatus.failed_delete, ].contains(event.message.status)) { logger?.info('add message from events'); add([event.message]); } else if (messageIndex != -1 && [ - MessageSendingStatus.SENT, + MessageSendingStatus.sent, null, ].contains(event.message.status)) { _messageQueue.remove(messageList[messageIndex]); @@ -184,16 +183,16 @@ class RetryQueue { static DateTime _getMessageDate(Message m1) { switch (m1.status) { - case MessageSendingStatus.FAILED_DELETE: - case MessageSendingStatus.DELETING: + case MessageSendingStatus.failed_delete: + case MessageSendingStatus.deleting: return m1.deletedAt; - case MessageSendingStatus.FAILED: - case MessageSendingStatus.SENDING: + case MessageSendingStatus.failed: + case MessageSendingStatus.sending: return m1.createdAt; - case MessageSendingStatus.FAILED_UPDATE: - case MessageSendingStatus.UPDATING: + case MessageSendingStatus.failed_update: + case MessageSendingStatus.updating: return m1.updatedAt; default: return null; diff --git a/packages/dart_client/lib/src/api/websocket.dart b/packages/dart_client/lib/src/api/websocket.dart index bdd14a1e..15102c6e 100644 --- a/packages/dart_client/lib/src/api/websocket.dart +++ b/packages/dart_client/lib/src/api/websocket.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; import 'package:logging/logging.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../models/event.dart'; @@ -99,9 +99,18 @@ class WebSocket { /// The timeout that uses the reconnection monitor timer to consider the connection unhealthy final int reconnectionMonitorTimeout; + final _connectionStatusController = + BehaviorSubject.seeded(ConnectionStatus.disconnected); + + set _connectionStatus(ConnectionStatus status) => + _connectionStatusController.add(status); + + /// The current connection status value + ConnectionStatus get connectionStatus => _connectionStatusController.value; + /// This notifies of connection status changes - final ValueNotifier connectionStatus = - ValueNotifier(ConnectionStatus.disconnected); + Stream get connectionStatusStream => + _connectionStatusController.stream; String _path; int _retryAttempt = 1; @@ -128,7 +137,7 @@ class WebSocket { } _connecting = true; - connectionStatus.value = ConnectionStatus.connecting; + _connectionStatus = ConnectionStatus.connecting; logger.info('connecting to $_path'); @@ -175,7 +184,7 @@ class WebSocket { _reconnecting = false; _lastEventAt = DateTime.now(); - connectionStatus.value = ConnectionStatus.connected; + _connectionStatus = ConnectionStatus.connected; _retryAttempt = 1; if (!_connectionCompleter.isCompleted) { @@ -199,7 +208,7 @@ class WebSocket { _connecting = false; if (!_reconnecting) { - connectionStatus.value = ConnectionStatus.disconnected; + _connectionStatus = ConnectionStatus.disconnected; } if (!_connectionCompleter.isCompleted) { @@ -258,7 +267,7 @@ class WebSocket { logger.info('reconnect'); if (!_reconnecting) { _reconnecting = true; - connectionStatus.value = ConnectionStatus.connecting; + _connectionStatus = ConnectionStatus.connecting; } _reconnectTimer(); @@ -300,8 +309,8 @@ class WebSocket { _cancelTimers(); _reconnecting = false; _manuallyDisconnected = true; - connectionStatus.value = ConnectionStatus.disconnected; - connectionStatus.dispose(); + _connectionStatus = ConnectionStatus.disconnected; + await _connectionStatusController.close(); return _channel.sink.close(); } } diff --git a/packages/dart_client/lib/src/client.dart b/packages/dart_client/lib/src/client.dart index 5f534864..7f9f560a 100644 --- a/packages/dart_client/lib/src/client.dart +++ b/packages/dart_client/lib/src/client.dart @@ -3,24 +3,22 @@ import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:flutter/cupertino.dart'; import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:pedantic/pedantic.dart' show unawaited; import 'package:rxdart/rxdart.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; import 'package:stream_chat/src/event_type.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/version.dart'; import 'package:uuid/uuid.dart'; -import 'package:pedantic/pedantic.dart' show unawaited; import 'api/channel.dart'; import 'api/connection_status.dart'; import 'api/requests.dart'; import 'api/responses.dart'; import 'api/websocket.dart'; -import 'db/offline_storage.dart'; +import 'db/chat_persistence_client.dart'; import 'exceptions.dart'; import 'models/event.dart'; import 'models/message.dart'; @@ -37,15 +35,6 @@ typedef DecoderFunction = T Function(Map); /// own backend server. Function requires a single [userId]. typedef TokenProvider = Future Function(String userId); -/// The key used to save the userId to sharedPreferences -const String KEY_USER_ID = 'KEY_USER_ID'; - -/// The key used to save the token to sharedPreferences -const String KEY_TOKEN = 'KEY_TOKEN'; - -/// The key used to save the apiKey to sharedPreferences -const String KEY_API_KEY = 'KEY_API_KEY'; - /// Provider used to send push notifications. enum PushProvider { /// Send notifications using Google's Firebase Cloud Messaging @@ -75,31 +64,27 @@ extension on PushProvider { /// websocket connection to Stream Chat servers. /// /// ```dart -/// final client = Client("stream-chat-api-key"); +/// final client = StreamChatClient("stream-chat-api-key"); /// ``` -class Client { +class StreamChatClient { /// Create a client instance with default options. /// You should only create the client once and re-use it across your application. - Client( + StreamChatClient( this.apiKey, { this.tokenProvider, this.baseURL = _defaultBaseURL, this.logLevel = Level.WARNING, this.logHandlerFunction, - this.persistenceEnabled = true, Duration connectTimeout = const Duration(seconds: 6), Duration receiveTimeout = const Duration(seconds: 6), Dio httpClient, - this.showLocalNotification, - this.backgroundKeepAlive = const Duration(minutes: 1), RetryPolicy retryPolicy, }) { - WidgetsFlutterBinding.ensureInitialized(); - _retryPolicy ??= RetryPolicy( - retryTimeout: (Client client, int attempt, ApiError error) => + retryTimeout: (StreamChatClient client, int attempt, ApiError error) => Duration(seconds: 1 * attempt), - shouldRetry: (Client client, int attempt, ApiError error) => attempt < 5, + shouldRetry: (StreamChatClient client, int attempt, ApiError error) => + attempt < 5, ); state = ClientState(this); @@ -110,10 +95,11 @@ class Client { logger.info('instantiating new client'); } - OfflineStorage _offlineStorage; + /// Chat persistence client + ChatPersistenceClient chatPersistenceClient; - /// If true chat data will persist on disk - final bool persistenceEnabled; + /// Whether the chat persistence is available or not + bool get persistenceEnabled => chatPersistenceClient != null; RetryPolicy _retryPolicy; @@ -122,25 +108,14 @@ class Client { /// The retry policy options getter RetryPolicy get retryPolicy => _retryPolicy; - /// Method used to show a local notification while the app is in background - /// Switching to another application will not disconnect the client immediately - /// So, use this method to show the notification when receiving a new message via events - final void Function(Message, ChannelModel) showLocalNotification; - - /// The amount of time that will pass before disconnecting the client in the background - final Duration backgroundKeepAlive; - - /// Client offline database - OfflineStorage get offlineStorage => _offlineStorage; - /// This client state ClientState state; - /// By default the Chat Client will write all messages with level Warn or Error to stdout. + /// By default the Chat client will write all messages with level Warn or Error to stdout. /// During development you might want to enable more logging information, you can change the default log level when constructing the client. /// /// ```dart - /// final client = Client("stream-chat-api-key", logLevel: Level.INFO); + /// final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO); /// ``` final Level logLevel; @@ -159,7 +134,7 @@ class Client { /// // do something with the record (ie. send it to Sentry or Fabric) /// } /// - /// final client = Client("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction); + /// final client = StreamChatClient("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction); ///``` LogHandlerFunction logHandlerFunction; @@ -183,7 +158,8 @@ class Client { static const _defaultBaseURL = 'chat-us-east-1.stream-io-api.com'; static const _tokenExpiredErrorCode = 40; - VoidCallback _connectionStatusListener; + StreamSubscription _connectionStatusSubscription; + Future Function(ConnectionStatus) _connectionStatusHandler; final BehaviorSubject _controller = BehaviorSubject(); @@ -191,10 +167,20 @@ class Client { /// Listen to this or use the [on] method to filter specific event types Stream get stream => _controller.stream; + final _wsConnectionStatusController = + BehaviorSubject.seeded(ConnectionStatus.disconnected); + + set _wsConnectionStatus(ConnectionStatus status) => + _wsConnectionStatusController.add(status); + + /// The current status value of the websocket connection + ConnectionStatus get wsConnectionStatus => + _wsConnectionStatusController.value; + /// This notifies the connection status of the websocket connection. /// Listen to this to get notified when the websocket tries to reconnect. - final ValueNotifier wsConnectionStatus = - ValueNotifier(ConnectionStatus.disconnected); + Stream get wsConnectionStatusStream => + _wsConnectionStatusController.stream; /// The current user token String token; @@ -342,12 +328,12 @@ class Client { /// Call this function to dispose the client void dispose() async { - await _offlineStorage?.disconnect(); + await chatPersistenceClient?.disconnect(); await _disconnect(); httpClient.close(); await _controller.close(); - state.channels.values.forEach((c) => c.dispose()); state.dispose(); + await _wsConnectionStatusController.close(); } Map get _httpHeaders => { @@ -372,11 +358,6 @@ class Client { this.token = token; _anonymous = false; - final sharedPreferences = await SharedPreferences.getInstance(); - await sharedPreferences.setString(KEY_USER_ID, user.id); - await sharedPreferences.setString(KEY_TOKEN, token); - await sharedPreferences.setString(KEY_API_KEY, apiKey); - return connect().then((event) { _connectCompleter.complete(event); return event; @@ -424,8 +405,8 @@ class Client { if (!event.isLocal) { if (_synced && event.createdAt != null) { - await _offlineStorage?.updateConnectionInfo(event); - await _offlineStorage?.updateLastSyncAt(event.createdAt); + await chatPersistenceClient?.updateConnectionInfo(event); + await chatPersistenceClient?.updateLastSyncAt(event.createdAt); } } @@ -444,21 +425,20 @@ class Client { /// Connect the client websocket Future connect() async { logger.info('connecting'); - if (wsConnectionStatus.value == ConnectionStatus.connecting) { + if (wsConnectionStatus == ConnectionStatus.connecting) { logger.warning('Already connecting'); throw Exception('Already connecting'); } - if (wsConnectionStatus.value == ConnectionStatus.connected) { + if (wsConnectionStatus == ConnectionStatus.connected) { logger.warning('Already connected'); throw Exception('Already connected'); } - wsConnectionStatus.value = ConnectionStatus.connecting; + _wsConnectionStatus = ConnectionStatus.connecting; - if (persistenceEnabled && _offlineStorage == null) { - _offlineStorage = - await connectDatabase(state.user, _detachedLogger('💽')); + if (persistenceEnabled) { + await chatPersistenceClient.connect(state.user.id); } _ws = WebSocket( @@ -478,15 +458,16 @@ class Client { logger: _detachedLogger('🔌'), ); - _connectionStatusListener = () async { - final value = _ws.connectionStatus.value; - wsConnectionStatus.value = value; - handleEvent(Event( - type: EventType.connectionChanged, - online: value == ConnectionStatus.connected, - )); + _connectionStatusHandler = (ConnectionStatus status) async { + _wsConnectionStatus = status; + handleEvent( + Event( + type: EventType.connectionChanged, + online: status == ConnectionStatus.connected, + ), + ); - if (value == ConnectionStatus.connected && + if (status == ConnectionStatus.connected && state.channels?.isNotEmpty == true) { unawaited(queryChannels(filter: { 'cid': { @@ -506,12 +487,13 @@ class Client { } }; - _ws.connectionStatus.addListener(_connectionStatusListener); + _connectionStatusSubscription = + _ws.connectionStatusStream.listen(_connectionStatusHandler); - var event = await _offlineStorage?.getConnectionInfo(); + var event = await chatPersistenceClient?.getConnectionInfo(); await _ws.connect().then((e) async { - await _offlineStorage?.updateConnectionInfo(e); + await chatPersistenceClient?.updateConnectionInfo(e); event = e; await resync(); }).catchError((err, stacktrace) { @@ -526,14 +508,14 @@ class Client { /// Get the events missed while offline to sync the offline storage Future resync([List cids]) async { - final lastSyncAt = await offlineStorage?.getLastSyncAt(); + final lastSyncAt = await chatPersistenceClient?.getLastSyncAt(); if (lastSyncAt == null) { _synced = true; return; } - cids ??= await offlineStorage?.getChannelCids(); + cids ??= await chatPersistenceClient?.getChannelCids(); if (cids?.isEmpty == true) { return; @@ -562,7 +544,7 @@ class Client { handleEvent(event); }); - await _offlineStorage?.updateLastSyncAt(DateTime.now()); + await chatPersistenceClient?.updateLastSyncAt(DateTime.now()); _synced = true; } catch (error) { logger.severe('Error during resync $error'); @@ -590,7 +572,7 @@ class Client { logger.info('awaiting connection completer'); await _connectCompleter.future; } - if (wsConnectionStatus.value != ConnectionStatus.connected) { + if (wsConnectionStatus != ConnectionStatus.connected) { final errorMessage = 'You cannot use queryChannels without an active connection. Please call setUser to connect the client.'; if (persistenceEnabled) { @@ -708,7 +690,7 @@ class Client { channels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); - await _offlineStorage + await chatPersistenceClient ?.updateChannelState(newChannel.state.channelState); newChannel.state?.updateChannelState(channelState); newChannels[newChannel.cid] = newChannel; @@ -719,7 +701,7 @@ class Client { state.channels = newChannels; - await _offlineStorage?.updateChannelQueries( + await chatPersistenceClient?.updateChannelQueries( filter, res.channels.map((c) => c.channel.cid).toList(), paginationParams?.offset == null || paginationParams.offset == 0, @@ -754,7 +736,7 @@ class Client { @required List sort, PaginationParams paginationParams = const PaginationParams(limit: 10), }) async { - final offlineChannels = await _offlineStorage?.getChannelStates( + final offlineChannels = await chatPersistenceClient?.getChannelStates( filter: filter, sort: sort, paginationParams: paginationParams, @@ -769,7 +751,8 @@ class Client { return channel; } else { final newChannel = Channel.fromState(this, channelState); - _offlineStorage?.updateChannelState(newChannel.state.channelState); + chatPersistenceClient + ?.updateChannelState(newChannel.state.channelState); newChannels[newChannel.cid] = newChannel; return newChannel; } @@ -923,17 +906,17 @@ class Client { } /// Closes the websocket connection and resets the client - /// If [flushOfflineStorage] is true the client deletes all offline user's data + /// If [flushChatPersistence] is true the client deletes all offline user's data /// If [clearUser] is true the client unsets the current user Future disconnect({ - bool flushOfflineStorage = false, + bool flushChatPersistence = false, bool clearUser = false, }) async { logger.info( - 'Disconnecting flushOfflineStorage: $flushOfflineStorage; clearUser: $clearUser'); + 'Disconnecting flushOfflineStorage: $flushChatPersistence; clearUser: $clearUser'); - await _offlineStorage?.disconnect(flush: flushOfflineStorage); - _offlineStorage = null; + await chatPersistenceClient?.disconnect(flush: flushChatPersistence); + chatPersistenceClient = null; if (clearUser == true) { state.dispose(); @@ -947,6 +930,7 @@ class Client { logger.info('Client disconnecting'); await _ws?.disconnect(); + await _connectionStatusSubscription?.cancel(); } /// Requests users with a given query. @@ -1196,7 +1180,7 @@ class Client { String cid, ]) async { message = message.copyWith( - status: MessageSendingStatus.UPDATING, + status: MessageSendingStatus.updating, updatedAt: message.updatedAt ?? DateTime.now(), ); @@ -1227,10 +1211,10 @@ class Client { /// Deletes the given message Future deleteMessage(Message message, [String cid]) async { - if (message.status == MessageSendingStatus.FAILED) { + if (message.status == MessageSendingStatus.failed) { state.channels[cid].state.addMessage(message.copyWith( type: 'deleted', - status: MessageSendingStatus.SENT, + status: MessageSendingStatus.sent, )); return EmptyResponse(); } @@ -1238,7 +1222,7 @@ class Client { try { message = message.copyWith( type: 'deleted', - status: MessageSendingStatus.DELETING, + status: MessageSendingStatus.deleting, deletedAt: message.deletedAt ?? DateTime.now(), ); @@ -1250,7 +1234,7 @@ class Client { if (state?.channels != null) { state.channels[cid]?.state - ?.addMessage(message.copyWith(status: MessageSendingStatus.SENT)); + ?.addMessage(message.copyWith(status: MessageSendingStatus.sent)); } return decode(response.data, EmptyResponse.fromJson); @@ -1322,7 +1306,7 @@ class ClientState { void _listenChannelHidden() { _subscriptions.add(_client.on(EventType.channelHidden).listen((event) { - _client._offlineStorage?.deleteChannels([event.cid]); + _client.chatPersistenceClient?.deleteChannels([event.cid]); if (channels != null) { channels = channels..removeWhere((cid, ch) => cid == event.cid); } @@ -1347,14 +1331,14 @@ class ClientState { ) .listen((Event event) async { final eventChannel = event.channel; - await _client._offlineStorage?.deleteChannels([eventChannel.cid]); + await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); if (channels != null) { channels = channels..remove(eventChannel.cid); } })); } - final Client _client; + final StreamChatClient _client; /// Update user information set user(OwnUser user) { diff --git a/packages/dart_client/lib/src/db/chat_persistence_client.dart b/packages/dart_client/lib/src/db/chat_persistence_client.dart new file mode 100644 index 00000000..a2a4d313 --- /dev/null +++ b/packages/dart_client/lib/src/db/chat_persistence_client.dart @@ -0,0 +1,224 @@ +import 'package:stream_chat/src/api/requests.dart'; +import 'package:stream_chat/src/models/channel_model.dart'; +import 'package:stream_chat/src/models/channel_state.dart'; +import 'package:stream_chat/src/models/event.dart'; +import 'package:stream_chat/src/models/member.dart'; +import 'package:stream_chat/src/models/message.dart'; +import 'package:stream_chat/src/models/reaction.dart'; +import 'package:stream_chat/src/models/read.dart'; +import 'package:stream_chat/src/models/user.dart'; + +/// A simple client used for persisting chat data locally. +abstract class ChatPersistenceClient { + /// Creates a new connection to the client + Future connect(String userId); + + /// Closes the client connection + /// If [flush] is true, the data will also be deleted + Future disconnect({bool flush = false}); + + /// Get stored replies by messageId + Future> getReplies( + String parentId, { + PaginationParams options, + }); + + /// Get stored connection event + Future getConnectionInfo(); + + /// Get stored lastSyncAt + Future getLastSyncAt(); + + /// Update stored connection event + Future updateConnectionInfo(Event event); + + /// Update stored lastSyncAt + Future updateLastSyncAt(DateTime lastSyncAt); + + /// Get the channel cids saved in the offline storage + Future> getChannelCids(); + + /// Get stored [ChannelModel]s by providing channel [cid] + Future getChannelByCid(String cid); + + /// Get stored channel [Member]s by providing channel [cid] + Future> getMembersByCid(String cid); + + /// Get stored channel [Read]s by providing channel [cid] + Future> getReadsByCid(String cid); + + /// Get stored [Message]s by providing channel [cid] + /// + /// Optionally, you can [messagePagination] + /// for filtering out messages + Future> getMessagesByCid( + String cid, { + PaginationParams messagePagination, + }); + + /// Get [ChannelState] data by providing channel [cid] + Future getChannelStateByCid( + String cid, { + PaginationParams messagePagination, + }) async { + final members = await getMembersByCid(cid); + final reads = await getReadsByCid(cid); + final channel = await getChannelByCid(cid); + final messages = await getMessagesByCid( + cid, + messagePagination: messagePagination, + ); + return ChannelState( + members: members, + read: reads, + messages: messages, + channel: channel, + ); + } + + /// Get all the stored [ChannelState]s + /// + /// Optionally, pass [filter], [sort], [paginationParams] + /// for filtering out states. + Future> getChannelStates({ + Map filter, + List sort = const [], + PaginationParams paginationParams, + }); + + /// Update list of channel queries. + /// + /// If [clearQueryCache] is true before the insert + /// the list of matching rows will be deleted + Future updateChannelQueries( + Map filter, + List cids, + bool clearQueryCache, + ); + + /// Remove a message by [messageId] + Future deleteMessageById(String messageId) { + return deleteMessageByIds([messageId]); + } + + /// Remove a message by [messageIds] + Future deleteMessageByIds(List messageIds); + + /// Remove a message by channel [cid] + Future deleteMessageByCid(String cid) { + return deleteMessageByCids([cid]); + } + + /// Remove a message by message [cids] + Future deleteMessageByCids(List cids); + + /// Remove a channel by [cid] + Future deleteChannels(List cids); + + /// Updates the message data of a particular channel [cid] with + /// the new [messages] data + Future updateMessages(String cid, List messages); + + /// Returns all the threads by parent message of a particular channel by + /// providing channel [cid] + Future>> getChannelThreads(String cid); + + /// Updates all the channels using the new [channels] data. + Future updateChannels(List channels); + + /// Updates all the members of a particular channle [cid] + /// with the new [members] data + Future updateMembers(String cid, List members); + + /// Updates the read data of a particular channel [cid] with + /// the new [reads] data + Future updateReads(String cid, List reads); + + /// Updates the users data with the new [users] data + Future updateUsers(List users); + + /// Updates the reactions data with the new [reactions] data + Future updateReactions(List reactions); + + /// Deletes all the reactions by [messageIds] + Future deleteReactionsByMessageId(List messageIds); + + /// Deletes all the members by channel [cids] + Future deleteMembersByCids(List cids); + + /// Update the channel state data using [channelState] + Future updateChannelState(ChannelState channelState) { + return updateChannelStates([channelState]); + } + + /// Update list of channel states + Future updateChannelStates(List channelStates) async { + final deleteReactions = deleteReactionsByMessageId(channelStates + .expand((it) => it.messages) + .map((m) => m.id) + .toList(growable: false)); + + final deleteMembers = deleteMembersByCids( + channelStates.map((it) => it.channel.cid).toList(growable: false), + ); + + await Future.wait([ + deleteReactions, + deleteMembers, + ]); + + final channels = channelStates.map((it) { + return it.channel; + }).where((it) => it != null); + + final reactions = channelStates.expand((it) => it.messages).expand((it) { + return [ + ...it.ownReactions.where((r) => r.userId != null), + ...it.latestReactions.where((r) => r.userId != null) + ]; + }).where((it) => it != null); + + final users = channelStates + .map((cs) => [ + cs.channel?.createdBy, + ...cs.messages?.map((m) { + return [ + m.user, + ...m.latestReactions?.map((r) => r.user), + ...m.ownReactions?.map((r) => r.user), + ]; + })?.expand((v) => v), + ...cs.read?.map((r) => r.user), + ...cs.members?.map((m) => m.user), + ]) + .expand((it) => it) + .where((it) => it != null); + + final updateMessagesFuture = channelStates.map((it) { + final cid = it.channel.cid; + final messages = it.messages.where((it) => it != null); + return updateMessages(cid, messages.toList(growable: false)); + }).toList(growable: false); + + final updateReadsFuture = channelStates.map((it) { + final cid = it.channel.cid; + final reads = it.read.where((it) => it != null); + return updateReads(cid, reads.toList(growable: false)); + }).toList(growable: false); + + final updateMembersFuture = channelStates.map((it) { + final cid = it.channel.cid; + final members = it.members.where((it) => it != null); + return updateMembers(cid, members.toList(growable: false)); + }).toList(growable: false); + + await Future.wait([ + ...updateMessagesFuture, + ...updateReadsFuture, + ...updateMembersFuture, + updateUsers(users.toList(growable: false)), + updateChannels(channels.toList(growable: false)), + updateReactions(reactions.toList(growable: false)), + ]); + } +} diff --git a/packages/dart_client/lib/src/db/models.part.dart b/packages/dart_client/lib/src/db/models.part.dart deleted file mode 100644 index 98fced53..00000000 --- a/packages/dart_client/lib/src/db/models.part.dart +++ /dev/null @@ -1,258 +0,0 @@ -part of 'offline_storage.dart'; - -@DataClassName('ChannelQuery') -class _ChannelQueries extends Table { - TextColumn get queryHash => text()(); - - TextColumn get channelCid => text()(); - - @override - Set get primaryKey => { - queryHash, - channelCid, - }; -} - -class _Channels extends Table { - TextColumn get id => text()(); - - TextColumn get type => text()(); - - TextColumn get cid => text()(); - - TextColumn get config => text()(); - - BoolColumn get frozen => boolean().withDefault(Constant(false))(); - - DateTimeColumn get lastMessageAt => dateTime().nullable()(); - - DateTimeColumn get createdAt => dateTime().nullable()(); - - DateTimeColumn get updatedAt => dateTime().nullable()(); - - DateTimeColumn get deletedAt => dateTime().nullable()(); - - IntColumn get memberCount => integer().nullable()(); - - TextColumn get extraData => text().nullable().map(_ExtraDataConverter())(); - - TextColumn get createdBy => text().nullable()(); - - @override - Set get primaryKey => {cid}; -} - -class _ConnectionEvent extends Table { - IntColumn get id => integer()(); - - TextColumn get ownUser => text().nullable().map(_ExtraDataConverter())(); - - IntColumn get totalUnreadCount => integer().nullable()(); - - IntColumn get unreadChannels => integer().nullable()(); - - DateTimeColumn get lastEventAt => dateTime().nullable()(); - - DateTimeColumn get lastSyncAt => dateTime().nullable()(); - - @override - Set get primaryKey => {id}; -} - -class _Users extends Table { - TextColumn get id => text()(); - - TextColumn get role => text().nullable()(); - - DateTimeColumn get createdAt => dateTime().nullable()(); - - DateTimeColumn get updatedAt => dateTime().nullable()(); - - DateTimeColumn get lastActive => dateTime().nullable()(); - - BoolColumn get online => boolean().nullable()(); - - BoolColumn get banned => boolean().nullable()(); - - TextColumn get extraData => text().nullable().map(_ExtraDataConverter())(); - - @override - Set get primaryKey => {id}; -} - -class _Reads extends Table { - DateTimeColumn get lastRead => dateTime()(); - - TextColumn get userId => text()(); - - TextColumn get channelCid => text()(); - - IntColumn get unreadMessages => integer().nullable()(); - - @override - Set get primaryKey => { - userId, - channelCid, - }; -} - -class _Reactions extends Table { - TextColumn get messageId => text()(); - - TextColumn get type => text()(); - - DateTimeColumn get createdAt => dateTime()(); - - IntColumn get score => integer().nullable()(); - - TextColumn get userId => text()(); - - TextColumn get extraData => text().nullable().map(_ExtraDataConverter())(); - - @override - Set get primaryKey => { - messageId, - type, - userId, - }; -} - -class _Messages extends Table { - TextColumn get id => text()(); - - TextColumn get messageText => text().nullable()(); - - TextColumn get attachmentJson => text().nullable()(); - - IntColumn get status => - integer().map(_MessageSendingStatusConverter()).nullable()(); - - TextColumn get type => text().nullable()(); - - List mentionedUsers; - - TextColumn get reactionCounts => - text().nullable().map(_ExtraDataConverter())(); - - TextColumn get reactionScores => - text().nullable().map(_ExtraDataConverter())(); - - TextColumn get parentId => text().nullable()(); - - TextColumn get quotedMessageId => text().nullable()(); - - IntColumn get replyCount => integer().nullable()(); - - BoolColumn get showInChannel => boolean().nullable()(); - - BoolColumn get shadowed => boolean().nullable()(); - - TextColumn get command => text().nullable()(); - - DateTimeColumn get createdAt => dateTime()(); - - DateTimeColumn get updatedAt => dateTime().nullable()(); - - DateTimeColumn get deletedAt => dateTime().nullable()(); - - TextColumn get userId => text().nullable()(); - - TextColumn get channelCid => text().nullable()(); - - TextColumn get extraData => text().nullable().map(_ExtraDataConverter())(); - - @override - Set get primaryKey => {id}; -} - -class _Members extends Table { - TextColumn get userId => text()(); - - TextColumn get channelCid => text()(); - - TextColumn get role => text().nullable()(); - - DateTimeColumn get inviteAcceptedAt => dateTime().nullable()(); - - DateTimeColumn get inviteRejectedAt => dateTime().nullable()(); - - BoolColumn get invited => boolean().nullable()(); - - BoolColumn get banned => boolean().nullable()(); - - BoolColumn get shadowBanned => boolean().nullable()(); - - BoolColumn get isModerator => boolean().nullable()(); - - DateTimeColumn get createdAt => dateTime()(); - - DateTimeColumn get updatedAt => dateTime().nullable()(); - - @override - Set get primaryKey => { - userId, - channelCid, - }; -} - -class _ExtraDataConverter extends TypeConverter, String> { - @override - Map mapToDart(fromDb) { - if (fromDb == null) { - return null; - } - return Map.from(jsonDecode(fromDb) ?? {}); - } - - @override - String mapToSql(value) { - return jsonEncode(value); - } -} - -class _MessageSendingStatusConverter - extends TypeConverter { - @override - MessageSendingStatus mapToDart(int fromDb) { - switch (fromDb) { - case 0: - return MessageSendingStatus.SENDING; - case 1: - return MessageSendingStatus.SENT; - case 2: - return MessageSendingStatus.FAILED; - case 3: - return MessageSendingStatus.UPDATING; - case 4: - return MessageSendingStatus.FAILED_UPDATE; - case 5: - return MessageSendingStatus.DELETING; - case 6: - return MessageSendingStatus.FAILED_DELETE; - default: - return null; - } - } - - @override - int mapToSql(MessageSendingStatus value) { - switch (value) { - case MessageSendingStatus.SENDING: - return 0; - case MessageSendingStatus.SENT: - return 1; - case MessageSendingStatus.FAILED: - return 2; - case MessageSendingStatus.UPDATING: - return 3; - case MessageSendingStatus.FAILED_UPDATE: - return 4; - case MessageSendingStatus.DELETING: - return 5; - case MessageSendingStatus.FAILED_DELETE: - return 6; - default: - return null; - } - } -} diff --git a/packages/dart_client/lib/src/db/offline_storage.dart b/packages/dart_client/lib/src/db/offline_storage.dart deleted file mode 100644 index b132ae02..00000000 --- a/packages/dart_client/lib/src/db/offline_storage.dart +++ /dev/null @@ -1,841 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart' show WidgetsFlutterBinding; -import 'package:logging/logging.dart'; -import 'package:moor/isolate.dart'; -import 'package:moor/moor.dart'; -import 'package:stream_chat/src/db/shared/shared_db.dart'; -import 'package:stream_chat/src/models/event.dart'; -import 'package:stream_chat/src/models/own_user.dart'; - -import '../api/requests.dart'; -import '../models/attachment.dart'; -import '../models/channel_config.dart'; -import '../models/channel_model.dart'; -import '../models/channel_state.dart'; -import '../models/member.dart'; -import '../models/message.dart'; -import '../models/reaction.dart'; -import '../models/read.dart'; -import '../models/user.dart'; - -part 'models.part.dart'; - -part 'offline_storage.g.dart'; - -/// Gets a new instance of the database running on a background isolate -Future connectDatabase(User user, Logger logger) async { - logger.info('Connecting on background isolate'); - WidgetsFlutterBinding.ensureInitialized(); - return SharedDB.constructOfflineStorage( - userId: user.id, - logger: logger, - ); -} - -LazyDatabase _openConnection(String userId) { - moorRuntimeOptions.dontWarnAboutMultipleDatabases = true; - return LazyDatabase(() async { - return await SharedDB.constructDatabase('db_$userId.sqlite'); - }); -} - -/// Offline database used for caching channel queries and state -@UseMoor(tables: [ - _ConnectionEvent, - _Channels, - _Users, - _Messages, - _Reads, - _Members, - _ChannelQueries, - _Reactions, -]) -class OfflineStorage extends _$OfflineStorage { - /// Creates a new database instance - OfflineStorage.connect( - DatabaseConnection connection, - this._userId, - this._isolate, - this._logger, - ) : super.connect(connection); - - /// Instantiate a new OfflineStorage - OfflineStorage( - this._userId, - this._logger, - ) : _isolate = null, - super(_openConnection(_userId)) { - _logger.info('Connecting on standard isolate'); - } - - final String _userId; - final MoorIsolate _isolate; - final Logger _logger; - - // you should bump this number whenever you change or add a table definition. Migrations - // are covered later in this readme. - @override - int get schemaVersion => 8; - - @override - MigrationStrategy get migration => MigrationStrategy( - onUpgrade: (openingDetails, before, after) async { - if (before != after) { - final m = createMigrator(); - for (final table in allTables) { - await m.deleteTable(table.actualTableName); - await m.createTable(table); - } - } - }, - ); - - /// Closes the database instance - /// If [flush] is true, the database data will be deleted - Future disconnect({bool flush = false}) async { - _logger.info('Disconnecting'); - if (flush) { - _logger.info('Flushing'); - await batch((batch) { - allTables.forEach((table) { - delete(table).go(); - }); - }); - } - - await _isolate?.shutdownAll(); - - await close(); - } - - /// Get stored replies by messageId - Future> getReplies( - String parentId, { - String lessThan, - }) async { - final offlineList = await Future.wait(await (select(messages).join([ - innerJoin(users, messages.userId.equalsExp(users.id)), - ]) - ..where(messages.parentId.equals(parentId)) - ..orderBy([ - OrderingTerm.asc(messages.createdAt), - ])) - .map(_messageFromJoinRow) - .get()); - if (lessThan != null) { - final lessThanIndex = offlineList.indexWhere((m) => m.id == lessThan); - offlineList.removeRange(lessThanIndex, offlineList.length); - } - - return offlineList; - } - - /// Get stored connection event - Future getConnectionInfo() async { - return select(connectionEvent).map((row) { - return Event( - me: row.ownUser != null ? OwnUser.fromJson(row.ownUser) : null, - totalUnreadCount: row.totalUnreadCount, - unreadChannels: row.unreadChannels, - ); - }).getSingle(); - } - - /// Get stored lastSyncAt - Future getLastSyncAt() async { - return select(connectionEvent).getSingle().then((r) => r?.lastSyncAt); - } - - /// Update stored connection event - Future updateConnectionInfo(Event event) async { - final connectionInfo = await select(connectionEvent).getSingle(); - - return into(connectionEvent).insert( - _ConnectionEventData( - id: 1, - lastSyncAt: connectionInfo?.lastSyncAt, - lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt, - totalUnreadCount: - event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, - ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, - unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels, - ), - mode: InsertMode.insertOrReplace, - ); - } - - /// Update stored lastSyncAt - Future updateLastSyncAt(DateTime lastSyncAt) async { - return await (update(connectionEvent)..where((r) => r.id.equals(1))).write( - _ConnectionEventCompanion( - id: Value(1), - lastSyncAt: Value(lastSyncAt), - ), - ); - } - - /// Get the channel cids saved in the offline storage - Future> getChannelCids() async { - return (select(channels) - ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) - ..limit(250)) - .map((c) => c.cid) - .get(); - } - - /// Get channel data by cid - Future getChannel( - String cid, { - int limit, - String messageLessThan, - String messageGreaterThan, - }) async { - return await (select(channels)..where((c) => c.cid.equals(cid))).join([ - leftOuterJoin(users, channels.createdBy.equalsExp(users.id)), - ]).map((row) { - return _channelFromRow( - row.readTable(channels), - row.readTable(users), - limit: limit, - messageLessThan: messageLessThan, - messageGreaterThan: messageGreaterThan, - ); - }).getSingle(); - } - - /// Get list of channels by filter, sort and paginationParams - Future> getChannelStates({ - Map filter, - List sort = const [], - PaginationParams paginationParams, - }) async { - _logger.info('Get channel states'); - final hash = _computeHash(filter); - final cachedChannels = await Future.wait(await (select(channelQueries) - ..where((c) => c.queryHash.equals(hash))) - .get() - .then((channelQueries) { - final cids = channelQueries.map((c) => c.channelCid).toList(); - final query = select(channels)..where((c) => c.cid.isIn(cids)); - - sort = sort - ?.where((s) => ChannelModel.topLevelFields.contains(s.field)) - ?.toList(); - - if (sort != null && sort.isNotEmpty) { - query.orderBy(sort.map((s) { - final orderExpression = CustomExpression('channels.${s.field}'); - return (c) => OrderingTerm( - expression: orderExpression, - mode: s.direction == 1 ? OrderingMode.asc : OrderingMode.desc, - ); - }).toList()); - } - - if (paginationParams != null) { - query.limit( - paginationParams.limit ?? 10, - offset: paginationParams.offset, - ); - } - - return query.join([ - leftOuterJoin(users, channels.createdBy.equalsExp(users.id)), - ]).map((row) async { - final userRow = row.readTable(users); - final channelRow = row.readTable(channels); - - return _channelFromRow(channelRow, userRow); - }).get(); - })); - - _logger.info('Got ${cachedChannels.length} channels'); - - if (sort?.isEmpty != false && cachedChannels?.isNotEmpty == true) { - cachedChannels - .sort((a, b) => b.channel.updatedAt.compareTo(a.channel.updatedAt)); - cachedChannels.sort((a, b) { - final dateA = a.channel.lastMessageAt ?? a.channel.createdAt; - final dateB = b.channel.lastMessageAt ?? b.channel.createdAt; - return dateB.compareTo(dateA); - }); - } - - return cachedChannels; - } - - /// Update list of channel queries - /// If [clearQueryCache] is true before the insert - /// the list of matching rows will be deleted - Future updateChannelQueries( - Map filter, - List cids, - bool clearQueryCache, - ) async { - final hash = _computeHash(filter); - if (clearQueryCache) { - await (delete(channelQueries) - ..where( - (_ChannelQueries query) => query.queryHash.equals(hash), - )) - .go(); - } - - return batch((batch) { - batch.insertAll( - channelQueries, - cids.map((cid) { - return ChannelQuery( - queryHash: hash, - channelCid: cid, - ); - }).toList(), - mode: InsertMode.insertOrReplace, - ); - }); - } - - /// Remove a message by message id - Future deleteMessages(List messageIds) { - return batch((batch) { - batch.deleteWhere<_Reactions, _Reaction>( - reactions, - (r) => r.messageId.isIn(messageIds), - ); - batch.deleteWhere<_Messages, _Message>( - messages, - (m) => m.id.isIn(messageIds), - ); - }); - } - - /// Remove a message by message id - Future deleteChannelsMessages(List cids) async { - final messageIds = await (select(messages) - ..where((m) => m.channelCid.isIn(cids))) - .map((m) => m.id) - .get(); - return batch((batch) { - batch.deleteWhere<_Reactions, _Reaction>( - reactions, - (r) => r.messageId.isIn(messageIds), - ); - batch.deleteWhere<_Messages, _Message>( - messages, - (m) => m.id.isIn(messageIds), - ); - }); - } - - /// Remove a channel by cid - Future deleteChannels(List cids) async { - await deleteChannelsMessages(cids); - return batch((batch) { - batch.deleteWhere<_Members, _Member>( - members, - (m) => m.channelCid.isIn(cids), - ); - batch.deleteWhere<_Reads, _Read>( - reads, - (r) => r.channelCid.isIn(cids), - ); - batch.deleteWhere<_Channels, _Channel>( - channels, - (c) => c.cid.isIn(cids), - ); - }); - } - - /// Update messages data from a list - Future updateMessages( - List newMessages, - String cid, - ) { - return batch((batch) { - batch.insertAll( - messages, - newMessages.map( - (m) { - return _Message( - id: m.id, - attachmentJson: m.attachments != null - ? jsonEncode(m.attachments.map((a) => a.toJson()).toList()) - : null, - channelCid: cid, - type: m.type, - parentId: m.parentId, - quotedMessageId: m.quotedMessageId, - command: m.command, - createdAt: m.createdAt, - shadowed: m.shadowed, - showInChannel: m.showInChannel, - replyCount: m.replyCount, - reactionScores: m.reactionScores, - reactionCounts: m.reactionCounts, - status: m.status, - updatedAt: m.updatedAt, - extraData: m.extraData, - userId: m.user.id, - deletedAt: m.deletedAt, - messageText: m.text, - ); - }, - ).toList(), - mode: InsertMode.insertOrReplace, - ); - }); - } - - /// Update single channel state - Future updateChannelState(ChannelState channelState) async { - await updateChannelStates([channelState]); - } - - /// Update list of channel states - Future updateChannelStates(List channelStates) async { - channelStates.forEach((cs) { - updateMessages( - cs.messages, - cs.channel.cid, - ); - }); - - await batch((batch) { - _updateReactions(batch, channelStates); - - _updateUsers(batch, channelStates); - - _updateReads(channelStates, batch); - - _updateMembers(channelStates, batch); - - _updateChannels(batch, channelStates); - }); - } - - /// Get the info about channel threads - Future>> getChannelThreads(String cid) async { - final rowMessages = await Future.wait(await (select(messages).join([ - leftOuterJoin(users, messages.userId.equalsExp(users.id)), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(isNotNull(messages.parentId)) - ..orderBy([ - OrderingTerm.asc(messages.createdAt), - ])) - .map(_messageFromJoinRow) - .get()); - - final threads = >{}; - rowMessages.forEach((message) { - if (threads.containsKey(message.parentId)) { - threads[message.parentId].add(message); - } else { - threads[message.parentId] = [message]; - } - }); - - return threads; - } - - Future _channelFromRow( - _Channel channelRow, - _User userRow, { - int limit, - String messageLessThan, - String messageGreaterThan, - }) async { - final rowMessages = await _getChannelMessages( - channelRow, - limit: limit, - lessThan: messageLessThan, - greaterThan: messageGreaterThan, - ); - final rowReads = await _getChannelReads(channelRow); - final rowMembers = await _getChannelMembers(channelRow); - - return ChannelState( - members: rowMembers, - read: rowReads, - messages: rowMessages, - channel: ChannelModel( - id: channelRow.id, - type: channelRow.type, - frozen: channelRow.frozen, - createdAt: channelRow.createdAt, - updatedAt: channelRow.updatedAt, - memberCount: channelRow.memberCount, - cid: channelRow.cid, - lastMessageAt: channelRow.lastMessageAt, - deletedAt: channelRow.deletedAt, - extraData: channelRow.extraData, - config: ChannelConfig.fromJson(jsonDecode(channelRow.config)), - createdBy: userRow != null ? _userFromUserRow(userRow) : null, - ), - ); - } - - String _computeHash(Map filter) { - if (filter == null) { - return 'allchannels'; - } - final hash = base64Encode(utf8.encode('filter: ${jsonEncode(filter)}')); - return hash; - } - - Future _getMessageById(String id) async { - if (id == null || id.isEmpty) return null; - final message = await Future.wait(await (select(messages).join([ - leftOuterJoin(users, messages.userId.equalsExp(users.id)), - ]) - ..where(messages.id.equals(id))) - .map(_messageFromJoinRow) - .get()); - return message.first; - } - - Future> _getChannelMessages( - _Channel channelRow, { - int limit, - String lessThan, - String greaterThan, - }) async { - final rowMessages = await Future.wait(await (select(messages).join([ - leftOuterJoin(users, messages.userId.equalsExp(users.id)), - ]) - ..where(messages.channelCid.equals(channelRow.cid)) - ..where( - isNull(messages.parentId) | messages.showInChannel.equals(true)) - ..orderBy([ - OrderingTerm.asc(messages.createdAt), - ])) - .map(_messageFromJoinRow) - .get()); - - if (lessThan != null) { - final lessThanIndex = rowMessages.indexWhere((m) => m.id == lessThan); - if (lessThanIndex != -1) { - rowMessages.removeRange(lessThanIndex, rowMessages.length); - } - } - if (greaterThan != null) { - final greaterThanIndex = - rowMessages.indexWhere((m) => m.id == greaterThan); - if (greaterThanIndex != -1) { - rowMessages.removeRange(0, greaterThanIndex); - } - } - if (limit != null) { - return rowMessages.take(limit).toList(); - } - return rowMessages; - } - - Future _messageFromJoinRow(row) async { - final messageRow = row.readTable(messages); - final userRow = row.readTable(users); - - final latestReactions = await _getLatestReactions(messageRow); - final ownReactions = await _getOwnReactions(messageRow); - final quotedMessage = await _getMessageById(messageRow.quotedMessageId); - - return Message( - shadowed: messageRow.shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: messageRow.attachmentJson != null - ? List>.from( - jsonDecode(messageRow.attachmentJson)) - .map((j) => Attachment.fromJson(j)) - .toList() - : null, - createdAt: messageRow.createdAt, - extraData: messageRow.extraData, - updatedAt: messageRow.updatedAt, - id: messageRow.id, - type: messageRow.type, - status: messageRow.status, - command: messageRow.command, - parentId: messageRow.parentId, - quotedMessageId: messageRow.quotedMessageId, - quotedMessage: quotedMessage, - reactionCounts: messageRow.reactionCounts, - reactionScores: messageRow.reactionScores, - replyCount: messageRow.replyCount, - showInChannel: messageRow.showInChannel, - text: messageRow.messageText, - user: _userFromUserRow(userRow), - deletedAt: messageRow.deletedAt, - ); - } - - Future> _getLatestReactions(_Message messageRow) async { - return await (select(reactions).join([ - leftOuterJoin(users, reactions.userId.equalsExp(users.id)), - ]) - ..where(reactions.messageId.equals(messageRow.id)) - ..orderBy([OrderingTerm.asc(reactions.createdAt)])) - .map((row) { - final r = row.readTable(reactions); - final u = row.readTable(users); - return _reactionFromRow(r, u); - }).get(); - } - - Reaction _reactionFromRow(_Reaction r, _User u) { - return Reaction( - extraData: r.extraData, - type: r.type, - createdAt: r.createdAt, - userId: r.userId, - user: _userFromUserRow(u), - messageId: r.messageId, - score: r.score, - ); - } - - Future> _getOwnReactions(_Message messageRow) async { - return await (select(reactions).join([ - leftOuterJoin(users, reactions.userId.equalsExp(users.id)), - ]) - ..where(reactions.userId.equals(_userId)) - ..where(reactions.messageId.equals(messageRow.id)) - ..orderBy([OrderingTerm.asc(reactions.createdAt)])) - .map((row) { - final r = row.readTable(reactions); - final u = row.readTable(users); - return _reactionFromRow(r, u); - }).get(); - } - - Future> _getChannelReads(_Channel channelRow) async { - final rowReads = await (select(reads).join([ - leftOuterJoin(users, reads.userId.equalsExp(users.id)), - ]) - ..where(reads.channelCid.equals(channelRow.cid)) - ..orderBy([ - OrderingTerm.asc(reads.lastRead), - ])) - .map((row) { - final userRow = row.readTable(users); - final readRow = row.readTable(reads); - return Read( - user: _userFromUserRow(userRow), - lastRead: readRow.lastRead, - unreadMessages: readRow.unreadMessages, - ); - }).get(); - return rowReads; - } - - Future> _getChannelMembers(_Channel channelRow) async { - final rowMembers = await (select(members).join([ - leftOuterJoin(users, members.userId.equalsExp(users.id)), - ]) - ..where(members.channelCid.equals(channelRow.cid)) - ..orderBy([ - OrderingTerm.asc(members.createdAt), - ])) - .map((row) { - final userRow = row.readTable(users); - final memberRow = row.readTable(members); - return Member( - user: _userFromUserRow(userRow), - userId: userRow.id, - banned: memberRow.banned, - shadowBanned: memberRow.shadowBanned, - updatedAt: memberRow.updatedAt, - createdAt: memberRow.createdAt, - role: memberRow.role, - inviteAcceptedAt: memberRow.inviteAcceptedAt, - invited: memberRow.invited, - inviteRejectedAt: memberRow.inviteRejectedAt, - isModerator: memberRow.isModerator, - ); - }).get(); - return rowMembers; - } - - User _userFromUserRow(_User userRow) { - return User( - updatedAt: userRow.updatedAt, - role: userRow.role, - online: userRow.online, - lastActive: userRow.lastActive, - extraData: userRow.extraData, - banned: userRow.banned, - createdAt: userRow.createdAt, - id: userRow.id, - ); - } - - void _updateChannels(Batch batch, List channelStates) { - batch.insertAll( - channels, - channelStates.map((cs) { - final channel = cs.channel; - return _channelDataFromChannelModel(channel); - }).toList(), - mode: InsertMode.insertOrReplace, - ); - } - - void _updateMembers(List channelStates, Batch batch) async { - await (delete(members) - ..where((tbl) => - tbl.channelCid.isIn(channelStates.map((e) => e.channel.cid)))) - .go(); - final newMembers = channelStates - .map((cs) => cs.members.map((m) => _Member( - userId: m.user.id, - banned: m.banned, - shadowBanned: m.shadowBanned, - channelCid: cs.channel.cid, - createdAt: m.createdAt, - isModerator: m.isModerator, - inviteRejectedAt: m.inviteRejectedAt, - invited: m.invited, - inviteAcceptedAt: m.inviteAcceptedAt, - role: m.role, - updatedAt: m.updatedAt, - ))) - .where((v) => v != null) - .expand((v) => v); - if (newMembers != null && newMembers.isNotEmpty) { - batch.insertAll( - members, - newMembers.toList(), - mode: InsertMode.insertOrReplace, - ); - } - } - - void _updateReads(List channelStates, Batch batch) { - final newReads = channelStates - .map((cs) => cs.read?.map((r) => _Read( - lastRead: r.lastRead, - userId: r.user.id, - channelCid: cs.channel.cid, - unreadMessages: r.unreadMessages, - ))) - .where((v) => v != null) - .expand((v) => v); - - if (newReads != null && newReads.isNotEmpty) { - batch.insertAll( - reads, - newReads.toList(), - mode: InsertMode.insertOrReplace, - ); - } - } - - void _updateUsers(Batch batch, List channelStates) { - batch.insertAll( - users, - channelStates - .map((cs) => [ - if (cs.channel.createdBy != null) - _userDataFromUser(cs.channel.createdBy), - if (cs.messages != null) - ...cs.messages - .map((m) => [ - _userDataFromUser(m.user), - if (m.latestReactions != null) - ...m.latestReactions - .where((r) => r.user != null) - .map((r) => _userDataFromUser(r.user)), - if (m.ownReactions != null) - ...m.ownReactions - .where((r) => r.user != null) - .map((r) => _userDataFromUser(r.user)), - ]) - .expand((v) => v), - if (cs.read != null) - ...cs.read.map((r) => _userDataFromUser(r.user)), - if (cs.members != null) - ...cs.members.map((m) => _userDataFromUser(m.user)), - ]) - .expand((v) => v) - .toList(), - mode: InsertMode.insertOrReplace, - ); - } - - void _updateReactions(Batch batch, List channelStates) { - batch.deleteWhere<_Reactions, _Reaction>( - reactions, - (r) => r.messageId.isIn(channelStates - .map((cs) => cs.messages.map((m) => m.id)) - .expand((v) => v)), - ); - final newReactions = channelStates - .map((cs) => cs.messages.map((m) { - final ownReactions = - m.ownReactions?.where((e) => e.userId != null)?.map( - (r) => _reactionDataFromReaction(m, r), - ) ?? - []; - final latestReactions = - m.latestReactions?.where((e) => e.userId != null)?.map( - (r) => _reactionDataFromReaction(m, r), - ) ?? - []; - return [ - ...ownReactions, - ...latestReactions, - ]; - }).expand((v) => v)) - .expand((v) => v); - - if (newReactions.isNotEmpty) { - batch.insertAll( - reactions, - newReactions.toList(), - mode: InsertMode.insertOrReplace, - ); - } - } - - _Reaction _reactionDataFromReaction(Message m, Reaction r) { - return _Reaction( - messageId: m.id, - type: r.type, - extraData: r.extraData, - score: r.score, - createdAt: r.createdAt, - userId: r.userId, - ); - } - - _User _userDataFromUser(User user) { - return _User( - id: user.id, - createdAt: user.createdAt, - banned: user.banned, - extraData: user.extraData, - lastActive: user.lastActive, - online: user.online, - role: user.role, - updatedAt: user.updatedAt, - ); - } - - _Channel _channelDataFromChannelModel(ChannelModel channel) { - return _Channel( - id: channel.id, - config: jsonEncode(channel.config?.toJson() ?? {}), - type: channel.type, - frozen: channel.frozen, - createdAt: channel.createdAt, - updatedAt: channel.updatedAt, - memberCount: channel.memberCount, - cid: channel.cid, - lastMessageAt: channel.lastMessageAt, - deletedAt: channel.deletedAt, - extraData: channel.extraData, - createdBy: channel.createdBy?.id, - ); - } -} diff --git a/packages/dart_client/lib/src/db/shared/native_db.dart b/packages/dart_client/lib/src/db/shared/native_db.dart deleted file mode 100644 index deee5b08..00000000 --- a/packages/dart_client/lib/src/db/shared/native_db.dart +++ /dev/null @@ -1,65 +0,0 @@ -//ignore_for_file: public_member_api_docs -import 'dart:io'; -import 'dart:isolate'; -import 'package:flutter/material.dart'; -import 'package:moor/ffi.dart'; -import 'package:moor/isolate.dart'; -import 'package:moor/moor.dart'; -import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:stream_chat/src/db/offline_storage.dart'; - -class SharedDB { - static Future constructDatabase(dbName) async { - final dir = await getApplicationDocumentsDirectory(); - final path = join(dir.path, dbName); - final file = File(path); - return VmDatabase(file); - } - - static Future createMoorIsolate(String userId) async { - WidgetsFlutterBinding.ensureInitialized(); - final dir = await getApplicationDocumentsDirectory(); - final path = join(dir.path, 'db_$userId.sqlite'); - - final receivePort = ReceivePort(); - await Isolate.spawn( - startBackground, - _IsolateStartRequest(receivePort.sendPort, path), - ); - - return (await receivePort.first as MoorIsolate); - } - - static void startBackground(_IsolateStartRequest request) { - final executor = LazyDatabase(() async { - return VmDatabase(File(request.targetPath)); - }); - final moorIsolate = MoorIsolate.inCurrent( - () => DatabaseConnection.fromExecutor(executor), - ); - request.sendMoorIsolate.send(moorIsolate); - } - - static Future constructOfflineStorage({ - userId, - logger, - }) async { - logger.info('Connecting on background isolate'); - final isolate = await createMoorIsolate(userId); - final connection = await isolate.connect(); - return OfflineStorage.connect( - connection, - userId, - isolate, - logger, - ); - } -} - -class _IsolateStartRequest { - final SendPort sendMoorIsolate; - final String targetPath; - - _IsolateStartRequest(this.sendMoorIsolate, this.targetPath); -} diff --git a/packages/dart_client/lib/src/db/shared/unsupported_db.dart b/packages/dart_client/lib/src/db/shared/unsupported_db.dart deleted file mode 100644 index bbb4ccb7..00000000 --- a/packages/dart_client/lib/src/db/shared/unsupported_db.dart +++ /dev/null @@ -1,14 +0,0 @@ -//ignore_for_file: public_member_api_docs -//ignore_for_file: always_declare_return_types -class SharedDB { - static constructDatabase(dbName) async { - print('Unsupported Platform'); - return null; - } - - static createMoorIsolate(userId) {} - - static startBackground(request) {} - - static constructOfflineStorage({userId, logger}) {} -} diff --git a/packages/dart_client/lib/src/db/shared/web_db.dart b/packages/dart_client/lib/src/db/shared/web_db.dart deleted file mode 100644 index f79c224a..00000000 --- a/packages/dart_client/lib/src/db/shared/web_db.dart +++ /dev/null @@ -1,20 +0,0 @@ -//ignore_for_file: public_member_api_docs -//ignore_for_file: always_declare_return_types -import 'package:moor/moor_web.dart'; -import 'package:stream_chat/src/db/offline_storage.dart'; - -class SharedDB { - static constructDatabase(dbName) async { - return WebDatabase(dbName); - } - - static Future constructOfflineStorage({ - userId, - logger, - }) async { - return OfflineStorage( - userId, - logger, - ); - } -} diff --git a/packages/dart_client/lib/src/models/message.dart b/packages/dart_client/lib/src/models/message.dart index e23eb51a..a2eef86e 100644 --- a/packages/dart_client/lib/src/models/message.dart +++ b/packages/dart_client/lib/src/models/message.dart @@ -10,25 +10,25 @@ part 'message.g.dart'; /// Enum defining the status of a sending message enum MessageSendingStatus { /// Message is being sent - SENDING, + sending, /// Message is being updated - UPDATING, + updating, /// Message is being deleted - DELETING, + deleting, /// Message failed to send - FAILED, + failed, /// Message failed to updated - FAILED_UPDATE, + failed_update, /// Message failed to delete - FAILED_DELETE, + failed_delete, /// Message correctly sent - SENT, + sent, } /// The class that contains the information about a message @@ -72,7 +72,7 @@ class Message { @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final List ownReactions; - /// The ID of the parent message, if the message is a reply. + /// The ID of the parent message, if the message is a thread reply. final String parentId; /// A quoted reply message @@ -186,7 +186,7 @@ class Message { this.user, this.extraData, this.deletedAt, - this.status = MessageSendingStatus.SENT, + this.status = MessageSendingStatus.sent, }); /// Create a new instance from a json diff --git a/packages/dart_client/lib/src/models/reaction.dart b/packages/dart_client/lib/src/models/reaction.dart index fb0c4fad..9a4869f7 100644 --- a/packages/dart_client/lib/src/models/reaction.dart +++ b/packages/dart_client/lib/src/models/reaction.dart @@ -29,7 +29,7 @@ class Reaction { @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final String userId; - /// The score of the reaction (ie. number of reactions sent) + /// Reaction custom extraData @JsonKey(includeIfNull: false) final Map extraData; diff --git a/packages/dart_client/lib/src/notifications.dart b/packages/dart_client/lib/src/notifications.dart deleted file mode 100644 index 2fc500cf..00000000 --- a/packages/dart_client/lib/src/notifications.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'dart:convert'; - -import 'package:logging/logging.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:stream_chat/src/api/responses.dart'; -import 'package:stream_chat/src/db/offline_storage.dart'; -import 'package:stream_chat/src/models/channel_model.dart'; -import 'package:stream_chat/src/models/channel_state.dart'; -import 'package:stream_chat/src/models/message.dart'; - -import 'client.dart'; -import 'models/own_user.dart'; - -/// Utility class to handle and show notifications -class NotificationService { - static Future _handleNotification( - Message message, - ChannelModel channelModel, - Client client, - ) async { - if (message != null && client.persistenceEnabled) { - if (client?.state?.channels == null) { - final sharedPreferences = await _getSharedPreferences(); - final userId = sharedPreferences.getString(KEY_USER_ID); - - final offlineStorage = OfflineStorage(userId, Logger('💽')); - await offlineStorage.updateChannelState( - ChannelState( - channel: channelModel, - messages: [message], - ), - ); - await offlineStorage.disconnect(); - } else { - final channel = client.state.channels[channelModel.cid]; - channel.state.updateChannelState( - ChannelState( - channel: channelModel, - messages: [message], - ), - ); - } - } - } - - static SharedPreferences _sharedPreferences; - static Future _getSharedPreferences() async { - _sharedPreferences ??= await SharedPreferences.getInstance(); - return _sharedPreferences; - } - - /// Gets the message using the client without storing it in the offline storage - /// It returns an object containing the information about the message and the channel - static Future getMessage(String messageId) async { - final sharedPreferences = await _getSharedPreferences(); - final apiKey = sharedPreferences.getString(KEY_API_KEY); - - final client = Client( - apiKey, - persistenceEnabled: false, - ); - final userId = sharedPreferences.getString(KEY_USER_ID); - final token = sharedPreferences.getString(KEY_TOKEN); - - client.state.user = OwnUser(id: userId); - client.token = token; - - final res = await client.getMessage(messageId); - return res; - } - - /// Stores the message in the offline storage - static Future storeMessage(GetMessageResponse messageResponse) async { - final sharedPreferences = await _getSharedPreferences(); - final userId = sharedPreferences.getString(KEY_USER_ID); - - final offlineStorage = OfflineStorage( - userId, - Logger('💽'), - ); - - await offlineStorage.updateChannelState(ChannelState( - messages: [messageResponse.message], - channel: messageResponse.channel, - )); - - await offlineStorage.disconnect(); - } - - /// Gets the message using the client and stores it in the offline storage - /// It returns an object containing the information about the message and the channel - static Future getAndStoreMessage(String messageId) async { - final getMessageResponse = await getMessage(messageId); - await storeMessage(getMessageResponse); - return getMessageResponse; - } - - /// Handles the ios message queue generated by the Notification Service Extension - static Future handleIosMessageQueue(Client client) async { - final sharedPreferences = await SharedPreferences.getInstance(); - await sharedPreferences.reload(); - final messageQueue = sharedPreferences.getStringList('messageQueue'); - if (messageQueue != null) { - messageQueue.forEach((m) { - final jsonMessage = jsonDecode(m); - - final message = Message.fromJson(jsonMessage); - final channelModel = ChannelModel.fromJson(jsonMessage['channel']); - - _handleNotification( - message, - channelModel, - client, - ); - }); - await sharedPreferences.remove('messageQueue'); - } - } -} diff --git a/packages/dart_client/lib/stream_chat.dart b/packages/dart_client/lib/stream_chat.dart index 101e9440..590175c4 100644 --- a/packages/dart_client/lib/stream_chat.dart +++ b/packages/dart_client/lib/stream_chat.dart @@ -2,7 +2,7 @@ library stream_chat; export 'package:dio/src/dio_error.dart'; export 'package:dio/src/multipart_file.dart'; -export 'package:logging/src/level.dart'; +export 'package:logging/logging.dart' show Logger, Level; export './src/api/channel.dart'; export './src/api/connection_status.dart'; @@ -26,4 +26,4 @@ export './src/models/own_user.dart'; export './src/models/reaction.dart'; export './src/models/read.dart'; export './src/models/user.dart'; -export './src/notifications.dart'; +export './src/db/chat_persistence_client.dart'; diff --git a/packages/dart_client/lib/version.dart b/packages/dart_client/lib/version.dart index 1d13b723..3f084128 100644 --- a/packages/dart_client/lib/version.dart +++ b/packages/dart_client/lib/version.dart @@ -1,5 +1,5 @@ import 'package:stream_chat/src/client.dart'; /// Current package version -/// Used in [Client] to build the `x-stream-client` header +/// Used in [StreamChatClient] to build the `x-stream-client` header const PACKAGE_VERSION = '0.2.24+2'; diff --git a/packages/dart_client/pubspec.yaml b/packages/dart_client/pubspec.yaml index 55a1f5ec..e9036a09 100644 --- a/packages/dart_client/pubspec.yaml +++ b/packages/dart_client/pubspec.yaml @@ -9,28 +9,18 @@ environment: sdk: ">=2.7.0 <3.0.0" dependencies: - flutter: - sdk: flutter json_annotation: ^3.0.1 - shared_preferences: ^0.5.7+3 logging: ^0.11.4 dio: ^3.0.10 web_socket_channel: ^1.1.0 uuid: ^2.2.2 async: ^2.4.1 - stream_channel: ^2.0.0 - moor: ^3.3.1 - path_provider: ^1.6.10 - path: ^1.6.4 rxdart: ^0.24.1 collection: ^1.14.12 - sqlite3_flutter_libs: ^0.3.0 - pedantic: ^1.9.2 dev_dependencies: build_runner: ^1.10.0 json_serializable: ^3.3.0 - moor_generator: ^3.1.0 - flutter_test: - sdk: flutter + test: ^1.15.7 mockito: ^4.1.1 + pedantic: ^1.9.2 diff --git a/packages/dart_client/test/src/api/channel_test.dart b/packages/dart_client/test/src/api/channel_test.dart index d83d6f12..4b274a94 100644 --- a/packages/dart_client/test/src/api/channel_test.dart +++ b/packages/dart_client/test/src/api/channel_test.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; @@ -8,6 +7,7 @@ import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; +import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} @@ -22,7 +22,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -51,7 +51,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -83,7 +83,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -108,7 +108,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -149,7 +149,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -173,7 +173,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -198,7 +198,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -223,7 +223,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -247,7 +247,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -272,7 +272,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -306,7 +306,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -340,7 +340,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -375,7 +375,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -414,7 +414,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -448,7 +448,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -475,7 +475,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -501,7 +501,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -527,7 +527,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -845,7 +845,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1159,7 +1159,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1474,7 +1474,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1789,7 +1789,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1815,7 +1815,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1842,7 +1842,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1863,7 +1863,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1885,7 +1885,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1910,7 +1910,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1935,7 +1935,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1961,7 +1961,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -1993,7 +1993,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -2024,7 +2024,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', @@ -2064,7 +2064,7 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', diff --git a/packages/dart_client/test/src/api/requests_test.dart b/packages/dart_client/test/src/api/requests_test.dart index 3ad762a7..6f7324ed 100644 --- a/packages/dart_client/test/src/api/requests_test.dart +++ b/packages/dart_client/test/src/api/requests_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; void main() { diff --git a/packages/dart_client/test/src/api/responses_test.dart b/packages/dart_client/test/src/api/responses_test.dart index df576ba4..bfd4e5a7 100644 --- a/packages/dart_client/test/src/api/responses_test.dart +++ b/packages/dart_client/test/src/api/responses_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/api/responses.dart'; import 'package:stream_chat/src/models/device.dart'; import 'package:stream_chat/src/models/member.dart'; diff --git a/packages/dart_client/test/src/api/web_socket_stub_test.dart b/packages/dart_client/test/src/api/web_socket_stub_test.dart index 5de61b4a..e60706dc 100644 --- a/packages/dart_client/test/src/api/web_socket_stub_test.dart +++ b/packages/dart_client/test/src/api/web_socket_stub_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/api/web_socket_channel_stub.dart'; void main() { diff --git a/packages/dart_client/test/src/api/websocket_test.dart b/packages/dart_client/test/src/api/websocket_test.dart index e5a88bd8..d9486477 100644 --- a/packages/dart_client/test/src/api/websocket_test.dart +++ b/packages/dart_client/test/src/api/websocket_test.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:logging/logging.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat/src/api/connection_status.dart'; @@ -67,7 +67,7 @@ void main() { await ws.connect(); verify(connectFunc(computedUrl)).called(1); - expect(ws.connectionStatus.value, ConnectionStatus.connected); + expect(ws.connectionStatus, ConnectionStatus.connected); await streamController.close(); timer.cancel(); diff --git a/packages/dart_client/test/src/client_test.dart b/packages/dart_client/test/src/client_test.dart index 8f4dc41e..a1fa2175 100644 --- a/packages/dart_client/test/src/client_test.dart +++ b/packages/dart_client/test/src/client_test.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:dio/native_imp.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:logging/logging.dart'; import 'package:mockito/mockito.dart'; import 'package:stream_chat/src/api/requests.dart'; @@ -11,6 +10,7 @@ import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/user.dart'; +import 'package:test/test.dart'; class MockDio extends Mock implements DioForNative {} @@ -41,10 +41,7 @@ void main() { }); test('should create the object correctly', () { - final client = Client( - 'api-key', - persistenceEnabled: false, - ); + final client = StreamChatClient('api-key'); expect(client.baseURL, 'chat-us-east-1.stream-io-api.com'); expect(client.apiKey, 'api-key'); @@ -58,9 +55,8 @@ void main() { print(record.message); }; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, connectTimeout: Duration(seconds: 10), receiveTimeout: Duration(seconds: 12), logLevel: Level.INFO, @@ -82,10 +78,7 @@ void main() { })); test('Channel', () { - final client = Client( - 'test', - persistenceEnabled: false, - ); + final client = StreamChatClient('test'); final Map data = {'test': 1}; final channelClient = client.channel('type', id: 'id', extraData: data); expect(channelClient.type, 'type'); @@ -100,10 +93,9 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, - persistenceEnabled: false, ); final queryParams = { @@ -132,10 +124,9 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', httpClient: mockDio, - persistenceEnabled: false, ); final queryFilter = { @@ -184,9 +175,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -213,9 +203,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -258,9 +247,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -285,9 +273,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -305,9 +292,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -323,10 +309,7 @@ void main() { }); test('devToken', () { - final client = Client( - 'api-key', - persistenceEnabled: false, - ); + final client = StreamChatClient('api-key'); final token = client.devToken('test'); expect( @@ -342,9 +325,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -371,9 +353,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -415,9 +396,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -437,9 +417,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -453,9 +432,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -475,9 +453,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -501,9 +478,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -531,9 +507,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -553,9 +528,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -576,9 +550,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -598,9 +571,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -622,9 +594,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -644,9 +615,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -666,9 +636,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -694,9 +663,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -716,9 +684,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -738,9 +705,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -761,9 +727,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -787,9 +752,8 @@ void main() { final mockHttpClientAdapter = MockHttpClientAdapter(); dioHttp.httpClientAdapter = mockHttpClientAdapter; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: dioHttp, ); @@ -807,9 +771,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -831,9 +794,8 @@ void main() { final mockHttpClientAdapter = MockHttpClientAdapter(); dioHttp.httpClientAdapter = mockHttpClientAdapter; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: dioHttp, ); @@ -851,9 +813,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -875,9 +836,8 @@ void main() { final mockHttpClientAdapter = MockHttpClientAdapter(); dioHttp.httpClientAdapter = mockHttpClientAdapter; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: dioHttp, ); @@ -895,9 +855,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -920,9 +879,8 @@ void main() { final mockHttpClientAdapter = MockHttpClientAdapter(); dioHttp.httpClientAdapter = mockHttpClientAdapter; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: dioHttp, ); @@ -940,9 +898,8 @@ void main() { when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: mockDio, ); @@ -966,9 +923,8 @@ void main() { final mockHttpClientAdapter = MockHttpClientAdapter(); dioHttp.httpClientAdapter = mockHttpClientAdapter; - final client = Client( + final client = StreamChatClient( 'api-key', - persistenceEnabled: false, httpClient: dioHttp, ); diff --git a/packages/dart_client/test/src/models/action_test.dart b/packages/dart_client/test/src/models/action_test.dart index 2f75dddb..4859fcbe 100644 --- a/packages/dart_client/test/src/models/action_test.dart +++ b/packages/dart_client/test/src/models/action_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/action.dart'; void main() { diff --git a/packages/dart_client/test/src/models/attachment_test.dart b/packages/dart_client/test/src/models/attachment_test.dart index eaa8e9cc..b15458ad 100644 --- a/packages/dart_client/test/src/models/attachment_test.dart +++ b/packages/dart_client/test/src/models/attachment_test.dart @@ -2,7 +2,7 @@ import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/action.dart'; import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; void main() { group('src/models/attachment', () { diff --git a/packages/dart_client/test/src/models/channel_state_test.dart b/packages/dart_client/test/src/models/channel_state_test.dart index 21ffb20c..b8ce3c80 100644 --- a/packages/dart_client/test/src/models/channel_state_test.dart +++ b/packages/dart_client/test/src/models/channel_state_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/channel_config.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/command.dart'; diff --git a/packages/dart_client/test/src/models/channel_test.dart b/packages/dart_client/test/src/models/channel_test.dart index 7197d083..69b18197 100644 --- a/packages/dart_client/test/src/models/channel_test.dart +++ b/packages/dart_client/test/src/models/channel_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/channel_model.dart'; void main() { diff --git a/packages/dart_client/test/src/models/command_test.dart b/packages/dart_client/test/src/models/command_test.dart index 51774b84..dc44f736 100644 --- a/packages/dart_client/test/src/models/command_test.dart +++ b/packages/dart_client/test/src/models/command_test.dart @@ -1,7 +1,7 @@ import 'package:stream_chat/src/models/command.dart'; import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; void main() { group('src/models/command', () { diff --git a/packages/dart_client/test/src/models/device_test.dart b/packages/dart_client/test/src/models/device_test.dart index 3f68929f..3f8de16e 100644 --- a/packages/dart_client/test/src/models/device_test.dart +++ b/packages/dart_client/test/src/models/device_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/device.dart'; void main() { diff --git a/packages/dart_client/test/src/models/event_test.dart b/packages/dart_client/test/src/models/event_test.dart index eb7d0f5a..db99b01a 100644 --- a/packages/dart_client/test/src/models/event_test.dart +++ b/packages/dart_client/test/src/models/event_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/event.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/stream_chat.dart'; diff --git a/packages/dart_client/test/src/models/member_test.dart b/packages/dart_client/test/src/models/member_test.dart index 121b470f..237f0030 100644 --- a/packages/dart_client/test/src/models/member_test.dart +++ b/packages/dart_client/test/src/models/member_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/member.dart'; import 'package:stream_chat/src/models/user.dart'; diff --git a/packages/dart_client/test/src/models/message_test.dart b/packages/dart_client/test/src/models/message_test.dart index f318daac..7541f722 100644 --- a/packages/dart_client/test/src/models/message_test.dart +++ b/packages/dart_client/test/src/models/message_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; @@ -116,7 +116,7 @@ void main() { showInChannel: true, parentId: 'parentId', extraData: {'hey': 'test'}, - status: MessageSendingStatus.SENT, + status: MessageSendingStatus.sent, ); expect( diff --git a/packages/dart_client/test/src/models/reaction_test.dart b/packages/dart_client/test/src/models/reaction_test.dart index 2e8fe5f8..8bc92081 100644 --- a/packages/dart_client/test/src/models/reaction_test.dart +++ b/packages/dart_client/test/src/models/reaction_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'dart:convert'; diff --git a/packages/dart_client/test/src/models/read_test.dart b/packages/dart_client/test/src/models/read_test.dart index 83596170..b15ce66a 100644 --- a/packages/dart_client/test/src/models/read_test.dart +++ b/packages/dart_client/test/src/models/read_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/read.dart'; import 'package:stream_chat/src/models/user.dart'; diff --git a/packages/dart_client/test/src/models/serialization_test.dart b/packages/dart_client/test/src/models/serialization_test.dart index c20d0012..e8a2de3f 100644 --- a/packages/dart_client/test/src/models/serialization_test.dart +++ b/packages/dart_client/test/src/models/serialization_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/serialization.dart'; void main() { diff --git a/packages/dart_client/test/src/models/user_test.dart b/packages/dart_client/test/src/models/user_test.dart index a148ec6e..6100b218 100644 --- a/packages/dart_client/test/src/models/user_test.dart +++ b/packages/dart_client/test/src/models/user_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/src/models/user.dart'; void main() { diff --git a/packages/dart_client/test/version_test.dart b/packages/dart_client/test/version_test.dart index 698f79e1..ada2a85c 100644 --- a/packages/dart_client/test/version_test.dart +++ b/packages/dart_client/test/version_test.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; import 'package:stream_chat/version.dart'; void prepareTest() { diff --git a/packages/dart_client/test_integration/client_test.dart b/packages/dart_client/test_integration/client_test.dart deleted file mode 100644 index 98ec39cf..00000000 --- a/packages/dart_client/test_integration/client_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:logging/logging.dart'; -import 'package:stream_chat/src/client.dart'; -import 'package:stream_chat/stream_chat.dart'; - -const API_KEY = '6xjf3dex3n7d'; -const TOKEN = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo'; - -void main() { - test('test', () async { - final client = Client( - '6xjf3dex3n7d', - logLevel: Level.INFO, - tokenProvider: (_) async => '', - ); - final user = User(id: 'wild-breeze-7'); - - await client.setGuestUser(user); - }); -} diff --git a/packages/flutter_widgets/README.md b/packages/flutter_widgets/README.md index 8104e83a..3504c78a 100644 --- a/packages/flutter_widgets/README.md +++ b/packages/flutter_widgets/README.md @@ -1,7 +1,7 @@ # Official Flutter SDK for [Stream Chat](https://getstream.io/chat/)

- Flutter Chat + Flutter Chat

> The official Flutter components for Stream Chat, a service for @@ -91,7 +91,7 @@ Out of the box, all chat widgets use their default styling, and there are two wa 1. Initialize the `StreamChatTheme` from your existing `MaterialApp` style ```dart class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); @@ -117,7 +117,7 @@ Out of the box, all chat widgets use their default styling, and there are two wa 2. Construct a custom theme and provide all the customizations needed ```dart class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/advanced_options_page.dart b/packages/flutter_widgets/example/lib/advanced_options_page.dart index 023ce32c..ee505148 100644 --- a/packages/flutter_widgets/example/lib/advanced_options_page.dart +++ b/packages/flutter_widgets/example/lib/advanced_options_page.dart @@ -1,17 +1,12 @@ -import 'dart:io'; - -import 'package:example/routes/app_routes.dart'; import 'package:example/routes/routes.dart'; import 'package:example/stream_version.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; import 'choose_user_page.dart'; -import 'notifications_service.dart'; +import 'main.dart'; class AdvancedOptionsPage extends StatefulWidget { @override @@ -272,14 +267,10 @@ class _AdvancedOptionsPageState extends State { ), ); - final client = Client( + final client = StreamChatClient( apiKey, logLevel: Level.INFO, - showLocalNotification: (!kIsWeb && Platform.isAndroid) - ? showLocalNotification - : null, - persistenceEnabled: true, - ); + )..chatPersistenceClient = chatPersistentClient; try { await client.setUser( @@ -289,10 +280,6 @@ class _AdvancedOptionsPageState extends State { userToken, ); - if (!kIsWeb) { - initNotifications(client); - } - final secureStorage = FlutterSecureStorage(); secureStorage.write( key: kStreamApiKey, @@ -319,73 +306,13 @@ class _AdvancedOptionsPageState extends State { await client.disconnect(); return; } - - if (!kIsWeb) { - initNotifications(client); - } - - Navigator.pop(context); - Navigator.pop(context); - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) { - return FutureBuilder( - future: StreamingSharedPreferences.instance, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox(); - } - return PreferenceBuilder( - preference: snapshot.data.getInt( - 'theme', - defaultValue: 0, - ), - builder: (context, snapshot) => MaterialApp( - builder: (context, child) { - return StreamChat( - client: client, - child: Builder( - builder: (context) => - AnnotatedRegion< - SystemUiOverlayStyle>( - child: child, - value: SystemUiOverlayStyle( - systemNavigationBarColor: - StreamChatTheme.of(context) - .colorTheme - .white, - systemNavigationBarIconBrightness: - Theme.of(context) - .brightness == - Brightness.dark - ? Brightness.light - : Brightness.dark, - ), - ), - ), - ); - }, - debugShowCheckedModeBanner: false, - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - themeMode: { - -1: ThemeMode.dark, - 0: ThemeMode.system, - 1: ThemeMode.light, - }[snapshot], - onGenerateRoute: AppRoutes.generateRoute, - initialRoute: client.state.user == null - ? Routes.CHOOSE_USER - : Routes.HOME, - ), - ); - }, - ); - }, - ), - ); loading = false; + await Navigator.pushNamedAndRemoveUntil( + context, + Routes.APP, + ModalRoute.withName(Routes.APP), + arguments: client, + ); } }, ), diff --git a/packages/flutter_widgets/example/lib/choose_user_page.dart b/packages/flutter_widgets/example/lib/choose_user_page.dart index 8e36e748..02852c64 100644 --- a/packages/flutter_widgets/example/lib/choose_user_page.dart +++ b/packages/flutter_widgets/example/lib/choose_user_page.dart @@ -1,11 +1,9 @@ import 'package:example/stream_version.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'notifications_service.dart'; import 'routes/routes.dart'; const kStreamApiKey = 'STREAM_API_KEY'; @@ -187,10 +185,6 @@ class ChooseUserPage extends StatelessWidget { key: kStreamToken, value: token, ); - - if (!kIsWeb) { - initNotifications(client); - } Navigator.pushNamedAndRemoveUntil( context, Routes.HOME, diff --git a/packages/flutter_widgets/example/lib/custom_message.dart b/packages/flutter_widgets/example/lib/custom_message.dart index 5da4541a..e2756430 100644 --- a/packages/flutter_widgets/example/lib/custom_message.dart +++ b/packages/flutter_widgets/example/lib/custom_message.dart @@ -15,7 +15,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly /// or to retrieve outer scope needed such as messages from the [Channel.state]. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -29,7 +29,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/custom_theme.dart b/packages/flutter_widgets/example/lib/custom_theme.dart index 817d2e67..a7ad5ba0 100644 --- a/packages/flutter_widgets/example/lib/custom_theme.dart +++ b/packages/flutter_widgets/example/lib/custom_theme.dart @@ -19,7 +19,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// We also change the message color posted by the current user. /// You can perform these more granular style changes using [StreamChatTheme.copyWith]. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -33,7 +33,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/customize_channel_preview.dart b/packages/flutter_widgets/example/lib/customize_channel_preview.dart index 9b7deb64..2f28fa80 100644 --- a/packages/flutter_widgets/example/lib/customize_channel_preview.dart +++ b/packages/flutter_widgets/example/lib/customize_channel_preview.dart @@ -20,7 +20,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// - We retrieve the count of unread messages from [Channel.state] void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -34,7 +34,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/customize_message_widget.dart b/packages/flutter_widgets/example/lib/customize_message_widget.dart index 14233dd7..d19d6023 100644 --- a/packages/flutter_widgets/example/lib/customize_message_widget.dart +++ b/packages/flutter_widgets/example/lib/customize_message_widget.dart @@ -15,7 +15,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly /// or to retrieve outer scope needed such as messages from the [Channel.state]. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -29,7 +29,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/group_chat_details_screen.dart b/packages/flutter_widgets/example/lib/group_chat_details_screen.dart index 83ec94bb..afc9d12f 100644 --- a/packages/flutter_widgets/example/lib/group_chat_details_screen.dart +++ b/packages/flutter_widgets/example/lib/group_chat_details_screen.dart @@ -150,116 +150,116 @@ class _GroupChatDetailsScreenState extends State { ), ], ), - body: ValueListenableBuilder( - valueListenable: StreamChat.of(context).client.wsConnectionStatus, - builder: (context, status, _) { - String statusString = ''; - bool showStatus = true; + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } - return InfoTile( - showMessage: showStatus, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: Column( - children: [ - Container( - width: double.maxFinite, - decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: Column( + children: [ + Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: Text( - '$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, - ), + child: Text( + '$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, ), ), ), - Expanded( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: ListView.separated( - itemCount: _selectedUsers.length + 1, - separatorBuilder: (_, __) => Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .greyWhisper, - ), - itemBuilder: (_, index) { - if (index == _selectedUsers.length) { - return Container( - height: 1, + ), + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: ListView.separated( + itemCount: _selectedUsers.length + 1, + separatorBuilder: (_, __) => Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ), + itemBuilder: (_, index) { + if (index == _selectedUsers.length) { + return Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ); + } + final user = _selectedUsers[index]; + return ListTile( + key: ObjectKey(user), + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tightFor( + width: 40, + height: 40, + ), + ), + title: Text( + user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + trailing: IconButton( + icon: Icon( + Icons.clear_rounded, color: StreamChatTheme.of(context) .colorTheme - .greyWhisper, - ); - } - final user = _selectedUsers[index]; - return ListTile( - key: ObjectKey(user), - leading: UserAvatar( - user: user, - constraints: BoxConstraints.tightFor( - width: 40, - height: 40, - ), + .black, ), - title: Text( - user.name, - style: TextStyle(fontWeight: FontWeight.bold), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - trailing: IconButton( - icon: Icon( - Icons.clear_rounded, - color: StreamChatTheme.of(context) - .colorTheme - .black, - ), - padding: const EdgeInsets.all(0), - splashRadius: 24, - onPressed: () { - setState(() { - _selectedUsers.remove(user); - }); - if (_selectedUsers.isEmpty) { - Navigator.pop(context, _selectedUsers); - } - }, - ), - ); - }, - ), + padding: const EdgeInsets.all(0), + splashRadius: 24, + onPressed: () { + setState(() { + _selectedUsers.remove(user); + }); + if (_selectedUsers.isEmpty) { + Navigator.pop(context, _selectedUsers); + } + }, + ), + ); + }, ), ), - ], - ), - ); - }), + ), + ], + ), + ); + }, + ), ), ); } diff --git a/packages/flutter_widgets/example/lib/main.dart b/packages/flutter_widgets/example/lib/main.dart index 10d73997..775b1d6e 100644 --- a/packages/flutter_widgets/example/lib/main.dart +++ b/packages/flutter_widgets/example/lib/main.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:example/chat_info_screen.dart'; import 'package:example/choose_user_page.dart'; @@ -10,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; import 'notifications_service.dart'; @@ -17,6 +17,11 @@ import 'routes/app_routes.dart'; import 'routes/routes.dart'; import 'search_text_field.dart'; +final chatPersistentClient = StreamChatPersistenceClient( + logLevel: Level.INFO, + connectionMode: ConnectionMode.background, +); + void main() async { WidgetsFlutterBinding.ensureInitialized(); final secureStorage = FlutterSecureStorage(); @@ -24,13 +29,10 @@ void main() async { final apiKey = await secureStorage.read(key: kStreamApiKey); final userId = await secureStorage.read(key: kStreamUserId); - final client = Client( + final client = StreamChatClient( apiKey ?? kDefaultStreamApiKey, logLevel: Level.INFO, - showLocalNotification: - (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, - persistenceEnabled: true, - ); + )..chatPersistenceClient = chatPersistentClient; if (userId != null) { final token = await secureStorage.read(key: kStreamToken); @@ -38,16 +40,13 @@ void main() async { User(id: userId), token, ); - if (!kIsWeb) { - initNotifications(client); - } } runApp(MyApp(client)); } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); @@ -68,6 +67,7 @@ class MyApp extends StatelessWidget { builder: (context, child) { return StreamChat( client: client, + onBackgroundEventReceived: showLocalNotification, child: Builder( builder: (context) => AnnotatedRegion( child: child, diff --git a/packages/flutter_widgets/example/lib/multiple_conversation.dart b/packages/flutter_widgets/example/lib/multiple_conversation.dart index b5fdd5af..f810231c 100644 --- a/packages/flutter_widgets/example/lib/multiple_conversation.dart +++ b/packages/flutter_widgets/example/lib/multiple_conversation.dart @@ -19,7 +19,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message. /// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -33,7 +33,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/new_chat_screen.dart b/packages/flutter_widgets/example/lib/new_chat_screen.dart index baddd570..7f47e871 100644 --- a/packages/flutter_widgets/example/lib/new_chat_screen.dart +++ b/packages/flutter_widgets/example/lib/new_chat_screen.dart @@ -134,9 +134,8 @@ class _NewChatScreenState extends State { ), centerTitle: true, ), - body: ValueListenableBuilder( - valueListenable: StreamChat.of(context).client.wsConnectionStatus, - builder: (context, status, _) { + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { String statusString = ''; bool showStatus = true; diff --git a/packages/flutter_widgets/example/lib/new_group_chat_screen.dart b/packages/flutter_widgets/example/lib/new_group_chat_screen.dart index 4d0274e2..4e924d86 100644 --- a/packages/flutter_widgets/example/lib/new_group_chat_screen.dart +++ b/packages/flutter_widgets/example/lib/new_group_chat_screen.dart @@ -87,227 +87,225 @@ class _NewGroupChatScreenState extends State { ) ], ), - body: ValueListenableBuilder( - valueListenable: StreamChat.of(context).client.wsConnectionStatus, - builder: (context, status, _) { - String statusString = ''; - bool showStatus = true; + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } - return InfoTile( - showMessage: showStatus, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: - (BuildContext context, bool innerBoxIsScrolled) { - return [ - SliverToBoxAdapter( - child: SearchTextField( - controller: _controller, - ), + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, ), - if (_selectedUsers.isNotEmpty) - SliverToBoxAdapter( - child: Container( - height: 104, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: _selectedUsers.length, - padding: const EdgeInsets.all(8), - separatorBuilder: (_, __) => SizedBox(width: 16), - itemBuilder: (_, index) { - final user = _selectedUsers.elementAt(index); - return Column( - children: [ - Stack( - children: [ - UserAvatar( - onlineIndicatorAlignment: - Alignment(0.9, 0.9), - user: user, - showOnlineStatus: true, - borderRadius: BorderRadius.circular(32), - constraints: BoxConstraints.tightFor( - height: 64, - width: 64, - ), + ), + if (_selectedUsers.isNotEmpty) + SliverToBoxAdapter( + child: Container( + height: 104, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: _selectedUsers.length, + padding: const EdgeInsets.all(8), + separatorBuilder: (_, __) => SizedBox(width: 16), + itemBuilder: (_, index) { + final user = _selectedUsers.elementAt(index); + return Column( + children: [ + Stack( + children: [ + UserAvatar( + onlineIndicatorAlignment: + Alignment(0.9, 0.9), + user: user, + showOnlineStatus: true, + borderRadius: BorderRadius.circular(32), + constraints: BoxConstraints.tightFor( + height: 64, + width: 64, ), - Positioned( - top: -4, - right: -4, - child: GestureDetector( - onTap: () { - if (_selectedUsers.contains(user)) { - setState(() => - _selectedUsers.remove(user)); - } - }, - child: Container( - decoration: BoxDecoration( + ), + Positioned( + top: -4, + right: -4, + child: GestureDetector( + onTap: () { + if (_selectedUsers.contains(user)) { + setState(() => + _selectedUsers.remove(user)); + } + }, + child: Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .white, + shape: BoxShape.circle, + border: Border.all( color: StreamChatTheme.of(context) .colorTheme - .white, - shape: BoxShape.circle, - border: Border.all( - color: - StreamChatTheme.of(context) - .colorTheme - .whiteSnow, - ), - ), - child: StreamSvgIcon.close( - color: StreamChatTheme.of(context) - .colorTheme - .black, - size: 24, + .whiteSnow, ), ), - ), - ) - ], - ), - SizedBox(height: 4), - Text( - user.name.split(' ')[0], - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ], - ); - }, - ), - ), - ), - SliverPersistentHeader( - pinned: true, - delegate: _HeaderDelegate( - height: 30, - child: Container( - width: double.maxFinite, - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: Text( - _isSearchActive - ? 'Matches for \"$_userNameQuery\"' - : 'On the platform', - style: TextStyle( - color: - StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ), - ), - ), - ), - ]; - }, - body: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: UsersBloc( - child: UserListView( - selectedUsers: _selectedUsers, - pullToRefresh: false, - groupAlphabetically: _isSearchActive ? false : true, - onUserTap: (user, _) { - if (!_selectedUsers.contains(user)) { - setState(() { - _selectedUsers.add(user); - }); - } else { - setState(() { - _selectedUsers.remove(user); - }); - } - }, - pagination: PaginationParams( - limit: 25, - ), - filter: { - if (_userNameQuery.isNotEmpty) - 'name': { - r'$autocomplete': _userNameQuery, - }, - 'id': { - r'$ne': StreamChat.of(context).user.id, - } - }, - sort: [ - SortOption( - 'name', - direction: 1, - ), - ], - emptyBuilder: (_) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(24), - child: StreamSvgIcon.search( - size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .grey, + child: StreamSvgIcon.close( + color: StreamChatTheme.of(context) + .colorTheme + .black, + size: 24, + ), ), ), - Text( - 'No user matches these keywords...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .grey, - ), - ), - ], + ) + ], + ), + SizedBox(height: 4), + Text( + user.name.split(' ')[0], + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, ), ), - ), + ], ); }, - ); - }, + ), + ), ), + SliverPersistentHeader( + pinned: true, + delegate: _HeaderDelegate( + height: 30, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + _isSearchActive + ? 'Matches for \"$_userNameQuery\"' + : 'On the platform', + style: TextStyle( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + ), + ), + ]; + }, + body: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: UsersBloc( + child: UserListView( + selectedUsers: _selectedUsers, + pullToRefresh: false, + groupAlphabetically: _isSearchActive ? false : true, + onUserTap: (user, _) { + if (!_selectedUsers.contains(user)) { + setState(() { + _selectedUsers.add(user); + }); + } else { + setState(() { + _selectedUsers.remove(user); + }); + } + }, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_userNameQuery.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + } + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, ), ), ), - ); - }), + ), + ); + }, + ), ); } } diff --git a/packages/flutter_widgets/example/lib/notifications_service.dart b/packages/flutter_widgets/example/lib/notifications_service.dart index 298ab3ab..246923df 100644 --- a/packages/flutter_widgets/example/lib/notifications_service.dart +++ b/packages/flutter_widgets/example/lib/notifications_service.dart @@ -1,11 +1,9 @@ -import 'dart:io'; - -import 'package:flutter_apns/flutter_apns.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -void showLocalNotification(Message message, ChannelModel channel) async { +void showLocalNotification(Event event) async { + if (event.message == null) return; final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final initializationSettingsAndroid = AndroidInitializationSettings('launch_background'); @@ -16,9 +14,9 @@ void showLocalNotification(Message message, ChannelModel channel) async { ); await flutterLocalNotificationsPlugin.initialize(initializationSettings); await flutterLocalNotificationsPlugin.show( - message.id.hashCode, - '${message.user.name} @ ${channel.name}', - message.text, + event.message.id.hashCode, + event.message.user.name, + event.message.text, NotificationDetails( android: AndroidNotificationDetails( 'message channel', @@ -31,33 +29,3 @@ void showLocalNotification(Message message, ChannelModel channel) async { ), ); } - -Future backgroundHandler(Map notification) async { - print('new notification ${notification}'); - final messageId = notification['data']['id']; - - final notificationData = - await NotificationService.getAndStoreMessage(messageId); - - showLocalNotification( - notificationData.message, - notificationData.channel, - ); -} - -void initNotifications(Client client) { - final connector = createPushConnector(); - connector.configure( - onBackgroundMessage: backgroundHandler, - ); - - connector.requestNotificationPermissions(); - connector.token.addListener(() { - if (connector.token.value != null) { - client.addDevice( - connector.token.value, - Platform.isAndroid ? PushProvider.firebase : PushProvider.apn, - ); - } - }); -} diff --git a/packages/flutter_widgets/example/lib/routes/app_routes.dart b/packages/flutter_widgets/example/lib/routes/app_routes.dart index 0c8a899f..e7c7e318 100644 --- a/packages/flutter_widgets/example/lib/routes/app_routes.dart +++ b/packages/flutter_widgets/example/lib/routes/app_routes.dart @@ -15,6 +15,12 @@ class AppRoutes { static Route generateRoute(RouteSettings settings) { final args = settings.arguments; switch (settings.name) { + case Routes.APP: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.APP), + builder: (_) { + return MyApp(args); + }); case Routes.HOME: return MaterialPageRoute( settings: const RouteSettings(name: Routes.HOME), diff --git a/packages/flutter_widgets/example/lib/routes/routes.dart b/packages/flutter_widgets/example/lib/routes/routes.dart index 0d2f4e5f..2280ca30 100644 --- a/packages/flutter_widgets/example/lib/routes/routes.dart +++ b/packages/flutter_widgets/example/lib/routes/routes.dart @@ -1,5 +1,6 @@ /// Define all the route names here class Routes { + static const String APP = '/app'; static const String HOME = '/home'; static const String CHOOSE_USER = '/choose_user'; static const String ADVANCED_OPTIONS = '/advance_options'; diff --git a/packages/flutter_widgets/example/lib/single_conversation.dart b/packages/flutter_widgets/example/lib/single_conversation.dart index 2093d38a..66a1091c 100644 --- a/packages/flutter_widgets/example/lib/single_conversation.dart +++ b/packages/flutter_widgets/example/lib/single_conversation.dart @@ -5,8 +5,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// There are three important things to notice that are common to all Flutter application using StreamChat: /// -/// 1. The Dart API [Client] is initialized with your API Key -/// 2. The current user is set by calling [Client.setUser] +/// 1. The Dart API [StreamChatClient] is initialized with your API Key +/// 2. The current user is set by calling [StreamChatClient.setUser] /// 3. The client is then passed to the top-level [StreamChat] widget /// [StreamChat] is an inherited widget and must be the parent of all Chat related widgets. /// @@ -15,9 +15,9 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// Let's have a look at what we've built: /// -/// - We set up the Chat [Client] with the API key +/// - We set up the Chat [StreamChatClient] with the API key /// -/// - We set the the current user for Chat with [Client.setUser] and a pre-generated user token +/// - We set the the current user for Chat with [StreamChatClient.setUser] and a pre-generated user token /// /// - We make [StreamChat] the root Widget of our application /// @@ -25,7 +25,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// If you now run the simulator you will see a single channel UI. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -44,7 +44,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; final Channel channel; MyApp(this.client, this.channel); diff --git a/packages/flutter_widgets/example/lib/split_view.dart b/packages/flutter_widgets/example/lib/split_view.dart index f99e69fc..08324759 100644 --- a/packages/flutter_widgets/example/lib/split_view.dart +++ b/packages/flutter_widgets/example/lib/split_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -16,7 +16,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/lib/threads.dart b/packages/flutter_widgets/example/lib/threads.dart index f89e6fce..6a3c7766 100644 --- a/packages/flutter_widgets/example/lib/threads.dart +++ b/packages/flutter_widgets/example/lib/threads.dart @@ -10,7 +10,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage]. void main() async { - final client = Client( + final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.INFO, ); @@ -24,7 +24,7 @@ void main() async { } class MyApp extends StatelessWidget { - final Client client; + final StreamChatClient client; MyApp(this.client); diff --git a/packages/flutter_widgets/example/pubspec.yaml b/packages/flutter_widgets/example/pubspec.yaml index 4814f3b0..9b387e39 100644 --- a/packages/flutter_widgets/example/pubspec.yaml +++ b/packages/flutter_widgets/example/pubspec.yaml @@ -9,8 +9,10 @@ environment: dependencies: flutter: sdk: flutter - stream_chat_flutter: + stream_chat_flutter: path: ../ + stream_chat_persistence: + path: ../../stream_chat_persistence flutter_apns: ^1.4.1 flutter_local_notifications: ^2.0.2 flutter_svg: ^0.19.1 diff --git a/packages/flutter_widgets/lib/src/channel_header.dart b/packages/flutter_widgets/lib/src/channel_header.dart index fb681d59..27194627 100644 --- a/packages/flutter_widgets/lib/src/channel_header.dart +++ b/packages/flutter_widgets/lib/src/channel_header.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import '../stream_chat_flutter.dart'; import './channel_name.dart'; +import '../stream_chat_flutter.dart'; import 'channel_image.dart'; +import 'connection_status_builder.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) @@ -17,7 +18,7 @@ import 'channel_image.dart'; /// /// ```dart /// class MyApp extends StatelessWidget { -/// final Client client; +/// final StreamChatClient client; /// final Channel channel; /// /// MyApp(this.client, this.channel); @@ -85,11 +86,9 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { final channel = StreamChannel.of(context).channel; - final _client = StreamChat.of(context).client; - return ValueListenableBuilder( - valueListenable: _client.wsConnectionStatus, - builder: (context, status, _) { + return ConnectionStatusBuilder( + statusBuilder: (context, status) { String statusString = ''; bool showStatus = true; diff --git a/packages/flutter_widgets/lib/src/channel_image.dart b/packages/flutter_widgets/lib/src/channel_image.dart index 18d67dfb..649a5880 100644 --- a/packages/flutter_widgets/lib/src/channel_image.dart +++ b/packages/flutter_widgets/lib/src/channel_image.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/group_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png) @@ -11,7 +11,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// ```dart /// class MyApp extends StatelessWidget { -/// final Client client; +/// final StreamChatClient client; /// final Channel channel; /// /// MyApp(this.client, this.channel); diff --git a/packages/flutter_widgets/lib/src/channel_info.dart b/packages/flutter_widgets/lib/src/channel_info.dart index b1ab568d..4244c910 100644 --- a/packages/flutter_widgets/lib/src/channel_info.dart +++ b/packages/flutter_widgets/lib/src/channel_info.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'connection_status_builder.dart'; + class ChannelInfo extends StatelessWidget { final Channel channel; @@ -25,9 +27,8 @@ class ChannelInfo extends StatelessWidget { stream: channel.state.membersStream, initialData: channel.state.members, builder: (context, snapshot) { - return ValueListenableBuilder( - valueListenable: client.wsConnectionStatus, - builder: (context, status, child) { + return ConnectionStatusBuilder( + statusBuilder: (context, status) { switch (status) { case ConnectionStatus.connected: return _buildConnectedTitleState(context, snapshot.data); @@ -110,7 +111,8 @@ class ChannelInfo extends StatelessWidget { ); } - Widget _buildDisconnectedTitleState(BuildContext context, Client client) { + Widget _buildDisconnectedTitleState( + BuildContext context, StreamChatClient client) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/packages/flutter_widgets/lib/src/channel_list_header.dart b/packages/flutter_widgets/lib/src/channel_list_header.dart index 8ced0422..045d24d5 100644 --- a/packages/flutter_widgets/lib/src/channel_list_header.dart +++ b/packages/flutter_widgets/lib/src/channel_list_header.dart @@ -2,25 +2,26 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'connection_status_builder.dart'; import 'info_tile.dart'; import 'stream_chat.dart'; typedef _TitleBuilder = Widget Function( BuildContext context, ConnectionStatus status, - Client client, + StreamChatClient client, ); /// -/// It shows the current [Client] status. +/// It shows the current [StreamChatClient] status. /// /// ```dart /// class MyApp extends StatelessWidget { -/// final Client client; +/// final StreamChatClient client; /// /// MyApp(this.client); /// @@ -41,8 +42,8 @@ typedef _TitleBuilder = Widget Function( /// Usually you would use this widget as an [AppBar] inside a [Scaffold]. /// However you can also use it as a normal widget. /// -/// The widget by default uses the inherited [Client] to fetch information about the status. -/// However you can also pass your own [Client] if you don't have it in the widget tree. +/// The widget by default uses the inherited [StreamChatClient] to fetch information about the status. +/// However you can also pass your own [StreamChatClient] if you don't have it in the widget tree. /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property. /// Modify it to change the widget appearance. @@ -58,8 +59,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { this.preNavigationCallback, }) : super(key: key); - /// Pass this if you don't have a [Client] in your widget tree. - final Client client; + /// Pass this if you don't have a [StreamChatClient] in your widget tree. + final StreamChatClient client; /// Use this to build your own title as per different [ConnectionStatus] final _TitleBuilder titleBuilder; @@ -79,9 +80,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { final _client = client ?? StreamChat.of(context).client; final user = _client.state.user; - return ValueListenableBuilder( - valueListenable: _client.wsConnectionStatus, - builder: (context, status, child) { + return ConnectionStatusBuilder( + statusBuilder: (context, status) { String statusString = ''; bool showStatus = true; @@ -130,9 +130,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { actions: [ StreamNeumorphicButton( child: IconButton( - icon: ValueListenableBuilder( - valueListenable: _client.wsConnectionStatus, - builder: (context, status, child) { + icon: ConnectionStatusBuilder( + statusBuilder: (context, status) { var color; switch (status) { case ConnectionStatus.connected: @@ -216,7 +215,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { ); } - Widget _buildDisconnectedTitleState(BuildContext context, Client client) { + Widget _buildDisconnectedTitleState( + BuildContext context, StreamChatClient client) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/packages/flutter_widgets/lib/src/connection_status_builder.dart b/packages/flutter_widgets/lib/src/connection_status_builder.dart new file mode 100644 index 00000000..01632b1b --- /dev/null +++ b/packages/flutter_widgets/lib/src/connection_status_builder.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'stream_chat.dart'; + +/// Widget that builds itself based on the latest snapshot of interaction with +/// a [Stream] of type [ConnectionStatus]. +/// +/// The widget will use the closest [StreamChatClient.wsConnectionStatusStream] in case no +/// stream is provided. +class ConnectionStatusBuilder extends StatelessWidget { + /// Creates a new ConnectionStatusBuilder + const ConnectionStatusBuilder({ + Key key, + @required this.statusBuilder, + this.initialStatus = ConnectionStatus.disconnected, + this.connectionStatusStream, + this.errorBuilder, + this.loadingBuilder, + }) : assert(statusBuilder != null), + super(key: key); + + /// The connection status that will be used to create the initial snapshot. + final ConnectionStatus initialStatus; + + /// The asynchronous computation to which this builder is currently connected. + final Stream connectionStatusStream; + + /// The builder that will be used in case of error + final Widget Function(BuildContext context, Object error) errorBuilder; + + /// The builder that will be used in case of loading + final WidgetBuilder loadingBuilder; + + /// The builder that will be used in case of data + final Widget Function(BuildContext context, ConnectionStatus status) + statusBuilder; + + @override + Widget build(BuildContext context) { + final stream = connectionStatusStream ?? + StreamChat.of(context).client.wsConnectionStatusStream; + return StreamBuilder( + initialData: initialStatus, + stream: stream, + builder: (context, snapshot) { + if (snapshot.hasError) { + if (errorBuilder != null) { + return errorBuilder(context, snapshot.error); + } + return Offstage(); + } + if (!snapshot.hasData) { + if (loadingBuilder != null) return loadingBuilder(context); + return Offstage(); + } + return statusBuilder(context, snapshot.data); + }, + ); + } +} diff --git a/packages/flutter_widgets/lib/src/message_actions_modal.dart b/packages/flutter_widgets/lib/src/message_actions_modal.dart index 295bcf26..6ca7c688 100644 --- a/packages/flutter_widgets/lib/src/message_actions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_actions_modal.dart @@ -126,7 +126,7 @@ class _MessageActionsModalState extends State { children: [ if (widget.showReactions && (widget.message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.sent || widget.message.status == null)) Align( alignment: Alignment( @@ -174,7 +174,7 @@ class _MessageActionsModalState extends State { showReactionPickerIndicator: widget.showReactions && (widget.message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.sent || widget.message.status == null), showInChannelIndicator: false, showSendingIndicator: false, @@ -203,13 +203,13 @@ class _MessageActionsModalState extends State { children: [ if (widget.showReplyMessage && (widget.message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.sent || widget.message.status == null) && widget.message.parentId == null) _buildReplyButton(context), if (widget.showThreadReplyMessage && (widget.message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.sent || widget.message.status == null) && widget.message.parentId == null) _buildThreadReplyButton(context), @@ -486,7 +486,7 @@ class _MessageActionsModalState extends State { Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = - widget.message.status == MessageSendingStatus.FAILED_DELETE; + widget.message.status == MessageSendingStatus.failed_delete; return InkWell( onTap: () => _showDeleteDialog(), child: Padding( @@ -561,7 +561,7 @@ class _MessageActionsModalState extends State { Widget _buildResendMessage(BuildContext context) { final isUpdateFailed = - widget.message.status == MessageSendingStatus.FAILED_UPDATE; + widget.message.status == MessageSendingStatus.failed_update; return InkWell( onTap: () { Navigator.pop(context); diff --git a/packages/flutter_widgets/lib/src/message_input.dart b/packages/flutter_widgets/lib/src/message_input.dart index 59d748c2..0966e75a 100644 --- a/packages/flutter_widgets/lib/src/message_input.dart +++ b/packages/flutter_widgets/lib/src/message_input.dart @@ -2210,7 +2210,7 @@ class MessageInputState extends State { _mentionedUsers.clear(); if (widget.editMessage == null || - widget.editMessage.status == MessageSendingStatus.FAILED) { + widget.editMessage.status == MessageSendingStatus.failed) { sendingFuture = channel.sendMessage(message); } else { sendingFuture = StreamChat.of(context).client.updateMessage( diff --git a/packages/flutter_widgets/lib/src/message_list_view.dart b/packages/flutter_widgets/lib/src/message_list_view.dart index 9ce29117..86c1445c 100644 --- a/packages/flutter_widgets/lib/src/message_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_list_view.dart @@ -14,6 +14,7 @@ import 'package:stream_chat_flutter/src/system_message.dart'; import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; +import 'connection_status_builder.dart'; import 'date_divider.dart'; import 'swipeable.dart'; import 'extension.dart'; @@ -301,197 +302,193 @@ class _MessageListViewState extends State { } _messageListLength = newMessagesListLength; - final _client = StreamChat.of(context).client; return Stack( alignment: Alignment.center, children: [ - ValueListenableBuilder( - valueListenable: _client.wsConnectionStatus, - builder: (context, status, _) { - String statusString = ''; - bool showStatus = true; + ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } + return InfoTile( + showMessage: widget.showConnectionStateTile ? showStatus : false, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: LazyLoadScrollView( + onStartOfPage: () async { + _inBetweenList = false; + if (!_upToDate) { + _topPaginationActive = false; + _bottomPaginationActive = true; + return _paginateData( + streamChannel, + QueryDirection.bottom, + ); + } + }, + onEndOfPage: () async { + _inBetweenList = false; + _topPaginationActive = true; + _bottomPaginationActive = false; + return _paginateData( + streamChannel, + QueryDirection.top, + ); + }, + onInBetweenOfPage: () { + _inBetweenList = true; + }, + child: ScrollablePositionedList.separated( + key: ValueKey(initialIndex + initialAlignment), + itemPositionsListener: _itemPositionListener, + addAutomaticKeepAlives: true, + initialScrollIndex: initialIndex ?? 0, + initialAlignment: initialAlignment ?? 0, + physics: widget.scrollPhysics, + itemScrollController: _scrollController, + reverse: true, + itemCount: + messages.length + 2 + (_isThreadConversation ? 1 : 0), + separatorBuilder: (context, i) { + if (i == messages.length) return Offstage(); + if (i == 0) return SizedBox(height: 30); + if (i == messages.length + 1) { + final replyCount = widget.parentMessage.replyCount; + return Container( + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', + textAlign: TextAlign.center, + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .lastMessageAt, + ), + ), + ); + } - return InfoTile( - showMessage: - widget.showConnectionStateTile ? showStatus : false, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: LazyLoadScrollView( - onStartOfPage: () async { - _inBetweenList = false; - if (!_upToDate) { - _topPaginationActive = false; - _bottomPaginationActive = true; - return _paginateData( + final message = messages[i]; + final nextMessage = messages[i - 1]; + if (!Jiffy(message.createdAt.toLocal()).isSame( + nextMessage.createdAt.toLocal(), + Units.DAY, + )) { + final divider = widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + nextMessage.createdAt.toLocal(), + ) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: divider, + ); + } + final timeDiff = + Jiffy(nextMessage.createdAt.toLocal()).diff( + message.createdAt.toLocal(), + Units.MINUTE, + ); + + final isNextUserSame = + message.user.id == nextMessage.user?.id; + final isThread = message.replyCount > 0; + final isDeleted = message.isDeleted; + if (timeDiff >= 1 || + !isNextUserSame || + isThread || + isDeleted) { + return SizedBox(height: 8); + } + return SizedBox(height: 2); + }, + itemBuilder: (context, i) { + if (i == messages.length + 2) { + if (widget.parentMessageBuilder != null) { + return widget.parentMessageBuilder( + context, + widget.parentMessage, + ); + } else { + return buildParentMessage(widget.parentMessage); + } + } + if (i == messages.length + 1) { + return _buildLoadingIndicator( + streamChannel, + QueryDirection.top, + ); + } + if (i == 0) { + return _buildLoadingIndicator( streamChannel, QueryDirection.bottom, ); } - }, - onEndOfPage: () async { - _inBetweenList = false; - _topPaginationActive = true; - _bottomPaginationActive = false; - return _paginateData( - streamChannel, - QueryDirection.top, - ); - }, - onInBetweenOfPage: () { - _inBetweenList = true; - }, - child: ScrollablePositionedList.separated( - key: ValueKey(initialIndex + initialAlignment), - itemPositionsListener: _itemPositionListener, - addAutomaticKeepAlives: true, - initialScrollIndex: initialIndex ?? 0, - initialAlignment: initialAlignment ?? 0, - physics: widget.scrollPhysics, - itemScrollController: _scrollController, - reverse: true, - itemCount: - messages.length + 2 + (_isThreadConversation ? 1 : 0), - separatorBuilder: (context, i) { - if (i == messages.length) return Offstage(); - if (i == 0) return SizedBox(height: 30); - if (i == messages.length + 1) { - final replyCount = widget.parentMessage.replyCount; - return Container( - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .lastMessageAt, - ), - ), - ); - } + final message = messages[i - 1]; - final message = messages[i]; - final nextMessage = messages[i - 1]; - if (!Jiffy(message.createdAt.toLocal()).isSame( - nextMessage.createdAt.toLocal(), - Units.DAY, - )) { - final divider = widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( - nextMessage.createdAt.toLocal(), - ) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: divider, - ); - } - final timeDiff = - Jiffy(nextMessage.createdAt.toLocal()).diff( - message.createdAt.toLocal(), - Units.MINUTE, + Widget messageWidget; + + if (i == 1) { + messageWidget = _buildBottomMessage( + context, + message, + messages, + streamChannel, ); - - final isNextUserSame = - message.user.id == nextMessage.user?.id; - final isThread = message.replyCount > 0; - final isDeleted = message.isDeleted; - if (timeDiff >= 1 || - !isNextUserSame || - isThread || - isDeleted) { - return SizedBox(height: 8); - } - return SizedBox(height: 2); - }, - itemBuilder: (context, i) { - if (i == messages.length + 2) { - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( - context, - widget.parentMessage, - ); - } else { - return buildParentMessage(widget.parentMessage); - } - } - if (i == messages.length + 1) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.top, - ); - } - if (i == 0) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.bottom, - ); - } - final message = messages[i - 1]; - - Widget messageWidget; - - if (i == 1) { - messageWidget = _buildBottomMessage( - context, - message, - messages, - streamChannel, - ); - } else if (i == messages.length - 1) { - messageWidget = _buildTopMessage( - context, - message, - messages, - streamChannel, + } else if (i == messages.length - 1) { + messageWidget = _buildTopMessage( + context, + message, + messages, + streamChannel, + ); + } else { + if (widget.messageBuilder != null) { + messageWidget = Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (context) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + messages, + i, + ), + messages), ); } else { - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), - builder: (context) => widget.messageBuilder( - context, - MessageDetails( - context, - message, - messages, - i, - ), - messages), - ); - } else { - messageWidget = buildMessage(message, messages, i); - } + messageWidget = buildMessage(message, messages, i); } - return messageWidget; - }, - ), + } + return messageWidget; + }, ), - ); - }), + ), + ); + }, + ), if (widget.showScrollToBottom) _buildScrollToBottom(), Positioned( top: 20.0, diff --git a/packages/flutter_widgets/lib/src/message_reactions_modal.dart b/packages/flutter_widgets/lib/src/message_reactions_modal.dart index eeb3d58b..e3d4f201 100644 --- a/packages/flutter_widgets/lib/src/message_reactions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_reactions_modal.dart @@ -93,7 +93,7 @@ class MessageReactionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (showReactions && - (message.status == MessageSendingStatus.SENT || + (message.status == MessageSendingStatus.sent || message.status == null)) Align( alignment: Alignment( @@ -141,7 +141,7 @@ class MessageReactionsModal extends StatelessWidget { ), showReactionPickerIndicator: showReactions && (message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.sent || message.status == null), ), ), diff --git a/packages/flutter_widgets/lib/src/message_widget.dart b/packages/flutter_widgets/lib/src/message_widget.dart index 2a15f898..c2cbdc35 100644 --- a/packages/flutter_widgets/lib/src/message_widget.dart +++ b/packages/flutter_widgets/lib/src/message_widget.dart @@ -263,13 +263,13 @@ class _MessageWidgetState extends State { bool get hasQuotedMessage => widget.message?.quotedMessage != null; - bool get isSendFailed => widget.message.status == MessageSendingStatus.FAILED; + bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; bool get isUpdateFailed => - widget.message.status == MessageSendingStatus.FAILED_UPDATE; + widget.message.status == MessageSendingStatus.failed_update; bool get isDeleteFailed => - widget.message.status == MessageSendingStatus.FAILED_DELETE; + widget.message.status == MessageSendingStatus.failed_delete; bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; @@ -875,7 +875,7 @@ class _MessageWidgetState extends State { void onLongPress(BuildContext context) { if (widget.message.isEphemeral || - widget.message.status == MessageSendingStatus.SENDING) { + widget.message.status == MessageSendingStatus.sending) { return; } @@ -986,11 +986,11 @@ class _MessageWidgetState extends State { void retryMessage(BuildContext context) { final channel = StreamChannel.of(context).channel; - if (widget.message.status == MessageSendingStatus.FAILED) { + if (widget.message.status == MessageSendingStatus.failed) { channel.sendMessage(widget.message); return; } - if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) { + if (widget.message.status == MessageSendingStatus.failed_update) { StreamChat.of(context).client.updateMessage( widget.message, channel.cid, @@ -998,7 +998,7 @@ class _MessageWidgetState extends State { return; } - if (widget.message.status == MessageSendingStatus.FAILED_DELETE) { + if (widget.message.status == MessageSendingStatus.failed_delete) { StreamChat.of(context).client.deleteMessage( widget.message, channel.cid, diff --git a/packages/flutter_widgets/lib/src/sending_indicator.dart b/packages/flutter_widgets/lib/src/sending_indicator.dart index 83e02bb3..3fca2fa9 100644 --- a/packages/flutter_widgets/lib/src/sending_indicator.dart +++ b/packages/flutter_widgets/lib/src/sending_indicator.dart @@ -22,14 +22,14 @@ class SendingIndicator extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.accentBlue, ); } - if (message.status == MessageSendingStatus.SENT || message.status == null) { + if (message.status == MessageSendingStatus.sent || message.status == null) { return StreamSvgIcon.check( size: size, color: IconTheme.of(context).color.withOpacity(0.5), ); } - if (message.status == MessageSendingStatus.SENDING || - message.status == MessageSendingStatus.UPDATING) { + if (message.status == MessageSendingStatus.sending || + message.status == MessageSendingStatus.updating) { return Icon( Icons.access_time, size: size, diff --git a/packages/flutter_widgets/lib/src/stream_chat.dart b/packages/flutter_widgets/lib/src/stream_chat.dart index 37a163ec..f093a81a 100644 --- a/packages/flutter_widgets/lib/src/stream_chat.dart +++ b/packages/flutter_widgets/lib/src/stream_chat.dart @@ -4,14 +4,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget used to provide information about the chat to the widget tree /// /// class MyApp extends StatelessWidget { -/// final Client client; +/// final StreamChatClient client; /// /// MyApp(this.client); /// @@ -30,15 +30,25 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { - final Client client; + final StreamChatClient client; final Widget child; final StreamChatThemeData streamChatThemeData; + /// The amount of time that will pass before disconnecting the client in the background + final Duration backgroundKeepAlive; + + /// Handler called whenever the [client] receives a new [Event] while the app + /// is in background. Can be used to display various notifications depending + /// upon the [Event.type] + final EventHandler onBackgroundEventReceived; + StreamChat({ Key key, @required this.client, @required this.child, this.streamChatThemeData, + this.onBackgroundEventReceived, + this.backgroundKeepAlive = const Duration(minutes: 1), }) : super( key: key, ); @@ -63,7 +73,7 @@ class StreamChat extends StatefulWidget { /// The current state of the StreamChat widget class StreamChatState extends State { - Client get client => widget.client; + StreamChatClient get client => widget.client; @override Widget build(BuildContext context) { @@ -82,8 +92,10 @@ class StreamChatState extends State { scaffoldBackgroundColor: streamTheme.colorTheme.white, ), child: StreamChatCore( - child: widget.child, client: client, + child: widget.child, + onBackgroundEventReceived: widget.onBackgroundEventReceived, + backgroundKeepAlive: widget.backgroundKeepAlive, ), ); }, diff --git a/packages/flutter_widgets/lib/stream_chat_flutter.dart b/packages/flutter_widgets/lib/stream_chat_flutter.dart index 3f52c8c6..fa296075 100644 --- a/packages/flutter_widgets/lib/stream_chat_flutter.dart +++ b/packages/flutter_widgets/lib/stream_chat_flutter.dart @@ -40,4 +40,5 @@ export 'src/channel_file_display_screen.dart'; export 'src/channel_media_display_screen.dart'; export 'src/info_tile.dart'; export 'src/stream_chat.dart'; +export 'src/connection_status_builder.dart'; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/flutter_widgets/pubspec.yaml b/packages/flutter_widgets/pubspec.yaml index 9321f992..af2e39f4 100644 --- a/packages/flutter_widgets/pubspec.yaml +++ b/packages/flutter_widgets/pubspec.yaml @@ -58,8 +58,7 @@ flutter: - animations/ dev_dependencies: - flutter_test: sdk: flutter mockito: ^4.1.3 - pedantic: ^1.9.2 + pedantic: ^1.9.2 \ No newline at end of file diff --git a/packages/flutter_widgets/test/src/mocks.dart b/packages/flutter_widgets/test/src/mocks.dart index 25b438d6..0abbf3ca 100644 --- a/packages/flutter_widgets/test/src/mocks.dart +++ b/packages/flutter_widgets/test/src/mocks.dart @@ -1,7 +1,7 @@ import 'package:mockito/mockito.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -class MockClient extends Mock implements Client {} +class MockClient extends Mock implements StreamChatClient {} class MockClientState extends Mock implements ClientState {} diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index ec6ae55e..819c0e5a 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0-rc +## 1.0.0-beta * First release diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md index a62da863..6fe86329 100644 --- a/packages/stream_chat_flutter_core/README.md +++ b/packages/stream_chat_flutter_core/README.md @@ -1,7 +1,7 @@ # Official Flutter SDK Core for [Stream Chat](https://getstream.io/chat/)

- Flutter Chat + Flutter Chat

> The official Flutter core components for Stream Chat, a service for diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 845510f2..9199ed15 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; Future main() async { - /// Create a new instance of [Client] passing the apikey obtained from your + /// Create a new instance of [StreamChatClient] passing the apikey obtained from your /// project dashboard. - final client = Client('b67pax5b2wdq'); + final client = StreamChatClient('b67pax5b2wdq'); /// Set the current user. In a production scenario, this should be done using /// a backend to generate a user token using our server SDK. @@ -43,10 +43,10 @@ class StreamExample extends StatelessWidget { }) : super(key: key); /// Instance of Stream Client. - /// Stream's [Client] can be used to connect to our servers and set the default + /// Stream's [StreamChatClient] can be used to connect to our servers and set the default /// user for the application. Performing these actions trigger a websocket connection /// allowing for real-time updates. - final Client client; + final StreamChatClient client; @override Widget build(BuildContext context) { @@ -287,8 +287,8 @@ class _MessageScreenState extends State { } /// Extensions can be used to add functionality to the SDK. In the examples -/// below, we add two simple extensions to the [Client] and [Channel]. -extension on Client { +/// below, we add two simple extensions to the [StreamChatClient] and [Channel]. +extension on StreamChatClient { /// Fetches the current user id. String get uid => state.user.id; } diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index 85e313fb..1820827f 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -16,7 +16,6 @@ class LazyLoadScrollView extends StatefulWidget { this.onPageScrollStart, this.onPageScrollEnd, this.onInBetweenOfPage, - this.isLoading = false, this.scrollOffset = 100, }) : assert(child != null), super(key: key); @@ -42,9 +41,6 @@ class LazyLoadScrollView extends StatefulWidget { /// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels final double scrollOffset; - /// Used to determine if loading of new data has finished. You should use set this if you aren't using a [FutureBuilder] or [StreamBuilder]. - final bool isLoading; - @override State createState() => _LazyLoadScrollViewState(); } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 6458bebf..9839994f 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -59,7 +59,7 @@ class MessageSearchBlocState extends State Stream get queryMessagesLoading => _queryMessagesLoadingController.stream; - /// Calls [Client.search] updating [messageResponses] stream + /// Calls [StreamChatClient.search] updating [messageResponses] stream Future search({ Map filter, Map messageFilter, @@ -82,7 +82,7 @@ class MessageSearchBlocState extends State } } - /// Calls [Client.search] updating [queryMessagesLoading] stream + /// Calls [StreamChatClient.search] updating [queryMessagesLoading] stream Future loadMore({ Map filter, Map messageFilter, diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index fbdc6d86..8a8d0185 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'typedef.dart'; + /// Widget used to provide information about the chat to the widget tree. /// This Widget is used to react to life cycle changes and system updates. /// When the app goes into the background, the websocket connection is kept alive @@ -13,7 +15,7 @@ import 'package:stream_chat/stream_chat.dart'; /// /// ```dart /// class MyApp extends StatelessWidget { -/// final Client client; +/// final StreamChatClient client; /// /// MyApp(this.client); /// @@ -40,17 +42,27 @@ class StreamChatCore extends StatefulWidget { Key key, @required this.client, @required this.child, + this.onBackgroundEventReceived, + this.backgroundKeepAlive = const Duration(minutes: 1), }) : assert(client != null), assert(child != null), super(key: key); /// Instance of Stream Chat Client containing information about the current /// application. - final Client client; + final StreamChatClient client; /// Widget descendant. final Widget child; + /// The amount of time that will pass before disconnecting the client in the background + final Duration backgroundKeepAlive; + + /// Handler called whenever the [client] receives a new [Event] while the app + /// is in background. Can be used to display various notifications depending + /// upon the [Event.type] + final EventHandler onBackgroundEventReceived; + @override StreamChatCoreState createState() => StreamChatCoreState(); @@ -73,7 +85,7 @@ class StreamChatCore extends StatefulWidget { class StreamChatCoreState extends State with WidgetsBindingObserver { /// Initialized client used throughout the application. - Client get client => widget.client; + StreamChatClient get client => widget.client; Timer _disconnectTimer; @@ -94,56 +106,28 @@ class StreamChatCoreState extends State WidgetsBinding.instance.addObserver(this); } - StreamSubscription _newMessageSubscription; + StreamSubscription _eventSubscription; @override void didChangeAppLifecycleState(AppLifecycleState state) { if (client.state?.user != null) { if (state == AppLifecycleState.paused) { - if (client.showLocalNotification != null) { - _newMessageSubscription = client - .on(EventType.messageNew) - .where((e) => e.user?.id != user.id) - .where((e) => e.message.silent != true) - .where((e) => e.message.shadowed != true) - .listen((event) async { - final channel = client.channel( - event.channelType, - id: event.channelId, - ); - - client.showLocalNotification( - event.message, - ChannelModel( - id: channel.id, - createdAt: channel.createdAt, - extraData: channel.extraData, - type: channel.type, - memberCount: channel.memberCount, - frozen: channel.frozen, - cid: channel.cid, - deletedAt: channel.deletedAt, - config: channel.config, - createdBy: channel.createdBy, - updatedAt: channel.updatedAt, - lastMessageAt: channel.lastMessageAt, - ), - ); - }); - _disconnectTimer = Timer(client.backgroundKeepAlive, () { - client.disconnect(); - }); + if (widget.onBackgroundEventReceived != null) { + _eventSubscription = + client.on().listen(widget.onBackgroundEventReceived); + _disconnectTimer = Timer( + widget.backgroundKeepAlive, + client.disconnect, + ); } else { client.disconnect(); } } else if (state == AppLifecycleState.resumed) { - _newMessageSubscription?.cancel(); + _eventSubscription?.cancel(); if (_disconnectTimer?.isActive == true) { _disconnectTimer.cancel(); } else { - if (client.wsConnectionStatus.value == - ConnectionStatus.disconnected) { - NotificationService.handleIosMessageQueue(client); + if (client.wsConnectionStatus == ConnectionStatus.disconnected) { client.connect(); } } @@ -154,6 +138,7 @@ class StreamChatCoreState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _eventSubscription?.cancel(); _disconnectTimer?.cancel(); super.dispose(); } diff --git a/packages/stream_chat_flutter_core/lib/src/typedef.dart b/packages/stream_chat_flutter_core/lib/src/typedef.dart index 3077574e..29fb2d31 100644 --- a/packages/stream_chat_flutter_core/lib/src/typedef.dart +++ b/packages/stream_chat_flutter_core/lib/src/typedef.dart @@ -1,6 +1,10 @@ import 'package:flutter/widgets.dart'; +import 'package:stream_chat/stream_chat.dart'; /// A signature for a callback which exposes an error and returns a function. /// This Callback can be used in cases where an API failure occurs and the widget /// is unable to render data. typedef ErrorBuilder = Widget Function(BuildContext context, Object error); + +/// A Signature for a handler function which will expose a [event]. +typedef EventHandler = void Function(Event event); diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 623c4424..48d31b45 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -10,4 +10,5 @@ export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; export 'src/user_list_core.dart'; export 'src/users_bloc.dart'; +export 'src/typedef.dart'; export 'package:stream_chat/stream_chat.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 19dd8eb0..cf7fb31d 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 1.0.0-rc +version: 1.0.0-beta repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -10,8 +10,8 @@ environment: flutter: ">=1.17.0" dependencies: - stream_chat: ^0.2.23+3 - rxdart: ^0.24.1 + stream_chat: + path: ../dart_client flutter: sdk: flutter @@ -20,4 +20,5 @@ dev_dependencies: flutter_test: sdk: flutter fake_async: ^1.1.0 + pedantic: ^1.9.2 \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index 37fa5530..c23a4be4 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -1,7 +1,7 @@ import 'package:mockito/mockito.dart'; import 'package:stream_chat/stream_chat.dart'; -class MockClient extends Mock implements Client {} +class MockClient extends Mock implements StreamChatClient {} class MockClientState extends Mock implements ClientState {} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 714375de..a8d7e227 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -9,7 +9,7 @@ import 'package:mockito/mockito.dart'; import 'mocks.dart'; class MockShowLocalNotifications extends Mock { - void call(Message m, ChannelModel cm); + void call(Event event); } void main() { @@ -97,14 +97,8 @@ void main() { ), ); final showLocalNotificationMock = MockShowLocalNotifications().call; - when(client.showLocalNotification) - .thenReturn(showLocalNotificationMock); - when(client.backgroundKeepAlive).thenReturn(Duration( - seconds: 4, - )); final eventStreamController = StreamController(); - when(client.on(EventType.messageNew)) - .thenAnswer((_) => eventStreamController.stream); + when(client.on()).thenAnswer((_) => eventStreamController.stream); when(client.channel('test', id: 'testid')).thenReturn(channel); @@ -113,6 +107,8 @@ void main() { StreamChatCore( key: scKey, client: client, + onBackgroundEventReceived: showLocalNotificationMock, + backgroundKeepAlive: const Duration(seconds: 4), child: Builder( builder: (context) { return Container(); @@ -144,13 +140,8 @@ void main() { ), ); final showLocalNotificationMock = MockShowLocalNotifications().call; - when(client.showLocalNotification).thenReturn(showLocalNotificationMock); - when(client.backgroundKeepAlive).thenReturn(Duration( - seconds: 4, - )); final eventStreamController = StreamController(); - when(client.on(EventType.messageNew)) - .thenAnswer((_) => eventStreamController.stream); + when(client.on()).thenAnswer((_) => eventStreamController.stream); when(client.channel('test', id: 'testid')).thenReturn(channel); @@ -159,6 +150,8 @@ void main() { StreamChatCore( key: scKey, client: client, + onBackgroundEventReceived: showLocalNotificationMock, + backgroundKeepAlive: const Duration(seconds: 4), child: Builder( builder: (context) { return Container(); @@ -178,12 +171,9 @@ void main() { ); eventStreamController.add(event); - await untilCalled(showLocalNotificationMock(any, any)); + await untilCalled(showLocalNotificationMock(event)); - verify(showLocalNotificationMock( - event.message, - any, - )).called(1); + verify(showLocalNotificationMock(event)).called(1); }, ); } diff --git a/packages/stream_chat_persistence/.gitignore b/packages/stream_chat_persistence/.gitignore new file mode 100644 index 00000000..1985397a --- /dev/null +++ b/packages/stream_chat_persistence/.gitignore @@ -0,0 +1,74 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 diff --git a/packages/stream_chat_persistence/.metadata b/packages/stream_chat_persistence/.metadata new file mode 100644 index 00000000..5eb50347 --- /dev/null +++ b/packages/stream_chat_persistence/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 78910062997c3a836feee883712c241a5fd22983 + channel: stable + +project_type: package diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md new file mode 100644 index 00000000..72eec406 --- /dev/null +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0-beta + +* Initial release \ No newline at end of file diff --git a/packages/stream_chat_persistence/LICENSE b/packages/stream_chat_persistence/LICENSE new file mode 100644 index 00000000..49088d47 --- /dev/null +++ b/packages/stream_chat_persistence/LICENSE @@ -0,0 +1,219 @@ +SOURCE CODE LICENSE AGREEMENT + +IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR +ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT. + +THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE +BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE +LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN +INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN +EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE +OF THE SOFTWARE BY CUSTOMER FOR CUSTOMER’S BUSINESS PURPOSES AS DESCRIBED IN +AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO +THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND +CUSTOMER TO THIS AGREEMENT. + +STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING +CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A +COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS +AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE +USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU +REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF +STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE +READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE +BOUND BY ALL THE TERMS OF THIS AGREEMENT. + +IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT, +STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO +NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND +CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE +SOFTWARE. + +1. SOFTWARE. The Stream.io software accompanying this Agreement, may include +Source Code, Executable Object Code, associated media, printed materials and +documentation (collectively, the “Software”). The Software also includes any +updates or upgrades to or new versions of the original Software, if and when +made available to you by Stream.io. “Source Code” means computer programming +code in human readable form that is not suitable for machine execution without +the intervening steps of interpretation or compilation. “Executable Object +Code" means the computer programming code in any other form than Source Code +that is not readily perceivable by humans and suitable for machine execution +without the intervening steps of interpretation or compilation. “Site” means a +Customer location controlled by Customer. “Authorized User” means any employee +or contractor of Customer working at the Site, who has signed a written +confidentiality agreement with Customer or is otherwise bound in writing by +confidentiality and use obligations at least as restrictive as those imposed +under this Agreement. + +2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in +consideration for the representations, warranties, and covenants made by +Customer in this Agreement, Stream.io grants to Customer, during the term of +this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable +license to: + +a. install and use Software Source Code on password protected computers at a Site, +restricted to Authorized Users; + +b. create derivative works, improvements (whether or not patentable), extensions +and other modifications to the Software Source Code (“Modifications”) to build +unique scalable newsfeeds, activity streams, and in-app messaging via Stream’s +application program interface (“API”); + +c. compile the Software Source Code to create Executable Object Code versions of +the Software Source Code and Modifications to build such newsfeeds, activity +streams, and in-app messaging via the API; + +d. install, execute and use such Executable Object Code versions solely for +Customer’s internal business use (including development of websites through +which data generated by Stream services will be streamed (“Apps”)); + +e. use and distribute such Executable Object Code as part of Customer’s Apps; and + +f. make electronic copies of the Software and Modifications as required for backup +or archival purposes. + +3. RESTRICTIONS. Customer is responsible for all activities that occur in +connection with the Software. Customer will not, and will not attempt to: (a) +sublicense or transfer the Software or any Source Code related to the Software +or any of Customer’s rights under this Agreement, except as otherwise provided +in this Agreement, (b) use the Software Source Code for the benefit of a third +party or to operate a service; (c) allow any third party to access or use the +Software Source Code; (d) sublicense or distribute the Software Source Code or +any Modifications in Source Code or other derivative works based on any part of +the Software Source Code; (e) use the Software in any manner that competes with +Stream.io or its business; or (e) otherwise use the Software in any manner that +exceeds the scope of use permitted in this Agreement. Customer shall use the +Software in compliance with any accompanying documentation any laws applicable +to Customer. + +4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or +software components that are open source in conjunction with the Software +Source Code or any Modifications in Source Code or in any way that could +subject the Software to any open source licenses. + +5. CONTRACTORS. Under the rights granted to Customer under this Agreement, +Customer may permit its employees, contractors, and agencies of Customer to +become Authorized Users to exercise the rights to the Software granted to +Customer in accordance with this Agreement solely on behalf of Customer to +provide services to Customer; provided that Customer shall be liable for the +acts and omissions of all Authorized Users to the extent any of such acts or +omissions, if performed by Customer, would constitute a breach of, or otherwise +give rise to liability to Customer under, this Agreement. Customer shall not +and shall not permit any Authorized User to use the Software except as +expressly permitted in this Agreement. + +6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way +to engage in the development of products or services which could be reasonably +construed to provide a complete or partial functional or commercial alternative +to Stream.io’s products or services (a “Competitive Product”). Customer shall +ensure that there is no direct or indirect use of, or sharing of, Software +source code, or other information based upon or derived from the Software to +develop such products or services. Without derogating from the generality of +the foregoing, development of Competitive Products shall include having direct +or indirect access to, supervising, consulting or assisting in the development +of, or producing any specifications, documentation, object code or source code +for, all or part of a Competitive Product. + +7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement, +Modifications may only be created and used by Customer as permitted by this +Agreement and Modification Source Code may not be distributed to third parties. +Customer will not assert against Stream.io, its affiliates, or their customers, +direct or indirect, agents and contractors, in any way, any patent rights that +Customer may obtain relating to any Modifications for Stream.io, its +affiliates’, or their customers’, direct or indirect, agents’ and contractors’ +manufacture, use, import, offer for sale or sale of any Stream.io products or +services. + +8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant +to Stream.io standard download procedures. The Software is deemed accepted upon +delivery. + +9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to +provide any support or consultation concerning the Software. + +10. TERM AND TERMINATION. The term of this Agreement begins when the Software is +downloaded or accessed and shall continue until terminated. Either party may +terminate this Agreement upon written notice. This Agreement shall +automatically terminate if Customer is or becomes a competitor of Stream.io or +makes or sells any Competitive Products. Upon termination of this Agreement for +any reason, (a) all rights granted to Customer in this Agreement immediately +cease to exist, (b) Customer must promptly discontinue all use of the Software +and return to Stream.io or destroy all copies of the Software in Customer’s +possession or control. Any continued use of the Software by Customer or attempt +by Customer to exercise any rights under this Agreement after this Agreement +has terminated shall be considered copyright infringement and subject Customer +to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9 +shall survive expiration or termination of this Agreement for any reason. + +11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual +property rights and proprietary rights relating thereto or embodied therein, +are the exclusive property of Stream.io and its suppliers. Stream.io and its +suppliers reserve all rights in and to the Software not expressly granted to +Customer in this Agreement, and no other licenses or rights are granted by +implication, estoppel or otherwise. + +12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMER’S +OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND +WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY +KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT +LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS, +QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS +ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED +THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS +SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO +MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND +DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW. +CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE +EXPRESS WARRANTIES IN THIS AGREEMENT. + +13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IO’S +TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR +THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE, +SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT, +CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND +WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING +TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON +ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO +THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY. + +14. General. Customer may not assign or transfer this Agreement, by operation of +law or otherwise, or any of its rights under this Agreement (including the +license rights granted to Customer) to any third party without Stream.io’s +prior written consent, which consent will not be unreasonably withheld or +delayed. Stream.io may assign this Agreement, without consent, including, but +limited to, affiliate or any successor to all or substantially all its business +or assets to which this Agreement relates, whether by merger, sale of assets, +sale of stock, reorganization or otherwise. Any attempted assignment or +transfer in violation of the foregoing will be null and void. Stream.io shall +not be liable hereunder by reason of any failure or delay in the performance of +its obligations hereunder for any cause which is beyond the reasonable control. +All notices, consents, and approvals under this Agreement must be delivered in +writing by courier, by electronic mail, or by certified or registered mail, +(postage prepaid and return receipt requested) to the other party at the +address set forth in the customer agreement between Stream.io and Customer and +will be effective upon receipt or when delivery is refused. This Agreement will +be governed by and interpreted in accordance with the laws of the State of +Colorado, without reference to its choice of laws rules. The United Nations +Convention on Contracts for the International Sale of Goods does not apply to +this Agreement. Any action or proceeding arising from or relating to this +Agreement shall be brought in a federal or state court in Denver, Colorado, and +each party irrevocably submits to the jurisdiction and venue of any such court +in any such action or proceeding. All waivers must be in writing. Any waiver or +failure to enforce any provision of this Agreement on one occasion will not be +deemed a waiver of any other provision or of such provision on any other +occasion. If any provision of this Agreement is unenforceable, such provision +will be changed and interpreted to accomplish the objectives of such provision +to the greatest extent possible under applicable law and the remaining +provisions will continue in full force and effect. Customer shall not violate +any applicable law, rule or regulation, including those regarding the export of +technical data. The headings of Sections of this Agreement are for convenience +and are not to be used in interpreting this Agreement. As used in this +Agreement, the word “including” means “including but not limited to.” This +Agreement (including all exhibits and attachments) constitutes the entire +agreement between the parties regarding the subject hereof and supersedes all +prior or contemporaneous agreements, understandings and communication, whether +written or oral. This Agreement may be amended only by a written document +signed by both parties. The terms of any purchase order or similar document +submitted by Customer to Stream.io will have no effect. diff --git a/packages/stream_chat_persistence/README.md b/packages/stream_chat_persistence/README.md new file mode 100644 index 00000000..77b2eea5 --- /dev/null +++ b/packages/stream_chat_persistence/README.md @@ -0,0 +1,71 @@ +# Official Chat Persistence Client for [Stream Chat](https://getstream.io/chat/) + +

+ Flutter Chat +

+ +> The official Chat Persistence Client for Stream Chat, a service for +> building chat applications. + +[![Pub](https://img.shields.io/pub/v/stream_chat_persistence.svg)](https://pub.dartlang.org/packages/stream_chat_persistence) +![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square) +[![Gitter](https://badges.gitter.im/GetStream/stream_chat_persistence.svg)](https://gitter.im/GetStream/stream-chat-flutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +![CI](https://github.com/GetStream/stream-chat-flutter/workflows/CI/badge.svg?branch=master) + + +This package provides a persistence client for fetching and saving chat data locally. +Stream Chat Persistence uses [Moor](https://github.com/simolus3/moor) as a disk cache. + +## Add dependency +Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_persistence.svg)](https://pub.dartlang.org/packages/stream_chat_persistence) +```yaml +dependencies: + stream_chat_persistence: ^latest_version +``` + +You should then run `flutter packages get` + +## Usage +The usage is pretty simple. +1. Create a new instance of StreamChatPersistenceClient providing `logLevel` and `connectionMode`. +```dart +final chatPersistentClient = StreamChatPersistenceClient( + logLevel: Level.INFO, + connectionMode: ConnectionMode.background, +); +``` +2. Pass the instance to the official Stream chat client. +```dart + final client = StreamChatClient( + apiKey ?? kDefaultStreamApiKey, + logLevel: Level.INFO, + )..chatPersistenceClient = chatPersistentClient; +``` + +And you are ready to go... + +## Flutter Web + +Due to Moor web (for offline storage) you need to include the sql.js library: + +```html + + + + + + + + + +``` + +You can grab the latest version of sql-wasm.js and sql-wasm.wasm [here](https://github.com/sql-js/sql.js/releases) and copy them into your `/web` folder. + +## Contributing + +We welcome code changes that improve this library or fix a problem, +please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. +We are pleased to merge your code into the official repository. +Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. +See our license file for more details. diff --git a/packages/stream_chat_persistence/build.yaml b/packages/stream_chat_persistence/build.yaml new file mode 100644 index 00000000..373486d0 --- /dev/null +++ b/packages/stream_chat_persistence/build.yaml @@ -0,0 +1,9 @@ +targets: + $default: + builders: + moor_generator: + options: + generate_connect_constructor: true + data_class_to_companions: false + apply_converters_on_variables: true + generate_values_in_copy_with: true diff --git a/packages/stream_chat_persistence/lib/src/converter/converter.dart b/packages/stream_chat_persistence/lib/src/converter/converter.dart new file mode 100644 index 00000000..a5d7bbdc --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/converter/converter.dart @@ -0,0 +1,3 @@ +export 'list_converter.dart'; +export 'map_converter.dart'; +export 'message_sending_status_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/converter/list_converter.dart b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart new file mode 100644 index 00000000..d14642ce --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/converter/list_converter.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'package:moor/moor.dart'; + +/// Maps a [List] of type [T] into a [String] understood +/// by the sqlite backend. +class ListConverter extends TypeConverter, String> { + @override + List mapToDart(fromDb) { + if (fromDb == null) { + return null; + } + return List.from(jsonDecode(fromDb) ?? []); + } + + @override + String mapToSql(value) { + if (value == null) { + return null; + } + return jsonEncode(value); + } +} diff --git a/packages/stream_chat_persistence/lib/src/converter/map_converter.dart b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart new file mode 100644 index 00000000..6e7a648d --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/converter/map_converter.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'package:moor/moor.dart'; + +/// Maps a [Map] of type [String], [T] into a [String] understood +/// by the sqlite backend. +class MapConverter extends TypeConverter, String> { + @override + Map mapToDart(fromDb) { + if (fromDb == null) { + return null; + } + return Map.from(jsonDecode(fromDb) ?? {}); + } + + @override + String mapToSql(value) { + if (value == null) { + return null; + } + return jsonEncode(value); + } +} diff --git a/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart b/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart new file mode 100644 index 00000000..75b006bb --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart @@ -0,0 +1,51 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; + +/// Maps a [MessageSendingStatus] into a [int] understood +/// by the sqlite backend. +class MessageSendingStatusConverter + extends TypeConverter { + @override + MessageSendingStatus mapToDart(int fromDb) { + switch (fromDb) { + case 0: + return MessageSendingStatus.sending; + case 1: + return MessageSendingStatus.sent; + case 2: + return MessageSendingStatus.failed; + case 3: + return MessageSendingStatus.updating; + case 4: + return MessageSendingStatus.failed_update; + case 5: + return MessageSendingStatus.deleting; + case 6: + return MessageSendingStatus.failed_delete; + default: + return null; + } + } + + @override + int mapToSql(MessageSendingStatus value) { + switch (value) { + case MessageSendingStatus.sending: + return 0; + case MessageSendingStatus.sent: + return 1; + case MessageSendingStatus.failed: + return 2; + case MessageSendingStatus.updating: + return 3; + case MessageSendingStatus.failed_update: + return 4; + case MessageSendingStatus.deleting: + return 5; + case MessageSendingStatus.failed_delete: + return 6; + default: + return null; + } + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart new file mode 100644 index 00000000..57ec95d3 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart @@ -0,0 +1,57 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/channels.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'channel_dao.g.dart'; + +/// The Data Access Object for operations in [Channels] table. +@UseDao(tables: [Channels, Users]) +class ChannelDao extends DatabaseAccessor + with _$ChannelDaoMixin { + /// Creates a new channel dao instance + ChannelDao(MoorChatDatabase db) : super(db); + + /// Get channel by cid + Future getChannelByCid(String cid) async { + return (select(channels)..where((c) => c.cid.equals(cid))).join([ + leftOuterJoin(users, channels.createdById.equalsExp(users.id)), + ]).map((rows) { + final channel = rows.readTable(channels); + final createdBy = rows.readTable(users); + return channel.toChannelModel(createdBy: createdBy?.toUser()); + }).getSingle(); + } + + /// Delete all channels by matching cid in [cids] + /// + /// This will automatically delete the following linked records + /// 1. Channel Reads + /// 2. Channel Members + /// 3. Channel Messages -> Messages Reactions + Future deleteChannelByCids(List cids) async { + return (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); + } + + /// Get the channel cids saved in the storage + Future> get cids { + return (select(channels) + ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) + ..limit(250)) + .map((c) => c.cid) + .get(); + } + + /// Updates all the channels using the new [channelList] data + Future updateChannels(List channelList) { + return batch( + (it) => it.insertAll( + channels, + channelList.map((c) => c.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.g.dart new file mode 100644 index 00000000..a3ac1713 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'channel_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$ChannelDaoMixin on DatabaseAccessor { + $ChannelsTable get channels => attachedDatabase.channels; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart new file mode 100644 index 00000000..1492420c --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; + +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/channel_queries.dart'; +import 'package:stream_chat_persistence/src/entity/channels.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'channel_query_dao.g.dart'; + +/// The Data Access Object for operations in [ChannelQueries] table. +@UseDao(tables: [ChannelQueries, Channels, Users]) +class ChannelQueryDao extends DatabaseAccessor + with _$ChannelQueryDaoMixin { + /// Creates a new channel query dao instance + ChannelQueryDao(this._db) : super(_db); + + final MoorChatDatabase _db; + + String _computeHash(Map filter) { + if (filter == null) { + return 'allchannels'; + } + final hash = base64Encode(utf8.encode('filter: ${jsonEncode(filter)}')); + return hash; + } + + /// Update list of channel queries + /// If [clearQueryCache] is true before the insert + /// the list of matching rows will be deleted + Future updateChannelQueries( + Map filter, + List cids, + bool clearQueryCache, + ) async { + final hash = _computeHash(filter); + if (clearQueryCache) { + await (delete(channelQueries) + ..where((query) => query.queryHash.equals(hash))) + .go(); + } + + return batch((batch) { + batch.insertAll( + channelQueries, + cids.map((cid) { + return ChannelQueryEntity( + queryHash: hash, + channelCid: cid, + ); + }).toList(), + mode: InsertMode.insertOrReplace, + ); + }); + } + + /// Get list of channels by filter, sort and paginationParams + Future> getChannelStates({ + Map filter, + List sort = const [], + PaginationParams paginationParams, + }) async { + final hash = _computeHash(filter); + final cachedChannels = await Future.wait(await (select(channelQueries) + ..where((c) => c.queryHash.equals(hash))) + .get() + .then((channelQueries) { + final cids = channelQueries.map((c) => c.channelCid).toList(); + final query = select(channels)..where((c) => c.cid.isIn(cids)); + + sort = sort + ?.where((s) => ChannelModel.topLevelFields.contains(s.field)) + ?.toList(); + + if (sort != null && sort.isNotEmpty) { + query.orderBy(sort.map((s) { + final orderExpression = CustomExpression('channels.${s.field}'); + return (c) => OrderingTerm( + expression: orderExpression, + mode: s.direction == 1 ? OrderingMode.asc : OrderingMode.desc, + ); + }).toList()); + } + + if (paginationParams != null) { + query.limit( + paginationParams.limit ?? 10, + offset: paginationParams.offset, + ); + } + + return query.join([ + leftOuterJoin(users, channels.createdById.equalsExp(users.id)), + ]).map((row) async { + final userEntity = row.readTable(users); + final channelEntity = row.readTable(channels); + + final cid = channelEntity.cid; + final members = await _db.memberDao.getMembersByCid(cid); + final reads = await _db.readDao.getReadsByCid(cid); + final messages = await _db.messageDao.getMessagesByCid(cid); + + return channelEntity.toChannelState( + createdBy: userEntity?.toUser(), + members: members, + reads: reads, + messages: messages, + ); + }).get(); + })); + + if (sort?.isEmpty != false && cachedChannels?.isNotEmpty == true) { + cachedChannels + .sort((a, b) => b.channel.updatedAt.compareTo(a.channel.updatedAt)); + cachedChannels.sort((a, b) { + final dateA = a.channel.lastMessageAt ?? a.channel.createdAt; + final dateB = b.channel.lastMessageAt ?? b.channel.createdAt; + return dateB.compareTo(dateA); + }); + } + + return cachedChannels; + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.g.dart new file mode 100644 index 00000000..5879d8bb --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'channel_query_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$ChannelQueryDaoMixin on DatabaseAccessor { + $ChannelQueriesTable get channelQueries => attachedDatabase.channelQueries; + $ChannelsTable get channels => attachedDatabase.channels; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart new file mode 100644 index 00000000..8d2033b2 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -0,0 +1,54 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/connection_events.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'connection_event_dao.g.dart'; + +/// The Data Access Object for operations in [ConnectionEvents] table. +@UseDao(tables: [ConnectionEvents, Users]) +class ConnectionEventDao extends DatabaseAccessor + with _$ConnectionEventDaoMixin { + /// Creates a new connection event dao instance + ConnectionEventDao(MoorChatDatabase db) : super(db); + + /// Get the latest stored connection event + Future get connectionEvent { + return select(connectionEvents).map((eventEntity) { + return eventEntity.toEvent(); + }).getSingle(); + } + + /// Get the latest stored lastSyncAt + Future get lastSyncAt { + return select(connectionEvents).getSingle().then((r) => r?.lastSyncAt); + } + + /// Update stored connection event with latest data + Future updateConnectionEvent(Event event) async { + final connectionInfo = await select(connectionEvents).getSingle(); + return into(connectionEvents).insert( + ConnectionEventEntity( + id: 1, + lastSyncAt: connectionInfo?.lastSyncAt, + lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt, + totalUnreadCount: + event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, + ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, + unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels, + ), + mode: InsertMode.insertOrReplace, + ); + } + + /// Update stored lastSyncAt with latest data + Future updateLastSyncAt(DateTime lastSyncAt) async { + return (update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write( + ConnectionEventsCompanion( + lastSyncAt: Value(lastSyncAt), + ), + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart new file mode 100644 index 00000000..a8245982 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'connection_event_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$ConnectionEventDaoMixin on DatabaseAccessor { + $ConnectionEventsTable get connectionEvents => + attachedDatabase.connectionEvents; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/dao.dart b/packages/stream_chat_persistence/lib/src/dao/dao.dart new file mode 100644 index 00000000..6f1e8221 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/dao.dart @@ -0,0 +1,8 @@ +export 'user_dao.dart'; +export 'channel_dao.dart'; +export 'message_dao.dart'; +export 'member_dao.dart'; +export 'connection_event_dao.dart'; +export 'reaction_dao.dart'; +export 'read_dao.dart'; +export 'channel_query_dao.dart'; diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart new file mode 100644 index 00000000..ed30a931 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -0,0 +1,53 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +import 'package:stream_chat_persistence/src/entity/members.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; + +import '../mapper/mapper.dart'; + +part 'member_dao.g.dart'; + +/// The Data Access Object for operations in [Members] table. +@UseDao(tables: [Members, Users]) +class MemberDao extends DatabaseAccessor + with _$MemberDaoMixin { + /// Creates a new member dao instance + MemberDao(MoorChatDatabase db) : super(db); + + /// Get all members where [Members.channelCid] matches [cid] + Future> getMembersByCid(String cid) async { + return (select(members).join([ + leftOuterJoin(users, members.userId.equalsExp(users.id)), + ]) + ..where(members.channelCid.equals(cid)) + ..orderBy([OrderingTerm.asc(members.createdAt)])) + .map((row) { + final userEntity = row.readTable(users); + final memberEntity = row.readTable(members); + return memberEntity.toMember(user: userEntity?.toUser()); + }).get(); + } + + /// Updates all the members using the new [memberList] data + Future updateMembers(String cid, List memberList) async { + return batch( + (it) => it.insertAll( + members, + memberList.map((m) => m.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + + /// Deletes all the members whose [Members.channelCid] is present in [cids] + Future deleteMemberByCids(List cids) async { + return batch((it) { + it.deleteWhere( + members, + (m) => m.channelCid.isIn(cids), + ); + }); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.g.dart new file mode 100644 index 00000000..dbda1351 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'member_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$MemberDaoMixin on DatabaseAccessor { + $MembersTable get members => attachedDatabase.members; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart new file mode 100644 index 00000000..97122232 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -0,0 +1,148 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/messages.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'message_dao.g.dart'; + +/// The Data Access Object for operations in [Messages] table. +@UseDao(tables: [Messages, Users]) +class MessageDao extends DatabaseAccessor + with _$MessageDaoMixin { + /// Creates a new message dao instance + MessageDao(this._db) : super(_db); + + final MoorChatDatabase _db; + + /// Removes all the messages by matching [Messages.id] in [messageIds] + /// + /// This will automatically delete the following linked records + /// 1. Message Reactions + Future deleteMessageByIds(List messageIds) { + return (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go(); + } + + /// Removes all the messages by matching [Messages.channelCid] in [cids] + /// + /// This will automatically delete the following linked records + /// 1. Message Reactions + Future deleteMessageByCids(List cids) async { + return (delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go(); + } + + Future _messageFromJoinRow(TypedResult rows) async { + final userEntity = rows.readTable(users); + final msgEntity = rows.readTable(messages); + final latestReactions = await _db.reactionDao.getReactions(msgEntity.id); + final ownReactions = await _db.reactionDao.getReactionsByUserId( + msgEntity.id, + _db.userId, + ); + Message quotedMessage; + if (msgEntity.quotedMessageId != null) { + quotedMessage = await getMessageById(msgEntity.quotedMessageId); + } + return msgEntity.toMessage( + user: userEntity?.toUser(), + latestReactions: latestReactions, + ownReactions: ownReactions, + quotedMessage: quotedMessage, + ); + } + + /// Returns a single message by matching the [Messages.id] with [id] + Future getMessageById(String id) async { + return await (select(messages).join([ + leftOuterJoin(users, messages.userId.equalsExp(users.id)), + ]) + ..where(messages.id.equals(id))) + .map(_messageFromJoinRow) + .getSingle(); + } + + /// Returns all the messages of a particular thread by matching + /// [Messages.channelCid] with [cid] + Future> getThreadMessages(String cid) async { + return Future.wait(await (select(messages).join([ + leftOuterJoin(users, messages.userId.equalsExp(users.id)), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(isNotNull(messages.parentId)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get()); + } + + /// Returns all the messages of a particular thread by matching + /// [Messages.parentId] with [parentId] + Future> getThreadMessagesByParentId( + String parentId, { + PaginationParams options, + }) async { + final msgList = await Future.wait(await (select(messages).join([ + innerJoin(users, messages.userId.equalsExp(users.id)), + ]) + ..where(messages.parentId.equals(parentId)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get()); + + if (options?.lessThan != null) { + final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan); + msgList.removeRange(lessThanIndex, msgList.length); + } + return msgList; + } + + /// Returns all the messages of a channel by matching + /// [Messages.channelCid] with [parentId] + Future> getMessagesByCid( + String cid, { + PaginationParams messagePagination, + }) async { + final msgList = await Future.wait(await (select(messages).join([ + leftOuterJoin(users, messages.userId.equalsExp(users.id)), + ]) + ..where(messages.channelCid.equals(cid)) + ..where( + isNull(messages.parentId) | messages.showInChannel.equals(true)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get()); + + if (messagePagination?.lessThan != null) { + final lessThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.lessThan, + ); + if (lessThanIndex != -1) { + msgList.removeRange(lessThanIndex, msgList.length); + } + } + if (messagePagination?.greaterThanOrEqual != null) { + final greaterThanIndex = msgList.indexWhere( + (m) => m.id == messagePagination.greaterThanOrEqual, + ); + if (greaterThanIndex != -1) { + msgList.removeRange(0, greaterThanIndex); + } + } + if (messagePagination?.limit != null) { + return msgList.take(messagePagination.limit).toList(); + } + return msgList; + } + + /// Updates the message data of a particular channel with + /// the new [messageList] data + Future updateMessages(String cid, List messageList) { + return batch((batch) { + batch.insertAll( + messages, + messageList.map((it) => it.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ); + }); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/message_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/message_dao.g.dart new file mode 100644 index 00000000..e27a5d42 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'message_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$MessageDaoMixin on DatabaseAccessor { + $MessagesTable get messages => attachedDatabase.messages; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart new file mode 100644 index 00000000..99053fca --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart @@ -0,0 +1,64 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/reactions.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'reaction_dao.g.dart'; + +/// The Data Access Object for operations in [Reactions] table. +@UseDao(tables: [Reactions, Users]) +class ReactionDao extends DatabaseAccessor + with _$ReactionDaoMixin { + /// Creates a new reaction dao instance + ReactionDao(MoorChatDatabase db) : super(db); + + /// Returns all the reactions of a particular message by matching + /// [Reactions.messageId] with [messageId] + Future> getReactions(String messageId) { + return (select(reactions).join([ + leftOuterJoin(users, reactions.userId.equalsExp(users.id)), + ]) + ..where(reactions.messageId.equals(messageId)) + ..orderBy([OrderingTerm.asc(reactions.createdAt)])) + .map((rows) { + final userEntity = rows.readTable(users); + final reactionEntity = rows.readTable(reactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }).get(); + } + + /// Returns all the reactions of a particular message + /// added by a particular user by matching + /// [Reactions.messageId] with [messageId] and + /// [Reactions.userId] with [userId] + Future> getReactionsByUserId( + String messageId, + String userId, + ) async { + final reactions = await getReactions(messageId); + return reactions.where((it) => it.userId == userId).toList(); + } + + /// Updates the reactions data with the new [reactionList] data + Future updateReactions(List reactionList) { + return batch((it) { + it.insertAll( + reactions, + reactionList.map((r) => r.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ); + }); + } + + /// Deletes all the reactions whose [Reactions.messageId] is present in [messageIds] + Future deleteReactionsByMessageIds(List messageIds) { + return batch((it) { + it.deleteWhere( + reactions, + (r) => r.messageId.isIn(messageIds), + ); + }); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.g.dart new file mode 100644 index 00000000..b95e2126 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reaction_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$ReactionDaoMixin on DatabaseAccessor { + $ReactionsTable get reactions => attachedDatabase.reactions; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart new file mode 100644 index 00000000..c78cab07 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart @@ -0,0 +1,43 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/reads.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/mapper.dart'; + +part 'read_dao.g.dart'; + +/// The Data Access Object for operations in [Reads] table. +@UseDao(tables: [Reads, Users]) +class ReadDao extends DatabaseAccessor with _$ReadDaoMixin { + /// Creates a new read dao instance + ReadDao(MoorChatDatabase db) : super(db); + + /// Get all reads where [Reads.channelCid] matches [cid] + Future> getReadsByCid(String cid) async { + return (select(reads).join([ + leftOuterJoin(users, reads.userId.equalsExp(users.id)), + ]) + ..where(reads.channelCid.equals(cid)) + ..orderBy([ + OrderingTerm.asc(reads.lastRead), + ])) + .map((row) { + final userEntity = row.readTable(users); + final readEntity = row.readTable(reads); + return readEntity.toRead(user: userEntity?.toUser()); + }).get(); + } + + /// Updates the read data of a particular channel with + /// the new [readList] data + Future updateReads(String cid, List readList) { + return batch( + (it) => it.insertAll( + reads, + readList.map((r) => r.toEntity(cid: cid)).toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.g.dart new file mode 100644 index 00000000..b9ac9228 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'read_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$ReadDaoMixin on DatabaseAccessor { + $ReadsTable get reads => attachedDatabase.reads; + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart new file mode 100644 index 00000000..795c7968 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart @@ -0,0 +1,25 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/entity/users.dart'; +import '../mapper/user_mapper.dart'; + +part 'user_dao.g.dart'; + +/// The Data Access Object for operations in [Users] table. +@UseDao(tables: [Users]) +class UserDao extends DatabaseAccessor with _$UserDaoMixin { + /// Creates a new user dao instance + UserDao(MoorChatDatabase db) : super(db); + + /// Updates the users data with the new [userList] data + Future updateUsers(List userList) { + return batch( + (it) => it.insertAll( + users, + userList.map((u) => u.toEntity()).toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.g.dart new file mode 100644 index 00000000..edd9a33b --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.g.dart @@ -0,0 +1,11 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_dao.dart'; + +// ************************************************************************** +// DaoGenerator +// ************************************************************************** + +mixin _$UserDaoMixin on DatabaseAccessor { + $UsersTable get users => attachedDatabase.users; +} diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart new file mode 100644 index 00000000..4230f278 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.dart @@ -0,0 +1,90 @@ +import 'package:moor/isolate.dart'; +import 'package:moor/moor.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import '../entity/entity.dart'; +import '../dao/dao.dart'; +import '../converter/converter.dart'; +import 'shared/shared_db.dart'; + +part 'moor_chat_database.g.dart'; + +LazyDatabase _openConnection( + String userId, { + logStatements = false, +}) { + return LazyDatabase(() async { + return await SharedDB.constructDatabase( + userId, + logStatements: logStatements, + ); + }); +} + +/// A chat database implemented using moor +@UseMoor(tables: [ + Channels, + Messages, + Reactions, + Users, + Members, + Reads, + ChannelQueries, + ConnectionEvents, +], daos: [ + UserDao, + ChannelDao, + MessageDao, + MemberDao, + ReactionDao, + ReadDao, + ChannelQueryDao, + ConnectionEventDao, +]) +class MoorChatDatabase extends _$MoorChatDatabase { + /// Creates a new moor chat database instance + MoorChatDatabase( + this._userId, { + logStatements = false, + }) : super(_openConnection( + _userId, + logStatements: logStatements, + )); + + /// Instantiate a new database instance + MoorChatDatabase.connect( + this._userId, + this._isolate, + DatabaseConnection connection, + ) : super.connect(connection); + + final String _userId; + + /// User id to which the database is connected + String get userId => _userId; + + MoorIsolate _isolate; + + // you should bump this number whenever you change or add a table definition. + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration => MigrationStrategy( + onUpgrade: (openingDetails, before, after) async { + if (before != after) { + final m = createMigrator(); + for (final table in allTables) { + await m.deleteTable(table.actualTableName); + await m.createTable(table); + } + } + }, + ); + + /// Closes the database instance + Future disconnect() async { + await _isolate?.shutdownAll(); + await close(); + } +} diff --git a/packages/dart_client/lib/src/db/offline_storage.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart similarity index 78% rename from packages/dart_client/lib/src/db/offline_storage.g.dart rename to packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index a87ecb92..0ff6e9f5 100644 --- a/packages/dart_client/lib/src/db/offline_storage.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -1,426 +1,26 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'offline_storage.dart'; +part of 'moor_chat_database.dart'; // ************************************************************************** // MoorGenerator // ************************************************************************** // ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this -class _ConnectionEventData extends DataClass - implements Insertable<_ConnectionEventData> { - final int id; - final Map ownUser; - final int totalUnreadCount; - final int unreadChannels; - final DateTime lastEventAt; - final DateTime lastSyncAt; - _ConnectionEventData( - {@required this.id, - this.ownUser, - this.totalUnreadCount, - this.unreadChannels, - this.lastEventAt, - this.lastSyncAt}); - factory _ConnectionEventData.fromData( - Map data, GeneratedDatabase db, - {String prefix}) { - final effectivePrefix = prefix ?? ''; - final intType = db.typeSystem.forDartType(); - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - return _ConnectionEventData( - id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - ownUser: $_ConnectionEventTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}own_user'])), - totalUnreadCount: intType.mapFromDatabaseResponse( - data['${effectivePrefix}total_unread_count']), - unreadChannels: intType - .mapFromDatabaseResponse(data['${effectivePrefix}unread_channels']), - lastEventAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}last_event_at']), - lastSyncAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}last_sync_at']), - ); - } - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || ownUser != null) { - final converter = $_ConnectionEventTable.$converter0; - map['own_user'] = Variable(converter.mapToSql(ownUser)); - } - if (!nullToAbsent || totalUnreadCount != null) { - map['total_unread_count'] = Variable(totalUnreadCount); - } - if (!nullToAbsent || unreadChannels != null) { - map['unread_channels'] = Variable(unreadChannels); - } - if (!nullToAbsent || lastEventAt != null) { - map['last_event_at'] = Variable(lastEventAt); - } - if (!nullToAbsent || lastSyncAt != null) { - map['last_sync_at'] = Variable(lastSyncAt); - } - return map; - } - - _ConnectionEventCompanion toCompanion(bool nullToAbsent) { - return _ConnectionEventCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - ownUser: ownUser == null && nullToAbsent - ? const Value.absent() - : Value(ownUser), - totalUnreadCount: totalUnreadCount == null && nullToAbsent - ? const Value.absent() - : Value(totalUnreadCount), - unreadChannels: unreadChannels == null && nullToAbsent - ? const Value.absent() - : Value(unreadChannels), - lastEventAt: lastEventAt == null && nullToAbsent - ? const Value.absent() - : Value(lastEventAt), - lastSyncAt: lastSyncAt == null && nullToAbsent - ? const Value.absent() - : Value(lastSyncAt), - ); - } - - factory _ConnectionEventData.fromJson(Map json, - {ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return _ConnectionEventData( - id: serializer.fromJson(json['id']), - ownUser: serializer.fromJson>(json['ownUser']), - totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), - unreadChannels: serializer.fromJson(json['unreadChannels']), - lastEventAt: serializer.fromJson(json['lastEventAt']), - lastSyncAt: serializer.fromJson(json['lastSyncAt']), - ); - } - @override - Map toJson({ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'ownUser': serializer.toJson>(ownUser), - 'totalUnreadCount': serializer.toJson(totalUnreadCount), - 'unreadChannels': serializer.toJson(unreadChannels), - 'lastEventAt': serializer.toJson(lastEventAt), - 'lastSyncAt': serializer.toJson(lastSyncAt), - }; - } - - _ConnectionEventData copyWith( - {int id, - Map ownUser, - int totalUnreadCount, - int unreadChannels, - DateTime lastEventAt, - DateTime lastSyncAt}) => - _ConnectionEventData( - id: id ?? this.id, - ownUser: ownUser ?? this.ownUser, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - lastEventAt: lastEventAt ?? this.lastEventAt, - lastSyncAt: lastSyncAt ?? this.lastSyncAt, - ); - @override - String toString() { - return (StringBuffer('_ConnectionEventData(') - ..write('id: $id, ') - ..write('ownUser: $ownUser, ') - ..write('totalUnreadCount: $totalUnreadCount, ') - ..write('unreadChannels: $unreadChannels, ') - ..write('lastEventAt: $lastEventAt, ') - ..write('lastSyncAt: $lastSyncAt') - ..write(')')) - .toString(); - } - - @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - ownUser.hashCode, - $mrjc( - totalUnreadCount.hashCode, - $mrjc(unreadChannels.hashCode, - $mrjc(lastEventAt.hashCode, lastSyncAt.hashCode)))))); - @override - bool operator ==(dynamic other) => - identical(this, other) || - (other is _ConnectionEventData && - other.id == this.id && - other.ownUser == this.ownUser && - other.totalUnreadCount == this.totalUnreadCount && - other.unreadChannels == this.unreadChannels && - other.lastEventAt == this.lastEventAt && - other.lastSyncAt == this.lastSyncAt); -} - -class _ConnectionEventCompanion extends UpdateCompanion<_ConnectionEventData> { - final Value id; - final Value> ownUser; - final Value totalUnreadCount; - final Value unreadChannels; - final Value lastEventAt; - final Value lastSyncAt; - const _ConnectionEventCompanion({ - this.id = const Value.absent(), - this.ownUser = const Value.absent(), - this.totalUnreadCount = const Value.absent(), - this.unreadChannels = const Value.absent(), - this.lastEventAt = const Value.absent(), - this.lastSyncAt = const Value.absent(), - }); - _ConnectionEventCompanion.insert({ - this.id = const Value.absent(), - this.ownUser = const Value.absent(), - this.totalUnreadCount = const Value.absent(), - this.unreadChannels = const Value.absent(), - this.lastEventAt = const Value.absent(), - this.lastSyncAt = const Value.absent(), - }); - static Insertable<_ConnectionEventData> custom({ - Expression id, - Expression ownUser, - Expression totalUnreadCount, - Expression unreadChannels, - Expression lastEventAt, - Expression lastSyncAt, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (ownUser != null) 'own_user': ownUser, - if (totalUnreadCount != null) 'total_unread_count': totalUnreadCount, - if (unreadChannels != null) 'unread_channels': unreadChannels, - if (lastEventAt != null) 'last_event_at': lastEventAt, - if (lastSyncAt != null) 'last_sync_at': lastSyncAt, - }); - } - - _ConnectionEventCompanion copyWith( - {Value id, - Value> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt}) { - return _ConnectionEventCompanion( - id: id ?? this.id, - ownUser: ownUser ?? this.ownUser, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - lastEventAt: lastEventAt ?? this.lastEventAt, - lastSyncAt: lastSyncAt ?? this.lastSyncAt, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (ownUser.present) { - final converter = $_ConnectionEventTable.$converter0; - map['own_user'] = Variable(converter.mapToSql(ownUser.value)); - } - if (totalUnreadCount.present) { - map['total_unread_count'] = Variable(totalUnreadCount.value); - } - if (unreadChannels.present) { - map['unread_channels'] = Variable(unreadChannels.value); - } - if (lastEventAt.present) { - map['last_event_at'] = Variable(lastEventAt.value); - } - if (lastSyncAt.present) { - map['last_sync_at'] = Variable(lastSyncAt.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('_ConnectionEventCompanion(') - ..write('id: $id, ') - ..write('ownUser: $ownUser, ') - ..write('totalUnreadCount: $totalUnreadCount, ') - ..write('unreadChannels: $unreadChannels, ') - ..write('lastEventAt: $lastEventAt, ') - ..write('lastSyncAt: $lastSyncAt') - ..write(')')) - .toString(); - } -} - -class $_ConnectionEventTable extends _ConnectionEvent - with TableInfo<$_ConnectionEventTable, _ConnectionEventData> { - final GeneratedDatabase _db; - final String _alias; - $_ConnectionEventTable(this._db, [this._alias]); - final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedIntColumn _id; - @override - GeneratedIntColumn get id => _id ??= _constructId(); - GeneratedIntColumn _constructId() { - return GeneratedIntColumn( - 'id', - $tableName, - false, - ); - } - - final VerificationMeta _ownUserMeta = const VerificationMeta('ownUser'); - GeneratedTextColumn _ownUser; - @override - GeneratedTextColumn get ownUser => _ownUser ??= _constructOwnUser(); - GeneratedTextColumn _constructOwnUser() { - return GeneratedTextColumn( - 'own_user', - $tableName, - true, - ); - } - - final VerificationMeta _totalUnreadCountMeta = - const VerificationMeta('totalUnreadCount'); - GeneratedIntColumn _totalUnreadCount; - @override - GeneratedIntColumn get totalUnreadCount => - _totalUnreadCount ??= _constructTotalUnreadCount(); - GeneratedIntColumn _constructTotalUnreadCount() { - return GeneratedIntColumn( - 'total_unread_count', - $tableName, - true, - ); - } - - final VerificationMeta _unreadChannelsMeta = - const VerificationMeta('unreadChannels'); - GeneratedIntColumn _unreadChannels; - @override - GeneratedIntColumn get unreadChannels => - _unreadChannels ??= _constructUnreadChannels(); - GeneratedIntColumn _constructUnreadChannels() { - return GeneratedIntColumn( - 'unread_channels', - $tableName, - true, - ); - } - - final VerificationMeta _lastEventAtMeta = - const VerificationMeta('lastEventAt'); - GeneratedDateTimeColumn _lastEventAt; - @override - GeneratedDateTimeColumn get lastEventAt => - _lastEventAt ??= _constructLastEventAt(); - GeneratedDateTimeColumn _constructLastEventAt() { - return GeneratedDateTimeColumn( - 'last_event_at', - $tableName, - true, - ); - } - - final VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); - GeneratedDateTimeColumn _lastSyncAt; - @override - GeneratedDateTimeColumn get lastSyncAt => - _lastSyncAt ??= _constructLastSyncAt(); - GeneratedDateTimeColumn _constructLastSyncAt() { - return GeneratedDateTimeColumn( - 'last_sync_at', - $tableName, - true, - ); - } - - @override - List get $columns => - [id, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt]; - @override - $_ConnectionEventTable get asDslTable => this; - @override - String get $tableName => _alias ?? 'connection_event'; - @override - final String actualTableName = 'connection_event'; - @override - VerificationContext validateIntegrity( - Insertable<_ConnectionEventData> instance, - {bool isInserting = false}) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); - } - context.handle(_ownUserMeta, const VerificationResult.success()); - if (data.containsKey('total_unread_count')) { - context.handle( - _totalUnreadCountMeta, - totalUnreadCount.isAcceptableOrUnknown( - data['total_unread_count'], _totalUnreadCountMeta)); - } - if (data.containsKey('unread_channels')) { - context.handle( - _unreadChannelsMeta, - unreadChannels.isAcceptableOrUnknown( - data['unread_channels'], _unreadChannelsMeta)); - } - if (data.containsKey('last_event_at')) { - context.handle( - _lastEventAtMeta, - lastEventAt.isAcceptableOrUnknown( - data['last_event_at'], _lastEventAtMeta)); - } - if (data.containsKey('last_sync_at')) { - context.handle( - _lastSyncAtMeta, - lastSyncAt.isAcceptableOrUnknown( - data['last_sync_at'], _lastSyncAtMeta)); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - _ConnectionEventData map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _ConnectionEventData.fromData(data, _db, prefix: effectivePrefix); - } - - @override - $_ConnectionEventTable createAlias(String alias) { - return $_ConnectionEventTable(_db, alias); - } - - static TypeConverter, String> $converter0 = - _ExtraDataConverter(); -} - -class _Channel extends DataClass implements Insertable<_Channel> { +class ChannelEntity extends DataClass implements Insertable { final String id; final String type; final String cid; - final String config; + final Map config; final bool frozen; final DateTime lastMessageAt; final DateTime createdAt; final DateTime updatedAt; final DateTime deletedAt; final int memberCount; - final Map extraData; - final String createdBy; - _Channel( + final String createdById; + final Map extraData; + ChannelEntity( {@required this.id, @required this.type, @required this.cid, @@ -431,21 +31,22 @@ class _Channel extends DataClass implements Insertable<_Channel> { this.updatedAt, this.deletedAt, this.memberCount, - this.extraData, - this.createdBy}); - factory _Channel.fromData(Map data, GeneratedDatabase db, + this.createdById, + this.extraData}); + factory ChannelEntity.fromData( + Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); - return _Channel( + return ChannelEntity( id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), cid: stringType.mapFromDatabaseResponse(data['${effectivePrefix}cid']), - config: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}config']), + config: $ChannelsTable.$converter0.mapToDart( + stringType.mapFromDatabaseResponse(data['${effectivePrefix}config'])), frozen: boolType.mapFromDatabaseResponse(data['${effectivePrefix}frozen']), lastMessageAt: dateTimeType @@ -458,10 +59,10 @@ class _Channel extends DataClass implements Insertable<_Channel> { .mapFromDatabaseResponse(data['${effectivePrefix}deleted_at']), memberCount: intType .mapFromDatabaseResponse(data['${effectivePrefix}member_count']), - extraData: $_ChannelsTable.$converter0.mapToDart(stringType + createdById: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}created_by_id']), + extraData: $ChannelsTable.$converter1.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), - createdBy: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}created_by']), ); } @override @@ -477,7 +78,8 @@ class _Channel extends DataClass implements Insertable<_Channel> { map['cid'] = Variable(cid); } if (!nullToAbsent || config != null) { - map['config'] = Variable(config); + final converter = $ChannelsTable.$converter0; + map['config'] = Variable(converter.mapToSql(config)); } if (!nullToAbsent || frozen != null) { map['frozen'] = Variable(frozen); @@ -497,65 +99,32 @@ class _Channel extends DataClass implements Insertable<_Channel> { if (!nullToAbsent || memberCount != null) { map['member_count'] = Variable(memberCount); } - if (!nullToAbsent || extraData != null) { - final converter = $_ChannelsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + if (!nullToAbsent || createdById != null) { + map['created_by_id'] = Variable(createdById); } - if (!nullToAbsent || createdBy != null) { - map['created_by'] = Variable(createdBy); + if (!nullToAbsent || extraData != null) { + final converter = $ChannelsTable.$converter1; + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } - _ChannelsCompanion toCompanion(bool nullToAbsent) { - return _ChannelsCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - type: type == null && nullToAbsent ? const Value.absent() : Value(type), - cid: cid == null && nullToAbsent ? const Value.absent() : Value(cid), - config: - config == null && nullToAbsent ? const Value.absent() : Value(config), - frozen: - frozen == null && nullToAbsent ? const Value.absent() : Value(frozen), - lastMessageAt: lastMessageAt == null && nullToAbsent - ? const Value.absent() - : Value(lastMessageAt), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), - updatedAt: updatedAt == null && nullToAbsent - ? const Value.absent() - : Value(updatedAt), - deletedAt: deletedAt == null && nullToAbsent - ? const Value.absent() - : Value(deletedAt), - memberCount: memberCount == null && nullToAbsent - ? const Value.absent() - : Value(memberCount), - extraData: extraData == null && nullToAbsent - ? const Value.absent() - : Value(extraData), - createdBy: createdBy == null && nullToAbsent - ? const Value.absent() - : Value(createdBy), - ); - } - - factory _Channel.fromJson(Map json, + factory ChannelEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return _Channel( + return ChannelEntity( id: serializer.fromJson(json['id']), type: serializer.fromJson(json['type']), cid: serializer.fromJson(json['cid']), - config: serializer.fromJson(json['config']), + config: serializer.fromJson>(json['config']), frozen: serializer.fromJson(json['frozen']), lastMessageAt: serializer.fromJson(json['lastMessageAt']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), deletedAt: serializer.fromJson(json['deletedAt']), memberCount: serializer.fromJson(json['memberCount']), - extraData: serializer.fromJson>(json['extraData']), - createdBy: serializer.fromJson(json['createdBy']), + createdById: serializer.fromJson(json['createdById']), + extraData: serializer.fromJson>(json['extraData']), ); } @override @@ -565,48 +134,49 @@ class _Channel extends DataClass implements Insertable<_Channel> { 'id': serializer.toJson(id), 'type': serializer.toJson(type), 'cid': serializer.toJson(cid), - 'config': serializer.toJson(config), + 'config': serializer.toJson>(config), 'frozen': serializer.toJson(frozen), 'lastMessageAt': serializer.toJson(lastMessageAt), 'createdAt': serializer.toJson(createdAt), 'updatedAt': serializer.toJson(updatedAt), 'deletedAt': serializer.toJson(deletedAt), 'memberCount': serializer.toJson(memberCount), - 'extraData': serializer.toJson>(extraData), - 'createdBy': serializer.toJson(createdBy), + 'createdById': serializer.toJson(createdById), + 'extraData': serializer.toJson>(extraData), }; } - _Channel copyWith( + ChannelEntity copyWith( {String id, String type, String cid, - String config, + Map config, bool frozen, - DateTime lastMessageAt, - DateTime createdAt, - DateTime updatedAt, - DateTime deletedAt, - int memberCount, - Map extraData, - String createdBy}) => - _Channel( + Value lastMessageAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value memberCount = const Value.absent(), + Value createdById = const Value.absent(), + Value> extraData = const Value.absent()}) => + ChannelEntity( id: id ?? this.id, type: type ?? this.type, cid: cid ?? this.cid, config: config ?? this.config, frozen: frozen ?? this.frozen, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - memberCount: memberCount ?? this.memberCount, - extraData: extraData ?? this.extraData, - createdBy: createdBy ?? this.createdBy, + lastMessageAt: + lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + memberCount: memberCount.present ? memberCount.value : this.memberCount, + createdById: createdById.present ? createdById.value : this.createdById, + extraData: extraData.present ? extraData.value : this.extraData, ); @override String toString() { - return (StringBuffer('_Channel(') + return (StringBuffer('ChannelEntity(') ..write('id: $id, ') ..write('type: $type, ') ..write('cid: $cid, ') @@ -617,8 +187,8 @@ class _Channel extends DataClass implements Insertable<_Channel> { ..write('updatedAt: $updatedAt, ') ..write('deletedAt: $deletedAt, ') ..write('memberCount: $memberCount, ') - ..write('extraData: $extraData, ') - ..write('createdBy: $createdBy') + ..write('createdById: $createdById, ') + ..write('extraData: $extraData') ..write(')')) .toString(); } @@ -644,12 +214,12 @@ class _Channel extends DataClass implements Insertable<_Channel> { deletedAt.hashCode, $mrjc( memberCount.hashCode, - $mrjc(extraData.hashCode, - createdBy.hashCode)))))))))))); + $mrjc(createdById.hashCode, + extraData.hashCode)))))))))))); @override bool operator ==(dynamic other) => identical(this, other) || - (other is _Channel && + (other is ChannelEntity && other.id == this.id && other.type == this.type && other.cid == this.cid && @@ -660,24 +230,24 @@ class _Channel extends DataClass implements Insertable<_Channel> { other.updatedAt == this.updatedAt && other.deletedAt == this.deletedAt && other.memberCount == this.memberCount && - other.extraData == this.extraData && - other.createdBy == this.createdBy); + other.createdById == this.createdById && + other.extraData == this.extraData); } -class _ChannelsCompanion extends UpdateCompanion<_Channel> { +class ChannelsCompanion extends UpdateCompanion { final Value id; final Value type; final Value cid; - final Value config; + final Value> config; final Value frozen; final Value lastMessageAt; final Value createdAt; final Value updatedAt; final Value deletedAt; final Value memberCount; - final Value> extraData; - final Value createdBy; - const _ChannelsCompanion({ + final Value createdById; + final Value> extraData; + const ChannelsCompanion({ this.id = const Value.absent(), this.type = const Value.absent(), this.cid = const Value.absent(), @@ -688,27 +258,27 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { this.updatedAt = const Value.absent(), this.deletedAt = const Value.absent(), this.memberCount = const Value.absent(), + this.createdById = const Value.absent(), this.extraData = const Value.absent(), - this.createdBy = const Value.absent(), }); - _ChannelsCompanion.insert({ + ChannelsCompanion.insert({ @required String id, @required String type, @required String cid, - @required String config, + @required Map config, this.frozen = const Value.absent(), this.lastMessageAt = const Value.absent(), this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.deletedAt = const Value.absent(), this.memberCount = const Value.absent(), + this.createdById = const Value.absent(), this.extraData = const Value.absent(), - this.createdBy = const Value.absent(), }) : id = Value(id), type = Value(type), cid = Value(cid), config = Value(config); - static Insertable<_Channel> custom({ + static Insertable custom({ Expression id, Expression type, Expression cid, @@ -719,8 +289,8 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { Expression updatedAt, Expression deletedAt, Expression memberCount, + Expression createdById, Expression extraData, - Expression createdBy, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -733,25 +303,25 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { if (updatedAt != null) 'updated_at': updatedAt, if (deletedAt != null) 'deleted_at': deletedAt, if (memberCount != null) 'member_count': memberCount, + if (createdById != null) 'created_by_id': createdById, if (extraData != null) 'extra_data': extraData, - if (createdBy != null) 'created_by': createdBy, }); } - _ChannelsCompanion copyWith( + ChannelsCompanion copyWith( {Value id, Value type, Value cid, - Value config, + Value> config, Value frozen, Value lastMessageAt, Value createdAt, Value updatedAt, Value deletedAt, Value memberCount, - Value> extraData, - Value createdBy}) { - return _ChannelsCompanion( + Value createdById, + Value> extraData}) { + return ChannelsCompanion( id: id ?? this.id, type: type ?? this.type, cid: cid ?? this.cid, @@ -762,8 +332,8 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { updatedAt: updatedAt ?? this.updatedAt, deletedAt: deletedAt ?? this.deletedAt, memberCount: memberCount ?? this.memberCount, + createdById: createdById ?? this.createdById, extraData: extraData ?? this.extraData, - createdBy: createdBy ?? this.createdBy, ); } @@ -780,7 +350,8 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { map['cid'] = Variable(cid.value); } if (config.present) { - map['config'] = Variable(config.value); + final converter = $ChannelsTable.$converter0; + map['config'] = Variable(converter.mapToSql(config.value)); } if (frozen.present) { map['frozen'] = Variable(frozen.value); @@ -800,19 +371,19 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { if (memberCount.present) { map['member_count'] = Variable(memberCount.value); } - if (extraData.present) { - final converter = $_ChannelsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + if (createdById.present) { + map['created_by_id'] = Variable(createdById.value); } - if (createdBy.present) { - map['created_by'] = Variable(createdBy.value); + if (extraData.present) { + final converter = $ChannelsTable.$converter1; + map['extra_data'] = Variable(converter.mapToSql(extraData.value)); } return map; } @override String toString() { - return (StringBuffer('_ChannelsCompanion(') + return (StringBuffer('ChannelsCompanion(') ..write('id: $id, ') ..write('type: $type, ') ..write('cid: $cid, ') @@ -823,18 +394,18 @@ class _ChannelsCompanion extends UpdateCompanion<_Channel> { ..write('updatedAt: $updatedAt, ') ..write('deletedAt: $deletedAt, ') ..write('memberCount: $memberCount, ') - ..write('extraData: $extraData, ') - ..write('createdBy: $createdBy') + ..write('createdById: $createdById, ') + ..write('extraData: $extraData') ..write(')')) .toString(); } } -class $_ChannelsTable extends _Channels - with TableInfo<$_ChannelsTable, _Channel> { +class $ChannelsTable extends Channels + with TableInfo<$ChannelsTable, ChannelEntity> { final GeneratedDatabase _db; final String _alias; - $_ChannelsTable(this._db, [this._alias]); + $ChannelsTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); GeneratedTextColumn _id; @override @@ -956,6 +527,20 @@ class $_ChannelsTable extends _Channels ); } + final VerificationMeta _createdByIdMeta = + const VerificationMeta('createdById'); + GeneratedTextColumn _createdById; + @override + GeneratedTextColumn get createdById => + _createdById ??= _constructCreatedById(); + GeneratedTextColumn _constructCreatedById() { + return GeneratedTextColumn( + 'created_by_id', + $tableName, + true, + ); + } + final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); GeneratedTextColumn _extraData; @override @@ -968,18 +553,6 @@ class $_ChannelsTable extends _Channels ); } - final VerificationMeta _createdByMeta = const VerificationMeta('createdBy'); - GeneratedTextColumn _createdBy; - @override - GeneratedTextColumn get createdBy => _createdBy ??= _constructCreatedBy(); - GeneratedTextColumn _constructCreatedBy() { - return GeneratedTextColumn( - 'created_by', - $tableName, - true, - ); - } - @override List get $columns => [ id, @@ -992,17 +565,17 @@ class $_ChannelsTable extends _Channels updatedAt, deletedAt, memberCount, - extraData, - createdBy + createdById, + extraData ]; @override - $_ChannelsTable get asDslTable => this; + $ChannelsTable get asDslTable => this; @override String get $tableName => _alias ?? 'channels'; @override final String actualTableName = 'channels'; @override - VerificationContext validateIntegrity(Insertable<_Channel> instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); @@ -1023,12 +596,7 @@ class $_ChannelsTable extends _Channels } else if (isInserting) { context.missing(_cidMeta); } - if (data.containsKey('config')) { - context.handle(_configMeta, - config.isAcceptableOrUnknown(data['config'], _configMeta)); - } else if (isInserting) { - context.missing(_configMeta); - } + context.handle(_configMeta, const VerificationResult.success()); if (data.containsKey('frozen')) { context.handle(_frozenMeta, frozen.isAcceptableOrUnknown(data['frozen'], _frozenMeta)); @@ -1057,508 +625,42 @@ class $_ChannelsTable extends _Channels memberCount.isAcceptableOrUnknown( data['member_count'], _memberCountMeta)); } - context.handle(_extraDataMeta, const VerificationResult.success()); - if (data.containsKey('created_by')) { - context.handle(_createdByMeta, - createdBy.isAcceptableOrUnknown(data['created_by'], _createdByMeta)); + if (data.containsKey('created_by_id')) { + context.handle( + _createdByIdMeta, + createdById.isAcceptableOrUnknown( + data['created_by_id'], _createdByIdMeta)); } + context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @override Set get $primaryKey => {cid}; @override - _Channel map(Map data, {String tablePrefix}) { + ChannelEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _Channel.fromData(data, _db, prefix: effectivePrefix); + return ChannelEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_ChannelsTable createAlias(String alias) { - return $_ChannelsTable(_db, alias); + $ChannelsTable createAlias(String alias) { + return $ChannelsTable(_db, alias); } - static TypeConverter, String> $converter0 = - _ExtraDataConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); + static TypeConverter, String> $converter1 = + MapConverter(); } -class _User extends DataClass implements Insertable<_User> { - final String id; - final String role; - final DateTime createdAt; - final DateTime updatedAt; - final DateTime lastActive; - final bool online; - final bool banned; - final Map extraData; - _User( - {@required this.id, - this.role, - this.createdAt, - this.updatedAt, - this.lastActive, - this.online, - this.banned, - this.extraData}); - factory _User.fromData(Map data, GeneratedDatabase db, - {String prefix}) { - final effectivePrefix = prefix ?? ''; - final stringType = db.typeSystem.forDartType(); - final dateTimeType = db.typeSystem.forDartType(); - final boolType = db.typeSystem.forDartType(); - return _User( - id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), - role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), - createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), - updatedAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), - lastActive: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}last_active']), - online: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}online']), - banned: - boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned']), - extraData: $_UsersTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), - ); - } - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } - if (!nullToAbsent || role != null) { - map['role'] = Variable(role); - } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } - if (!nullToAbsent || updatedAt != null) { - map['updated_at'] = Variable(updatedAt); - } - if (!nullToAbsent || lastActive != null) { - map['last_active'] = Variable(lastActive); - } - if (!nullToAbsent || online != null) { - map['online'] = Variable(online); - } - if (!nullToAbsent || banned != null) { - map['banned'] = Variable(banned); - } - if (!nullToAbsent || extraData != null) { - final converter = $_UsersTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); - } - return map; - } - - _UsersCompanion toCompanion(bool nullToAbsent) { - return _UsersCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - role: role == null && nullToAbsent ? const Value.absent() : Value(role), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), - updatedAt: updatedAt == null && nullToAbsent - ? const Value.absent() - : Value(updatedAt), - lastActive: lastActive == null && nullToAbsent - ? const Value.absent() - : Value(lastActive), - online: - online == null && nullToAbsent ? const Value.absent() : Value(online), - banned: - banned == null && nullToAbsent ? const Value.absent() : Value(banned), - extraData: extraData == null && nullToAbsent - ? const Value.absent() - : Value(extraData), - ); - } - - factory _User.fromJson(Map json, - {ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return _User( - id: serializer.fromJson(json['id']), - role: serializer.fromJson(json['role']), - createdAt: serializer.fromJson(json['createdAt']), - updatedAt: serializer.fromJson(json['updatedAt']), - lastActive: serializer.fromJson(json['lastActive']), - online: serializer.fromJson(json['online']), - banned: serializer.fromJson(json['banned']), - extraData: serializer.fromJson>(json['extraData']), - ); - } - @override - Map toJson({ValueSerializer serializer}) { - serializer ??= moorRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'role': serializer.toJson(role), - 'createdAt': serializer.toJson(createdAt), - 'updatedAt': serializer.toJson(updatedAt), - 'lastActive': serializer.toJson(lastActive), - 'online': serializer.toJson(online), - 'banned': serializer.toJson(banned), - 'extraData': serializer.toJson>(extraData), - }; - } - - _User copyWith( - {String id, - String role, - DateTime createdAt, - DateTime updatedAt, - DateTime lastActive, - bool online, - bool banned, - Map extraData}) => - _User( - id: id ?? this.id, - role: role ?? this.role, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - banned: banned ?? this.banned, - extraData: extraData ?? this.extraData, - ); - @override - String toString() { - return (StringBuffer('_User(') - ..write('id: $id, ') - ..write('role: $role, ') - ..write('createdAt: $createdAt, ') - ..write('updatedAt: $updatedAt, ') - ..write('lastActive: $lastActive, ') - ..write('online: $online, ') - ..write('banned: $banned, ') - ..write('extraData: $extraData') - ..write(')')) - .toString(); - } - - @override - int get hashCode => $mrjf($mrjc( - id.hashCode, - $mrjc( - role.hashCode, - $mrjc( - createdAt.hashCode, - $mrjc( - updatedAt.hashCode, - $mrjc( - lastActive.hashCode, - $mrjc(online.hashCode, - $mrjc(banned.hashCode, extraData.hashCode)))))))); - @override - bool operator ==(dynamic other) => - identical(this, other) || - (other is _User && - other.id == this.id && - other.role == this.role && - other.createdAt == this.createdAt && - other.updatedAt == this.updatedAt && - other.lastActive == this.lastActive && - other.online == this.online && - other.banned == this.banned && - other.extraData == this.extraData); -} - -class _UsersCompanion extends UpdateCompanion<_User> { - final Value id; - final Value role; - final Value createdAt; - final Value updatedAt; - final Value lastActive; - final Value online; - final Value banned; - final Value> extraData; - const _UsersCompanion({ - this.id = const Value.absent(), - this.role = const Value.absent(), - this.createdAt = const Value.absent(), - this.updatedAt = const Value.absent(), - this.lastActive = const Value.absent(), - this.online = const Value.absent(), - this.banned = const Value.absent(), - this.extraData = const Value.absent(), - }); - _UsersCompanion.insert({ - @required String id, - this.role = const Value.absent(), - this.createdAt = const Value.absent(), - this.updatedAt = const Value.absent(), - this.lastActive = const Value.absent(), - this.online = const Value.absent(), - this.banned = const Value.absent(), - this.extraData = const Value.absent(), - }) : id = Value(id); - static Insertable<_User> custom({ - Expression id, - Expression role, - Expression createdAt, - Expression updatedAt, - Expression lastActive, - Expression online, - Expression banned, - Expression extraData, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (role != null) 'role': role, - if (createdAt != null) 'created_at': createdAt, - if (updatedAt != null) 'updated_at': updatedAt, - if (lastActive != null) 'last_active': lastActive, - if (online != null) 'online': online, - if (banned != null) 'banned': banned, - if (extraData != null) 'extra_data': extraData, - }); - } - - _UsersCompanion copyWith( - {Value id, - Value role, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value> extraData}) { - return _UsersCompanion( - id: id ?? this.id, - role: role ?? this.role, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - banned: banned ?? this.banned, - extraData: extraData ?? this.extraData, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (role.present) { - map['role'] = Variable(role.value); - } - if (createdAt.present) { - map['created_at'] = Variable(createdAt.value); - } - if (updatedAt.present) { - map['updated_at'] = Variable(updatedAt.value); - } - if (lastActive.present) { - map['last_active'] = Variable(lastActive.value); - } - if (online.present) { - map['online'] = Variable(online.value); - } - if (banned.present) { - map['banned'] = Variable(banned.value); - } - if (extraData.present) { - final converter = $_UsersTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); - } - return map; - } - - @override - String toString() { - return (StringBuffer('_UsersCompanion(') - ..write('id: $id, ') - ..write('role: $role, ') - ..write('createdAt: $createdAt, ') - ..write('updatedAt: $updatedAt, ') - ..write('lastActive: $lastActive, ') - ..write('online: $online, ') - ..write('banned: $banned, ') - ..write('extraData: $extraData') - ..write(')')) - .toString(); - } -} - -class $_UsersTable extends _Users with TableInfo<$_UsersTable, _User> { - final GeneratedDatabase _db; - final String _alias; - $_UsersTable(this._db, [this._alias]); - final VerificationMeta _idMeta = const VerificationMeta('id'); - GeneratedTextColumn _id; - @override - GeneratedTextColumn get id => _id ??= _constructId(); - GeneratedTextColumn _constructId() { - return GeneratedTextColumn( - 'id', - $tableName, - false, - ); - } - - final VerificationMeta _roleMeta = const VerificationMeta('role'); - GeneratedTextColumn _role; - @override - GeneratedTextColumn get role => _role ??= _constructRole(); - GeneratedTextColumn _constructRole() { - return GeneratedTextColumn( - 'role', - $tableName, - true, - ); - } - - final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; - @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - true, - ); - } - - final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); - GeneratedDateTimeColumn _updatedAt; - @override - GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); - GeneratedDateTimeColumn _constructUpdatedAt() { - return GeneratedDateTimeColumn( - 'updated_at', - $tableName, - true, - ); - } - - final VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); - GeneratedDateTimeColumn _lastActive; - @override - GeneratedDateTimeColumn get lastActive => - _lastActive ??= _constructLastActive(); - GeneratedDateTimeColumn _constructLastActive() { - return GeneratedDateTimeColumn( - 'last_active', - $tableName, - true, - ); - } - - final VerificationMeta _onlineMeta = const VerificationMeta('online'); - GeneratedBoolColumn _online; - @override - GeneratedBoolColumn get online => _online ??= _constructOnline(); - GeneratedBoolColumn _constructOnline() { - return GeneratedBoolColumn( - 'online', - $tableName, - true, - ); - } - - final VerificationMeta _bannedMeta = const VerificationMeta('banned'); - GeneratedBoolColumn _banned; - @override - GeneratedBoolColumn get banned => _banned ??= _constructBanned(); - GeneratedBoolColumn _constructBanned() { - return GeneratedBoolColumn( - 'banned', - $tableName, - true, - ); - } - - final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; - @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', - $tableName, - true, - ); - } - - @override - List get $columns => - [id, role, createdAt, updatedAt, lastActive, online, banned, extraData]; - @override - $_UsersTable get asDslTable => this; - @override - String get $tableName => _alias ?? 'users'; - @override - final String actualTableName = 'users'; - @override - VerificationContext validateIntegrity(Insertable<_User> instance, - {bool isInserting = false}) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); - } else if (isInserting) { - context.missing(_idMeta); - } - if (data.containsKey('role')) { - context.handle( - _roleMeta, role.isAcceptableOrUnknown(data['role'], _roleMeta)); - } - if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } - if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); - } - if (data.containsKey('last_active')) { - context.handle( - _lastActiveMeta, - lastActive.isAcceptableOrUnknown( - data['last_active'], _lastActiveMeta)); - } - if (data.containsKey('online')) { - context.handle(_onlineMeta, - online.isAcceptableOrUnknown(data['online'], _onlineMeta)); - } - if (data.containsKey('banned')) { - context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned'], _bannedMeta)); - } - context.handle(_extraDataMeta, const VerificationResult.success()); - return context; - } - - @override - Set get $primaryKey => {id}; - @override - _User map(Map data, {String tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _User.fromData(data, _db, prefix: effectivePrefix); - } - - @override - $_UsersTable createAlias(String alias) { - return $_UsersTable(_db, alias); - } - - static TypeConverter, String> $converter0 = - _ExtraDataConverter(); -} - -class _Message extends DataClass implements Insertable<_Message> { +class MessageEntity extends DataClass implements Insertable { final String id; final String messageText; - final String attachmentJson; + final List attachments; final MessageSendingStatus status; final String type; + final List mentionedUsers; final Map reactionCounts; final Map reactionScores; final String parentId; @@ -1572,13 +674,14 @@ class _Message extends DataClass implements Insertable<_Message> { final DateTime deletedAt; final String userId; final String channelCid; - final Map extraData; - _Message( + final Map extraData; + MessageEntity( {@required this.id, this.messageText, - this.attachmentJson, + this.attachments, this.status, this.type, + this.mentionedUsers, this.reactionCounts, this.reactionScores, this.parentId, @@ -1593,25 +696,28 @@ class _Message extends DataClass implements Insertable<_Message> { this.userId, this.channelCid, this.extraData}); - factory _Message.fromData(Map data, GeneratedDatabase db, + factory MessageEntity.fromData( + Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); - return _Message( + return MessageEntity( id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), messageText: stringType .mapFromDatabaseResponse(data['${effectivePrefix}message_text']), - attachmentJson: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}attachment_json']), - status: $_MessagesTable.$converter0.mapToDart( + attachments: $MessagesTable.$converter0.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}attachments'])), + status: $MessagesTable.$converter1.mapToDart( intType.mapFromDatabaseResponse(data['${effectivePrefix}status'])), type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), - reactionCounts: $_MessagesTable.$converter1.mapToDart(stringType + mentionedUsers: $MessagesTable.$converter2.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}mentioned_users'])), + reactionCounts: $MessagesTable.$converter3.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}reaction_counts'])), - reactionScores: $_MessagesTable.$converter2.mapToDart(stringType + reactionScores: $MessagesTable.$converter4.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}reaction_scores'])), parentId: stringType .mapFromDatabaseResponse(data['${effectivePrefix}parent_id']), @@ -1635,7 +741,7 @@ class _Message extends DataClass implements Insertable<_Message> { stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), channelCid: stringType .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - extraData: $_MessagesTable.$converter3.mapToDart(stringType + extraData: $MessagesTable.$converter5.mapToDart(stringType .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @@ -1648,23 +754,29 @@ class _Message extends DataClass implements Insertable<_Message> { if (!nullToAbsent || messageText != null) { map['message_text'] = Variable(messageText); } - if (!nullToAbsent || attachmentJson != null) { - map['attachment_json'] = Variable(attachmentJson); + if (!nullToAbsent || attachments != null) { + final converter = $MessagesTable.$converter0; + map['attachments'] = Variable(converter.mapToSql(attachments)); } if (!nullToAbsent || status != null) { - final converter = $_MessagesTable.$converter0; + final converter = $MessagesTable.$converter1; map['status'] = Variable(converter.mapToSql(status)); } if (!nullToAbsent || type != null) { map['type'] = Variable(type); } + if (!nullToAbsent || mentionedUsers != null) { + final converter = $MessagesTable.$converter2; + map['mentioned_users'] = + Variable(converter.mapToSql(mentionedUsers)); + } if (!nullToAbsent || reactionCounts != null) { - final converter = $_MessagesTable.$converter1; + final converter = $MessagesTable.$converter3; map['reaction_counts'] = Variable(converter.mapToSql(reactionCounts)); } if (!nullToAbsent || reactionScores != null) { - final converter = $_MessagesTable.$converter2; + final converter = $MessagesTable.$converter4; map['reaction_scores'] = Variable(converter.mapToSql(reactionScores)); } @@ -1702,77 +814,22 @@ class _Message extends DataClass implements Insertable<_Message> { map['channel_cid'] = Variable(channelCid); } if (!nullToAbsent || extraData != null) { - final converter = $_MessagesTable.$converter3; + final converter = $MessagesTable.$converter5; map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } - _MessagesCompanion toCompanion(bool nullToAbsent) { - return _MessagesCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), - messageText: messageText == null && nullToAbsent - ? const Value.absent() - : Value(messageText), - attachmentJson: attachmentJson == null && nullToAbsent - ? const Value.absent() - : Value(attachmentJson), - status: - status == null && nullToAbsent ? const Value.absent() : Value(status), - type: type == null && nullToAbsent ? const Value.absent() : Value(type), - reactionCounts: reactionCounts == null && nullToAbsent - ? const Value.absent() - : Value(reactionCounts), - reactionScores: reactionScores == null && nullToAbsent - ? const Value.absent() - : Value(reactionScores), - parentId: parentId == null && nullToAbsent - ? const Value.absent() - : Value(parentId), - quotedMessageId: quotedMessageId == null && nullToAbsent - ? const Value.absent() - : Value(quotedMessageId), - replyCount: replyCount == null && nullToAbsent - ? const Value.absent() - : Value(replyCount), - showInChannel: showInChannel == null && nullToAbsent - ? const Value.absent() - : Value(showInChannel), - shadowed: shadowed == null && nullToAbsent - ? const Value.absent() - : Value(shadowed), - command: command == null && nullToAbsent - ? const Value.absent() - : Value(command), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), - updatedAt: updatedAt == null && nullToAbsent - ? const Value.absent() - : Value(updatedAt), - deletedAt: deletedAt == null && nullToAbsent - ? const Value.absent() - : Value(deletedAt), - userId: - userId == null && nullToAbsent ? const Value.absent() : Value(userId), - channelCid: channelCid == null && nullToAbsent - ? const Value.absent() - : Value(channelCid), - extraData: extraData == null && nullToAbsent - ? const Value.absent() - : Value(extraData), - ); - } - - factory _Message.fromJson(Map json, + factory MessageEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return _Message( + return MessageEntity( id: serializer.fromJson(json['id']), messageText: serializer.fromJson(json['messageText']), - attachmentJson: serializer.fromJson(json['attachmentJson']), + attachments: serializer.fromJson>(json['attachments']), status: serializer.fromJson(json['status']), type: serializer.fromJson(json['type']), + mentionedUsers: serializer.fromJson>(json['mentionedUsers']), reactionCounts: serializer.fromJson>(json['reactionCounts']), reactionScores: @@ -1788,7 +845,7 @@ class _Message extends DataClass implements Insertable<_Message> { deletedAt: serializer.fromJson(json['deletedAt']), userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), - extraData: serializer.fromJson>(json['extraData']), + extraData: serializer.fromJson>(json['extraData']), ); } @override @@ -1797,9 +854,10 @@ class _Message extends DataClass implements Insertable<_Message> { return { 'id': serializer.toJson(id), 'messageText': serializer.toJson(messageText), - 'attachmentJson': serializer.toJson(attachmentJson), + 'attachments': serializer.toJson>(attachments), 'status': serializer.toJson(status), 'type': serializer.toJson(type), + 'mentionedUsers': serializer.toJson>(mentionedUsers), 'reactionCounts': serializer.toJson>(reactionCounts), 'reactionScores': serializer.toJson>(reactionScores), 'parentId': serializer.toJson(parentId), @@ -1813,59 +871,68 @@ class _Message extends DataClass implements Insertable<_Message> { 'deletedAt': serializer.toJson(deletedAt), 'userId': serializer.toJson(userId), 'channelCid': serializer.toJson(channelCid), - 'extraData': serializer.toJson>(extraData), + 'extraData': serializer.toJson>(extraData), }; } - _Message copyWith( + MessageEntity copyWith( {String id, - String messageText, - String attachmentJson, - MessageSendingStatus status, - String type, - Map reactionCounts, - Map reactionScores, - String parentId, - String quotedMessageId, - int replyCount, - bool showInChannel, - bool shadowed, - String command, + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value status = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value> reactionCounts = const Value.absent(), + Value> reactionScores = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), DateTime createdAt, - DateTime updatedAt, - DateTime deletedAt, - String userId, - String channelCid, - Map extraData}) => - _Message( + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelCid = const Value.absent(), + Value> extraData = const Value.absent()}) => + MessageEntity( id: id ?? this.id, - messageText: messageText ?? this.messageText, - attachmentJson: attachmentJson ?? this.attachmentJson, - status: status ?? this.status, - type: type ?? this.type, - reactionCounts: reactionCounts ?? this.reactionCounts, - reactionScores: reactionScores ?? this.reactionScores, - parentId: parentId ?? this.parentId, - quotedMessageId: quotedMessageId ?? this.quotedMessageId, - replyCount: replyCount ?? this.replyCount, - showInChannel: showInChannel ?? this.showInChannel, - shadowed: shadowed ?? this.shadowed, - command: command ?? this.command, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments.present ? attachments.value : this.attachments, + status: status.present ? status.value : this.status, + type: type.present ? type.value : this.type, + mentionedUsers: + mentionedUsers.present ? mentionedUsers.value : this.mentionedUsers, + reactionCounts: + reactionCounts.present ? reactionCounts.value : this.reactionCounts, + reactionScores: + reactionScores.present ? reactionScores.value : this.reactionScores, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present + ? quotedMessageId.value + : this.quotedMessageId, + replyCount: replyCount.present ? replyCount.value : this.replyCount, + showInChannel: + showInChannel.present ? showInChannel.value : this.showInChannel, + shadowed: shadowed.present ? shadowed.value : this.shadowed, + command: command.present ? command.value : this.command, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - extraData: extraData ?? this.extraData, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + userId: userId.present ? userId.value : this.userId, + channelCid: channelCid.present ? channelCid.value : this.channelCid, + extraData: extraData.present ? extraData.value : this.extraData, ); @override String toString() { - return (StringBuffer('_Message(') + return (StringBuffer('MessageEntity(') ..write('id: $id, ') ..write('messageText: $messageText, ') - ..write('attachmentJson: $attachmentJson, ') + ..write('attachments: $attachments, ') ..write('status: $status, ') ..write('type: $type, ') + ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') ..write('reactionScores: $reactionScores, ') ..write('parentId: $parentId, ') @@ -1890,52 +957,54 @@ class _Message extends DataClass implements Insertable<_Message> { $mrjc( messageText.hashCode, $mrjc( - attachmentJson.hashCode, + attachments.hashCode, $mrjc( status.hashCode, $mrjc( type.hashCode, $mrjc( - reactionCounts.hashCode, + mentionedUsers.hashCode, $mrjc( - reactionScores.hashCode, + reactionCounts.hashCode, $mrjc( - parentId.hashCode, + reactionScores.hashCode, $mrjc( - quotedMessageId.hashCode, + parentId.hashCode, $mrjc( - replyCount.hashCode, + quotedMessageId.hashCode, $mrjc( - showInChannel.hashCode, + replyCount.hashCode, $mrjc( - shadowed.hashCode, + showInChannel.hashCode, $mrjc( - command.hashCode, + shadowed.hashCode, $mrjc( - createdAt.hashCode, + command.hashCode, $mrjc( - updatedAt + createdAt .hashCode, $mrjc( - deletedAt + updatedAt .hashCode, $mrjc( - userId + deletedAt .hashCode, $mrjc( - channelCid + userId .hashCode, - extraData - .hashCode))))))))))))))))))); + $mrjc( + channelCid.hashCode, + extraData.hashCode)))))))))))))))))))); @override bool operator ==(dynamic other) => identical(this, other) || - (other is _Message && + (other is MessageEntity && other.id == this.id && other.messageText == this.messageText && - other.attachmentJson == this.attachmentJson && + other.attachments == this.attachments && other.status == this.status && other.type == this.type && + other.mentionedUsers == this.mentionedUsers && other.reactionCounts == this.reactionCounts && other.reactionScores == this.reactionScores && other.parentId == this.parentId && @@ -1952,12 +1021,13 @@ class _Message extends DataClass implements Insertable<_Message> { other.extraData == this.extraData); } -class _MessagesCompanion extends UpdateCompanion<_Message> { +class MessagesCompanion extends UpdateCompanion { final Value id; final Value messageText; - final Value attachmentJson; + final Value> attachments; final Value status; final Value type; + final Value> mentionedUsers; final Value> reactionCounts; final Value> reactionScores; final Value parentId; @@ -1971,13 +1041,14 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { final Value deletedAt; final Value userId; final Value channelCid; - final Value> extraData; - const _MessagesCompanion({ + final Value> extraData; + const MessagesCompanion({ this.id = const Value.absent(), this.messageText = const Value.absent(), - this.attachmentJson = const Value.absent(), + this.attachments = const Value.absent(), this.status = const Value.absent(), this.type = const Value.absent(), + this.mentionedUsers = const Value.absent(), this.reactionCounts = const Value.absent(), this.reactionScores = const Value.absent(), this.parentId = const Value.absent(), @@ -1993,12 +1064,13 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { this.channelCid = const Value.absent(), this.extraData = const Value.absent(), }); - _MessagesCompanion.insert({ + MessagesCompanion.insert({ @required String id, this.messageText = const Value.absent(), - this.attachmentJson = const Value.absent(), + this.attachments = const Value.absent(), this.status = const Value.absent(), this.type = const Value.absent(), + this.mentionedUsers = const Value.absent(), this.reactionCounts = const Value.absent(), this.reactionScores = const Value.absent(), this.parentId = const Value.absent(), @@ -2015,12 +1087,13 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { this.extraData = const Value.absent(), }) : id = Value(id), createdAt = Value(createdAt); - static Insertable<_Message> custom({ + static Insertable custom({ Expression id, Expression messageText, - Expression attachmentJson, + Expression attachments, Expression status, Expression type, + Expression mentionedUsers, Expression reactionCounts, Expression reactionScores, Expression parentId, @@ -2039,9 +1112,10 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { return RawValuesInsertable({ if (id != null) 'id': id, if (messageText != null) 'message_text': messageText, - if (attachmentJson != null) 'attachment_json': attachmentJson, + if (attachments != null) 'attachments': attachments, if (status != null) 'status': status, if (type != null) 'type': type, + if (mentionedUsers != null) 'mentioned_users': mentionedUsers, if (reactionCounts != null) 'reaction_counts': reactionCounts, if (reactionScores != null) 'reaction_scores': reactionScores, if (parentId != null) 'parent_id': parentId, @@ -2059,12 +1133,13 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { }); } - _MessagesCompanion copyWith( + MessagesCompanion copyWith( {Value id, Value messageText, - Value attachmentJson, + Value> attachments, Value status, Value type, + Value> mentionedUsers, Value> reactionCounts, Value> reactionScores, Value parentId, @@ -2078,13 +1153,14 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { Value deletedAt, Value userId, Value channelCid, - Value> extraData}) { - return _MessagesCompanion( + Value> extraData}) { + return MessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, - attachmentJson: attachmentJson ?? this.attachmentJson, + attachments: attachments ?? this.attachments, status: status ?? this.status, type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts ?? this.reactionCounts, reactionScores: reactionScores ?? this.reactionScores, parentId: parentId ?? this.parentId, @@ -2111,23 +1187,30 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { if (messageText.present) { map['message_text'] = Variable(messageText.value); } - if (attachmentJson.present) { - map['attachment_json'] = Variable(attachmentJson.value); + if (attachments.present) { + final converter = $MessagesTable.$converter0; + map['attachments'] = + Variable(converter.mapToSql(attachments.value)); } if (status.present) { - final converter = $_MessagesTable.$converter0; + final converter = $MessagesTable.$converter1; map['status'] = Variable(converter.mapToSql(status.value)); } if (type.present) { map['type'] = Variable(type.value); } + if (mentionedUsers.present) { + final converter = $MessagesTable.$converter2; + map['mentioned_users'] = + Variable(converter.mapToSql(mentionedUsers.value)); + } if (reactionCounts.present) { - final converter = $_MessagesTable.$converter1; + final converter = $MessagesTable.$converter3; map['reaction_counts'] = Variable(converter.mapToSql(reactionCounts.value)); } if (reactionScores.present) { - final converter = $_MessagesTable.$converter2; + final converter = $MessagesTable.$converter4; map['reaction_scores'] = Variable(converter.mapToSql(reactionScores.value)); } @@ -2165,7 +1248,7 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { map['channel_cid'] = Variable(channelCid.value); } if (extraData.present) { - final converter = $_MessagesTable.$converter3; + final converter = $MessagesTable.$converter5; map['extra_data'] = Variable(converter.mapToSql(extraData.value)); } return map; @@ -2173,12 +1256,13 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { @override String toString() { - return (StringBuffer('_MessagesCompanion(') + return (StringBuffer('MessagesCompanion(') ..write('id: $id, ') ..write('messageText: $messageText, ') - ..write('attachmentJson: $attachmentJson, ') + ..write('attachments: $attachments, ') ..write('status: $status, ') ..write('type: $type, ') + ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') ..write('reactionScores: $reactionScores, ') ..write('parentId: $parentId, ') @@ -2198,11 +1282,11 @@ class _MessagesCompanion extends UpdateCompanion<_Message> { } } -class $_MessagesTable extends _Messages - with TableInfo<$_MessagesTable, _Message> { +class $MessagesTable extends Messages + with TableInfo<$MessagesTable, MessageEntity> { final GeneratedDatabase _db; final String _alias; - $_MessagesTable(this._db, [this._alias]); + $MessagesTable(this._db, [this._alias]); final VerificationMeta _idMeta = const VerificationMeta('id'); GeneratedTextColumn _id; @override @@ -2229,15 +1313,15 @@ class $_MessagesTable extends _Messages ); } - final VerificationMeta _attachmentJsonMeta = - const VerificationMeta('attachmentJson'); - GeneratedTextColumn _attachmentJson; + final VerificationMeta _attachmentsMeta = + const VerificationMeta('attachments'); + GeneratedTextColumn _attachments; @override - GeneratedTextColumn get attachmentJson => - _attachmentJson ??= _constructAttachmentJson(); - GeneratedTextColumn _constructAttachmentJson() { + GeneratedTextColumn get attachments => + _attachments ??= _constructAttachments(); + GeneratedTextColumn _constructAttachments() { return GeneratedTextColumn( - 'attachment_json', + 'attachments', $tableName, true, ); @@ -2267,6 +1351,20 @@ class $_MessagesTable extends _Messages ); } + final VerificationMeta _mentionedUsersMeta = + const VerificationMeta('mentionedUsers'); + GeneratedTextColumn _mentionedUsers; + @override + GeneratedTextColumn get mentionedUsers => + _mentionedUsers ??= _constructMentionedUsers(); + GeneratedTextColumn _constructMentionedUsers() { + return GeneratedTextColumn( + 'mentioned_users', + $tableName, + true, + ); + } + final VerificationMeta _reactionCountsMeta = const VerificationMeta('reactionCounts'); GeneratedTextColumn _reactionCounts; @@ -2424,11 +1522,9 @@ class $_MessagesTable extends _Messages @override GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn( - 'channel_cid', - $tableName, - true, - ); + return GeneratedTextColumn('channel_cid', $tableName, true, + $customConstraints: + 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE'); } final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); @@ -2447,9 +1543,10 @@ class $_MessagesTable extends _Messages List get $columns => [ id, messageText, - attachmentJson, + attachments, status, type, + mentionedUsers, reactionCounts, reactionScores, parentId, @@ -2466,13 +1563,13 @@ class $_MessagesTable extends _Messages extraData ]; @override - $_MessagesTable get asDslTable => this; + $MessagesTable get asDslTable => this; @override String get $tableName => _alias ?? 'messages'; @override final String actualTableName = 'messages'; @override - VerificationContext validateIntegrity(Insertable<_Message> instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); @@ -2487,17 +1584,13 @@ class $_MessagesTable extends _Messages messageText.isAcceptableOrUnknown( data['message_text'], _messageTextMeta)); } - if (data.containsKey('attachment_json')) { - context.handle( - _attachmentJsonMeta, - attachmentJson.isAcceptableOrUnknown( - data['attachment_json'], _attachmentJsonMeta)); - } + context.handle(_attachmentsMeta, const VerificationResult.success()); context.handle(_statusMeta, const VerificationResult.success()); if (data.containsKey('type')) { context.handle( _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); } + context.handle(_mentionedUsersMeta, const VerificationResult.success()); context.handle(_reactionCountsMeta, const VerificationResult.success()); context.handle(_reactionScoresMeta, const VerificationResult.success()); if (data.containsKey('parent_id')) { @@ -2561,237 +1654,268 @@ class $_MessagesTable extends _Messages @override Set get $primaryKey => {id}; @override - _Message map(Map data, {String tablePrefix}) { + MessageEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _Message.fromData(data, _db, prefix: effectivePrefix); + return MessageEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_MessagesTable createAlias(String alias) { - return $_MessagesTable(_db, alias); + $MessagesTable createAlias(String alias) { + return $MessagesTable(_db, alias); } - static TypeConverter $converter0 = - _MessageSendingStatusConverter(); - static TypeConverter, String> $converter1 = - _ExtraDataConverter(); - static TypeConverter, String> $converter2 = - _ExtraDataConverter(); - static TypeConverter, String> $converter3 = - _ExtraDataConverter(); + static TypeConverter, String> $converter0 = + ListConverter(); + static TypeConverter $converter1 = + MessageSendingStatusConverter(); + static TypeConverter, String> $converter2 = + ListConverter(); + static TypeConverter, String> $converter3 = + MapConverter(); + static TypeConverter, String> $converter4 = + MapConverter(); + static TypeConverter, String> $converter5 = + MapConverter(); } -class _Read extends DataClass implements Insertable<_Read> { - final DateTime lastRead; +class ReactionEntity extends DataClass implements Insertable { final String userId; - final String channelCid; - final int unreadMessages; - _Read( - {@required this.lastRead, - @required this.userId, - @required this.channelCid, - this.unreadMessages}); - factory _Read.fromData(Map data, GeneratedDatabase db, + final String messageId; + final String type; + final DateTime createdAt; + final int score; + final Map extraData; + ReactionEntity( + {@required this.userId, + @required this.messageId, + @required this.type, + @required this.createdAt, + this.score, + this.extraData}); + factory ReactionEntity.fromData( + Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; - final dateTimeType = db.typeSystem.forDartType(); final stringType = db.typeSystem.forDartType(); + final dateTimeType = db.typeSystem.forDartType(); final intType = db.typeSystem.forDartType(); - return _Read( - lastRead: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}last_read']), + return ReactionEntity( userId: stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - channelCid: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), - unreadMessages: intType - .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages']), + messageId: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}message_id']), + type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), + createdAt: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score']), + extraData: $ReactionsTable.$converter0.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || lastRead != null) { - map['last_read'] = Variable(lastRead); - } if (!nullToAbsent || userId != null) { map['user_id'] = Variable(userId); } - if (!nullToAbsent || channelCid != null) { - map['channel_cid'] = Variable(channelCid); + if (!nullToAbsent || messageId != null) { + map['message_id'] = Variable(messageId); } - if (!nullToAbsent || unreadMessages != null) { - map['unread_messages'] = Variable(unreadMessages); + if (!nullToAbsent || type != null) { + map['type'] = Variable(type); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || score != null) { + map['score'] = Variable(score); + } + if (!nullToAbsent || extraData != null) { + final converter = $ReactionsTable.$converter0; + map['extra_data'] = Variable(converter.mapToSql(extraData)); } return map; } - _ReadsCompanion toCompanion(bool nullToAbsent) { - return _ReadsCompanion( - lastRead: lastRead == null && nullToAbsent - ? const Value.absent() - : Value(lastRead), - userId: - userId == null && nullToAbsent ? const Value.absent() : Value(userId), - channelCid: channelCid == null && nullToAbsent - ? const Value.absent() - : Value(channelCid), - unreadMessages: unreadMessages == null && nullToAbsent - ? const Value.absent() - : Value(unreadMessages), - ); - } - - factory _Read.fromJson(Map json, + factory ReactionEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return _Read( - lastRead: serializer.fromJson(json['lastRead']), + return ReactionEntity( userId: serializer.fromJson(json['userId']), - channelCid: serializer.fromJson(json['channelCid']), - unreadMessages: serializer.fromJson(json['unreadMessages']), + messageId: serializer.fromJson(json['messageId']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + score: serializer.fromJson(json['score']), + extraData: serializer.fromJson>(json['extraData']), ); } @override Map toJson({ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { - 'lastRead': serializer.toJson(lastRead), 'userId': serializer.toJson(userId), - 'channelCid': serializer.toJson(channelCid), - 'unreadMessages': serializer.toJson(unreadMessages), + 'messageId': serializer.toJson(messageId), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'score': serializer.toJson(score), + 'extraData': serializer.toJson>(extraData), }; } - _Read copyWith( - {DateTime lastRead, - String userId, - String channelCid, - int unreadMessages}) => - _Read( - lastRead: lastRead ?? this.lastRead, + ReactionEntity copyWith( + {String userId, + String messageId, + String type, + DateTime createdAt, + Value score = const Value.absent(), + Value> extraData = const Value.absent()}) => + ReactionEntity( userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - unreadMessages: unreadMessages ?? this.unreadMessages, + messageId: messageId ?? this.messageId, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + score: score.present ? score.value : this.score, + extraData: extraData.present ? extraData.value : this.extraData, ); @override String toString() { - return (StringBuffer('_Read(') - ..write('lastRead: $lastRead, ') + return (StringBuffer('ReactionEntity(') ..write('userId: $userId, ') - ..write('channelCid: $channelCid, ') - ..write('unreadMessages: $unreadMessages') + ..write('messageId: $messageId, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('score: $score, ') + ..write('extraData: $extraData') ..write(')')) .toString(); } @override int get hashCode => $mrjf($mrjc( - lastRead.hashCode, - $mrjc(userId.hashCode, - $mrjc(channelCid.hashCode, unreadMessages.hashCode)))); + userId.hashCode, + $mrjc( + messageId.hashCode, + $mrjc( + type.hashCode, + $mrjc(createdAt.hashCode, + $mrjc(score.hashCode, extraData.hashCode)))))); @override bool operator ==(dynamic other) => identical(this, other) || - (other is _Read && - other.lastRead == this.lastRead && + (other is ReactionEntity && other.userId == this.userId && - other.channelCid == this.channelCid && - other.unreadMessages == this.unreadMessages); + other.messageId == this.messageId && + other.type == this.type && + other.createdAt == this.createdAt && + other.score == this.score && + other.extraData == this.extraData); } -class _ReadsCompanion extends UpdateCompanion<_Read> { - final Value lastRead; +class ReactionsCompanion extends UpdateCompanion { final Value userId; - final Value channelCid; - final Value unreadMessages; - const _ReadsCompanion({ - this.lastRead = const Value.absent(), + final Value messageId; + final Value type; + final Value createdAt; + final Value score; + final Value> extraData; + const ReactionsCompanion({ this.userId = const Value.absent(), - this.channelCid = const Value.absent(), - this.unreadMessages = const Value.absent(), + this.messageId = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.score = const Value.absent(), + this.extraData = const Value.absent(), }); - _ReadsCompanion.insert({ - @required DateTime lastRead, + ReactionsCompanion.insert({ @required String userId, - @required String channelCid, - this.unreadMessages = const Value.absent(), - }) : lastRead = Value(lastRead), - userId = Value(userId), - channelCid = Value(channelCid); - static Insertable<_Read> custom({ - Expression lastRead, + @required String messageId, + @required String type, + @required DateTime createdAt, + this.score = const Value.absent(), + this.extraData = const Value.absent(), + }) : userId = Value(userId), + messageId = Value(messageId), + type = Value(type), + createdAt = Value(createdAt); + static Insertable custom({ Expression userId, - Expression channelCid, - Expression unreadMessages, + Expression messageId, + Expression type, + Expression createdAt, + Expression score, + Expression extraData, }) { return RawValuesInsertable({ - if (lastRead != null) 'last_read': lastRead, if (userId != null) 'user_id': userId, - if (channelCid != null) 'channel_cid': channelCid, - if (unreadMessages != null) 'unread_messages': unreadMessages, + if (messageId != null) 'message_id': messageId, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (score != null) 'score': score, + if (extraData != null) 'extra_data': extraData, }); } - _ReadsCompanion copyWith( - {Value lastRead, - Value userId, - Value channelCid, - Value unreadMessages}) { - return _ReadsCompanion( - lastRead: lastRead ?? this.lastRead, + ReactionsCompanion copyWith( + {Value userId, + Value messageId, + Value type, + Value createdAt, + Value score, + Value> extraData}) { + return ReactionsCompanion( userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - unreadMessages: unreadMessages ?? this.unreadMessages, + messageId: messageId ?? this.messageId, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + score: score ?? this.score, + extraData: extraData ?? this.extraData, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (lastRead.present) { - map['last_read'] = Variable(lastRead.value); - } if (userId.present) { map['user_id'] = Variable(userId.value); } - if (channelCid.present) { - map['channel_cid'] = Variable(channelCid.value); + if (messageId.present) { + map['message_id'] = Variable(messageId.value); } - if (unreadMessages.present) { - map['unread_messages'] = Variable(unreadMessages.value); + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (score.present) { + map['score'] = Variable(score.value); + } + if (extraData.present) { + final converter = $ReactionsTable.$converter0; + map['extra_data'] = Variable(converter.mapToSql(extraData.value)); } return map; } @override String toString() { - return (StringBuffer('_ReadsCompanion(') - ..write('lastRead: $lastRead, ') + return (StringBuffer('ReactionsCompanion(') ..write('userId: $userId, ') - ..write('channelCid: $channelCid, ') - ..write('unreadMessages: $unreadMessages') + ..write('messageId: $messageId, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('score: $score, ') + ..write('extraData: $extraData') ..write(')')) .toString(); } } -class $_ReadsTable extends _Reads with TableInfo<$_ReadsTable, _Read> { +class $ReactionsTable extends Reactions + with TableInfo<$ReactionsTable, ReactionEntity> { final GeneratedDatabase _db; final String _alias; - $_ReadsTable(this._db, [this._alias]); - final VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); - GeneratedDateTimeColumn _lastRead; - @override - GeneratedDateTimeColumn get lastRead => _lastRead ??= _constructLastRead(); - GeneratedDateTimeColumn _constructLastRead() { - return GeneratedDateTimeColumn( - 'last_read', - $tableName, - false, - ); - } - + $ReactionsTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); GeneratedTextColumn _userId; @override @@ -2804,27 +1928,58 @@ class $_ReadsTable extends _Reads with TableInfo<$_ReadsTable, _Read> { ); } - final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); - GeneratedTextColumn _channelCid; + final VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); + GeneratedTextColumn _messageId; @override - GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); - GeneratedTextColumn _constructChannelCid() { + GeneratedTextColumn get messageId => _messageId ??= _constructMessageId(); + GeneratedTextColumn _constructMessageId() { + return GeneratedTextColumn('message_id', $tableName, false, + $customConstraints: 'REFERENCES messages(id) ON DELETE CASCADE'); + } + + final VerificationMeta _typeMeta = const VerificationMeta('type'); + GeneratedTextColumn _type; + @override + GeneratedTextColumn get type => _type ??= _constructType(); + GeneratedTextColumn _constructType() { return GeneratedTextColumn( - 'channel_cid', + 'type', $tableName, false, ); } - final VerificationMeta _unreadMessagesMeta = - const VerificationMeta('unreadMessages'); - GeneratedIntColumn _unreadMessages; + final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); + GeneratedDateTimeColumn _createdAt; @override - GeneratedIntColumn get unreadMessages => - _unreadMessages ??= _constructUnreadMessages(); - GeneratedIntColumn _constructUnreadMessages() { + GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + GeneratedDateTimeColumn _constructCreatedAt() { + return GeneratedDateTimeColumn( + 'created_at', + $tableName, + false, + ); + } + + final VerificationMeta _scoreMeta = const VerificationMeta('score'); + GeneratedIntColumn _score; + @override + GeneratedIntColumn get score => _score ??= _constructScore(); + GeneratedIntColumn _constructScore() { return GeneratedIntColumn( - 'unread_messages', + 'score', + $tableName, + true, + ); + } + + final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); + GeneratedTextColumn _extraData; + @override + GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + GeneratedTextColumn _constructExtraData() { + return GeneratedTextColumn( + 'extra_data', $tableName, true, ); @@ -2832,62 +1987,516 @@ class $_ReadsTable extends _Reads with TableInfo<$_ReadsTable, _Read> { @override List get $columns => - [lastRead, userId, channelCid, unreadMessages]; + [userId, messageId, type, createdAt, score, extraData]; @override - $_ReadsTable get asDslTable => this; + $ReactionsTable get asDslTable => this; @override - String get $tableName => _alias ?? 'reads'; + String get $tableName => _alias ?? 'reactions'; @override - final String actualTableName = 'reads'; + final String actualTableName = 'reactions'; @override - VerificationContext validateIntegrity(Insertable<_Read> instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('last_read')) { - context.handle(_lastReadMeta, - lastRead.isAcceptableOrUnknown(data['last_read'], _lastReadMeta)); - } else if (isInserting) { - context.missing(_lastReadMeta); - } if (data.containsKey('user_id')) { context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } - if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid'], _channelCidMeta)); + if (data.containsKey('message_id')) { + context.handle(_messageIdMeta, + messageId.isAcceptableOrUnknown(data['message_id'], _messageIdMeta)); } else if (isInserting) { - context.missing(_channelCidMeta); + context.missing(_messageIdMeta); } - if (data.containsKey('unread_messages')) { + if (data.containsKey('type')) { context.handle( - _unreadMessagesMeta, - unreadMessages.isAcceptableOrUnknown( - data['unread_messages'], _unreadMessagesMeta)); + _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('score')) { + context.handle( + _scoreMeta, score.isAcceptableOrUnknown(data['score'], _scoreMeta)); + } + context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @override - Set get $primaryKey => {userId, channelCid}; + Set get $primaryKey => {messageId, type, userId}; @override - _Read map(Map data, {String tablePrefix}) { + ReactionEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _Read.fromData(data, _db, prefix: effectivePrefix); + return ReactionEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_ReadsTable createAlias(String alias) { - return $_ReadsTable(_db, alias); + $ReactionsTable createAlias(String alias) { + return $ReactionsTable(_db, alias); + } + + static TypeConverter, String> $converter0 = + MapConverter(); +} + +class UserEntity extends DataClass implements Insertable { + final String id; + final String role; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime lastActive; + final bool online; + final bool banned; + final Map extraData; + UserEntity( + {@required this.id, + this.role, + this.createdAt, + this.updatedAt, + this.lastActive, + this.online, + this.banned, + this.extraData}); + factory UserEntity.fromData(Map data, GeneratedDatabase db, + {String prefix}) { + final effectivePrefix = prefix ?? ''; + final stringType = db.typeSystem.forDartType(); + final dateTimeType = db.typeSystem.forDartType(); + final boolType = db.typeSystem.forDartType(); + return UserEntity( + id: stringType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + role: stringType.mapFromDatabaseResponse(data['${effectivePrefix}role']), + createdAt: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), + updatedAt: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}updated_at']), + lastActive: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}last_active']), + online: + boolType.mapFromDatabaseResponse(data['${effectivePrefix}online']), + banned: + boolType.mapFromDatabaseResponse(data['${effectivePrefix}banned']), + extraData: $UsersTable.$converter0.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), + ); + } + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || id != null) { + map['id'] = Variable(id); + } + if (!nullToAbsent || role != null) { + map['role'] = Variable(role); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || updatedAt != null) { + map['updated_at'] = Variable(updatedAt); + } + if (!nullToAbsent || lastActive != null) { + map['last_active'] = Variable(lastActive); + } + if (!nullToAbsent || online != null) { + map['online'] = Variable(online); + } + if (!nullToAbsent || banned != null) { + map['banned'] = Variable(banned); + } + if (!nullToAbsent || extraData != null) { + final converter = $UsersTable.$converter0; + map['extra_data'] = Variable(converter.mapToSql(extraData)); + } + return map; + } + + factory UserEntity.fromJson(Map json, + {ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return UserEntity( + id: serializer.fromJson(json['id']), + role: serializer.fromJson(json['role']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + lastActive: serializer.fromJson(json['lastActive']), + online: serializer.fromJson(json['online']), + banned: serializer.fromJson(json['banned']), + extraData: serializer.fromJson>(json['extraData']), + ); + } + @override + Map toJson({ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'role': serializer.toJson(role), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'lastActive': serializer.toJson(lastActive), + 'online': serializer.toJson(online), + 'banned': serializer.toJson(banned), + 'extraData': serializer.toJson>(extraData), + }; + } + + UserEntity copyWith( + {String id, + Value role = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + Value online = const Value.absent(), + Value banned = const Value.absent(), + Value> extraData = const Value.absent()}) => + UserEntity( + id: id ?? this.id, + role: role.present ? role.value : this.role, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + lastActive: lastActive.present ? lastActive.value : this.lastActive, + online: online.present ? online.value : this.online, + banned: banned.present ? banned.value : this.banned, + extraData: extraData.present ? extraData.value : this.extraData, + ); + @override + String toString() { + return (StringBuffer('UserEntity(') + ..write('id: $id, ') + ..write('role: $role, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('lastActive: $lastActive, ') + ..write('online: $online, ') + ..write('banned: $banned, ') + ..write('extraData: $extraData') + ..write(')')) + .toString(); + } + + @override + int get hashCode => $mrjf($mrjc( + id.hashCode, + $mrjc( + role.hashCode, + $mrjc( + createdAt.hashCode, + $mrjc( + updatedAt.hashCode, + $mrjc( + lastActive.hashCode, + $mrjc(online.hashCode, + $mrjc(banned.hashCode, extraData.hashCode)))))))); + @override + bool operator ==(dynamic other) => + identical(this, other) || + (other is UserEntity && + other.id == this.id && + other.role == this.role && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.lastActive == this.lastActive && + other.online == this.online && + other.banned == this.banned && + other.extraData == this.extraData); +} + +class UsersCompanion extends UpdateCompanion { + final Value id; + final Value role; + final Value createdAt; + final Value updatedAt; + final Value lastActive; + final Value online; + final Value banned; + final Value> extraData; + const UsersCompanion({ + this.id = const Value.absent(), + this.role = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.lastActive = const Value.absent(), + this.online = const Value.absent(), + this.banned = const Value.absent(), + this.extraData = const Value.absent(), + }); + UsersCompanion.insert({ + @required String id, + this.role = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.lastActive = const Value.absent(), + this.online = const Value.absent(), + this.banned = const Value.absent(), + this.extraData = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression id, + Expression role, + Expression createdAt, + Expression updatedAt, + Expression lastActive, + Expression online, + Expression banned, + Expression extraData, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (role != null) 'role': role, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (lastActive != null) 'last_active': lastActive, + if (online != null) 'online': online, + if (banned != null) 'banned': banned, + if (extraData != null) 'extra_data': extraData, + }); + } + + UsersCompanion copyWith( + {Value id, + Value role, + Value createdAt, + Value updatedAt, + Value lastActive, + Value online, + Value banned, + Value> extraData}) { + return UsersCompanion( + id: id ?? this.id, + role: role ?? this.role, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + banned: banned ?? this.banned, + extraData: extraData ?? this.extraData, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (lastActive.present) { + map['last_active'] = Variable(lastActive.value); + } + if (online.present) { + map['online'] = Variable(online.value); + } + if (banned.present) { + map['banned'] = Variable(banned.value); + } + if (extraData.present) { + final converter = $UsersTable.$converter0; + map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('id: $id, ') + ..write('role: $role, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('lastActive: $lastActive, ') + ..write('online: $online, ') + ..write('banned: $banned, ') + ..write('extraData: $extraData') + ..write(')')) + .toString(); } } -class _Member extends DataClass implements Insertable<_Member> { +class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { + final GeneratedDatabase _db; + final String _alias; + $UsersTable(this._db, [this._alias]); + final VerificationMeta _idMeta = const VerificationMeta('id'); + GeneratedTextColumn _id; + @override + GeneratedTextColumn get id => _id ??= _constructId(); + GeneratedTextColumn _constructId() { + return GeneratedTextColumn( + 'id', + $tableName, + false, + ); + } + + final VerificationMeta _roleMeta = const VerificationMeta('role'); + GeneratedTextColumn _role; + @override + GeneratedTextColumn get role => _role ??= _constructRole(); + GeneratedTextColumn _constructRole() { + return GeneratedTextColumn( + 'role', + $tableName, + true, + ); + } + + final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); + GeneratedDateTimeColumn _createdAt; + @override + GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); + GeneratedDateTimeColumn _constructCreatedAt() { + return GeneratedDateTimeColumn( + 'created_at', + $tableName, + true, + ); + } + + final VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); + GeneratedDateTimeColumn _updatedAt; + @override + GeneratedDateTimeColumn get updatedAt => _updatedAt ??= _constructUpdatedAt(); + GeneratedDateTimeColumn _constructUpdatedAt() { + return GeneratedDateTimeColumn( + 'updated_at', + $tableName, + true, + ); + } + + final VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); + GeneratedDateTimeColumn _lastActive; + @override + GeneratedDateTimeColumn get lastActive => + _lastActive ??= _constructLastActive(); + GeneratedDateTimeColumn _constructLastActive() { + return GeneratedDateTimeColumn( + 'last_active', + $tableName, + true, + ); + } + + final VerificationMeta _onlineMeta = const VerificationMeta('online'); + GeneratedBoolColumn _online; + @override + GeneratedBoolColumn get online => _online ??= _constructOnline(); + GeneratedBoolColumn _constructOnline() { + return GeneratedBoolColumn( + 'online', + $tableName, + true, + ); + } + + final VerificationMeta _bannedMeta = const VerificationMeta('banned'); + GeneratedBoolColumn _banned; + @override + GeneratedBoolColumn get banned => _banned ??= _constructBanned(); + GeneratedBoolColumn _constructBanned() { + return GeneratedBoolColumn( + 'banned', + $tableName, + true, + ); + } + + final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); + GeneratedTextColumn _extraData; + @override + GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); + GeneratedTextColumn _constructExtraData() { + return GeneratedTextColumn( + 'extra_data', + $tableName, + true, + ); + } + + @override + List get $columns => + [id, role, createdAt, updatedAt, lastActive, online, banned, extraData]; + @override + $UsersTable get asDslTable => this; + @override + String get $tableName => _alias ?? 'users'; + @override + final String actualTableName = 'users'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('role')) { + context.handle( + _roleMeta, role.isAcceptableOrUnknown(data['role'], _roleMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at'], _updatedAtMeta)); + } + if (data.containsKey('last_active')) { + context.handle( + _lastActiveMeta, + lastActive.isAcceptableOrUnknown( + data['last_active'], _lastActiveMeta)); + } + if (data.containsKey('online')) { + context.handle(_onlineMeta, + online.isAcceptableOrUnknown(data['online'], _onlineMeta)); + } + if (data.containsKey('banned')) { + context.handle(_bannedMeta, + banned.isAcceptableOrUnknown(data['banned'], _bannedMeta)); + } + context.handle(_extraDataMeta, const VerificationResult.success()); + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserEntity map(Map data, {String tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; + return UserEntity.fromData(data, _db, prefix: effectivePrefix); + } + + @override + $UsersTable createAlias(String alias) { + return $UsersTable(_db, alias); + } + + static TypeConverter, String> $converter0 = + MapConverter(); +} + +class MemberEntity extends DataClass implements Insertable { final String userId; final String channelCid; final String role; @@ -2899,7 +2508,7 @@ class _Member extends DataClass implements Insertable<_Member> { final bool isModerator; final DateTime createdAt; final DateTime updatedAt; - _Member( + MemberEntity( {@required this.userId, @required this.channelCid, this.role, @@ -2911,13 +2520,13 @@ class _Member extends DataClass implements Insertable<_Member> { this.isModerator, @required this.createdAt, this.updatedAt}); - factory _Member.fromData(Map data, GeneratedDatabase db, + factory MemberEntity.fromData(Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); final boolType = db.typeSystem.forDartType(); - return _Member( + return MemberEntity( userId: stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), channelCid: stringType @@ -2980,44 +2589,10 @@ class _Member extends DataClass implements Insertable<_Member> { return map; } - _MembersCompanion toCompanion(bool nullToAbsent) { - return _MembersCompanion( - userId: - userId == null && nullToAbsent ? const Value.absent() : Value(userId), - channelCid: channelCid == null && nullToAbsent - ? const Value.absent() - : Value(channelCid), - role: role == null && nullToAbsent ? const Value.absent() : Value(role), - inviteAcceptedAt: inviteAcceptedAt == null && nullToAbsent - ? const Value.absent() - : Value(inviteAcceptedAt), - inviteRejectedAt: inviteRejectedAt == null && nullToAbsent - ? const Value.absent() - : Value(inviteRejectedAt), - invited: invited == null && nullToAbsent - ? const Value.absent() - : Value(invited), - banned: - banned == null && nullToAbsent ? const Value.absent() : Value(banned), - shadowBanned: shadowBanned == null && nullToAbsent - ? const Value.absent() - : Value(shadowBanned), - isModerator: isModerator == null && nullToAbsent - ? const Value.absent() - : Value(isModerator), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), - updatedAt: updatedAt == null && nullToAbsent - ? const Value.absent() - : Value(updatedAt), - ); - } - - factory _Member.fromJson(Map json, + factory MemberEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return _Member( + return MemberEntity( userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), role: serializer.fromJson(json['role']), @@ -3049,34 +2624,39 @@ class _Member extends DataClass implements Insertable<_Member> { }; } - _Member copyWith( + MemberEntity copyWith( {String userId, String channelCid, - String role, - DateTime inviteAcceptedAt, - DateTime inviteRejectedAt, - bool invited, - bool banned, - bool shadowBanned, - bool isModerator, + Value role = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + Value invited = const Value.absent(), + Value banned = const Value.absent(), + Value shadowBanned = const Value.absent(), + Value isModerator = const Value.absent(), DateTime createdAt, - DateTime updatedAt}) => - _Member( + Value updatedAt = const Value.absent()}) => + MemberEntity( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, - role: role ?? this.role, - inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt, - invited: invited ?? this.invited, - banned: banned ?? this.banned, - shadowBanned: shadowBanned ?? this.shadowBanned, - isModerator: isModerator ?? this.isModerator, + role: role.present ? role.value : this.role, + inviteAcceptedAt: inviteAcceptedAt.present + ? inviteAcceptedAt.value + : this.inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt.present + ? inviteRejectedAt.value + : this.inviteRejectedAt, + invited: invited.present ? invited.value : this.invited, + banned: banned.present ? banned.value : this.banned, + shadowBanned: + shadowBanned.present ? shadowBanned.value : this.shadowBanned, + isModerator: isModerator.present ? isModerator.value : this.isModerator, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, ); @override String toString() { - return (StringBuffer('_Member(') + return (StringBuffer('MemberEntity(') ..write('userId: $userId, ') ..write('channelCid: $channelCid, ') ..write('role: $role, ') @@ -3116,7 +2696,7 @@ class _Member extends DataClass implements Insertable<_Member> { @override bool operator ==(dynamic other) => identical(this, other) || - (other is _Member && + (other is MemberEntity && other.userId == this.userId && other.channelCid == this.channelCid && other.role == this.role && @@ -3130,7 +2710,7 @@ class _Member extends DataClass implements Insertable<_Member> { other.updatedAt == this.updatedAt); } -class _MembersCompanion extends UpdateCompanion<_Member> { +class MembersCompanion extends UpdateCompanion { final Value userId; final Value channelCid; final Value role; @@ -3142,7 +2722,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { final Value isModerator; final Value createdAt; final Value updatedAt; - const _MembersCompanion({ + const MembersCompanion({ this.userId = const Value.absent(), this.channelCid = const Value.absent(), this.role = const Value.absent(), @@ -3155,7 +2735,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), }); - _MembersCompanion.insert({ + MembersCompanion.insert({ @required String userId, @required String channelCid, this.role = const Value.absent(), @@ -3170,7 +2750,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { }) : userId = Value(userId), channelCid = Value(channelCid), createdAt = Value(createdAt); - static Insertable<_Member> custom({ + static Insertable custom({ Expression userId, Expression channelCid, Expression role, @@ -3198,7 +2778,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { }); } - _MembersCompanion copyWith( + MembersCompanion copyWith( {Value userId, Value channelCid, Value role, @@ -3210,7 +2790,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { Value isModerator, Value createdAt, Value updatedAt}) { - return _MembersCompanion( + return MembersCompanion( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, role: role ?? this.role, @@ -3266,7 +2846,7 @@ class _MembersCompanion extends UpdateCompanion<_Member> { @override String toString() { - return (StringBuffer('_MembersCompanion(') + return (StringBuffer('MembersCompanion(') ..write('userId: $userId, ') ..write('channelCid: $channelCid, ') ..write('role: $role, ') @@ -3283,10 +2863,11 @@ class _MembersCompanion extends UpdateCompanion<_Member> { } } -class $_MembersTable extends _Members with TableInfo<$_MembersTable, _Member> { +class $MembersTable extends Members + with TableInfo<$MembersTable, MemberEntity> { final GeneratedDatabase _db; final String _alias; - $_MembersTable(this._db, [this._alias]); + $MembersTable(this._db, [this._alias]); final VerificationMeta _userIdMeta = const VerificationMeta('userId'); GeneratedTextColumn _userId; @override @@ -3304,11 +2885,8 @@ class $_MembersTable extends _Members with TableInfo<$_MembersTable, _Member> { @override GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); GeneratedTextColumn _constructChannelCid() { - return GeneratedTextColumn( - 'channel_cid', - $tableName, - false, - ); + return GeneratedTextColumn('channel_cid', $tableName, false, + $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); } final VerificationMeta _roleMeta = const VerificationMeta('role'); @@ -3442,13 +3020,13 @@ class $_MembersTable extends _Members with TableInfo<$_MembersTable, _Member> { updatedAt ]; @override - $_MembersTable get asDslTable => this; + $MembersTable get asDslTable => this; @override String get $tableName => _alias ?? 'members'; @override final String actualTableName = 'members'; @override - VerificationContext validateIntegrity(Insertable<_Member> instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); @@ -3518,26 +3096,316 @@ class $_MembersTable extends _Members with TableInfo<$_MembersTable, _Member> { @override Set get $primaryKey => {userId, channelCid}; @override - _Member map(Map data, {String tablePrefix}) { + MemberEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _Member.fromData(data, _db, prefix: effectivePrefix); + return MemberEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_MembersTable createAlias(String alias) { - return $_MembersTable(_db, alias); + $MembersTable createAlias(String alias) { + return $MembersTable(_db, alias); } } -class ChannelQuery extends DataClass implements Insertable { +class ReadEntity extends DataClass implements Insertable { + final DateTime lastRead; + final String userId; + final String channelCid; + final int unreadMessages; + ReadEntity( + {@required this.lastRead, + @required this.userId, + @required this.channelCid, + this.unreadMessages}); + factory ReadEntity.fromData(Map data, GeneratedDatabase db, + {String prefix}) { + final effectivePrefix = prefix ?? ''; + final dateTimeType = db.typeSystem.forDartType(); + final stringType = db.typeSystem.forDartType(); + final intType = db.typeSystem.forDartType(); + return ReadEntity( + lastRead: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}last_read']), + userId: + stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + channelCid: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), + unreadMessages: intType + .mapFromDatabaseResponse(data['${effectivePrefix}unread_messages']), + ); + } + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || lastRead != null) { + map['last_read'] = Variable(lastRead); + } + if (!nullToAbsent || userId != null) { + map['user_id'] = Variable(userId); + } + if (!nullToAbsent || channelCid != null) { + map['channel_cid'] = Variable(channelCid); + } + if (!nullToAbsent || unreadMessages != null) { + map['unread_messages'] = Variable(unreadMessages); + } + return map; + } + + factory ReadEntity.fromJson(Map json, + {ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return ReadEntity( + lastRead: serializer.fromJson(json['lastRead']), + userId: serializer.fromJson(json['userId']), + channelCid: serializer.fromJson(json['channelCid']), + unreadMessages: serializer.fromJson(json['unreadMessages']), + ); + } + @override + Map toJson({ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'lastRead': serializer.toJson(lastRead), + 'userId': serializer.toJson(userId), + 'channelCid': serializer.toJson(channelCid), + 'unreadMessages': serializer.toJson(unreadMessages), + }; + } + + ReadEntity copyWith( + {DateTime lastRead, + String userId, + String channelCid, + Value unreadMessages = const Value.absent()}) => + ReadEntity( + lastRead: lastRead ?? this.lastRead, + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + unreadMessages: + unreadMessages.present ? unreadMessages.value : this.unreadMessages, + ); + @override + String toString() { + return (StringBuffer('ReadEntity(') + ..write('lastRead: $lastRead, ') + ..write('userId: $userId, ') + ..write('channelCid: $channelCid, ') + ..write('unreadMessages: $unreadMessages') + ..write(')')) + .toString(); + } + + @override + int get hashCode => $mrjf($mrjc( + lastRead.hashCode, + $mrjc(userId.hashCode, + $mrjc(channelCid.hashCode, unreadMessages.hashCode)))); + @override + bool operator ==(dynamic other) => + identical(this, other) || + (other is ReadEntity && + other.lastRead == this.lastRead && + other.userId == this.userId && + other.channelCid == this.channelCid && + other.unreadMessages == this.unreadMessages); +} + +class ReadsCompanion extends UpdateCompanion { + final Value lastRead; + final Value userId; + final Value channelCid; + final Value unreadMessages; + const ReadsCompanion({ + this.lastRead = const Value.absent(), + this.userId = const Value.absent(), + this.channelCid = const Value.absent(), + this.unreadMessages = const Value.absent(), + }); + ReadsCompanion.insert({ + @required DateTime lastRead, + @required String userId, + @required String channelCid, + this.unreadMessages = const Value.absent(), + }) : lastRead = Value(lastRead), + userId = Value(userId), + channelCid = Value(channelCid); + static Insertable custom({ + Expression lastRead, + Expression userId, + Expression channelCid, + Expression unreadMessages, + }) { + return RawValuesInsertable({ + if (lastRead != null) 'last_read': lastRead, + if (userId != null) 'user_id': userId, + if (channelCid != null) 'channel_cid': channelCid, + if (unreadMessages != null) 'unread_messages': unreadMessages, + }); + } + + ReadsCompanion copyWith( + {Value lastRead, + Value userId, + Value channelCid, + Value unreadMessages}) { + return ReadsCompanion( + lastRead: lastRead ?? this.lastRead, + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + unreadMessages: unreadMessages ?? this.unreadMessages, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (lastRead.present) { + map['last_read'] = Variable(lastRead.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (channelCid.present) { + map['channel_cid'] = Variable(channelCid.value); + } + if (unreadMessages.present) { + map['unread_messages'] = Variable(unreadMessages.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ReadsCompanion(') + ..write('lastRead: $lastRead, ') + ..write('userId: $userId, ') + ..write('channelCid: $channelCid, ') + ..write('unreadMessages: $unreadMessages') + ..write(')')) + .toString(); + } +} + +class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { + final GeneratedDatabase _db; + final String _alias; + $ReadsTable(this._db, [this._alias]); + final VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); + GeneratedDateTimeColumn _lastRead; + @override + GeneratedDateTimeColumn get lastRead => _lastRead ??= _constructLastRead(); + GeneratedDateTimeColumn _constructLastRead() { + return GeneratedDateTimeColumn( + 'last_read', + $tableName, + false, + ); + } + + final VerificationMeta _userIdMeta = const VerificationMeta('userId'); + GeneratedTextColumn _userId; + @override + GeneratedTextColumn get userId => _userId ??= _constructUserId(); + GeneratedTextColumn _constructUserId() { + return GeneratedTextColumn( + 'user_id', + $tableName, + false, + ); + } + + final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); + GeneratedTextColumn _channelCid; + @override + GeneratedTextColumn get channelCid => _channelCid ??= _constructChannelCid(); + GeneratedTextColumn _constructChannelCid() { + return GeneratedTextColumn('channel_cid', $tableName, false, + $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE'); + } + + final VerificationMeta _unreadMessagesMeta = + const VerificationMeta('unreadMessages'); + GeneratedIntColumn _unreadMessages; + @override + GeneratedIntColumn get unreadMessages => + _unreadMessages ??= _constructUnreadMessages(); + GeneratedIntColumn _constructUnreadMessages() { + return GeneratedIntColumn( + 'unread_messages', + $tableName, + true, + ); + } + + @override + List get $columns => + [lastRead, userId, channelCid, unreadMessages]; + @override + $ReadsTable get asDslTable => this; + @override + String get $tableName => _alias ?? 'reads'; + @override + final String actualTableName = 'reads'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('last_read')) { + context.handle(_lastReadMeta, + lastRead.isAcceptableOrUnknown(data['last_read'], _lastReadMeta)); + } else if (isInserting) { + context.missing(_lastReadMeta); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('channel_cid')) { + context.handle( + _channelCidMeta, + channelCid.isAcceptableOrUnknown( + data['channel_cid'], _channelCidMeta)); + } else if (isInserting) { + context.missing(_channelCidMeta); + } + if (data.containsKey('unread_messages')) { + context.handle( + _unreadMessagesMeta, + unreadMessages.isAcceptableOrUnknown( + data['unread_messages'], _unreadMessagesMeta)); + } + return context; + } + + @override + Set get $primaryKey => {userId, channelCid}; + @override + ReadEntity map(Map data, {String tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; + return ReadEntity.fromData(data, _db, prefix: effectivePrefix); + } + + @override + $ReadsTable createAlias(String alias) { + return $ReadsTable(_db, alias); + } +} + +class ChannelQueryEntity extends DataClass + implements Insertable { final String queryHash; final String channelCid; - ChannelQuery({@required this.queryHash, @required this.channelCid}); - factory ChannelQuery.fromData(Map data, GeneratedDatabase db, + ChannelQueryEntity({@required this.queryHash, @required this.channelCid}); + factory ChannelQueryEntity.fromData( + Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; final stringType = db.typeSystem.forDartType(); - return ChannelQuery( + return ChannelQueryEntity( queryHash: stringType .mapFromDatabaseResponse(data['${effectivePrefix}query_hash']), channelCid: stringType @@ -3556,21 +3424,10 @@ class ChannelQuery extends DataClass implements Insertable { return map; } - _ChannelQueriesCompanion toCompanion(bool nullToAbsent) { - return _ChannelQueriesCompanion( - queryHash: queryHash == null && nullToAbsent - ? const Value.absent() - : Value(queryHash), - channelCid: channelCid == null && nullToAbsent - ? const Value.absent() - : Value(channelCid), - ); - } - - factory ChannelQuery.fromJson(Map json, + factory ChannelQueryEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return ChannelQuery( + return ChannelQueryEntity( queryHash: serializer.fromJson(json['queryHash']), channelCid: serializer.fromJson(json['channelCid']), ); @@ -3584,13 +3441,14 @@ class ChannelQuery extends DataClass implements Insertable { }; } - ChannelQuery copyWith({String queryHash, String channelCid}) => ChannelQuery( + ChannelQueryEntity copyWith({String queryHash, String channelCid}) => + ChannelQueryEntity( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, ); @override String toString() { - return (StringBuffer('ChannelQuery(') + return (StringBuffer('ChannelQueryEntity(') ..write('queryHash: $queryHash, ') ..write('channelCid: $channelCid') ..write(')')) @@ -3602,24 +3460,24 @@ class ChannelQuery extends DataClass implements Insertable { @override bool operator ==(dynamic other) => identical(this, other) || - (other is ChannelQuery && + (other is ChannelQueryEntity && other.queryHash == this.queryHash && other.channelCid == this.channelCid); } -class _ChannelQueriesCompanion extends UpdateCompanion { +class ChannelQueriesCompanion extends UpdateCompanion { final Value queryHash; final Value channelCid; - const _ChannelQueriesCompanion({ + const ChannelQueriesCompanion({ this.queryHash = const Value.absent(), this.channelCid = const Value.absent(), }); - _ChannelQueriesCompanion.insert({ + ChannelQueriesCompanion.insert({ @required String queryHash, @required String channelCid, }) : queryHash = Value(queryHash), channelCid = Value(channelCid); - static Insertable custom({ + static Insertable custom({ Expression queryHash, Expression channelCid, }) { @@ -3629,9 +3487,9 @@ class _ChannelQueriesCompanion extends UpdateCompanion { }); } - _ChannelQueriesCompanion copyWith( + ChannelQueriesCompanion copyWith( {Value queryHash, Value channelCid}) { - return _ChannelQueriesCompanion( + return ChannelQueriesCompanion( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, ); @@ -3651,7 +3509,7 @@ class _ChannelQueriesCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('_ChannelQueriesCompanion(') + return (StringBuffer('ChannelQueriesCompanion(') ..write('queryHash: $queryHash, ') ..write('channelCid: $channelCid') ..write(')')) @@ -3659,11 +3517,11 @@ class _ChannelQueriesCompanion extends UpdateCompanion { } } -class $_ChannelQueriesTable extends _ChannelQueries - with TableInfo<$_ChannelQueriesTable, ChannelQuery> { +class $ChannelQueriesTable extends ChannelQueries + with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { final GeneratedDatabase _db; final String _alias; - $_ChannelQueriesTable(this._db, [this._alias]); + $ChannelQueriesTable(this._db, [this._alias]); final VerificationMeta _queryHashMeta = const VerificationMeta('queryHash'); GeneratedTextColumn _queryHash; @override @@ -3691,13 +3549,13 @@ class $_ChannelQueriesTable extends _ChannelQueries @override List get $columns => [queryHash, channelCid]; @override - $_ChannelQueriesTable get asDslTable => this; + $ChannelQueriesTable get asDslTable => this; @override String get $tableName => _alias ?? 'channel_queries'; @override final String actualTableName = 'channel_queries'; @override - VerificationContext validateIntegrity(Insertable instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); @@ -3721,340 +3579,331 @@ class $_ChannelQueriesTable extends _ChannelQueries @override Set get $primaryKey => {queryHash, channelCid}; @override - ChannelQuery map(Map data, {String tablePrefix}) { + ChannelQueryEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return ChannelQuery.fromData(data, _db, prefix: effectivePrefix); + return ChannelQueryEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_ChannelQueriesTable createAlias(String alias) { - return $_ChannelQueriesTable(_db, alias); + $ChannelQueriesTable createAlias(String alias) { + return $ChannelQueriesTable(_db, alias); } } -class _Reaction extends DataClass implements Insertable<_Reaction> { - final String messageId; - final String type; - final DateTime createdAt; - final int score; - final String userId; - final Map extraData; - _Reaction( - {@required this.messageId, - @required this.type, - @required this.createdAt, - this.score, - @required this.userId, - this.extraData}); - factory _Reaction.fromData(Map data, GeneratedDatabase db, +class ConnectionEventEntity extends DataClass + implements Insertable { + final int id; + final Map ownUser; + final int totalUnreadCount; + final int unreadChannels; + final DateTime lastEventAt; + final DateTime lastSyncAt; + ConnectionEventEntity( + {@required this.id, + this.ownUser, + this.totalUnreadCount, + this.unreadChannels, + this.lastEventAt, + this.lastSyncAt}); + factory ConnectionEventEntity.fromData( + Map data, GeneratedDatabase db, {String prefix}) { final effectivePrefix = prefix ?? ''; + final intType = db.typeSystem.forDartType(); final stringType = db.typeSystem.forDartType(); final dateTimeType = db.typeSystem.forDartType(); - final intType = db.typeSystem.forDartType(); - return _Reaction( - messageId: stringType - .mapFromDatabaseResponse(data['${effectivePrefix}message_id']), - type: stringType.mapFromDatabaseResponse(data['${effectivePrefix}type']), - createdAt: dateTimeType - .mapFromDatabaseResponse(data['${effectivePrefix}created_at']), - score: intType.mapFromDatabaseResponse(data['${effectivePrefix}score']), - userId: - stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), - extraData: $_ReactionsTable.$converter0.mapToDart(stringType - .mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])), + return ConnectionEventEntity( + id: intType.mapFromDatabaseResponse(data['${effectivePrefix}id']), + ownUser: $ConnectionEventsTable.$converter0.mapToDart(stringType + .mapFromDatabaseResponse(data['${effectivePrefix}own_user'])), + totalUnreadCount: intType.mapFromDatabaseResponse( + data['${effectivePrefix}total_unread_count']), + unreadChannels: intType + .mapFromDatabaseResponse(data['${effectivePrefix}unread_channels']), + lastEventAt: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}last_event_at']), + lastSyncAt: dateTimeType + .mapFromDatabaseResponse(data['${effectivePrefix}last_sync_at']), ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || messageId != null) { - map['message_id'] = Variable(messageId); + if (!nullToAbsent || id != null) { + map['id'] = Variable(id); } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); + if (!nullToAbsent || ownUser != null) { + final converter = $ConnectionEventsTable.$converter0; + map['own_user'] = Variable(converter.mapToSql(ownUser)); } - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); + if (!nullToAbsent || totalUnreadCount != null) { + map['total_unread_count'] = Variable(totalUnreadCount); } - if (!nullToAbsent || score != null) { - map['score'] = Variable(score); + if (!nullToAbsent || unreadChannels != null) { + map['unread_channels'] = Variable(unreadChannels); } - if (!nullToAbsent || userId != null) { - map['user_id'] = Variable(userId); + if (!nullToAbsent || lastEventAt != null) { + map['last_event_at'] = Variable(lastEventAt); } - if (!nullToAbsent || extraData != null) { - final converter = $_ReactionsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData)); + if (!nullToAbsent || lastSyncAt != null) { + map['last_sync_at'] = Variable(lastSyncAt); } return map; } - _ReactionsCompanion toCompanion(bool nullToAbsent) { - return _ReactionsCompanion( - messageId: messageId == null && nullToAbsent - ? const Value.absent() - : Value(messageId), - type: type == null && nullToAbsent ? const Value.absent() : Value(type), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), - score: - score == null && nullToAbsent ? const Value.absent() : Value(score), - userId: - userId == null && nullToAbsent ? const Value.absent() : Value(userId), - extraData: extraData == null && nullToAbsent - ? const Value.absent() - : Value(extraData), - ); - } - - factory _Reaction.fromJson(Map json, + factory ConnectionEventEntity.fromJson(Map json, {ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; - return _Reaction( - messageId: serializer.fromJson(json['messageId']), - type: serializer.fromJson(json['type']), - createdAt: serializer.fromJson(json['createdAt']), - score: serializer.fromJson(json['score']), - userId: serializer.fromJson(json['userId']), - extraData: serializer.fromJson>(json['extraData']), + return ConnectionEventEntity( + id: serializer.fromJson(json['id']), + ownUser: serializer.fromJson>(json['ownUser']), + totalUnreadCount: serializer.fromJson(json['totalUnreadCount']), + unreadChannels: serializer.fromJson(json['unreadChannels']), + lastEventAt: serializer.fromJson(json['lastEventAt']), + lastSyncAt: serializer.fromJson(json['lastSyncAt']), ); } @override Map toJson({ValueSerializer serializer}) { serializer ??= moorRuntimeOptions.defaultSerializer; return { - 'messageId': serializer.toJson(messageId), - 'type': serializer.toJson(type), - 'createdAt': serializer.toJson(createdAt), - 'score': serializer.toJson(score), - 'userId': serializer.toJson(userId), - 'extraData': serializer.toJson>(extraData), + 'id': serializer.toJson(id), + 'ownUser': serializer.toJson>(ownUser), + 'totalUnreadCount': serializer.toJson(totalUnreadCount), + 'unreadChannels': serializer.toJson(unreadChannels), + 'lastEventAt': serializer.toJson(lastEventAt), + 'lastSyncAt': serializer.toJson(lastSyncAt), }; } - _Reaction copyWith( - {String messageId, - String type, - DateTime createdAt, - int score, - String userId, - Map extraData}) => - _Reaction( - messageId: messageId ?? this.messageId, - type: type ?? this.type, - createdAt: createdAt ?? this.createdAt, - score: score ?? this.score, - userId: userId ?? this.userId, - extraData: extraData ?? this.extraData, + ConnectionEventEntity copyWith( + {int id, + Value> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent()}) => + ConnectionEventEntity( + id: id ?? this.id, + ownUser: ownUser.present ? ownUser.value : this.ownUser, + totalUnreadCount: totalUnreadCount.present + ? totalUnreadCount.value + : this.totalUnreadCount, + unreadChannels: + unreadChannels.present ? unreadChannels.value : this.unreadChannels, + lastEventAt: lastEventAt.present ? lastEventAt.value : this.lastEventAt, + lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, ); @override String toString() { - return (StringBuffer('_Reaction(') - ..write('messageId: $messageId, ') - ..write('type: $type, ') - ..write('createdAt: $createdAt, ') - ..write('score: $score, ') - ..write('userId: $userId, ') - ..write('extraData: $extraData') + return (StringBuffer('ConnectionEventEntity(') + ..write('id: $id, ') + ..write('ownUser: $ownUser, ') + ..write('totalUnreadCount: $totalUnreadCount, ') + ..write('unreadChannels: $unreadChannels, ') + ..write('lastEventAt: $lastEventAt, ') + ..write('lastSyncAt: $lastSyncAt') ..write(')')) .toString(); } @override int get hashCode => $mrjf($mrjc( - messageId.hashCode, + id.hashCode, $mrjc( - type.hashCode, + ownUser.hashCode, $mrjc( - createdAt.hashCode, - $mrjc(score.hashCode, - $mrjc(userId.hashCode, extraData.hashCode)))))); + totalUnreadCount.hashCode, + $mrjc(unreadChannels.hashCode, + $mrjc(lastEventAt.hashCode, lastSyncAt.hashCode)))))); @override bool operator ==(dynamic other) => identical(this, other) || - (other is _Reaction && - other.messageId == this.messageId && - other.type == this.type && - other.createdAt == this.createdAt && - other.score == this.score && - other.userId == this.userId && - other.extraData == this.extraData); + (other is ConnectionEventEntity && + other.id == this.id && + other.ownUser == this.ownUser && + other.totalUnreadCount == this.totalUnreadCount && + other.unreadChannels == this.unreadChannels && + other.lastEventAt == this.lastEventAt && + other.lastSyncAt == this.lastSyncAt); } -class _ReactionsCompanion extends UpdateCompanion<_Reaction> { - final Value messageId; - final Value type; - final Value createdAt; - final Value score; - final Value userId; - final Value> extraData; - const _ReactionsCompanion({ - this.messageId = const Value.absent(), - this.type = const Value.absent(), - this.createdAt = const Value.absent(), - this.score = const Value.absent(), - this.userId = const Value.absent(), - this.extraData = const Value.absent(), +class ConnectionEventsCompanion extends UpdateCompanion { + final Value id; + final Value> ownUser; + final Value totalUnreadCount; + final Value unreadChannels; + final Value lastEventAt; + final Value lastSyncAt; + const ConnectionEventsCompanion({ + this.id = const Value.absent(), + this.ownUser = const Value.absent(), + this.totalUnreadCount = const Value.absent(), + this.unreadChannels = const Value.absent(), + this.lastEventAt = const Value.absent(), + this.lastSyncAt = const Value.absent(), }); - _ReactionsCompanion.insert({ - @required String messageId, - @required String type, - @required DateTime createdAt, - this.score = const Value.absent(), - @required String userId, - this.extraData = const Value.absent(), - }) : messageId = Value(messageId), - type = Value(type), - createdAt = Value(createdAt), - userId = Value(userId); - static Insertable<_Reaction> custom({ - Expression messageId, - Expression type, - Expression createdAt, - Expression score, - Expression userId, - Expression extraData, + ConnectionEventsCompanion.insert({ + this.id = const Value.absent(), + this.ownUser = const Value.absent(), + this.totalUnreadCount = const Value.absent(), + this.unreadChannels = const Value.absent(), + this.lastEventAt = const Value.absent(), + this.lastSyncAt = const Value.absent(), + }); + static Insertable custom({ + Expression id, + Expression ownUser, + Expression totalUnreadCount, + Expression unreadChannels, + Expression lastEventAt, + Expression lastSyncAt, }) { return RawValuesInsertable({ - if (messageId != null) 'message_id': messageId, - if (type != null) 'type': type, - if (createdAt != null) 'created_at': createdAt, - if (score != null) 'score': score, - if (userId != null) 'user_id': userId, - if (extraData != null) 'extra_data': extraData, + if (id != null) 'id': id, + if (ownUser != null) 'own_user': ownUser, + if (totalUnreadCount != null) 'total_unread_count': totalUnreadCount, + if (unreadChannels != null) 'unread_channels': unreadChannels, + if (lastEventAt != null) 'last_event_at': lastEventAt, + if (lastSyncAt != null) 'last_sync_at': lastSyncAt, }); } - _ReactionsCompanion copyWith( - {Value messageId, - Value type, - Value createdAt, - Value score, - Value userId, - Value> extraData}) { - return _ReactionsCompanion( - messageId: messageId ?? this.messageId, - type: type ?? this.type, - createdAt: createdAt ?? this.createdAt, - score: score ?? this.score, - userId: userId ?? this.userId, - extraData: extraData ?? this.extraData, + ConnectionEventsCompanion copyWith( + {Value id, + Value> ownUser, + Value totalUnreadCount, + Value unreadChannels, + Value lastEventAt, + Value lastSyncAt}) { + return ConnectionEventsCompanion( + id: id ?? this.id, + ownUser: ownUser ?? this.ownUser, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + lastEventAt: lastEventAt ?? this.lastEventAt, + lastSyncAt: lastSyncAt ?? this.lastSyncAt, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (messageId.present) { - map['message_id'] = Variable(messageId.value); + if (id.present) { + map['id'] = Variable(id.value); } - if (type.present) { - map['type'] = Variable(type.value); + if (ownUser.present) { + final converter = $ConnectionEventsTable.$converter0; + map['own_user'] = Variable(converter.mapToSql(ownUser.value)); } - if (createdAt.present) { - map['created_at'] = Variable(createdAt.value); + if (totalUnreadCount.present) { + map['total_unread_count'] = Variable(totalUnreadCount.value); } - if (score.present) { - map['score'] = Variable(score.value); + if (unreadChannels.present) { + map['unread_channels'] = Variable(unreadChannels.value); } - if (userId.present) { - map['user_id'] = Variable(userId.value); + if (lastEventAt.present) { + map['last_event_at'] = Variable(lastEventAt.value); } - if (extraData.present) { - final converter = $_ReactionsTable.$converter0; - map['extra_data'] = Variable(converter.mapToSql(extraData.value)); + if (lastSyncAt.present) { + map['last_sync_at'] = Variable(lastSyncAt.value); } return map; } @override String toString() { - return (StringBuffer('_ReactionsCompanion(') - ..write('messageId: $messageId, ') - ..write('type: $type, ') - ..write('createdAt: $createdAt, ') - ..write('score: $score, ') - ..write('userId: $userId, ') - ..write('extraData: $extraData') + return (StringBuffer('ConnectionEventsCompanion(') + ..write('id: $id, ') + ..write('ownUser: $ownUser, ') + ..write('totalUnreadCount: $totalUnreadCount, ') + ..write('unreadChannels: $unreadChannels, ') + ..write('lastEventAt: $lastEventAt, ') + ..write('lastSyncAt: $lastSyncAt') ..write(')')) .toString(); } } -class $_ReactionsTable extends _Reactions - with TableInfo<$_ReactionsTable, _Reaction> { +class $ConnectionEventsTable extends ConnectionEvents + with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { final GeneratedDatabase _db; final String _alias; - $_ReactionsTable(this._db, [this._alias]); - final VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); - GeneratedTextColumn _messageId; + $ConnectionEventsTable(this._db, [this._alias]); + final VerificationMeta _idMeta = const VerificationMeta('id'); + GeneratedIntColumn _id; @override - GeneratedTextColumn get messageId => _messageId ??= _constructMessageId(); - GeneratedTextColumn _constructMessageId() { - return GeneratedTextColumn( - 'message_id', - $tableName, - false, - ); - } - - final VerificationMeta _typeMeta = const VerificationMeta('type'); - GeneratedTextColumn _type; - @override - GeneratedTextColumn get type => _type ??= _constructType(); - GeneratedTextColumn _constructType() { - return GeneratedTextColumn( - 'type', - $tableName, - false, - ); - } - - final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); - GeneratedDateTimeColumn _createdAt; - @override - GeneratedDateTimeColumn get createdAt => _createdAt ??= _constructCreatedAt(); - GeneratedDateTimeColumn _constructCreatedAt() { - return GeneratedDateTimeColumn( - 'created_at', - $tableName, - false, - ); - } - - final VerificationMeta _scoreMeta = const VerificationMeta('score'); - GeneratedIntColumn _score; - @override - GeneratedIntColumn get score => _score ??= _constructScore(); - GeneratedIntColumn _constructScore() { + GeneratedIntColumn get id => _id ??= _constructId(); + GeneratedIntColumn _constructId() { return GeneratedIntColumn( - 'score', + 'id', + $tableName, + false, + ); + } + + final VerificationMeta _ownUserMeta = const VerificationMeta('ownUser'); + GeneratedTextColumn _ownUser; + @override + GeneratedTextColumn get ownUser => _ownUser ??= _constructOwnUser(); + GeneratedTextColumn _constructOwnUser() { + return GeneratedTextColumn( + 'own_user', $tableName, true, ); } - final VerificationMeta _userIdMeta = const VerificationMeta('userId'); - GeneratedTextColumn _userId; + final VerificationMeta _totalUnreadCountMeta = + const VerificationMeta('totalUnreadCount'); + GeneratedIntColumn _totalUnreadCount; @override - GeneratedTextColumn get userId => _userId ??= _constructUserId(); - GeneratedTextColumn _constructUserId() { - return GeneratedTextColumn( - 'user_id', + GeneratedIntColumn get totalUnreadCount => + _totalUnreadCount ??= _constructTotalUnreadCount(); + GeneratedIntColumn _constructTotalUnreadCount() { + return GeneratedIntColumn( + 'total_unread_count', $tableName, - false, + true, ); } - final VerificationMeta _extraDataMeta = const VerificationMeta('extraData'); - GeneratedTextColumn _extraData; + final VerificationMeta _unreadChannelsMeta = + const VerificationMeta('unreadChannels'); + GeneratedIntColumn _unreadChannels; @override - GeneratedTextColumn get extraData => _extraData ??= _constructExtraData(); - GeneratedTextColumn _constructExtraData() { - return GeneratedTextColumn( - 'extra_data', + GeneratedIntColumn get unreadChannels => + _unreadChannels ??= _constructUnreadChannels(); + GeneratedIntColumn _constructUnreadChannels() { + return GeneratedIntColumn( + 'unread_channels', + $tableName, + true, + ); + } + + final VerificationMeta _lastEventAtMeta = + const VerificationMeta('lastEventAt'); + GeneratedDateTimeColumn _lastEventAt; + @override + GeneratedDateTimeColumn get lastEventAt => + _lastEventAt ??= _constructLastEventAt(); + GeneratedDateTimeColumn _constructLastEventAt() { + return GeneratedDateTimeColumn( + 'last_event_at', + $tableName, + true, + ); + } + + final VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); + GeneratedDateTimeColumn _lastSyncAt; + @override + GeneratedDateTimeColumn get lastSyncAt => + _lastSyncAt ??= _constructLastSyncAt(); + GeneratedDateTimeColumn _constructLastSyncAt() { + return GeneratedDateTimeColumn( + 'last_sync_at', $tableName, true, ); @@ -4062,99 +3911,120 @@ class $_ReactionsTable extends _Reactions @override List get $columns => - [messageId, type, createdAt, score, userId, extraData]; + [id, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt]; @override - $_ReactionsTable get asDslTable => this; + $ConnectionEventsTable get asDslTable => this; @override - String get $tableName => _alias ?? 'reactions'; + String get $tableName => _alias ?? 'connection_events'; @override - final String actualTableName = 'reactions'; + final String actualTableName = 'connection_events'; @override - VerificationContext validateIntegrity(Insertable<_Reaction> instance, + VerificationContext validateIntegrity( + Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id'], _messageIdMeta)); - } else if (isInserting) { - context.missing(_messageIdMeta); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id'], _idMeta)); } - if (data.containsKey('type')) { + context.handle(_ownUserMeta, const VerificationResult.success()); + if (data.containsKey('total_unread_count')) { context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta)); - } else if (isInserting) { - context.missing(_typeMeta); + _totalUnreadCountMeta, + totalUnreadCount.isAcceptableOrUnknown( + data['total_unread_count'], _totalUnreadCountMeta)); } - if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at'], _createdAtMeta)); - } else if (isInserting) { - context.missing(_createdAtMeta); - } - if (data.containsKey('score')) { + if (data.containsKey('unread_channels')) { context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score'], _scoreMeta)); + _unreadChannelsMeta, + unreadChannels.isAcceptableOrUnknown( + data['unread_channels'], _unreadChannelsMeta)); } - if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); - } else if (isInserting) { - context.missing(_userIdMeta); + if (data.containsKey('last_event_at')) { + context.handle( + _lastEventAtMeta, + lastEventAt.isAcceptableOrUnknown( + data['last_event_at'], _lastEventAtMeta)); + } + if (data.containsKey('last_sync_at')) { + context.handle( + _lastSyncAtMeta, + lastSyncAt.isAcceptableOrUnknown( + data['last_sync_at'], _lastSyncAtMeta)); } - context.handle(_extraDataMeta, const VerificationResult.success()); return context; } @override - Set get $primaryKey => {messageId, type, userId}; + Set get $primaryKey => {id}; @override - _Reaction map(Map data, {String tablePrefix}) { + ConnectionEventEntity map(Map data, {String tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; - return _Reaction.fromData(data, _db, prefix: effectivePrefix); + return ConnectionEventEntity.fromData(data, _db, prefix: effectivePrefix); } @override - $_ReactionsTable createAlias(String alias) { - return $_ReactionsTable(_db, alias); + $ConnectionEventsTable createAlias(String alias) { + return $ConnectionEventsTable(_db, alias); } - static TypeConverter, String> $converter0 = - _ExtraDataConverter(); + static TypeConverter, String> $converter0 = + MapConverter(); } -abstract class _$OfflineStorage extends GeneratedDatabase { - _$OfflineStorage(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); - _$OfflineStorage.connect(DatabaseConnection c) : super.connect(c); - $_ConnectionEventTable _connectionEvent; - $_ConnectionEventTable get connectionEvent => - _connectionEvent ??= $_ConnectionEventTable(this); - $_ChannelsTable _channels; - $_ChannelsTable get channels => _channels ??= $_ChannelsTable(this); - $_UsersTable _users; - $_UsersTable get users => _users ??= $_UsersTable(this); - $_MessagesTable _messages; - $_MessagesTable get messages => _messages ??= $_MessagesTable(this); - $_ReadsTable _reads; - $_ReadsTable get reads => _reads ??= $_ReadsTable(this); - $_MembersTable _members; - $_MembersTable get members => _members ??= $_MembersTable(this); - $_ChannelQueriesTable _channelQueries; - $_ChannelQueriesTable get channelQueries => - _channelQueries ??= $_ChannelQueriesTable(this); - $_ReactionsTable _reactions; - $_ReactionsTable get reactions => _reactions ??= $_ReactionsTable(this); +abstract class _$MoorChatDatabase extends GeneratedDatabase { + _$MoorChatDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); + _$MoorChatDatabase.connect(DatabaseConnection c) : super.connect(c); + $ChannelsTable _channels; + $ChannelsTable get channels => _channels ??= $ChannelsTable(this); + $MessagesTable _messages; + $MessagesTable get messages => _messages ??= $MessagesTable(this); + $ReactionsTable _reactions; + $ReactionsTable get reactions => _reactions ??= $ReactionsTable(this); + $UsersTable _users; + $UsersTable get users => _users ??= $UsersTable(this); + $MembersTable _members; + $MembersTable get members => _members ??= $MembersTable(this); + $ReadsTable _reads; + $ReadsTable get reads => _reads ??= $ReadsTable(this); + $ChannelQueriesTable _channelQueries; + $ChannelQueriesTable get channelQueries => + _channelQueries ??= $ChannelQueriesTable(this); + $ConnectionEventsTable _connectionEvents; + $ConnectionEventsTable get connectionEvents => + _connectionEvents ??= $ConnectionEventsTable(this); + UserDao _userDao; + UserDao get userDao => _userDao ??= UserDao(this as MoorChatDatabase); + ChannelDao _channelDao; + ChannelDao get channelDao => + _channelDao ??= ChannelDao(this as MoorChatDatabase); + MessageDao _messageDao; + MessageDao get messageDao => + _messageDao ??= MessageDao(this as MoorChatDatabase); + MemberDao _memberDao; + MemberDao get memberDao => _memberDao ??= MemberDao(this as MoorChatDatabase); + ReactionDao _reactionDao; + ReactionDao get reactionDao => + _reactionDao ??= ReactionDao(this as MoorChatDatabase); + ReadDao _readDao; + ReadDao get readDao => _readDao ??= ReadDao(this as MoorChatDatabase); + ChannelQueryDao _channelQueryDao; + ChannelQueryDao get channelQueryDao => + _channelQueryDao ??= ChannelQueryDao(this as MoorChatDatabase); + ConnectionEventDao _connectionEventDao; + ConnectionEventDao get connectionEventDao => + _connectionEventDao ??= ConnectionEventDao(this as MoorChatDatabase); @override Iterable get allTables => allSchemaEntities.whereType(); @override List get allSchemaEntities => [ - connectionEvent, channels, - users, messages, - reads, + reactions, + users, members, + reads, channelQueries, - reactions + connectionEvents ]; } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart new file mode 100644 index 00000000..74cf91f8 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -0,0 +1,99 @@ +import 'dart:io'; +import 'dart:isolate'; +import 'package:moor/ffi.dart'; +import 'package:moor/isolate.dart'; +import 'package:moor/moor.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; +import 'package:stream_chat_persistence/stream_chat_persistence.dart'; + +import '../moor_chat_database.dart'; + +/// A Helper class to construct new instances of [MoorChatDatabase] specifically +/// for native platform applications +class SharedDB { + /// Returns a new instance of [VmDatabase] created using [userId] + /// on a regular isolate. + /// + /// Generally used with [ConnectionMode.regular]. + static Future constructDatabase( + String userId, { + bool logStatements = false, + }) async { + final dbName = 'db_$userId'; + if (Platform.isIOS || Platform.isAndroid) { + final dir = await getApplicationDocumentsDirectory(); + final path = join(dir.path, '$dbName.sqlite'); + final file = File(path); + return VmDatabase(file, logStatements: logStatements); + } + if (Platform.isMacOS || Platform.isLinux) { + final file = File('$dbName.sqlite'); + return VmDatabase(file, logStatements: logStatements); + } + return VmDatabase.memory(logStatements: logStatements); + } + + static void _startBackground(_IsolateStartRequest request) { + final executor = LazyDatabase(() async { + return VmDatabase( + File(request.targetPath), + logStatements: request.logStatements, + ); + }); + final moorIsolate = MoorIsolate.inCurrent( + () => DatabaseConnection.fromExecutor(executor), + ); + request.sendMoorIsolate.send(moorIsolate); + } + + static Future _createMoorIsolate( + String dbName, { + bool logStatements = false, + }) async { + final dir = await getApplicationDocumentsDirectory(); + final path = join(dir.path, '$dbName.sqlite'); + + final receivePort = ReceivePort(); + await Isolate.spawn( + _startBackground, + _IsolateStartRequest( + receivePort.sendPort, + path, + logStatements: logStatements, + ), + ); + + return (await receivePort.first as MoorIsolate); + } + + /// Returns a new instance of [MoorChatDatabase] using the factory constructor + /// [MoorChatDatabase.connect] created on a background isolate. + /// + /// Generally used with [ConnectionMode.background]. + static Future constructMoorChatDatabase( + String userId, { + bool logStatements = false, + }) async { + final dbName = 'db_$userId'; + final isolate = await _createMoorIsolate( + dbName, + logStatements: logStatements, + ); + final connection = await isolate.connect(); + return MoorChatDatabase.connect(userId, isolate, connection); + } +} + +class _IsolateStartRequest { + final SendPort sendMoorIsolate; + final String targetPath; + final bool logStatements; + + const _IsolateStartRequest( + this.sendMoorIsolate, + this.targetPath, { + this.logStatements = false, + }); +} diff --git a/packages/dart_client/lib/src/db/shared/shared_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/shared_db.dart similarity index 100% rename from packages/dart_client/lib/src/db/shared/shared_db.dart rename to packages/stream_chat_persistence/lib/src/db/shared/shared_db.dart diff --git a/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart new file mode 100644 index 00000000..303d925f --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/db/shared/unsupported_db.dart @@ -0,0 +1,24 @@ +import 'package:stream_chat_persistence/stream_chat_persistence.dart'; + +/// A Helper class to construct new instances of [MoorChatDatabase] +class SharedDB { + /// Returns a new instance of database. + /// + /// Generally used with [ConnectionMode.regular]. + static dynamic constructDatabase( + String userId, { + bool logStatements = false, + }) { + throw 'Unsupported Platform'; + } + + /// Return a new instance of moor chat database. + /// + /// Generally used with [ConnectionMode.background]. + static dynamic constructMoorChatDatabase( + String userId, { + bool logStatements = false, + }) { + throw 'Unsupported Platform'; + } +} diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart new file mode 100644 index 00000000..6f4360c0 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -0,0 +1,31 @@ +import 'package:moor/moor_web.dart'; +import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; + +import '../moor_chat_database.dart'; + +/// A Helper class to construct new instances of [MoorChatDatabase] specifically +/// for Web applications +class SharedDB { + /// Returns a new instance of [WebDatabase] created using [userId]. + /// + /// Generally used with [ConnectionMode.regular]. + static Future constructDatabase( + String userId, { + bool logStatements = false, + }) async { + final dbName = 'db_$userId'; + return WebDatabase(dbName, logStatements: logStatements); + } + + /// Returns a new instance of [MoorChatDatabase] creating using the + /// default constructor. + /// + /// Generally used with [ConnectionMode.background]. + static Future constructMoorChatDatabase( + String userId, { + bool logStatements = false, + }) async { + final dbName = 'db_$userId'; + return MoorChatDatabase(dbName, logStatements: logStatements); + } +} diff --git a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart new file mode 100644 index 00000000..301e3592 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart @@ -0,0 +1,17 @@ +import 'package:moor/moor.dart'; + +/// Represents a [ChannelQueries] table in [MoorChatDatabase]. +@DataClassName('ChannelQueryEntity') +class ChannelQueries extends Table { + /// The unique hash of this query + TextColumn get queryHash => text()(); + + /// The channel cid of this query + TextColumn get channelCid => text()(); + + @override + Set get primaryKey => { + queryHash, + channelCid, + }; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart new file mode 100644 index 00000000..38a40081 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -0,0 +1,45 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; + +/// Represents a [Channels] table in [MoorChatDatabase]. +@DataClassName('ChannelEntity') +class Channels extends Table { + /// The id of this channel + TextColumn get id => text()(); + + /// The type of this channel + TextColumn get type => text()(); + + /// The cid of this channel + TextColumn get cid => text()(); + + /// The channel configuration data + TextColumn get config => text().map(MapConverter())(); + + /// True if this channel entity is frozen + BoolColumn get frozen => boolean().withDefault(Constant(false))(); + + /// The date of the last message + DateTimeColumn get lastMessageAt => dateTime().nullable()(); + + /// The date of channel creation + DateTimeColumn get createdAt => dateTime().nullable()(); + + /// The date of the last channel update + DateTimeColumn get updatedAt => dateTime().nullable()(); + + /// The date of channel deletion + DateTimeColumn get deletedAt => dateTime().nullable()(); + + /// The count of this channel members + IntColumn get memberCount => integer().nullable()(); + + /// The id of the user that created this channel + TextColumn get createdById => text().nullable()(); + + /// Map of custom channel extraData + TextColumn get extraData => text().nullable().map(MapConverter())(); + + @override + Set get primaryKey => {cid}; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/connection_events.dart b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart new file mode 100644 index 00000000..7caa0407 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/connection_events.dart @@ -0,0 +1,27 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; + +/// Represents a [ConnectionEvents] table in [MoorChatDatabase]. +@DataClassName('ConnectionEventEntity') +class ConnectionEvents extends Table { + /// event id + IntColumn get id => integer()(); + + /// User object of the current user + TextColumn get ownUser => text().nullable().map(MapConverter())(); + + /// The number of unread messages for current user + IntColumn get totalUnreadCount => integer().nullable()(); + + /// User total unread channels for current user + IntColumn get unreadChannels => integer().nullable()(); + + /// DateTime of the last event + DateTimeColumn get lastEventAt => dateTime().nullable()(); + + /// DateTime of the last sync + DateTimeColumn get lastSyncAt => dateTime().nullable()(); + + @override + Set get primaryKey => {id}; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/entity.dart b/packages/stream_chat_persistence/lib/src/entity/entity.dart new file mode 100644 index 00000000..8751f538 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/entity.dart @@ -0,0 +1,8 @@ +export 'channels.dart'; +export 'messages.dart'; +export 'reactions.dart'; +export 'users.dart'; +export 'members.dart'; +export 'reads.dart'; +export 'channel_queries.dart'; +export 'connection_events.dart'; diff --git a/packages/stream_chat_persistence/lib/src/entity/members.dart b/packages/stream_chat_persistence/lib/src/entity/members.dart new file mode 100644 index 00000000..9eafd4aa --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/members.dart @@ -0,0 +1,45 @@ +import 'package:moor/moor.dart'; + +/// Represents a [Members] table in [MoorChatDatabase]. +@DataClassName('MemberEntity') +class Members extends Table { + /// The interested user id + TextColumn get userId => text()(); + + /// The channel cid of which this user is part of + TextColumn get channelCid => + text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')(); + + /// The role of the user in the channel + TextColumn get role => text().nullable()(); + + /// The date on which the user accepted the invite to the channel + DateTimeColumn get inviteAcceptedAt => dateTime().nullable()(); + + /// The date on which the user rejected the invite to the channel + DateTimeColumn get inviteRejectedAt => dateTime().nullable()(); + + /// True if the user has been invited to the channel + BoolColumn get invited => boolean().nullable()(); + + /// True if the member is banned from the channel + BoolColumn get banned => boolean().nullable()(); + + /// True if the member is shadow banned from the channel + BoolColumn get shadowBanned => boolean().nullable()(); + + /// True if the user is a moderator of the channel + BoolColumn get isModerator => boolean().nullable()(); + + /// The date of creation + DateTimeColumn get createdAt => dateTime()(); + + /// The last date of update + DateTimeColumn get updatedAt => dateTime().nullable()(); + + @override + Set get primaryKey => { + userId, + channelCid, + }; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart new file mode 100644 index 00000000..8a151ae6 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -0,0 +1,76 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat_persistence/src/converter/list_converter.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; +import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart'; + +/// Represents a [Messages] table in [MoorChatDatabase]. +@DataClassName('MessageEntity') +class Messages extends Table { + /// The message id + TextColumn get id => text()(); + + /// The text of this message + TextColumn get messageText => text().nullable()(); + + /// The list of attachments, either provided by the user + /// or generated from a command or as a result of URL scraping. + TextColumn get attachments => + text().nullable().map(ListConverter())(); + + /// The status of a sending message + IntColumn get status => + integer().nullable().map(MessageSendingStatusConverter())(); + + /// The message type + TextColumn get type => text().nullable()(); + + /// The list of user mentioned in the message + TextColumn get mentionedUsers => + text().nullable().map(ListConverter())(); + + /// A map describing the count of number of every reaction + TextColumn get reactionCounts => text().nullable().map(MapConverter())(); + + /// A map describing the count of score of every reaction + TextColumn get reactionScores => text().nullable().map(MapConverter())(); + + /// The ID of the parent message, if the message is a thread reply. + TextColumn get parentId => text().nullable()(); + + /// The ID of the quoted message, if the message is a quoted reply. + TextColumn get quotedMessageId => text().nullable()(); + + /// Number of replies for this message. + IntColumn get replyCount => integer().nullable()(); + + /// Check if this message needs to show in the channel. + BoolColumn get showInChannel => boolean().nullable()(); + + /// If true the message is shadowed + BoolColumn get shadowed => boolean().nullable()(); + + /// A used command name. + TextColumn get command => text().nullable()(); + + /// The DateTime when the message was created. + DateTimeColumn get createdAt => dateTime()(); + + /// The DateTime when the message was updated last time. + DateTimeColumn get updatedAt => dateTime().nullable()(); + + /// The DateTime when the message was deleted. + DateTimeColumn get deletedAt => dateTime().nullable()(); + + /// Id of the User who sent the message + TextColumn get userId => text().nullable()(); + + /// The channel cid of which this message is part of + TextColumn get channelCid => text().nullable().customConstraint( + 'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')(); + + /// Message custom extraData + TextColumn get extraData => text().nullable().map(MapConverter())(); + + @override + Set get primaryKey => {id}; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart new file mode 100644 index 00000000..b7e6342f --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -0,0 +1,32 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; + +/// Represents a [Reactions] table in [MoorChatDatabase]. +@DataClassName('ReactionEntity') +class Reactions extends Table { + /// The id of the user that sent the reaction + TextColumn get userId => text()(); + + /// The messageId to which the reaction belongs + TextColumn get messageId => + text().customConstraint('REFERENCES messages(id) ON DELETE CASCADE')(); + + /// The type of the reaction + TextColumn get type => text()(); + + /// The DateTime on which the reaction is created + DateTimeColumn get createdAt => dateTime()(); + + /// The score of the reaction (ie. number of reactions sent) + IntColumn get score => integer().nullable()(); + + /// Reaction custom extraData + TextColumn get extraData => text().nullable().map(MapConverter())(); + + @override + Set get primaryKey => { + messageId, + type, + userId, + }; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/reads.dart b/packages/stream_chat_persistence/lib/src/entity/reads.dart new file mode 100644 index 00000000..4b9ce6fb --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/reads.dart @@ -0,0 +1,24 @@ +import 'package:moor/moor.dart'; + +/// Represents a [Reads] table in [MoorChatDatabase]. +@DataClassName('ReadEntity') +class Reads extends Table { + /// Date of the read event + DateTimeColumn get lastRead => dateTime()(); + + /// Id of the User who sent the event + TextColumn get userId => text()(); + + /// The channel cid of which this read belongs + TextColumn get channelCid => + text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')(); + + /// Number of unread messages + IntColumn get unreadMessages => integer().nullable()(); + + @override + Set get primaryKey => { + userId, + channelCid, + }; +} diff --git a/packages/stream_chat_persistence/lib/src/entity/users.dart b/packages/stream_chat_persistence/lib/src/entity/users.dart new file mode 100644 index 00000000..4916ad28 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/entity/users.dart @@ -0,0 +1,33 @@ +import 'package:moor/moor.dart'; +import 'package:stream_chat_persistence/src/converter/map_converter.dart'; + +/// Represents a [Users] table in [MoorChatDatabase]. +@DataClassName('UserEntity') +class Users extends Table { + /// User id + TextColumn get id => text()(); + + /// User role + TextColumn get role => text().nullable()(); + + /// Date of user creation + DateTimeColumn get createdAt => dateTime().nullable()(); + + /// Date of last user update + DateTimeColumn get updatedAt => dateTime().nullable()(); + + /// Date of last user connection + DateTimeColumn get lastActive => dateTime().nullable()(); + + /// True if user is online + BoolColumn get online => boolean().nullable()(); + + /// True if user is banned from the chat + BoolColumn get banned => boolean().nullable()(); + + /// Map of custom user extraData + TextColumn get extraData => text().nullable().map(MapConverter())(); + + @override + Set get primaryKey => {id}; +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart new file mode 100644 index 00000000..7d0e152f --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -0,0 +1,60 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [ChannelEntity] +extension ChannelEntityX on ChannelEntity { + /// Maps a [ChannelEntity] into [ChannelModel] + ChannelModel toChannelModel({User createdBy}) { + final config = ChannelConfig.fromJson(this.config ?? {}); + return ChannelModel( + id: id, + config: config, + type: type, + frozen: frozen, + createdAt: createdAt, + updatedAt: updatedAt, + memberCount: memberCount, + cid: cid, + lastMessageAt: lastMessageAt, + deletedAt: deletedAt, + extraData: extraData, + createdBy: createdBy, + ); + } + + /// Maps a [ChannelEntity] into [ChannelState] + ChannelState toChannelState({ + User createdBy, + List members, + List reads, + List messages, + }) { + return ChannelState( + members: members, + read: reads, + messages: messages, + channel: toChannelModel(createdBy: createdBy), + ); + } +} + +/// Useful mapping functions for [ChannelModel] +extension ChannelModelX on ChannelModel { + /// Maps a [ChannelModel] into [ChannelEntity] + ChannelEntity toEntity() { + return ChannelEntity( + id: id, + type: type, + cid: cid, + config: config.toJson(), + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + createdById: createdBy.id, + extraData: extraData, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart new file mode 100644 index 00000000..7bc1739c --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -0,0 +1,14 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [ConnectionEventEntity] +extension ConnectionEventX on ConnectionEventEntity { + /// Maps a [ConnectionEventEntity] into [Event] + Event toEvent() { + return Event( + me: ownUser != null ? OwnUser.fromJson(ownUser) : null, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/mapper.dart new file mode 100644 index 00000000..8d19729d --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/mapper.dart @@ -0,0 +1,7 @@ +export 'user_mapper.dart'; +export 'reaction_mapper.dart'; +export 'channel_mapper.dart'; +export 'event_mapper.dart'; +export 'member_mapper.dart'; +export 'read_mapper.dart'; +export 'message_mapper.dart'; diff --git a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart new file mode 100644 index 00000000..2d32c9f1 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart @@ -0,0 +1,42 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [MemberEntity] +extension MemberEntityX on MemberEntity { + /// Maps a [MemberEntity] into [Member] + Member toMember({User user}) { + return Member( + user: user, + userId: userId, + banned: banned, + shadowBanned: shadowBanned, + updatedAt: updatedAt, + createdAt: createdAt, + role: role, + inviteAcceptedAt: inviteAcceptedAt, + invited: invited, + inviteRejectedAt: inviteRejectedAt, + isModerator: isModerator, + ); + } +} + +/// Useful mapping functions for [Member] +extension MemberX on Member { + /// Maps a [Member] into [MemberEntity] + MemberEntity toEntity({String cid}) { + return MemberEntity( + userId: user?.id, + banned: banned, + shadowBanned: shadowBanned, + channelCid: cid, + createdAt: createdAt, + isModerator: isModerator, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + inviteAcceptedAt: inviteAcceptedAt, + role: role, + updatedAt: updatedAt, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart new file mode 100644 index 00000000..510675e6 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [MessageEntity] +extension MessageEntityX on MessageEntity { + /// Maps a [MessageEntity] into [Message] + Message toMessage({ + User user, + List latestReactions, + List ownReactions, + Message quotedMessage, + }) { + return Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments?.map((it) { + final json = jsonDecode(it); + return Attachment.fromJson(json); + })?.toList(), + createdAt: createdAt, + extraData: extraData, + updatedAt: updatedAt, + id: id, + type: type, + status: status, + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + reactionCounts: reactionCounts, + reactionScores: reactionScores, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + deletedAt: deletedAt, + ); + } +} + +/// Useful mapping functions for [Message] +extension MessageX on Message { + /// Maps a [Message] into [MessageEntity] + MessageEntity toEntity({String cid}) { + return MessageEntity( + id: id, + attachments: attachments.map((it) => jsonEncode(it)).toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + command: command, + createdAt: createdAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionScores: reactionScores, + reactionCounts: reactionCounts, + status: status, + updatedAt: updatedAt, + extraData: extraData, + userId: user?.id, + deletedAt: deletedAt, + messageText: text, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart new file mode 100644 index 00000000..d265fcb7 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -0,0 +1,33 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [ReactionEntity] +extension ReactionEntityX on ReactionEntity { + /// Maps a [ReactionEntity] into [Reaction] + Reaction toReaction({User user}) { + return Reaction( + extraData: extraData, + type: type, + createdAt: createdAt, + userId: userId, + user: user, + messageId: messageId, + score: score, + ); + } +} + +/// Useful mapping functions for [Reaction] +extension ReactionX on Reaction { + /// Maps a [Reaction] into [ReactionEntity] + ReactionEntity toEntity() { + return ReactionEntity( + extraData: extraData, + type: type, + createdAt: createdAt, + userId: userId, + messageId: messageId, + score: score, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart new file mode 100644 index 00000000..c79cf8d5 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -0,0 +1,27 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [ReadEntity] +extension ReadEntityX on ReadEntity { + /// Maps a [ReadEntity] into [Read] + Read toRead({User user}) { + return Read( + user: user, + lastRead: lastRead, + unreadMessages: unreadMessages, + ); + } +} + +/// Useful mapping functions for [Read] +extension ReadX on Read { + /// Maps a [Read] into [ReadEntity] + ReadEntity toEntity({String cid}) { + return ReadEntity( + lastRead: lastRead, + userId: user?.id, + channelCid: cid, + unreadMessages: unreadMessages, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart new file mode 100644 index 00000000..b1e53786 --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart @@ -0,0 +1,36 @@ +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; + +/// Useful mapping functions for [UserEntity] +extension UserEntityX on UserEntity { + /// Maps a [UserEntity] into [User] + User toUser() { + return User( + id: id, + updatedAt: updatedAt, + role: role, + online: online, + lastActive: lastActive, + extraData: extraData, + banned: banned, + createdAt: createdAt, + ); + } +} + +/// Useful mapping functions for [User] +extension UserX on User { + /// Maps a [User] into [UserEntity] + UserEntity toEntity() { + return UserEntity( + id: id, + role: role, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + extraData: extraData, + ); + } +} diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart new file mode 100644 index 00000000..0962c9ac --- /dev/null +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -0,0 +1,222 @@ +import 'package:stream_chat/stream_chat.dart'; + +import 'db/moor_chat_database.dart'; +import 'db/shared/shared_db.dart'; + +/// Various connection modes on which [StreamChatPersistenceClient] can work +enum ConnectionMode { + /// Connects the [StreamChatPersistenceClient] on a regular/default isolate + regular, + + /// Connects the [StreamChatPersistenceClient] on a background isolate + background, +} + +/// A [MoorChatDatabase] based implementation of the [ChatPersistenceClient] +class StreamChatPersistenceClient extends ChatPersistenceClient { + /// Creates a new instance of the stream chat persistence client + StreamChatPersistenceClient({ + /// Connection mode on which the client will work + ConnectionMode connectionMode = ConnectionMode.regular, + Level logLevel = Level.WARNING, + }) : assert(connectionMode != null), + assert(logLevel != null), + _connectionMode = connectionMode, + _logger = Logger.detached('💽')..level = logLevel; + + MoorChatDatabase _db; + final Logger _logger; + final ConnectionMode _connectionMode; + + @override + Future connect(String userId) async { + if (_db != null) { + throw Exception( + 'An instance of StreamChatDatabase is already connected.\n' + 'disconnect the previous instance before connecting again.', + ); + } + switch (_connectionMode) { + case ConnectionMode.regular: + _logger.info('Connecting on a regular isolate'); + _db = MoorChatDatabase(userId); + return; + case ConnectionMode.background: + _logger.info('Connecting on background isolate'); + _db = await SharedDB.constructMoorChatDatabase(userId); + return; + } + } + + @override + Future getConnectionInfo() { + return _db.connectionEventDao.connectionEvent; + } + + @override + Future updateConnectionInfo(Event event) { + return _db.connectionEventDao.updateConnectionEvent(event); + } + + @override + Future updateLastSyncAt(DateTime lastSyncAt) { + return _db.connectionEventDao.updateLastSyncAt(lastSyncAt); + } + + @override + Future getLastSyncAt() { + return _db.connectionEventDao.lastSyncAt; + } + + @override + Future deleteChannels(List cids) { + return _db.channelDao.deleteChannelByCids(cids); + } + + @override + Future> getChannelCids() => _db.channelDao.cids; + + @override + Future deleteMessageByIds(List messageIds) { + return _db.messageDao.deleteMessageByIds(messageIds); + } + + @override + Future deleteMessageByCids(List cids) { + return _db.messageDao.deleteMessageByCids(cids); + } + + @override + Future> getMembersByCid(String cid) { + return _db.memberDao.getMembersByCid(cid); + } + + @override + Future getChannelByCid(String cid) { + return _db.channelDao.getChannelByCid(cid); + } + + @override + Future> getMessagesByCid( + String cid, { + PaginationParams messagePagination, + }) { + return _db.messageDao.getMessagesByCid( + cid, + messagePagination: messagePagination, + ); + } + + @override + Future> getReadsByCid(String cid) { + return _db.readDao.getReadsByCid(cid); + } + + @override + Future>> getChannelThreads(String cid) async { + final messages = await _db.messageDao.getThreadMessages(cid); + final messageByParentIdDictionary = >{}; + for (final message in messages) { + final parentId = message.parentId; + messageByParentIdDictionary[parentId] = [ + ...messageByParentIdDictionary[parentId] ?? [], + message + ]; + } + return messageByParentIdDictionary; + } + + @override + Future> getReplies( + String parentId, { + PaginationParams options, + }) { + return _db.messageDao.getThreadMessagesByParentId( + parentId, + options: options, + ); + } + + @override + Future> getChannelStates({ + Map filter, + List sort = const [], + PaginationParams paginationParams, + }) { + return _db.channelQueryDao.getChannelStates( + filter: filter, + sort: sort, + paginationParams: paginationParams, + ); + } + + @override + Future updateChannelQueries( + Map filter, + List cids, + bool clearQueryCache, + ) { + return _db.channelQueryDao.updateChannelQueries( + filter, + cids, + clearQueryCache, + ); + } + + @override + Future updateChannels(List channels) { + return _db.channelDao.updateChannels(channels); + } + + @override + Future updateMembers(String cid, List members) { + return _db.memberDao.updateMembers(cid, members); + } + + @override + Future updateMessages(String cid, List messages) { + return _db.messageDao.updateMessages(cid, messages); + } + + @override + Future updateReactions(List reactions) { + return _db.reactionDao.updateReactions(reactions); + } + + @override + Future updateReads(String cid, List reads) { + return _db.readDao.updateReads(cid, reads); + } + + @override + Future updateUsers(List users) { + return _db.userDao.updateUsers(users); + } + + @override + Future deleteReactionsByMessageId(List messageIds) { + return _db.reactionDao.deleteReactionsByMessageIds(messageIds); + } + + @override + Future deleteMembersByCids(List cids) { + return _db.memberDao.deleteMemberByCids(cids); + } + + @override + Future disconnect({bool flush = false}) async { + if (_db != null) { + _logger.info('Disconnecting'); + if (flush) { + _logger.info('Flushing'); + await _db.batch((batch) { + _db.allTables.forEach((table) { + _db.delete(table).go(); + }); + }); + } + await _db.disconnect(); + _db = null; + } + } +} diff --git a/packages/stream_chat_persistence/lib/stream_chat_persistence.dart b/packages/stream_chat_persistence/lib/stream_chat_persistence.dart new file mode 100644 index 00000000..5f9aff5b --- /dev/null +++ b/packages/stream_chat_persistence/lib/stream_chat_persistence.dart @@ -0,0 +1,3 @@ +library stream_chat_persistence; + +export 'src/stream_chat_persistence_client.dart'; diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml new file mode 100644 index 00000000..06015495 --- /dev/null +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -0,0 +1,23 @@ +name: stream_chat_persistence +homepage: https://github.com/GetStream/stream-chat-flutter +description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. +version: 1.0.0-beta +repository: https://github.com/GetStream/stream-chat-flutter +issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + moor: ^3.4.0 + path: ^1.7.0 + path_provider: ^1.6.27 + sqlite3_flutter_libs: ^0.3.0 + stream_chat: + path: ../dart_client + +dev_dependencies: + test: ^1.15.7 + build_runner: ^1.11.0 + moor_generator: ^3.4.1 + pedantic: ^1.9.2