@@ -95,7 +95,7 @@ jobs:
|
|||||||
TOTAL_MAX: ${{ steps.analysis.outputs.total_max }}
|
TOTAL_MAX: ${{ steps.analysis.outputs.total_max }}
|
||||||
run: |
|
run: |
|
||||||
PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX ))
|
PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX ))
|
||||||
if (( $PERCENTAGE < 90 ))
|
if (( $PERCENTAGE < 80 ))
|
||||||
then
|
then
|
||||||
echo Score too low!
|
echo Score too low!
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ on:
|
|||||||
- opened
|
- opened
|
||||||
- edited
|
- edited
|
||||||
- synchronize
|
- synchronize
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
flutter pub global activate melos 0.4.0+1
|
flutter pub global activate melos
|
||||||
echo "::add-path::$HOME/.pub-cache/bin"
|
echo "::add-path::$HOME/.pub-cache/bin"
|
||||||
echo "::add-path::$GITHUB_WORKSPACE/_flutter/.pub-cache/bin"
|
echo "::add-path::$GITHUB_WORKSPACE/_flutter/.pub-cache/bin"
|
||||||
echo "::add-path::$GITHUB_WORKSPACE/_flutter/bin/cache/dart-sdk/bin"
|
echo "::add-path::$GITHUB_WORKSPACE/_flutter/bin/cache/dart-sdk/bin"
|
||||||
@@ -13,7 +13,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
if: github.base_ref == 'master'
|
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -48,7 +47,6 @@ jobs:
|
|||||||
- name: 'Install Tools'
|
- name: 'Install Tools'
|
||||||
run: |
|
run: |
|
||||||
./.github/workflows/scripts/install-tools.sh
|
./.github/workflows/scripts/install-tools.sh
|
||||||
curl -sL https://github.com/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar -o $HOME/google-java-format.jar
|
|
||||||
- name: 'Bootstrap Workspace'
|
- name: 'Bootstrap Workspace'
|
||||||
run: melos bootstrap
|
run: melos bootstrap
|
||||||
- name: 'Dart'
|
- name: 'Dart'
|
||||||
@@ -56,20 +54,8 @@ jobs:
|
|||||||
melos exec -c 1 -- \
|
melos exec -c 1 -- \
|
||||||
flutter format .
|
flutter format .
|
||||||
./.github/workflows/scripts/validate-formatting.sh
|
./.github/workflows/scripts/validate-formatting.sh
|
||||||
- name: 'Objective-C'
|
|
||||||
if: ${{ success() || failure() }}
|
|
||||||
run: |
|
|
||||||
melos exec -c 4 --ignore="*platform_interface*" --ignore="*web*" -- \
|
|
||||||
find . -maxdepth 3 -name "*.h" -o -name "*.m" -print0 \| xargs -0 clang-format -i --style=Google --verbose
|
|
||||||
./.github/workflows/scripts/validate-formatting.sh
|
|
||||||
- name: 'Java'
|
|
||||||
if: ${{ success() || failure() }}
|
|
||||||
run: |
|
|
||||||
melos exec -c 4 --ignore="*platform_interface*" --ignore="*web*" -- \
|
|
||||||
find . -maxdepth 12 -name "*.java" -print0 \| xargs -0 java -jar $HOME/google-java-format.jar --replace
|
|
||||||
./.github/workflows/scripts/validate-formatting.sh
|
|
||||||
|
|
||||||
test_dart:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
@@ -79,26 +65,35 @@ jobs:
|
|||||||
- name: 'Install Flutter'
|
- name: 'Install Flutter'
|
||||||
run: ./.github/workflows/scripts/install-flutter.sh stable
|
run: ./.github/workflows/scripts/install-flutter.sh stable
|
||||||
- name: 'Install Tools'
|
- name: 'Install Tools'
|
||||||
run: ./.github/workflows/scripts/install-tools.sh
|
run: |
|
||||||
- name: 'Bootstrap Workspace'
|
./.github/workflows/scripts/install-tools.sh
|
||||||
run: melos bootstrap
|
flutter pub global activate coverage
|
||||||
- name: 'Flutter Test'
|
|
||||||
run: cd packages/stream_chat && flutter pub run test
|
|
||||||
|
|
||||||
test_flutter:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
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'
|
- name: 'Bootstrap Workspace'
|
||||||
run: melos bootstrap
|
run: melos bootstrap
|
||||||
|
- name: 'Dart Test'
|
||||||
|
run: |
|
||||||
|
cd packages/stream_chat
|
||||||
|
flutter pub run test --coverage coverage/
|
||||||
|
format_coverage --lcov --in=coverage/ --out=lcov.info --packages=.packages --report-on=lib
|
||||||
- name: 'Flutter Test'
|
- name: 'Flutter Test'
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \
|
melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \
|
||||||
flutter test
|
flutter test --coverage
|
||||||
|
- name: CodeCov
|
||||||
|
run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
- uses: VeryGoodOpenSource/[email protected]
|
||||||
|
with:
|
||||||
|
path: packages/stream_chat/lcov.info
|
||||||
|
min_coverage: 50
|
||||||
|
- uses: VeryGoodOpenSource/[email protected]
|
||||||
|
with:
|
||||||
|
path: packages/stream_chat_persistence/coverage/lcov.info
|
||||||
|
min_coverage: 0.2
|
||||||
|
- uses: VeryGoodOpenSource/[email protected]
|
||||||
|
with:
|
||||||
|
path: packages/stream_chat_flutter_core/coverage/lcov.info
|
||||||
|
min_coverage: 4.5
|
||||||
|
- uses: VeryGoodOpenSource/[email protected]
|
||||||
|
with:
|
||||||
|
path: packages/stream_chat_flutter/coverage/lcov.info
|
||||||
|
min_coverage: 16
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
.atom/
|
.atom/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
**/lcov.info
|
||||||
|
coverage
|
||||||
.packages
|
.packages
|
||||||
.pub/
|
.pub/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
- Minor fixes and improvements
|
||||||
|
|
||||||
## 1.4.0-beta
|
## 1.4.0-beta
|
||||||
|
|
||||||
- Improved attachment uploading
|
- Improved attachment uploading
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ linter:
|
|||||||
- avoid_private_typedef_functions
|
- avoid_private_typedef_functions
|
||||||
- avoid_redundant_argument_values
|
- avoid_redundant_argument_values
|
||||||
- avoid_return_types_on_setters
|
- avoid_return_types_on_setters
|
||||||
- avoid_returning_null
|
|
||||||
- avoid_returning_null_for_void
|
- avoid_returning_null_for_void
|
||||||
- avoid_shadowing_type_parameters
|
- avoid_shadowing_type_parameters
|
||||||
- avoid_single_cascade_in_expression_statements
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:stream_chat/src/api/retry_queue.dart';
|
import 'package:stream_chat/src/api/retry_queue.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
|
import 'package:stream_chat/src/extensions/rate_limit.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||||
import 'package:stream_chat/src/models/channel_state.dart';
|
import 'package:stream_chat/src/models/channel_state.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/models/user.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat/src/extensions/rate_limit.dart';
|
|
||||||
|
|
||||||
/// This a the class that manages a specific channel.
|
/// This a the class that manages a specific channel.
|
||||||
class Channel {
|
class Channel {
|
||||||
@@ -1209,9 +1209,10 @@ class ChannelClientState {
|
|||||||
ChannelClientState(
|
ChannelClientState(
|
||||||
this._channel,
|
this._channel,
|
||||||
ChannelState channelState,
|
ChannelState channelState,
|
||||||
) : _debouncedUpdatePersistenceChannelState = _channel
|
//ignore: unnecessary_parenthesis
|
||||||
?._client?.chatPersistenceClient?.updateChannelState
|
) : _debouncedUpdatePersistenceChannelState = ((ChannelState state) {
|
||||||
?.debounced(const Duration(seconds: 1)) {
|
_channel?._client?.chatPersistenceClient?.updateChannelState(state);
|
||||||
|
}).debounced(const Duration(seconds: 1)) {
|
||||||
retryQueue = RetryQueue(
|
retryQueue = RetryQueue(
|
||||||
channel: _channel,
|
channel: _channel,
|
||||||
logger: Logger('RETRY QUEUE ${_channel.cid}'),
|
logger: Logger('RETRY QUEUE ${_channel.cid}'),
|
||||||
@@ -1463,10 +1464,16 @@ class ChannelClientState {
|
|||||||
void addMessage(Message message) {
|
void addMessage(Message message) {
|
||||||
if (message.parentId == null || message.showInChannel == true) {
|
if (message.parentId == null || message.showInChannel == true) {
|
||||||
final newMessages = List<Message>.from(_channelState.messages);
|
final newMessages = List<Message>.from(_channelState.messages);
|
||||||
|
|
||||||
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
||||||
if (oldIndex != -1) {
|
if (oldIndex != -1) {
|
||||||
newMessages[oldIndex] = message;
|
Message m;
|
||||||
|
if (message.quotedMessageId != null && message.quotedMessage == null) {
|
||||||
|
final oldMessage = newMessages[oldIndex];
|
||||||
|
m = message.copyWith(
|
||||||
|
quotedMessage: oldMessage.quotedMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
newMessages[oldIndex] = m ?? message;
|
||||||
} else {
|
} else {
|
||||||
newMessages.add(message);
|
newMessages.add(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
// ignore_for_file: unnecessary_getters_setters
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:stream_chat/src/extensions/map_extension.dart';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -16,6 +17,7 @@ import 'package:stream_chat/src/attachment_file_uploader.dart';
|
|||||||
import 'package:stream_chat/src/db/chat_persistence_client.dart';
|
import 'package:stream_chat/src/db/chat_persistence_client.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/exceptions.dart';
|
import 'package:stream_chat/src/exceptions.dart';
|
||||||
|
import 'package:stream_chat/src/extensions/map_extension.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||||
import 'package:stream_chat/src/models/channel_model.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/channel_state.dart';
|
||||||
@@ -106,14 +108,22 @@ class StreamChatClient {
|
|||||||
logger.info('instantiating new client');
|
logger.info('instantiating new client');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set chatPersistenceClient(ChatPersistenceClient value) {
|
||||||
|
_originalChatPersistenceClient = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPersistenceClient _originalChatPersistenceClient;
|
||||||
|
|
||||||
/// Chat persistence client
|
/// Chat persistence client
|
||||||
ChatPersistenceClient chatPersistenceClient;
|
ChatPersistenceClient get chatPersistenceClient => _chatPersistenceClient;
|
||||||
|
|
||||||
|
ChatPersistenceClient _chatPersistenceClient;
|
||||||
|
|
||||||
/// Attachment uploader
|
/// Attachment uploader
|
||||||
AttachmentFileUploader attachmentFileUploader;
|
AttachmentFileUploader attachmentFileUploader;
|
||||||
|
|
||||||
/// Whether the chat persistence is available or not
|
/// Whether the chat persistence is available or not
|
||||||
bool get persistenceEnabled => chatPersistenceClient != null;
|
bool get persistenceEnabled => _chatPersistenceClient != null;
|
||||||
|
|
||||||
RetryPolicy _retryPolicy;
|
RetryPolicy _retryPolicy;
|
||||||
|
|
||||||
@@ -357,7 +367,7 @@ class StreamChatClient {
|
|||||||
|
|
||||||
/// Call this function to dispose the client
|
/// Call this function to dispose the client
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
await chatPersistenceClient?.disconnect();
|
await _chatPersistenceClient?.disconnect();
|
||||||
await _disconnect();
|
await _disconnect();
|
||||||
httpClient.close();
|
httpClient.close();
|
||||||
await _controller.close();
|
await _controller.close();
|
||||||
@@ -446,8 +456,8 @@ class StreamChatClient {
|
|||||||
|
|
||||||
if (!event.isLocal) {
|
if (!event.isLocal) {
|
||||||
if (_synced && event.createdAt != null) {
|
if (_synced && event.createdAt != null) {
|
||||||
await chatPersistenceClient?.updateConnectionInfo(event);
|
await _chatPersistenceClient?.updateConnectionInfo(event);
|
||||||
await chatPersistenceClient?.updateLastSyncAt(event.createdAt);
|
await _chatPersistenceClient?.updateLastSyncAt(event.createdAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,8 +488,9 @@ class StreamChatClient {
|
|||||||
|
|
||||||
_wsConnectionStatus = ConnectionStatus.connecting;
|
_wsConnectionStatus = ConnectionStatus.connecting;
|
||||||
|
|
||||||
if (persistenceEnabled) {
|
if (_originalChatPersistenceClient != null) {
|
||||||
await chatPersistenceClient.connect(state.user.id);
|
_chatPersistenceClient = _originalChatPersistenceClient;
|
||||||
|
await _chatPersistenceClient.connect(state.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ws = WebSocket(
|
_ws = WebSocket(
|
||||||
@@ -508,34 +519,35 @@ class StreamChatClient {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status == ConnectionStatus.connected &&
|
if (status == ConnectionStatus.connected) {
|
||||||
state.channels?.isNotEmpty == true) {
|
handleEvent(Event(
|
||||||
// ignore: unawaited_futures
|
type: EventType.connectionRecovered,
|
||||||
queryChannelsOnline(filter: {
|
online: true,
|
||||||
'cid': {
|
));
|
||||||
'\$in': state.channels.keys.toList(),
|
if (state.channels?.isNotEmpty == true) {
|
||||||
},
|
// ignore: unawaited_futures
|
||||||
}).then(
|
queryChannelsOnline(filter: {
|
||||||
(_) async {
|
'cid': {
|
||||||
await resync();
|
'\$in': state.channels.keys.toList(),
|
||||||
handleEvent(Event(
|
},
|
||||||
type: EventType.connectionRecovered,
|
}).then(
|
||||||
online: true,
|
(_) async {
|
||||||
));
|
await resync();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_synced = false;
|
_synced = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_connectionStatusSubscription =
|
_connectionStatusSubscription =
|
||||||
_ws.connectionStatusStream.listen(_connectionStatusHandler);
|
_ws.connectionStatusStream.listen(_connectionStatusHandler);
|
||||||
|
|
||||||
var event = await chatPersistenceClient?.getConnectionInfo();
|
var event = await _chatPersistenceClient?.getConnectionInfo();
|
||||||
|
|
||||||
await _ws.connect().then((e) async {
|
await _ws.connect().then((e) async {
|
||||||
await chatPersistenceClient?.updateConnectionInfo(e);
|
await _chatPersistenceClient?.updateConnectionInfo(e);
|
||||||
event = e;
|
event = e;
|
||||||
await resync();
|
await resync();
|
||||||
}).catchError((err, stacktrace) {
|
}).catchError((err, stacktrace) {
|
||||||
@@ -551,14 +563,14 @@ class StreamChatClient {
|
|||||||
|
|
||||||
/// Get the events missed while offline to sync the offline storage
|
/// Get the events missed while offline to sync the offline storage
|
||||||
Future<void> resync([List<String> cids]) async {
|
Future<void> resync([List<String> cids]) async {
|
||||||
final lastSyncAt = await chatPersistenceClient?.getLastSyncAt();
|
final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt();
|
||||||
|
|
||||||
if (lastSyncAt == null) {
|
if (lastSyncAt == null) {
|
||||||
_synced = true;
|
_synced = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cids ??= await chatPersistenceClient?.getChannelCids();
|
cids ??= await _chatPersistenceClient?.getChannelCids();
|
||||||
|
|
||||||
if (cids?.isEmpty == true) {
|
if (cids?.isEmpty == true) {
|
||||||
return;
|
return;
|
||||||
@@ -586,7 +598,7 @@ class StreamChatClient {
|
|||||||
|
|
||||||
res.events.forEach(handleEvent);
|
res.events.forEach(handleEvent);
|
||||||
|
|
||||||
await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
|
await _chatPersistenceClient?.updateLastSyncAt(DateTime.now());
|
||||||
_synced = true;
|
_synced = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.severe('Error during resync $error');
|
logger.severe('Error during resync $error');
|
||||||
@@ -723,7 +735,7 @@ class StreamChatClient {
|
|||||||
|
|
||||||
final updateData = _mapChannelStateToChannel(channels);
|
final updateData = _mapChannelStateToChannel(channels);
|
||||||
|
|
||||||
await chatPersistenceClient?.updateChannelQueries(
|
await _chatPersistenceClient?.updateChannelQueries(
|
||||||
filter,
|
filter,
|
||||||
channels.map((c) => c.channel.cid).toList(),
|
channels.map((c) => c.channel.cid).toList(),
|
||||||
paginationParams?.offset == null || paginationParams.offset == 0,
|
paginationParams?.offset == null || paginationParams.offset == 0,
|
||||||
@@ -739,7 +751,7 @@ class StreamChatClient {
|
|||||||
@required List<SortOption<ChannelModel>> sort,
|
@required List<SortOption<ChannelModel>> sort,
|
||||||
PaginationParams paginationParams = const PaginationParams(),
|
PaginationParams paginationParams = const PaginationParams(),
|
||||||
}) async {
|
}) async {
|
||||||
final offlineChannels = await chatPersistenceClient?.getChannelStates(
|
final offlineChannels = await _chatPersistenceClient?.getChannelStates(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
@@ -960,8 +972,8 @@ class StreamChatClient {
|
|||||||
logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; '
|
logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; '
|
||||||
'clearUser: $clearUser');
|
'clearUser: $clearUser');
|
||||||
|
|
||||||
await chatPersistenceClient?.disconnect(flush: flushChatPersistence);
|
await _chatPersistenceClient?.disconnect(flush: flushChatPersistence);
|
||||||
chatPersistenceClient = null;
|
_chatPersistenceClient = null;
|
||||||
|
|
||||||
_connectCompleter = null;
|
_connectCompleter = null;
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client.dart';
|
|||||||
/// Current package version
|
/// Current package version
|
||||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||||
// ignore: constant_identifier_names
|
// ignore: constant_identifier_names
|
||||||
const PACKAGE_VERSION = '1.4.0-beta';
|
const PACKAGE_VERSION = '1.5.0';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: stream_chat
|
name: stream_chat
|
||||||
homepage: https://getstream.io/
|
homepage: https://getstream.io/
|
||||||
description: The official Dart client for Stream Chat, a service for building chat applications.
|
description: The official Dart client for Stream Chat, a service for building chat applications.
|
||||||
version: 1.4.0-beta
|
version: 1.5.0
|
||||||
repository: https://github.com/GetStream/stream-chat-flutter
|
repository: https://github.com/GetStream/stream-chat-flutter
|
||||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
- Fixed swipeable visible on navigation back
|
||||||
|
- Fixed video upload
|
||||||
|
- `MessageInput`: added more actions locations, merge actions and add `showCommandsButton` property
|
||||||
|
- 🛑 **BREAKING** Updated AttachmentBuilder signature
|
||||||
|
- Fixed image reloading on reaction.new
|
||||||
|
|
||||||
## 1.4.0-beta
|
## 1.4.0-beta
|
||||||
|
|
||||||
- Unfocus `MessageInput` only when sending commands
|
- Unfocus `MessageInput` only when sending commands
|
||||||
@@ -10,7 +18,7 @@
|
|||||||
- Added `MessageListView.onAttachmentTap` callback
|
- Added `MessageListView.onAttachmentTap` callback
|
||||||
- Fixed message newline issue
|
- Fixed message newline issue
|
||||||
- Fixed `MessageListView` scroll keyboard behaviour
|
- Fixed `MessageListView` scroll keyboard behaviour
|
||||||
- Minor fixes and improveqments
|
- Minor fixes and improvements
|
||||||
|
|
||||||
## 1.3.2-beta
|
## 1.3.2-beta
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.0
|
cupertino_icons: ^1.0.0
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
stream_chat:
|
||||||
|
path: ../../stream_chat
|
||||||
|
stream_chat_flutter_core:
|
||||||
|
path: ../../stream_chat_flutter_core
|
||||||
|
stream_chat_persistence:
|
||||||
|
path: ../../stream_chat_persistence
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'attachment_widget.dart';
|
|||||||
class FileAttachment extends AttachmentWidget {
|
class FileAttachment extends AttachmentWidget {
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final Widget trailing;
|
final Widget trailing;
|
||||||
|
final VoidCallback onAttachmentTap;
|
||||||
|
|
||||||
const FileAttachment({
|
const FileAttachment({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -21,6 +22,7 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
Size size,
|
Size size,
|
||||||
this.title,
|
this.title,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
|
this.onAttachmentTap,
|
||||||
}) : super(key: key, message: message, attachment: attachment, size: size);
|
}) : super(key: key, message: message, attachment: attachment, size: size);
|
||||||
|
|
||||||
bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video';
|
bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video';
|
||||||
@@ -30,45 +32,48 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
child: Container(
|
child: GestureDetector(
|
||||||
width: size?.width ?? 100,
|
onTap: onAttachmentTap,
|
||||||
height: 56.0,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
width: size?.width ?? 100,
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
height: 56.0,
|
||||||
borderRadius: BorderRadius.circular(12),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Row(
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Container(
|
height: 40.0,
|
||||||
height: 40.0,
|
width: 33.33,
|
||||||
width: 33.33,
|
margin: EdgeInsets.all(8.0),
|
||||||
margin: EdgeInsets.all(8.0),
|
child: _getFileTypeImage(context),
|
||||||
child: _getFileTypeImage(context),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8.0),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
attachment?.title ?? 'File',
|
|
||||||
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.0),
|
|
||||||
_buildSubtitle(context),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: 8.0),
|
||||||
SizedBox(width: 8.0),
|
Expanded(
|
||||||
_buildTrailing(context),
|
child: Column(
|
||||||
],
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
attachment?.title ?? 'File',
|
||||||
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
SizedBox(height: 3.0),
|
||||||
|
_buildSubtitle(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.0),
|
||||||
|
_buildTrailing(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final ShowMessageCallback onShowMessage;
|
final ShowMessageCallback onShowMessage;
|
||||||
final ValueChanged<ReturnActionType> onReturnAction;
|
final ValueChanged<ReturnActionType> onReturnAction;
|
||||||
|
final VoidCallback onAttachmentTap;
|
||||||
|
|
||||||
const GiphyAttachment({
|
const GiphyAttachment({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -21,6 +22,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
this.messageTheme,
|
this.messageTheme,
|
||||||
this.onShowMessage,
|
this.onShowMessage,
|
||||||
this.onReturnAction,
|
this.onReturnAction,
|
||||||
|
this.onAttachmentTap,
|
||||||
}) : super(key: key, message: message, attachment: attachment, size: size);
|
}) : super(key: key, message: message, attachment: attachment, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -62,7 +64,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _onImageTap(context),
|
onTap: () => onAttachmentTap ?? _onImageTap(context),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(8),
|
topLeft: Radius.circular(8),
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import 'package:shimmer/shimmer.dart';
|
|||||||
import 'package:stream_chat_flutter/src/attachment/attachment_upload_state_builder.dart';
|
import 'package:stream_chat_flutter/src/attachment/attachment_upload_state_builder.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
import 'attachment_title.dart';
|
|
||||||
import '../full_screen_media.dart';
|
import '../full_screen_media.dart';
|
||||||
import '../stream_chat_theme.dart';
|
import '../stream_chat_theme.dart';
|
||||||
|
import 'attachment_title.dart';
|
||||||
import 'attachment_widget.dart';
|
import 'attachment_widget.dart';
|
||||||
|
|
||||||
class ImageAttachment extends AttachmentWidget {
|
class ImageAttachment extends AttachmentWidget {
|
||||||
@@ -81,6 +81,7 @@ class ImageAttachment extends AttachmentWidget {
|
|||||||
return _buildImageAttachment(
|
return _buildImageAttachment(
|
||||||
context,
|
context,
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
|
cacheKey: imageUri.path,
|
||||||
height: size?.height,
|
height: size?.height,
|
||||||
width: size?.width,
|
width: size?.width,
|
||||||
placeholder: (_, __) {
|
placeholder: (_, __) {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_action.dart';
|
import 'package:stream_chat_flutter/src/message_action.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
import 'message_input.dart';
|
import 'message_input.dart';
|
||||||
@@ -34,6 +34,7 @@ class MessageActionsModal extends StatefulWidget {
|
|||||||
final ShapeBorder messageShape;
|
final ShapeBorder messageShape;
|
||||||
final ShapeBorder attachmentShape;
|
final ShapeBorder attachmentShape;
|
||||||
final DisplayWidget showUserAvatar;
|
final DisplayWidget showUserAvatar;
|
||||||
|
final BorderRadius attachmentBorderRadiusGeometry;
|
||||||
|
|
||||||
/// List of custom actions
|
/// List of custom actions
|
||||||
final List<MessageAction> customActions;
|
final List<MessageAction> customActions;
|
||||||
@@ -58,6 +59,7 @@ class MessageActionsModal extends StatefulWidget {
|
|||||||
this.attachmentShape,
|
this.attachmentShape,
|
||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
this.customActions = const [],
|
this.customActions = const [],
|
||||||
|
this.attachmentBorderRadiusGeometry,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -153,6 +155,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
child: MessageWidget(
|
child: MessageWidget(
|
||||||
key: Key('MessageWidget'),
|
key: Key('MessageWidget'),
|
||||||
reverse: widget.reverse,
|
reverse: widget.reverse,
|
||||||
|
attachmentBorderRadiusGeometry:
|
||||||
|
widget.attachmentBorderRadiusGeometry,
|
||||||
message: widget.message.copyWith(
|
message: widget.message.copyWith(
|
||||||
text: widget.message.text.length > 200
|
text: widget.message.text.length > 200
|
||||||
? '${widget.message.text.substring(0, 200)}...'
|
? '${widget.message.text.substring(0, 200)}...'
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
import 'package:stream_chat_flutter/src/video_service.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/media_list_view.dart';
|
import 'package:stream_chat_flutter/src/media_list_view.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/video_service.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
import 'package:substring_highlight/substring_highlight.dart';
|
import 'package:substring_highlight/substring_highlight.dart';
|
||||||
|
|
||||||
@@ -35,6 +35,8 @@ typedef AttachmentThumbnailBuilder = Widget Function(
|
|||||||
enum ActionsLocation {
|
enum ActionsLocation {
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
|
leftInside,
|
||||||
|
rightInside,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DefaultAttachmentTypes {
|
enum DefaultAttachmentTypes {
|
||||||
@@ -122,6 +124,7 @@ class MessageInput extends StatefulWidget {
|
|||||||
this.hideSendAsDm = false,
|
this.hideSendAsDm = false,
|
||||||
this.idleSendButton,
|
this.idleSendButton,
|
||||||
this.activeSendButton,
|
this.activeSendButton,
|
||||||
|
this.showCommandsButton = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Message to edit
|
/// Message to edit
|
||||||
@@ -149,6 +152,9 @@ class MessageInput extends StatefulWidget {
|
|||||||
/// If true the attachments button will not be displayed
|
/// If true the attachments button will not be displayed
|
||||||
final bool disableAttachments;
|
final bool disableAttachments;
|
||||||
|
|
||||||
|
/// Use this property to hide/show the commands button
|
||||||
|
final bool showCommandsButton;
|
||||||
|
|
||||||
/// Hide send as dm checkbox
|
/// Hide send as dm checkbox
|
||||||
final bool hideSendAsDm;
|
final bool hideSendAsDm;
|
||||||
|
|
||||||
@@ -308,7 +314,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: _buildTextField(context),
|
child: _buildTextField(context),
|
||||||
),
|
),
|
||||||
if (widget.parentMessage != null && !widget.hideSendAsDm)
|
if (widget.parentMessage != null && !widget.hideSendAsDm)
|
||||||
@@ -336,12 +342,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (!_commandEnabled) _buildExpandActionsButton(),
|
if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left)
|
||||||
if (widget.actionsLocation == ActionsLocation.left)
|
_buildExpandActionsButton(),
|
||||||
...widget.actions ?? [],
|
|
||||||
_buildTextInput(context),
|
_buildTextInput(context),
|
||||||
if (widget.actionsLocation == ActionsLocation.right)
|
if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right)
|
||||||
...widget.actions ?? [],
|
_buildExpandActionsButton(),
|
||||||
if (widget.sendButtonLocation == SendButtonLocation.outside)
|
if (widget.sendButtonLocation == SendButtonLocation.outside)
|
||||||
_animateSendButton(context),
|
_animateSendButton(context),
|
||||||
],
|
],
|
||||||
@@ -421,32 +426,36 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: widget.activeSendButton,
|
child: widget.activeSendButton,
|
||||||
)
|
)
|
||||||
: _buildSendButton(context);
|
: _buildSendButton(context);
|
||||||
return Padding(
|
return AnimatedCrossFade(
|
||||||
padding: const EdgeInsets.all(8.0),
|
crossFadeState: (_messageIsPresent || _attachments.isNotEmpty)
|
||||||
child: AnimatedCrossFade(
|
? CrossFadeState.showFirst
|
||||||
crossFadeState: (_messageIsPresent || _attachments.isNotEmpty)
|
: CrossFadeState.showSecond,
|
||||||
? CrossFadeState.showFirst
|
firstChild: sendButton,
|
||||||
: CrossFadeState.showSecond,
|
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
|
||||||
firstChild: sendButton,
|
duration:
|
||||||
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
|
StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration,
|
||||||
duration:
|
alignment: Alignment.center,
|
||||||
StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildExpandActionsButton() {
|
Widget _buildExpandActionsButton() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: AnimatedCrossFade(
|
child: AnimatedCrossFade(
|
||||||
crossFadeState: _actionsShrunk
|
crossFadeState: _actionsShrunk
|
||||||
? CrossFadeState.showFirst
|
? CrossFadeState.showFirst
|
||||||
: CrossFadeState.showSecond,
|
: CrossFadeState.showSecond,
|
||||||
firstChild: IconButton(
|
firstChild: IconButton(
|
||||||
onPressed: () => setState(() => _actionsShrunk = false),
|
onPressed: () => setState(() => _actionsShrunk = false),
|
||||||
icon: StreamSvgIcon.emptyCircleLeft(
|
icon: Transform.rotate(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
alignment: Alignment.center,
|
||||||
|
angle: (widget.actionsLocation == ActionsLocation.right ||
|
||||||
|
widget.actionsLocation == ActionsLocation.rightInside)
|
||||||
|
? pi
|
||||||
|
: 0,
|
||||||
|
child: StreamSvgIcon.emptyCircleLeft(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -457,10 +466,12 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
secondChild: FittedBox(
|
secondChild: FittedBox(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: <Widget>[
|
||||||
if (!widget.disableAttachments) _buildAttachmentButton(),
|
if (!widget.disableAttachments) _buildAttachmentButton(),
|
||||||
if (widget.editMessage == null &&
|
if (widget.showCommandsButton &&
|
||||||
|
widget.editMessage == null &&
|
||||||
StreamChannel.of(context)
|
StreamChannel.of(context)
|
||||||
.channel
|
.channel
|
||||||
?.config
|
?.config
|
||||||
@@ -468,6 +479,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true)
|
true)
|
||||||
_buildCommandButton(),
|
_buildCommandButton(),
|
||||||
|
...widget.actions ?? [],
|
||||||
].insertBetween(const SizedBox(width: 8)),
|
].insertBetween(const SizedBox(width: 8)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -565,51 +577,75 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11),
|
contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11),
|
||||||
prefixIconConstraints: BoxConstraints.tight(Size(78, 24)),
|
|
||||||
suffixIconConstraints: BoxConstraints.tight(Size(40, 40)),
|
|
||||||
prefixIcon: _commandEnabled
|
prefixIcon: _commandEnabled
|
||||||
? Container(
|
? Row(
|
||||||
decoration: BoxDecoration(
|
mainAxisSize: MainAxisSize.min,
|
||||||
borderRadius: BorderRadius.circular(12),
|
children: [
|
||||||
color: theme.colorTheme.accentBlue,
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.all(8.0),
|
||||||
margin: const EdgeInsets.only(right: 4, left: 8),
|
child: Container(
|
||||||
alignment: Alignment.center,
|
constraints: BoxConstraints.tight(Size(64, 24)),
|
||||||
child: Row(
|
decoration: BoxDecoration(
|
||||||
mainAxisSize: MainAxisSize.min,
|
borderRadius: BorderRadius.circular(12),
|
||||||
children: [
|
color: theme.colorTheme.accentBlue,
|
||||||
StreamSvgIcon.lightning(
|
),
|
||||||
color: Colors.white,
|
alignment: Alignment.center,
|
||||||
size: 16.0,
|
child: Row(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(
|
children: [
|
||||||
_chosenCommand?.name?.toUpperCase() ?? '',
|
StreamSvgIcon.lightning(
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.footnoteBold
|
|
||||||
.copyWith(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
size: 16.0,
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
_chosenCommand?.name?.toUpperCase() ?? '',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.footnoteBold
|
||||||
|
.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)
|
)
|
||||||
: null,
|
: (widget.actionsLocation == ActionsLocation.leftInside
|
||||||
|
? Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildExpandActionsButton(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null),
|
||||||
|
suffixIconConstraints: BoxConstraints.tightFor(height: 40),
|
||||||
|
prefixIconConstraints: BoxConstraints.tightFor(height: 40),
|
||||||
suffixIcon: Row(
|
suffixIcon: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (_commandEnabled)
|
if (_commandEnabled)
|
||||||
IconButton(
|
Padding(
|
||||||
icon: StreamSvgIcon.closeSmall(),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
splashRadius: 24,
|
child: IconButton(
|
||||||
padding: const EdgeInsets.all(0),
|
icon: StreamSvgIcon.closeSmall(),
|
||||||
constraints: BoxConstraints.tightFor(
|
splashRadius: 24,
|
||||||
height: 24,
|
padding: const EdgeInsets.all(0),
|
||||||
width: 24,
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _commandEnabled = false);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () {
|
|
||||||
setState(() => _commandEnabled = false);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
if (!_commandEnabled &&
|
||||||
|
widget.actionsLocation == ActionsLocation.rightInside)
|
||||||
|
_buildExpandActionsButton(),
|
||||||
if (widget.sendButtonLocation == SendButtonLocation.inside)
|
if (widget.sendButtonLocation == SendButtonLocation.inside)
|
||||||
_animateSendButton(context),
|
_animateSendButton(context),
|
||||||
],
|
],
|
||||||
@@ -619,7 +655,13 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
Timer _debounce;
|
Timer _debounce;
|
||||||
|
|
||||||
|
String _previousValue;
|
||||||
void _onChanged(BuildContext context, String s) {
|
void _onChanged(BuildContext context, String s) {
|
||||||
|
if (s == _previousValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_previousValue = s;
|
||||||
|
|
||||||
if (_debounce?.isActive == true) _debounce.cancel();
|
if (_debounce?.isActive == true) _debounce.cancel();
|
||||||
_debounce = Timer(
|
_debounce = Timer(
|
||||||
const Duration(milliseconds: 350),
|
const Duration(milliseconds: 350),
|
||||||
@@ -631,7 +673,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_messageIsPresent = s.trim().isNotEmpty;
|
_messageIsPresent = s.trim().isNotEmpty;
|
||||||
_actionsShrunk = s.trim().isNotEmpty;
|
_actionsShrunk = s.trim().isNotEmpty &&
|
||||||
|
((widget.actions?.length ?? 0) +
|
||||||
|
(widget.showCommandsButton ? 1 : 0) +
|
||||||
|
(widget.disableAttachments ? 0 : 1) >
|
||||||
|
1);
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandsOverlay?.remove();
|
_commandsOverlay?.remove();
|
||||||
@@ -1684,13 +1730,19 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommandButton() {
|
Widget _buildCommandButton() {
|
||||||
|
final s = textEditingController.text.trim();
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: StreamSvgIcon.lightning(
|
icon: StreamSvgIcon.lightning(
|
||||||
color: _commandsOverlay != null
|
color: s.isNotEmpty
|
||||||
? StreamChatTheme.of(context).messageInputTheme.actionButtonColor
|
? StreamChatTheme.of(context).colorTheme.greyGainsboro
|
||||||
: StreamChatTheme.of(context)
|
: (_commandsOverlay != null
|
||||||
.messageInputTheme
|
? StreamChatTheme.of(context)
|
||||||
.actionButtonIdleColor,
|
.messageInputTheme
|
||||||
|
.actionButtonColor
|
||||||
|
: StreamChatTheme.of(context)
|
||||||
|
.messageInputTheme
|
||||||
|
.actionButtonIdleColor),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -1711,7 +1763,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
if (_commandsOverlay == null) {
|
if (_commandsOverlay == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_commandsOverlay = _buildCommandsOverlayEntry();
|
_commandsOverlay = _buildCommandsOverlayEntry();
|
||||||
Overlay.of(context).insert(_commandsOverlay);
|
if (_commandsOverlay != null) {
|
||||||
|
Overlay.of(context).insert(_commandsOverlay);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -1904,11 +1958,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
||||||
final mimeType = file.name?.mimeType;
|
final mimeType = file.name?.mimeType ?? file.path.split('/').last.mimeType;
|
||||||
|
|
||||||
final extraDataMap = <String, dynamic>{};
|
final extraDataMap = <String, dynamic>{};
|
||||||
|
|
||||||
if (mimeType.type == 'video' || mimeType.type == 'image') {
|
if (mimeType?.type == 'video' || mimeType?.type == 'image') {
|
||||||
attachmentType = mimeType.type;
|
attachmentType = mimeType.type;
|
||||||
} else {
|
} else {
|
||||||
attachmentType = 'file';
|
attachmentType = 'file';
|
||||||
@@ -1965,24 +2019,31 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildIdleSendButton(BuildContext context) {
|
Widget _buildIdleSendButton(BuildContext context) {
|
||||||
return StreamSvgIcon(
|
return Padding(
|
||||||
assetName: _getIdleSendIcon(),
|
padding: const EdgeInsets.all(8.0),
|
||||||
color: StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor,
|
child: StreamSvgIcon(
|
||||||
|
assetName: _getIdleSendIcon(),
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSendButton(BuildContext context) {
|
Widget _buildSendButton(BuildContext context) {
|
||||||
return IconButton(
|
return Padding(
|
||||||
onPressed: sendMessage,
|
padding: const EdgeInsets.all(8.0),
|
||||||
padding: const EdgeInsets.all(0),
|
child: IconButton(
|
||||||
splashRadius: 24,
|
onPressed: sendMessage,
|
||||||
constraints: BoxConstraints.tightFor(
|
padding: const EdgeInsets.all(0),
|
||||||
height: 24,
|
splashRadius: 24,
|
||||||
width: 24,
|
constraints: BoxConstraints.tightFor(
|
||||||
),
|
height: 24,
|
||||||
icon: StreamSvgIcon(
|
width: 24,
|
||||||
assetName: _getSendIcon(),
|
),
|
||||||
color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor,
|
icon: StreamSvgIcon(
|
||||||
|
assetName: _getSendIcon(),
|
||||||
|
color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1021,15 +1021,19 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
!message.isSystem &&
|
!message.isSystem &&
|
||||||
!message.isEphemeral &&
|
!message.isEphemeral &&
|
||||||
widget.onMessageSwiped != null) {
|
widget.onMessageSwiped != null) {
|
||||||
child = Swipeable(
|
child = Container(
|
||||||
onSwipeEnd: () {
|
decoration: BoxDecoration(),
|
||||||
FocusScope.of(context).unfocus();
|
clipBehavior: Clip.hardEdge,
|
||||||
widget.onMessageSwiped(message);
|
child: Swipeable(
|
||||||
},
|
onSwipeEnd: () {
|
||||||
backgroundIcon: StreamSvgIcon.reply(
|
FocusScope.of(context).unfocus();
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
widget.onMessageSwiped(message);
|
||||||
|
},
|
||||||
|
backgroundIcon: StreamSvgIcon.reply(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
child: child,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
|
import 'extension.dart';
|
||||||
import 'message_widget.dart';
|
import 'message_widget.dart';
|
||||||
import 'stream_chat_theme.dart';
|
import 'stream_chat_theme.dart';
|
||||||
import 'extension.dart';
|
|
||||||
|
|
||||||
class MessageReactionsModal extends StatelessWidget {
|
class MessageReactionsModal extends StatelessWidget {
|
||||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||||
@@ -23,6 +23,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
final ShapeBorder messageShape;
|
final ShapeBorder messageShape;
|
||||||
final ShapeBorder attachmentShape;
|
final ShapeBorder attachmentShape;
|
||||||
final void Function(User) onUserAvatarTap;
|
final void Function(User) onUserAvatarTap;
|
||||||
|
final BorderRadius attachmentBorderRadiusGeometry;
|
||||||
|
|
||||||
const MessageReactionsModal({
|
const MessageReactionsModal({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -36,6 +37,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
this.reverse = false,
|
this.reverse = false,
|
||||||
this.showUserAvatar = DisplayWidget.show,
|
this.showUserAvatar = DisplayWidget.show,
|
||||||
this.onUserAvatarTap,
|
this.onUserAvatarTap,
|
||||||
|
this.attachmentBorderRadiusGeometry,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -132,6 +134,8 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
shape: messageShape,
|
shape: messageShape,
|
||||||
attachmentShape: attachmentShape,
|
attachmentShape: attachmentShape,
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
|
attachmentBorderRadiusGeometry:
|
||||||
|
attachmentBorderRadiusGeometry,
|
||||||
attachmentPadding: EdgeInsets.all(
|
attachmentPadding: EdgeInsets.all(
|
||||||
hasFileAttachment ? 4 : 2,
|
hasFileAttachment ? 4 : 2,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import 'extension.dart';
|
|||||||
import 'image_group.dart';
|
import 'image_group.dart';
|
||||||
import 'message_text.dart';
|
import 'message_text.dart';
|
||||||
|
|
||||||
typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment);
|
typedef AttachmentBuilder = Widget Function(
|
||||||
|
BuildContext,
|
||||||
|
Message,
|
||||||
|
List<Attachment>,
|
||||||
|
);
|
||||||
typedef OnQuotedMessageTap = void Function(String);
|
typedef OnQuotedMessageTap = void Function(String);
|
||||||
|
|
||||||
/// The display behaviour of a widget
|
/// The display behaviour of a widget
|
||||||
@@ -205,63 +209,152 @@ class MessageWidget extends StatefulWidget {
|
|||||||
this.customActions = const [],
|
this.customActions = const [],
|
||||||
this.onAttachmentTap,
|
this.onAttachmentTap,
|
||||||
}) : attachmentBuilders = {
|
}) : attachmentBuilders = {
|
||||||
'image': (context, message, attachment) {
|
'image': (context, message, attachments) {
|
||||||
return ImageAttachment(
|
var border = RoundedRectangleBorder(
|
||||||
attachment: attachment,
|
side: BorderSide.none,
|
||||||
message: message,
|
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
messageTheme: messageTheme,
|
);
|
||||||
size: Size(
|
|
||||||
MediaQuery.of(context).size.width * 0.8,
|
if (attachments.length > 1) {
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
return Padding(
|
||||||
|
padding: attachmentPadding,
|
||||||
|
child: wrapAttachmentWidget(
|
||||||
|
context,
|
||||||
|
Material(
|
||||||
|
color: messageTheme.messageBackgroundColor,
|
||||||
|
child: ImageGroup(
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
images: attachments,
|
||||||
|
message: message,
|
||||||
|
messageTheme: messageTheme,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
border,
|
||||||
|
reverse,
|
||||||
|
attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapAttachmentWidget(
|
||||||
|
context,
|
||||||
|
ImageAttachment(
|
||||||
|
attachment: attachments[0],
|
||||||
|
message: message,
|
||||||
|
messageTheme: messageTheme,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
|
onReturnAction: onReturnAction,
|
||||||
|
onAttachmentTap: onAttachmentTap != null
|
||||||
|
? () {
|
||||||
|
onAttachmentTap?.call(message, attachments[0]);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
onShowMessage: onShowMessage,
|
border,
|
||||||
onReturnAction: onReturnAction,
|
reverse,
|
||||||
onAttachmentTap: onAttachmentTap != null
|
attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
? () {
|
|
||||||
onAttachmentTap?.call(message, attachment);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'video': (context, message, attachment) {
|
'video': (context, message, attachments) {
|
||||||
return VideoAttachment(
|
var border = RoundedRectangleBorder(
|
||||||
attachment: attachment,
|
side: BorderSide.none,
|
||||||
messageTheme: messageTheme,
|
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
size: Size(
|
);
|
||||||
MediaQuery.of(context).size.width * 0.8,
|
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
return wrapAttachmentWidget(
|
||||||
|
context,
|
||||||
|
Column(
|
||||||
|
children: attachments.map((attachment) {
|
||||||
|
return VideoAttachment(
|
||||||
|
attachment: attachment,
|
||||||
|
messageTheme: messageTheme,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
|
onReturnAction: onReturnAction,
|
||||||
|
onAttachmentTap: onAttachmentTap != null
|
||||||
|
? () {
|
||||||
|
onAttachmentTap?.call(message, attachment);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
message: message,
|
border,
|
||||||
onShowMessage: onShowMessage,
|
reverse,
|
||||||
onReturnAction: onReturnAction,
|
attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
onAttachmentTap: onAttachmentTap != null
|
|
||||||
? () {
|
|
||||||
onAttachmentTap?.call(message, attachment);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'giphy': (context, message, attachment) {
|
'giphy': (context, message, attachments) {
|
||||||
return GiphyAttachment(
|
var border = RoundedRectangleBorder(
|
||||||
attachment: attachment,
|
side: BorderSide.none,
|
||||||
messageTheme: messageTheme,
|
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
message: message,
|
);
|
||||||
size: Size(
|
|
||||||
MediaQuery.of(context).size.width * 0.8,
|
return wrapAttachmentWidget(
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
context,
|
||||||
|
Column(
|
||||||
|
children: attachments.map((attachment) {
|
||||||
|
return GiphyAttachment(
|
||||||
|
attachment: attachment,
|
||||||
|
messageTheme: messageTheme,
|
||||||
|
message: message,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
|
onReturnAction: onReturnAction,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
onShowMessage: onShowMessage,
|
border,
|
||||||
onReturnAction: onReturnAction,
|
reverse,
|
||||||
|
attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'file': (context, message, attachment) {
|
'file': (context, message, attachments) {
|
||||||
return FileAttachment(
|
var border = RoundedRectangleBorder(
|
||||||
message: message,
|
side: attachmentBorderSide ??
|
||||||
attachment: attachment,
|
BorderSide(
|
||||||
size: Size(
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
MediaQuery.of(context).size.width * 0.8,
|
),
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
),
|
);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: attachments
|
||||||
|
.map<Widget>((attachment) {
|
||||||
|
return wrapAttachmentWidget(
|
||||||
|
context,
|
||||||
|
FileAttachment(
|
||||||
|
message: message,
|
||||||
|
attachment: attachment,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
border,
|
||||||
|
reverse,
|
||||||
|
attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.insertBetween(SizedBox(
|
||||||
|
height: attachmentPadding.vertical / 2,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}..addAll(customAttachmentBuilders ?? {}),
|
}..addAll(customAttachmentBuilders ?? {}),
|
||||||
@@ -739,6 +832,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageActionsModal(
|
child: MessageActionsModal(
|
||||||
|
attachmentBorderRadiusGeometry:
|
||||||
|
widget.attachmentBorderRadiusGeometry,
|
||||||
showUserAvatar:
|
showUserAvatar:
|
||||||
widget.message.user.id == channel.client.state.user.id
|
widget.message.user.id == channel.client.state.user.id
|
||||||
? DisplayWidget.gone
|
? DisplayWidget.gone
|
||||||
@@ -786,6 +881,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageReactionsModal(
|
child: MessageReactionsModal(
|
||||||
|
attachmentBorderRadiusGeometry:
|
||||||
|
widget.attachmentBorderRadiusGeometry,
|
||||||
showUserAvatar:
|
showUserAvatar:
|
||||||
widget.message.user.id == channel.client.state.user.id
|
widget.message.user.id == channel.client.state.user.id
|
||||||
? DisplayWidget.gone
|
? DisplayWidget.gone
|
||||||
@@ -830,55 +927,37 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _parseAttachments() {
|
Widget _parseAttachments() {
|
||||||
final images = widget.message.attachments
|
final attachmentGroups = <String, List<Attachment>>{};
|
||||||
?.where((element) =>
|
|
||||||
element.type == 'image' && element.ogScrapeUrl == null)
|
|
||||||
?.toList() ??
|
|
||||||
[];
|
|
||||||
|
|
||||||
if (images.length > 1) {
|
widget.message.attachments
|
||||||
return Padding(
|
.where((element) => element.ogScrapeUrl == null)
|
||||||
padding: widget.attachmentPadding,
|
.forEach((e) {
|
||||||
child: wrapAttachmentWidget(
|
if (attachmentGroups[e.type] == null) {
|
||||||
context,
|
attachmentGroups[e.type] = [];
|
||||||
Material(
|
}
|
||||||
color: widget.messageTheme.messageBackgroundColor,
|
|
||||||
child: ImageGroup(
|
attachmentGroups[e.type].add(e);
|
||||||
size: Size(
|
});
|
||||||
MediaQuery.of(context).size.width * 0.8,
|
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
final attachmentList = <Widget>[];
|
||||||
),
|
|
||||||
images: images,
|
attachmentGroups.forEach((type, attachments) {
|
||||||
message: widget.message,
|
final attachmentBuilder = widget.attachmentBuilders[type];
|
||||||
messageTheme: widget.messageTheme,
|
|
||||||
onShowMessage: widget.onShowMessage,
|
if (attachmentBuilder == null) return SizedBox();
|
||||||
),
|
final attachmentWidget = attachmentBuilder(
|
||||||
),
|
context,
|
||||||
),
|
widget.message,
|
||||||
|
attachments,
|
||||||
);
|
);
|
||||||
}
|
attachmentList.add(attachmentWidget);
|
||||||
|
});
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: widget.attachmentPadding,
|
padding: widget.attachmentPadding,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: widget.message.attachments
|
children: attachmentList?.insertBetween(SizedBox(
|
||||||
?.where((element) => element.ogScrapeUrl == null)
|
|
||||||
?.map((attachment) {
|
|
||||||
final attachmentBuilder =
|
|
||||||
widget.attachmentBuilders[attachment.type];
|
|
||||||
|
|
||||||
if (attachmentBuilder == null) return SizedBox();
|
|
||||||
final attachmentWidget = attachmentBuilder(
|
|
||||||
context,
|
|
||||||
widget.message,
|
|
||||||
attachment,
|
|
||||||
);
|
|
||||||
return wrapAttachmentWidget(
|
|
||||||
context,
|
|
||||||
attachmentWidget,
|
|
||||||
);
|
|
||||||
})?.insertBetween(SizedBox(
|
|
||||||
height: widget.attachmentPadding.vertical / 2,
|
height: widget.attachmentPadding.vertical / 2,
|
||||||
)) ??
|
)) ??
|
||||||
[],
|
[],
|
||||||
@@ -886,24 +965,6 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget wrapAttachmentWidget(
|
|
||||||
BuildContext context,
|
|
||||||
Widget attachmentWidget,
|
|
||||||
) {
|
|
||||||
final attachmentShape =
|
|
||||||
widget.attachmentShape ?? _getDefaultAttachmentShape(context);
|
|
||||||
return Material(
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
shape: attachmentShape,
|
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: Transform(
|
|
||||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: attachmentWidget,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onLongPress(BuildContext context) {
|
void onLongPress(BuildContext context) {
|
||||||
if (widget.message.isEphemeral ||
|
if (widget.message.isEphemeral ||
|
||||||
widget.message.status == MessageSendingStatus.sending) {
|
widget.message.status == MessageSendingStatus.sending) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -348,3 +350,25 @@ StreamSvgIcon getFileTypeImage(String type) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget wrapAttachmentWidget(
|
||||||
|
BuildContext context,
|
||||||
|
Widget attachmentWidget,
|
||||||
|
ShapeBorder attachmentShape,
|
||||||
|
bool reverse,
|
||||||
|
BorderRadius borderRadius,
|
||||||
|
) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
child: Material(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
shape: attachmentShape,
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.rotationY(reverse ? pi : 0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: attachmentWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: stream_chat_flutter
|
name: stream_chat_flutter
|
||||||
homepage: https://github.com/GetStream/stream-chat-flutter
|
homepage: https://github.com/GetStream/stream-chat-flutter
|
||||||
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
|
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
|
||||||
version: 1.4.0-beta
|
version: 1.5.0
|
||||||
repository: https://github.com/GetStream/stream-chat-flutter
|
repository: https://github.com/GetStream/stream-chat-flutter
|
||||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter_core: ^1.4.0-beta
|
stream_chat_flutter_core: ^1.5.0
|
||||||
photo_view: ^0.11.0
|
photo_view: ^0.11.0
|
||||||
rxdart: ^0.25.0
|
rxdart: ^0.25.0
|
||||||
scrollable_positioned_list: ^0.1.8
|
scrollable_positioned_list: ^0.1.8
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
* Minor fixes and improvements
|
||||||
|
|
||||||
## 1.4.0-beta
|
## 1.4.0-beta
|
||||||
|
|
||||||
* Added `MessageListCore.messageFilter` to filter messages locally
|
* Added `MessageListCore.messageFilter` to filter messages locally
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: stream_chat_flutter_core
|
name: stream_chat_flutter_core
|
||||||
homepage: https://github.com/GetStream/stream-chat-flutter
|
homepage: https://github.com/GetStream/stream-chat-flutter
|
||||||
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
|
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
|
||||||
version: 1.4.0-beta
|
version: 1.5.0
|
||||||
repository: https://github.com/GetStream/stream-chat-flutter
|
repository: https://github.com/GetStream/stream-chat-flutter
|
||||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ environment:
|
|||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
stream_chat: ^1.4.0-beta
|
stream_chat: ^1.5.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
rxdart: ^0.25.0
|
rxdart: ^0.25.0
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
* Update llc dependency
|
||||||
|
* Wait for all operations to finish before disconnecting
|
||||||
|
|
||||||
## 1.4.0-beta
|
## 1.4.0-beta
|
||||||
|
|
||||||
* Update llc dependency
|
* Update llc dependency
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/**/*.g.dart
|
||||||
|
- lib/**/*.freezed.dart
|
||||||
|
- example/*
|
||||||
|
- test/*
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- always_use_package_imports
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_slow_async_io
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- cancel_subscriptions
|
||||||
|
- close_sinks
|
||||||
|
- control_flow_in_finally
|
||||||
|
- diagnostic_describe_all_properties
|
||||||
|
- empty_statements
|
||||||
|
- hash_and_equals
|
||||||
|
- invariant_booleans
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
- literal_only_boolean_expressions
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- prefer_void_to_null
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- unnecessary_statements
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- omit_local_variable_types
|
||||||
|
- use_key_in_widget_constructors
|
||||||
|
- valid_regexps
|
||||||
|
- always_declare_return_types
|
||||||
|
- always_put_required_named_parameters_first
|
||||||
|
- always_require_non_null_named_parameters
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_catching_errors
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_positional_boolean_parameters
|
||||||
|
- avoid_private_typedef_functions
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_unused_constructor_parameters
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cascade_invocations
|
||||||
|
|
||||||
|
- constant_identifier_names
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- implementation_imports
|
||||||
|
- join_return_with_assignment
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- lines_longer_than_80_chars
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- null_closures
|
||||||
|
- one_member_abstracts
|
||||||
|
- only_throw_errors
|
||||||
|
- package_api_docs
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- parameter_assignments
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_asserts_with_message
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_constructors_over_static_methods
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_equal_for_default_values
|
||||||
|
- prefer_expression_function_bodies
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_int_literals
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- provide_deprecation_message
|
||||||
|
- public_member_api_docs
|
||||||
|
- recursive_getters
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_child_properties_last
|
||||||
|
- sort_constructors_first
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
|
||||||
|
- type_annotate_public_apis
|
||||||
|
- type_init_formals
|
||||||
|
- unnecessary_await_in_return
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_lambdas
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_raw_strings
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_to_and_as_if_applicable
|
||||||
|
- package_names
|
||||||
|
- sort_pub_dependencies
|
||||||
|
|
||||||
|
# To be added when null-safe:
|
||||||
|
# - cast_nullable_to_non_nullable
|
||||||
|
#- unnecessary_null_checks
|
||||||
|
# - tighten_type_of_initializing_formals
|
||||||
|
# - null_check_on_nullable_type_parameter
|
||||||
@@ -6,7 +6,7 @@ import 'package:moor/moor.dart';
|
|||||||
/// by the sqlite backend.
|
/// by the sqlite backend.
|
||||||
class ListConverter<T> extends TypeConverter<List<T>, String> {
|
class ListConverter<T> extends TypeConverter<List<T>, String> {
|
||||||
@override
|
@override
|
||||||
List<T> mapToDart(fromDb) {
|
List<T> mapToDart(String fromDb) {
|
||||||
if (fromDb == null) {
|
if (fromDb == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ class ListConverter<T> extends TypeConverter<List<T>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String mapToSql(value) {
|
String mapToSql(List<T> value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:moor/moor.dart';
|
|||||||
/// by the sqlite backend.
|
/// by the sqlite backend.
|
||||||
class MapConverter<T> extends TypeConverter<Map<String, T>, String> {
|
class MapConverter<T> extends TypeConverter<Map<String, T>, String> {
|
||||||
@override
|
@override
|
||||||
Map<String, T> mapToDart(fromDb) {
|
Map<String, T> mapToDart(String fromDb) {
|
||||||
if (fromDb == null) {
|
if (fromDb == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ class MapConverter<T> extends TypeConverter<Map<String, T>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String mapToSql(value) {
|
String mapToSql(Map<String, T> value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart';
|
|||||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.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/channels.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'channel_dao.g.dart';
|
part 'channel_dao.g.dart';
|
||||||
|
|
||||||
@@ -15,15 +15,14 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
ChannelDao(MoorChatDatabase db) : super(db);
|
ChannelDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
/// Get channel by cid
|
/// Get channel by cid
|
||||||
Future<ChannelModel> getChannelByCid(String cid) async {
|
Future<ChannelModel> getChannelByCid(String cid) async =>
|
||||||
return (select(channels)..where((c) => c.cid.equals(cid))).join([
|
(select(channels)..where((c) => c.cid.equals(cid))).join([
|
||||||
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
||||||
]).map((rows) {
|
]).map((rows) {
|
||||||
final channel = rows.readTable(channels);
|
final channel = rows.readTable(channels);
|
||||||
final createdBy = rows.readTable(users);
|
final createdBy = rows.readTable(users);
|
||||||
return channel.toChannelModel(createdBy: createdBy?.toUser());
|
return channel.toChannelModel(createdBy: createdBy?.toUser());
|
||||||
}).getSingle();
|
}).getSingle();
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete all channels by matching cid in [cids]
|
/// Delete all channels by matching cid in [cids]
|
||||||
///
|
///
|
||||||
@@ -31,27 +30,22 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
/// 1. Channel Reads
|
/// 1. Channel Reads
|
||||||
/// 2. Channel Members
|
/// 2. Channel Members
|
||||||
/// 3. Channel Messages -> Messages Reactions
|
/// 3. Channel Messages -> Messages Reactions
|
||||||
Future<void> deleteChannelByCids(List<String> cids) async {
|
Future<void> deleteChannelByCids(List<String> cids) async =>
|
||||||
return (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go();
|
(delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go();
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the channel cids saved in the storage
|
/// Get the channel cids saved in the storage
|
||||||
Future<List<String>> get cids {
|
Future<List<String>> get cids => (select(channels)
|
||||||
return (select(channels)
|
..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)])
|
||||||
..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)])
|
..limit(250))
|
||||||
..limit(250))
|
.map((c) => c.cid)
|
||||||
.map((c) => c.cid)
|
.get();
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates all the channels using the new [channelList] data
|
/// Updates all the channels using the new [channelList] data
|
||||||
Future<void> updateChannels(List<ChannelModel> channelList) {
|
Future<void> updateChannels(List<ChannelModel> channelList) => batch(
|
||||||
return batch(
|
(it) => it.insertAll(
|
||||||
(it) => it.insertAll(
|
channels,
|
||||||
channels,
|
channelList.map((c) => c.toEntity()).toList(),
|
||||||
channelList.map((c) => c.toEntity()).toList(),
|
mode: InsertMode.insertOrReplace,
|
||||||
mode: InsertMode.insertOrReplace,
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ 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/channel_queries.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/channels.dart';
|
import 'package:stream_chat_persistence/src/entity/channels.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/mapper.dart';
|
|
||||||
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'channel_query_dao.g.dart';
|
part 'channel_query_dao.g.dart';
|
||||||
|
|
||||||
@@ -30,29 +31,31 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
/// the list of matching rows will be deleted
|
/// the list of matching rows will be deleted
|
||||||
Future<void> updateChannelQueries(
|
Future<void> updateChannelQueries(
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<String> cids,
|
List<String> cids, {
|
||||||
bool clearQueryCache,
|
bool clearQueryCache,
|
||||||
) async {
|
}) async =>
|
||||||
final hash = _computeHash(filter);
|
transaction(() async {
|
||||||
if (clearQueryCache) {
|
final hash = _computeHash(filter);
|
||||||
await batch((it) {
|
if (clearQueryCache) {
|
||||||
it.deleteWhere<ChannelQueries, ChannelQueryEntity>(
|
await batch((it) {
|
||||||
channelQueries,
|
it.deleteWhere<ChannelQueries, ChannelQueryEntity>(
|
||||||
(c) => c.queryHash.equals(hash),
|
channelQueries,
|
||||||
);
|
(c) => c.queryHash.equals(hash),
|
||||||
});
|
);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return batch((it) {
|
await batch((it) {
|
||||||
it.insertAll(
|
it.insertAll(
|
||||||
channelQueries,
|
channelQueries,
|
||||||
cids.map((cid) {
|
cids
|
||||||
return ChannelQueryEntity(queryHash: hash, channelCid: cid);
|
.map((cid) =>
|
||||||
}).toList(),
|
ChannelQueryEntity(queryHash: hash, channelCid: cid))
|
||||||
mode: InsertMode.insertOrReplace,
|
.toList(),
|
||||||
);
|
mode: InsertMode.insertOrReplace,
|
||||||
});
|
);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/// Get list of channels by filter, sort and paginationParams
|
/// Get list of channels by filter, sort and paginationParams
|
||||||
Future<List<ChannelModel>> getChannels({
|
Future<List<ChannelModel>> getChannels({
|
||||||
@@ -67,7 +70,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}(), '');
|
||||||
|
|
||||||
final hash = _computeHash(filter);
|
final hash = _computeHash(filter);
|
||||||
final cachedChannelCids = await (select(channelQueries)
|
final cachedChannelCids = await (select(channelQueries)
|
||||||
@@ -86,10 +89,11 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
})).get();
|
})).get();
|
||||||
|
|
||||||
final possibleSortingFields = cachedChannels.fold<List<String>>(
|
final possibleSortingFields = cachedChannels.fold<List<String>>(
|
||||||
ChannelModel.topLevelFields, (previousValue, element) {
|
ChannelModel.topLevelFields,
|
||||||
return {...previousValue, ...element.extraData.keys}.toList();
|
(previousValue, element) =>
|
||||||
});
|
{...previousValue, ...element.extraData.keys}.toList());
|
||||||
|
|
||||||
|
// ignore: parameter_assignments
|
||||||
sort = sort
|
sort = sort
|
||||||
?.where((s) => possibleSortingFields.contains(s.field))
|
?.where((s) => possibleSortingFields.contains(s.field))
|
||||||
?.toList(growable: false);
|
?.toList(growable: false);
|
||||||
@@ -117,7 +121,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
|
|
||||||
cachedChannels.sort(chainedComparator);
|
cachedChannels.sort(chainedComparator);
|
||||||
|
|
||||||
if (paginationParams?.offset != null) {
|
if (paginationParams?.offset != null && cachedChannels.isNotEmpty) {
|
||||||
cachedChannels.removeRange(0, paginationParams.offset);
|
cachedChannels.removeRange(0, paginationParams.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import 'package:stream_chat/stream_chat.dart';
|
|||||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.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/connection_events.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/mapper.dart';
|
|
||||||
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'connection_event_dao.g.dart';
|
part 'connection_event_dao.g.dart';
|
||||||
|
|
||||||
@@ -15,40 +16,38 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
ConnectionEventDao(MoorChatDatabase db) : super(db);
|
ConnectionEventDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
/// Get the latest stored connection event
|
/// Get the latest stored connection event
|
||||||
Future<Event> get connectionEvent {
|
Future<Event> get connectionEvent => select(connectionEvents)
|
||||||
return select(connectionEvents).map((eventEntity) {
|
.map((eventEntity) => eventEntity.toEvent())
|
||||||
return eventEntity.toEvent();
|
.getSingle();
|
||||||
}).getSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the latest stored lastSyncAt
|
/// Get the latest stored lastSyncAt
|
||||||
Future<DateTime> get lastSyncAt {
|
Future<DateTime> get lastSyncAt =>
|
||||||
return select(connectionEvents).getSingle().then((r) => r?.lastSyncAt);
|
select(connectionEvents).getSingle().then((r) => r?.lastSyncAt);
|
||||||
}
|
|
||||||
|
|
||||||
/// Update stored connection event with latest data
|
/// Update stored connection event with latest data
|
||||||
Future<int> updateConnectionEvent(Event event) async {
|
Future<void> updateConnectionEvent(Event event) async =>
|
||||||
final connectionInfo = await select(connectionEvents).getSingle();
|
transaction(() async {
|
||||||
return into(connectionEvents).insert(
|
final connectionInfo = await select(connectionEvents).getSingle();
|
||||||
ConnectionEventEntity(
|
await into(connectionEvents).insert(
|
||||||
id: 1,
|
ConnectionEventEntity(
|
||||||
lastSyncAt: connectionInfo?.lastSyncAt,
|
id: 1,
|
||||||
lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt,
|
lastSyncAt: connectionInfo?.lastSyncAt,
|
||||||
totalUnreadCount:
|
lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt,
|
||||||
event.totalUnreadCount ?? connectionInfo?.totalUnreadCount,
|
totalUnreadCount:
|
||||||
ownUser: event.me?.toJson() ?? connectionInfo?.ownUser,
|
event.totalUnreadCount ?? connectionInfo?.totalUnreadCount,
|
||||||
unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels,
|
ownUser: event.me?.toJson() ?? connectionInfo?.ownUser,
|
||||||
),
|
unreadChannels:
|
||||||
mode: InsertMode.insertOrReplace,
|
event.unreadChannels ?? connectionInfo?.unreadChannels,
|
||||||
);
|
),
|
||||||
}
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
/// Update stored lastSyncAt with latest data
|
/// Update stored lastSyncAt with latest data
|
||||||
Future<int> updateLastSyncAt(DateTime lastSyncAt) async {
|
Future<int> updateLastSyncAt(DateTime lastSyncAt) async =>
|
||||||
return (update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write(
|
(update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write(
|
||||||
ConnectionEventsCompanion(
|
ConnectionEventsCompanion(
|
||||||
lastSyncAt: Value(lastSyncAt),
|
lastSyncAt: Value(lastSyncAt),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export 'user_dao.dart';
|
|
||||||
export 'channel_dao.dart';
|
export 'channel_dao.dart';
|
||||||
|
export 'channel_query_dao.dart';
|
||||||
|
export 'connection_event_dao.dart';
|
||||||
|
export 'member_dao.dart';
|
||||||
export 'message_dao.dart';
|
export 'message_dao.dart';
|
||||||
export 'pinned_message_dao.dart';
|
export 'pinned_message_dao.dart';
|
||||||
export 'member_dao.dart';
|
|
||||||
export 'connection_event_dao.dart';
|
|
||||||
export 'reaction_dao.dart';
|
export 'reaction_dao.dart';
|
||||||
export 'read_dao.dart';
|
export 'read_dao.dart';
|
||||||
export 'channel_query_dao.dart';
|
export 'user_dao.dart';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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/members.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
|
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'member_dao.g.dart';
|
part 'member_dao.g.dart';
|
||||||
|
|
||||||
@@ -17,37 +17,33 @@ class MemberDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
MemberDao(MoorChatDatabase db) : super(db);
|
MemberDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
/// Get all members where [Members.channelCid] matches [cid]
|
/// Get all members where [Members.channelCid] matches [cid]
|
||||||
Future<List<Member>> getMembersByCid(String cid) async {
|
Future<List<Member>> getMembersByCid(String cid) async =>
|
||||||
return (select(members).join([
|
(select(members).join([
|
||||||
leftOuterJoin(users, members.userId.equalsExp(users.id)),
|
leftOuterJoin(users, members.userId.equalsExp(users.id)),
|
||||||
])
|
])
|
||||||
..where(members.channelCid.equals(cid))
|
..where(members.channelCid.equals(cid))
|
||||||
..orderBy([OrderingTerm.asc(members.createdAt)]))
|
..orderBy([OrderingTerm.asc(members.createdAt)]))
|
||||||
.map((row) {
|
.map((row) {
|
||||||
final userEntity = row.readTable(users);
|
final userEntity = row.readTable(users);
|
||||||
final memberEntity = row.readTable(members);
|
final memberEntity = row.readTable(members);
|
||||||
return memberEntity.toMember(user: userEntity?.toUser());
|
return memberEntity.toMember(user: userEntity?.toUser());
|
||||||
}).get();
|
}).get();
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates all the members using the new [memberList] data
|
/// Updates all the members using the new [memberList] data
|
||||||
Future<void> updateMembers(String cid, List<Member> memberList) async {
|
Future<void> updateMembers(String cid, List<Member> memberList) async =>
|
||||||
return batch(
|
batch(
|
||||||
(it) => it.insertAll(
|
(it) => it.insertAll(
|
||||||
members,
|
members,
|
||||||
memberList.map((m) => m.toEntity(cid: cid)).toList(),
|
memberList.map((m) => m.toEntity(cid: cid)).toList(),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all the members whose [Members.channelCid] is present in [cids]
|
/// Deletes all the members whose [Members.channelCid] is present in [cids]
|
||||||
Future<void> deleteMemberByCids(List<String> cids) async {
|
Future<void> deleteMemberByCids(List<String> cids) async => batch((it) {
|
||||||
return batch((it) {
|
it.deleteWhere<Members, MemberEntity>(
|
||||||
it.deleteWhere<Members, MemberEntity>(
|
members,
|
||||||
members,
|
(m) => m.channelCid.isIn(cids),
|
||||||
(m) => m.channelCid.isIn(cids),
|
);
|
||||||
);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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/messages.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
|
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'message_dao.g.dart';
|
part 'message_dao.g.dart';
|
||||||
|
|
||||||
@@ -25,17 +25,15 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
///
|
///
|
||||||
/// This will automatically delete the following linked records
|
/// This will automatically delete the following linked records
|
||||||
/// 1. Message Reactions
|
/// 1. Message Reactions
|
||||||
Future<void> deleteMessageByIds(List<String> messageIds) {
|
Future<void> deleteMessageByIds(List<String> messageIds) =>
|
||||||
return (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go();
|
(delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go();
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes all the messages by matching [Messages.channelCid] in [cids]
|
/// Removes all the messages by matching [Messages.channelCid] in [cids]
|
||||||
///
|
///
|
||||||
/// This will automatically delete the following linked records
|
/// This will automatically delete the following linked records
|
||||||
/// 1. Message Reactions
|
/// 1. Message Reactions
|
||||||
Future<void> deleteMessageByCids(List<String> cids) async {
|
Future<void> deleteMessageByCids(List<String> cids) async =>
|
||||||
return (delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
(delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
||||||
}
|
|
||||||
|
|
||||||
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
||||||
final userEntity = rows.readTable(_users);
|
final userEntity = rows.readTable(_users);
|
||||||
@@ -60,31 +58,29 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a single message by matching the [Messages.id] with [id]
|
/// Returns a single message by matching the [Messages.id] with [id]
|
||||||
Future<Message> getMessageById(String id) async {
|
Future<Message> getMessageById(String id) async =>
|
||||||
return await (select(messages).join([
|
await (select(messages).join([
|
||||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(
|
leftOuterJoin(_pinnedByUsers,
|
||||||
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||||
])
|
])
|
||||||
..where(messages.id.equals(id)))
|
..where(messages.id.equals(id)))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
.getSingle();
|
.getSingle();
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the messages of a particular thread by matching
|
/// Returns all the messages of a particular thread by matching
|
||||||
/// [Messages.channelCid] with [cid]
|
/// [Messages.channelCid] with [cid]
|
||||||
Future<List<Message>> getThreadMessages(String cid) async {
|
Future<List<Message>> getThreadMessages(String cid) async =>
|
||||||
return Future.wait(await (select(messages).join([
|
Future.wait(await (select(messages).join([
|
||||||
leftOuterJoin(users, messages.userId.equalsExp(_users.id)),
|
leftOuterJoin(users, messages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(
|
leftOuterJoin(_pinnedByUsers,
|
||||||
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||||
])
|
])
|
||||||
..where(messages.channelCid.equals(cid))
|
..where(messages.channelCid.equals(cid))
|
||||||
..where(isNotNull(messages.parentId))
|
..where(isNotNull(messages.parentId))
|
||||||
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
.get());
|
.get());
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the messages of a particular thread by matching
|
/// Returns all the messages of a particular thread by matching
|
||||||
/// [Messages.parentId] with [parentId]
|
/// [Messages.parentId] with [parentId]
|
||||||
@@ -102,7 +98,7 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
.get());
|
.get());
|
||||||
|
|
||||||
if (options?.lessThan != null) {
|
if (options?.lessThan != null && msgList.isNotEmpty) {
|
||||||
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
|
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
|
||||||
msgList.removeRange(lessThanIndex, msgList.length);
|
msgList.removeRange(lessThanIndex, msgList.length);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
import 'package:stream_chat_persistence/src/entity/pinned_messages.dart';
|
import 'package:stream_chat_persistence/src/entity/pinned_messages.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
|
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'pinned_message_dao.g.dart';
|
part 'pinned_message_dao.g.dart';
|
||||||
|
|
||||||
@@ -25,19 +25,15 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
///
|
///
|
||||||
/// This will automatically delete the following linked records
|
/// This will automatically delete the following linked records
|
||||||
/// 1. Message Reactions
|
/// 1. Message Reactions
|
||||||
Future<void> deleteMessageByIds(List<String> messageIds) {
|
Future<void> deleteMessageByIds(List<String> messageIds) =>
|
||||||
return (delete(pinnedMessages)..where((tbl) => tbl.id.isIn(messageIds)))
|
(delete(pinnedMessages)..where((tbl) => tbl.id.isIn(messageIds))).go();
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes all the messages by matching [PinnedMessages.channelCid] in [cids]
|
/// Removes all the messages by matching [PinnedMessages.channelCid] in [cids]
|
||||||
///
|
///
|
||||||
/// This will automatically delete the following linked records
|
/// This will automatically delete the following linked records
|
||||||
/// 1. Message Reactions
|
/// 1. Message Reactions
|
||||||
Future<void> deleteMessageByCids(List<String> cids) async {
|
Future<void> deleteMessageByCids(List<String> cids) async =>
|
||||||
return (delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids)))
|
(delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
||||||
final userEntity = rows.readTable(users);
|
final userEntity = rows.readTable(users);
|
||||||
@@ -62,31 +58,29 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a single message by matching the [PinnedMessages.id] with [id]
|
/// Returns a single message by matching the [PinnedMessages.id] with [id]
|
||||||
Future<Message> getMessageById(String id) async {
|
Future<Message> getMessageById(String id) async =>
|
||||||
return await (select(pinnedMessages).join([
|
await (select(pinnedMessages).join([
|
||||||
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(_pinnedByUsers,
|
leftOuterJoin(_pinnedByUsers,
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.id.equals(id)))
|
..where(pinnedMessages.id.equals(id)))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
.getSingle();
|
.getSingle();
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the messages of a particular thread by matching
|
/// Returns all the messages of a particular thread by matching
|
||||||
/// [PinnedMessages.channelCid] with [cid]
|
/// [PinnedMessages.channelCid] with [cid]
|
||||||
Future<List<Message>> getThreadMessages(String cid) async {
|
Future<List<Message>> getThreadMessages(String cid) async =>
|
||||||
return Future.wait(await (select(pinnedMessages).join([
|
Future.wait(await (select(pinnedMessages).join([
|
||||||
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(_pinnedByUsers,
|
leftOuterJoin(_pinnedByUsers,
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.channelCid.equals(cid))
|
..where(pinnedMessages.channelCid.equals(cid))
|
||||||
..where(isNotNull(pinnedMessages.parentId))
|
..where(isNotNull(pinnedMessages.parentId))
|
||||||
..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]))
|
..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
.get());
|
.get());
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the messages of a particular thread by matching
|
/// Returns all the messages of a particular thread by matching
|
||||||
/// [PinnedMessages.parentId] with [parentId]
|
/// [PinnedMessages.parentId] with [parentId]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart';
|
|||||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.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/reactions.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'reaction_dao.g.dart';
|
part 'reaction_dao.g.dart';
|
||||||
|
|
||||||
@@ -16,18 +16,17 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
|
|
||||||
/// Returns all the reactions of a particular message by matching
|
/// Returns all the reactions of a particular message by matching
|
||||||
/// [Reactions.messageId] with [messageId]
|
/// [Reactions.messageId] with [messageId]
|
||||||
Future<List<Reaction>> getReactions(String messageId) {
|
Future<List<Reaction>> getReactions(String messageId) =>
|
||||||
return (select(reactions).join([
|
(select(reactions).join([
|
||||||
leftOuterJoin(users, reactions.userId.equalsExp(users.id)),
|
leftOuterJoin(users, reactions.userId.equalsExp(users.id)),
|
||||||
])
|
])
|
||||||
..where(reactions.messageId.equals(messageId))
|
..where(reactions.messageId.equals(messageId))
|
||||||
..orderBy([OrderingTerm.asc(reactions.createdAt)]))
|
..orderBy([OrderingTerm.asc(reactions.createdAt)]))
|
||||||
.map((rows) {
|
.map((rows) {
|
||||||
final userEntity = rows.readTable(users);
|
final userEntity = rows.readTable(users);
|
||||||
final reactionEntity = rows.readTable(reactions);
|
final reactionEntity = rows.readTable(reactions);
|
||||||
return reactionEntity.toReaction(user: userEntity?.toUser());
|
return reactionEntity.toReaction(user: userEntity?.toUser());
|
||||||
}).get();
|
}).get();
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all the reactions of a particular message
|
/// Returns all the reactions of a particular message
|
||||||
/// added by a particular user by matching
|
/// added by a particular user by matching
|
||||||
@@ -42,23 +41,21 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the reactions data with the new [reactionList] data
|
/// Updates the reactions data with the new [reactionList] data
|
||||||
Future<void> updateReactions(List<Reaction> reactionList) {
|
Future<void> updateReactions(List<Reaction> reactionList) => batch((it) {
|
||||||
return batch((it) {
|
it.insertAll(
|
||||||
it.insertAll(
|
reactions,
|
||||||
reactions,
|
reactionList.map((r) => r.toEntity()).toList(),
|
||||||
reactionList.map((r) => r.toEntity()).toList(),
|
mode: InsertMode.insertOrReplace,
|
||||||
mode: InsertMode.insertOrReplace,
|
);
|
||||||
);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all the reactions whose [Reactions.messageId] is present in [messageIds]
|
/// Deletes all the reactions whose [Reactions.messageId] is
|
||||||
Future<void> deleteReactionsByMessageIds(List<String> messageIds) {
|
/// present in [messageIds]
|
||||||
return batch((it) {
|
Future<void> deleteReactionsByMessageIds(List<String> messageIds) =>
|
||||||
it.deleteWhere<Reactions, ReactionEntity>(
|
batch((it) {
|
||||||
reactions,
|
it.deleteWhere<Reactions, ReactionEntity>(
|
||||||
(r) => r.messageId.isIn(messageIds),
|
reactions,
|
||||||
);
|
(r) => r.messageId.isIn(messageIds),
|
||||||
});
|
);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:stream_chat/stream_chat.dart';
|
|||||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.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/reads.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||||
|
|
||||||
part 'read_dao.g.dart';
|
part 'read_dao.g.dart';
|
||||||
|
|
||||||
@@ -14,30 +14,26 @@ class ReadDao extends DatabaseAccessor<MoorChatDatabase> with _$ReadDaoMixin {
|
|||||||
ReadDao(MoorChatDatabase db) : super(db);
|
ReadDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
/// Get all reads where [Reads.channelCid] matches [cid]
|
/// Get all reads where [Reads.channelCid] matches [cid]
|
||||||
Future<List<Read>> getReadsByCid(String cid) async {
|
Future<List<Read>> getReadsByCid(String cid) async => (select(reads).join([
|
||||||
return (select(reads).join([
|
leftOuterJoin(users, reads.userId.equalsExp(users.id)),
|
||||||
leftOuterJoin(users, reads.userId.equalsExp(users.id)),
|
])
|
||||||
])
|
..where(reads.channelCid.equals(cid))
|
||||||
..where(reads.channelCid.equals(cid))
|
..orderBy([
|
||||||
..orderBy([
|
OrderingTerm.asc(reads.lastRead),
|
||||||
OrderingTerm.asc(reads.lastRead),
|
]))
|
||||||
]))
|
.map((row) {
|
||||||
.map((row) {
|
final userEntity = row.readTable(users);
|
||||||
final userEntity = row.readTable(users);
|
final readEntity = row.readTable(reads);
|
||||||
final readEntity = row.readTable(reads);
|
return readEntity.toRead(user: userEntity?.toUser());
|
||||||
return readEntity.toRead(user: userEntity?.toUser());
|
}).get();
|
||||||
}).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the read data of a particular channel with
|
/// Updates the read data of a particular channel with
|
||||||
/// the new [readList] data
|
/// the new [readList] data
|
||||||
Future<void> updateReads(String cid, List<Read> readList) {
|
Future<void> updateReads(String cid, List<Read> readList) => batch(
|
||||||
return batch(
|
(it) => it.insertAll(
|
||||||
(it) => it.insertAll(
|
reads,
|
||||||
reads,
|
readList.map((r) => r.toEntity(cid: cid)).toList(),
|
||||||
readList.map((r) => r.toEntity(cid: cid)).toList(),
|
mode: InsertMode.insertOrReplace,
|
||||||
mode: InsertMode.insertOrReplace,
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:moor/moor.dart';
|
|||||||
import 'package:stream_chat/stream_chat.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/db/moor_chat_database.dart';
|
||||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||||
import '../mapper/user_mapper.dart';
|
import 'package:stream_chat_persistence/src/mapper/user_mapper.dart';
|
||||||
|
|
||||||
part 'user_dao.g.dart';
|
part 'user_dao.g.dart';
|
||||||
|
|
||||||
@@ -13,13 +13,11 @@ class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
|
|||||||
UserDao(MoorChatDatabase db) : super(db);
|
UserDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
/// Updates the users data with the new [userList] data
|
/// Updates the users data with the new [userList] data
|
||||||
Future<void> updateUsers(List<User> userList) {
|
Future<void> updateUsers(List<User> userList) => batch(
|
||||||
return batch(
|
(it) => it.insertAll(
|
||||||
(it) => it.insertAll(
|
users,
|
||||||
users,
|
userList.map((u) => u.toEntity()).toList(),
|
||||||
userList.map((u) => u.toEntity()).toList(),
|
mode: InsertMode.insertOrReplace,
|
||||||
mode: InsertMode.insertOrReplace,
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
import 'package:moor/isolate.dart';
|
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_persistence/src/converter/converter.dart';
|
||||||
import '../entity/entity.dart';
|
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||||
import '../dao/dao.dart';
|
import 'package:stream_chat_persistence/src/db/shared/shared_db.dart';
|
||||||
import '../converter/converter.dart';
|
import 'package:stream_chat_persistence/src/entity/entity.dart';
|
||||||
import 'shared/shared_db.dart';
|
|
||||||
|
|
||||||
part 'moor_chat_database.g.dart';
|
part 'moor_chat_database.g.dart';
|
||||||
|
|
||||||
LazyDatabase _openConnection(
|
LazyDatabase _openConnection(
|
||||||
String userId, {
|
String userId, {
|
||||||
logStatements = false,
|
bool logStatements = false,
|
||||||
}) {
|
bool persistOnDisk = true,
|
||||||
return LazyDatabase(() async {
|
}) =>
|
||||||
return await SharedDB.constructDatabase(
|
LazyDatabase(() async => SharedDB.constructDatabase(
|
||||||
userId,
|
userId,
|
||||||
logStatements: logStatements,
|
logStatements: logStatements,
|
||||||
);
|
persistOnDisk: persistOnDisk,
|
||||||
});
|
));
|
||||||
}
|
|
||||||
|
|
||||||
/// A chat database implemented using moor
|
/// A chat database implemented using moor
|
||||||
@UseMoor(tables: [
|
@UseMoor(tables: [
|
||||||
@@ -48,15 +45,16 @@ class MoorChatDatabase extends _$MoorChatDatabase {
|
|||||||
MoorChatDatabase(
|
MoorChatDatabase(
|
||||||
this._userId, {
|
this._userId, {
|
||||||
logStatements = false,
|
logStatements = false,
|
||||||
|
bool persistOnDisk = true,
|
||||||
}) : super(_openConnection(
|
}) : super(_openConnection(
|
||||||
_userId,
|
_userId,
|
||||||
logStatements: logStatements,
|
logStatements: logStatements,
|
||||||
|
persistOnDisk: persistOnDisk,
|
||||||
));
|
));
|
||||||
|
|
||||||
/// Instantiate a new database instance
|
/// Instantiate a new database instance
|
||||||
MoorChatDatabase.connect(
|
MoorChatDatabase.connect(
|
||||||
this._userId,
|
this._userId,
|
||||||
this._isolate,
|
|
||||||
DatabaseConnection connection,
|
DatabaseConnection connection,
|
||||||
) : super.connect(connection);
|
) : super.connect(connection);
|
||||||
|
|
||||||
@@ -65,8 +63,6 @@ class MoorChatDatabase extends _$MoorChatDatabase {
|
|||||||
/// User id to which the database is connected
|
/// User id to which the database is connected
|
||||||
String get userId => _userId;
|
String get userId => _userId;
|
||||||
|
|
||||||
MoorIsolate _isolate;
|
|
||||||
|
|
||||||
// you should bump this number whenever you change or add a table definition.
|
// you should bump this number whenever you change or add a table definition.
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 2;
|
int get schemaVersion => 2;
|
||||||
@@ -85,8 +81,5 @@ class MoorChatDatabase extends _$MoorChatDatabase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// Closes the database instance
|
/// Closes the database instance
|
||||||
Future<void> disconnect() async {
|
Future<void> disconnect() => close();
|
||||||
await _isolate?.shutdownAll();
|
|
||||||
await close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:moor/ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:moor/isolate.dart';
|
import 'package:moor/isolate.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
@@ -8,7 +9,7 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
||||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||||
|
|
||||||
import '../moor_chat_database.dart';
|
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||||
|
|
||||||
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
||||||
/// for native platform applications
|
/// for native platform applications
|
||||||
@@ -20,28 +21,29 @@ class SharedDB {
|
|||||||
static Future<VmDatabase> constructDatabase(
|
static Future<VmDatabase> constructDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
|
bool persistOnDisk = true,
|
||||||
}) async {
|
}) async {
|
||||||
final dbName = 'db_$userId';
|
final dbName = 'db_$userId';
|
||||||
if (Platform.isIOS || Platform.isAndroid) {
|
if (persistOnDisk) {
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
if (Platform.isIOS || Platform.isAndroid) {
|
||||||
final path = join(dir.path, '$dbName.sqlite');
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
final file = File(path);
|
final path = join(dir.path, '$dbName.sqlite');
|
||||||
return VmDatabase(file, logStatements: logStatements);
|
final file = File(path);
|
||||||
}
|
return VmDatabase(file, logStatements: logStatements);
|
||||||
if (Platform.isMacOS || Platform.isLinux) {
|
}
|
||||||
final file = File('$dbName.sqlite');
|
if (Platform.isMacOS || Platform.isLinux) {
|
||||||
return VmDatabase(file, logStatements: logStatements);
|
final file = File('$dbName.sqlite');
|
||||||
|
return VmDatabase(file, logStatements: logStatements);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return VmDatabase.memory(logStatements: logStatements);
|
return VmDatabase.memory(logStatements: logStatements);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _startBackground(_IsolateStartRequest request) {
|
static void _startBackground(_IsolateStartRequest request) {
|
||||||
final executor = LazyDatabase(() async {
|
final executor = LazyDatabase(() async => VmDatabase(
|
||||||
return VmDatabase(
|
File(request.targetPath),
|
||||||
File(request.targetPath),
|
logStatements: request.logStatements,
|
||||||
logStatements: request.logStatements,
|
));
|
||||||
);
|
|
||||||
});
|
|
||||||
final moorIsolate = MoorIsolate.inCurrent(
|
final moorIsolate = MoorIsolate.inCurrent(
|
||||||
() => DatabaseConnection.fromExecutor(executor),
|
() => DatabaseConnection.fromExecutor(executor),
|
||||||
);
|
);
|
||||||
@@ -65,35 +67,39 @@ class SharedDB {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await receivePort.first as MoorIsolate);
|
return await receivePort.first as MoorIsolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new instance of [MoorChatDatabase] using the factory constructor
|
/// Returns a new instance of [MoorChatDatabase] using the factory constructor
|
||||||
/// [MoorChatDatabase.connect] created on a background isolate.
|
/// [MoorChatDatabase.connect] created on a background isolate.
|
||||||
///
|
///
|
||||||
/// Generally used with [ConnectionMode.background].
|
/// Generally used with [ConnectionMode.background].
|
||||||
static Future<MoorChatDatabase> constructMoorChatDatabase(
|
static MoorChatDatabase constructMoorChatDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
}) async {
|
}) {
|
||||||
final dbName = 'db_$userId';
|
final dbName = 'db_$userId';
|
||||||
final isolate = await _createMoorIsolate(
|
return MoorChatDatabase.connect(
|
||||||
dbName,
|
userId,
|
||||||
logStatements: logStatements,
|
DatabaseConnection.delayed(Future(() async {
|
||||||
|
final isolate = await _createMoorIsolate(
|
||||||
|
dbName,
|
||||||
|
logStatements: logStatements,
|
||||||
|
);
|
||||||
|
return isolate.connect();
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
final connection = await isolate.connect();
|
|
||||||
return MoorChatDatabase.connect(userId, isolate, connection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _IsolateStartRequest {
|
class _IsolateStartRequest {
|
||||||
final SendPort sendMoorIsolate;
|
|
||||||
final String targetPath;
|
|
||||||
final bool logStatements;
|
|
||||||
|
|
||||||
const _IsolateStartRequest(
|
const _IsolateStartRequest(
|
||||||
this.sendMoorIsolate,
|
this.sendMoorIsolate,
|
||||||
this.targetPath, {
|
this.targetPath, {
|
||||||
this.logStatements = false,
|
this.logStatements = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final SendPort sendMoorIsolate;
|
||||||
|
final String targetPath;
|
||||||
|
final bool logStatements;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:moor/backends.dart';
|
||||||
|
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||||
|
|
||||||
/// A Helper class to construct new instances of [MoorChatDatabase]
|
/// A Helper class to construct new instances of [MoorChatDatabase]
|
||||||
@@ -5,20 +7,23 @@ class SharedDB {
|
|||||||
/// Returns a new instance of database.
|
/// Returns a new instance of database.
|
||||||
///
|
///
|
||||||
/// Generally used with [ConnectionMode.regular].
|
/// Generally used with [ConnectionMode.regular].
|
||||||
static dynamic constructDatabase(
|
static Future<DelegatedDatabase> constructDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
|
bool persistOnDisk = true,
|
||||||
}) {
|
}) {
|
||||||
throw 'Unsupported Platform';
|
throw UnsupportedError(
|
||||||
|
'No implementation of the constructDatabase api provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new instance of moor chat database.
|
/// Return a new instance of moor chat database.
|
||||||
///
|
///
|
||||||
/// Generally used with [ConnectionMode.background].
|
/// Generally used with [ConnectionMode.background].
|
||||||
static dynamic constructMoorChatDatabase(
|
static MoorChatDatabase constructMoorChatDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
}) {
|
}) {
|
||||||
throw 'Unsupported Platform';
|
throw UnsupportedError(
|
||||||
|
'No implementation of the constructMoorChatDatabase api provided');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:moor/moor_web.dart';
|
import 'package:moor/moor_web.dart';
|
||||||
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
||||||
|
|
||||||
import '../moor_chat_database.dart';
|
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||||
|
|
||||||
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
||||||
/// for Web applications
|
/// for Web applications
|
||||||
@@ -12,6 +12,7 @@ class SharedDB {
|
|||||||
static Future<WebDatabase> constructDatabase(
|
static Future<WebDatabase> constructDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
|
bool persistOnDisk = true, // ignored on web
|
||||||
}) async {
|
}) async {
|
||||||
final dbName = 'db_$userId';
|
final dbName = 'db_$userId';
|
||||||
return WebDatabase(dbName, logStatements: logStatements);
|
return WebDatabase(dbName, logStatements: logStatements);
|
||||||
@@ -21,10 +22,10 @@ class SharedDB {
|
|||||||
/// default constructor.
|
/// default constructor.
|
||||||
///
|
///
|
||||||
/// Generally used with [ConnectionMode.background].
|
/// Generally used with [ConnectionMode.background].
|
||||||
static Future<MoorChatDatabase> constructMoorChatDatabase(
|
static MoorChatDatabase constructMoorChatDatabase(
|
||||||
String userId, {
|
String userId, {
|
||||||
bool logStatements = false,
|
bool logStatements = false,
|
||||||
}) async {
|
}) {
|
||||||
final dbName = 'db_$userId';
|
final dbName = 'db_$userId';
|
||||||
return MoorChatDatabase(dbName, logStatements: logStatements);
|
return MoorChatDatabase(dbName, logStatements: logStatements);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Channels extends Table {
|
|||||||
TextColumn get config => text().map(MapConverter<Object>())();
|
TextColumn get config => text().map(MapConverter<Object>())();
|
||||||
|
|
||||||
/// True if this channel entity is frozen
|
/// True if this channel entity is frozen
|
||||||
BoolColumn get frozen => boolean().withDefault(Constant(false))();
|
BoolColumn get frozen => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
/// The date of the last message
|
/// The date of the last message
|
||||||
DateTimeColumn get lastMessageAt => dateTime().nullable()();
|
DateTimeColumn get lastMessageAt => dateTime().nullable()();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
export 'channel_queries.dart';
|
||||||
export 'channels.dart';
|
export 'channels.dart';
|
||||||
|
export 'connection_events.dart';
|
||||||
|
export 'members.dart';
|
||||||
export 'messages.dart';
|
export 'messages.dart';
|
||||||
export 'pinned_messages.dart';
|
export 'pinned_messages.dart';
|
||||||
export 'reactions.dart';
|
export 'reactions.dart';
|
||||||
export 'users.dart';
|
|
||||||
export 'members.dart';
|
|
||||||
export 'reads.dart';
|
export 'reads.dart';
|
||||||
export 'channel_queries.dart';
|
export 'users.dart';
|
||||||
export 'connection_events.dart';
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
|
|
||||||
import 'messages.dart';
|
import 'package:stream_chat_persistence/src/entity/messages.dart';
|
||||||
|
|
||||||
/// Represents a [PinnedMessages] table in [MoorChatDatabase].
|
/// Represents a [PinnedMessages] table in [MoorChatDatabase].
|
||||||
@DataClassName('PinnedMessageEntity')
|
@DataClassName('PinnedMessageEntity')
|
||||||
|
|||||||
@@ -29,34 +29,31 @@ extension ChannelEntityX on ChannelEntity {
|
|||||||
List<Read> reads,
|
List<Read> reads,
|
||||||
List<Message> messages,
|
List<Message> messages,
|
||||||
List<Message> pinnedMessages,
|
List<Message> pinnedMessages,
|
||||||
}) {
|
}) =>
|
||||||
return ChannelState(
|
ChannelState(
|
||||||
members: members,
|
members: members,
|
||||||
read: reads,
|
read: reads,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
pinnedMessages: pinnedMessages,
|
pinnedMessages: pinnedMessages,
|
||||||
channel: toChannelModel(createdBy: createdBy),
|
channel: toChannelModel(createdBy: createdBy),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [ChannelModel]
|
/// Useful mapping functions for [ChannelModel]
|
||||||
extension ChannelModelX on ChannelModel {
|
extension ChannelModelX on ChannelModel {
|
||||||
/// Maps a [ChannelModel] into [ChannelEntity]
|
/// Maps a [ChannelModel] into [ChannelEntity]
|
||||||
ChannelEntity toEntity() {
|
ChannelEntity toEntity() => ChannelEntity(
|
||||||
return ChannelEntity(
|
id: id,
|
||||||
id: id,
|
type: type,
|
||||||
type: type,
|
cid: cid,
|
||||||
cid: cid,
|
config: config.toJson(),
|
||||||
config: config.toJson(),
|
frozen: frozen,
|
||||||
frozen: frozen,
|
lastMessageAt: lastMessageAt,
|
||||||
lastMessageAt: lastMessageAt,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
updatedAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
deletedAt: deletedAt,
|
||||||
deletedAt: deletedAt,
|
memberCount: memberCount,
|
||||||
memberCount: memberCount,
|
createdById: createdBy.id,
|
||||||
createdById: createdBy.id,
|
extraData: extraData,
|
||||||
extraData: extraData,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
/// Useful mapping functions for [ConnectionEventEntity]
|
/// Useful mapping functions for [ConnectionEventEntity]
|
||||||
extension ConnectionEventX on ConnectionEventEntity {
|
extension ConnectionEventX on ConnectionEventEntity {
|
||||||
/// Maps a [ConnectionEventEntity] into [Event]
|
/// Maps a [ConnectionEventEntity] into [Event]
|
||||||
Event toEvent() {
|
Event toEvent() => Event(
|
||||||
return Event(
|
me: ownUser != null ? OwnUser.fromJson(ownUser) : null,
|
||||||
me: ownUser != null ? OwnUser.fromJson(ownUser) : null,
|
totalUnreadCount: totalUnreadCount,
|
||||||
totalUnreadCount: totalUnreadCount,
|
unreadChannels: unreadChannels,
|
||||||
unreadChannels: unreadChannels,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export 'user_mapper.dart';
|
|
||||||
export 'reaction_mapper.dart';
|
|
||||||
export 'channel_mapper.dart';
|
export 'channel_mapper.dart';
|
||||||
export 'event_mapper.dart';
|
export 'event_mapper.dart';
|
||||||
export 'member_mapper.dart';
|
export 'member_mapper.dart';
|
||||||
export 'read_mapper.dart';
|
|
||||||
export 'message_mapper.dart';
|
export 'message_mapper.dart';
|
||||||
export 'pinned_message_mapper.dart';
|
export 'pinned_message_mapper.dart';
|
||||||
|
export 'reaction_mapper.dart';
|
||||||
|
export 'read_mapper.dart';
|
||||||
|
export 'user_mapper.dart';
|
||||||
|
|||||||
@@ -4,39 +4,35 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
/// Useful mapping functions for [MemberEntity]
|
/// Useful mapping functions for [MemberEntity]
|
||||||
extension MemberEntityX on MemberEntity {
|
extension MemberEntityX on MemberEntity {
|
||||||
/// Maps a [MemberEntity] into [Member]
|
/// Maps a [MemberEntity] into [Member]
|
||||||
Member toMember({User user}) {
|
Member toMember({User user}) => Member(
|
||||||
return Member(
|
user: user,
|
||||||
user: user,
|
userId: userId,
|
||||||
userId: userId,
|
banned: banned,
|
||||||
banned: banned,
|
shadowBanned: shadowBanned,
|
||||||
shadowBanned: shadowBanned,
|
updatedAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
role: role,
|
||||||
role: role,
|
inviteAcceptedAt: inviteAcceptedAt,
|
||||||
inviteAcceptedAt: inviteAcceptedAt,
|
invited: invited,
|
||||||
invited: invited,
|
inviteRejectedAt: inviteRejectedAt,
|
||||||
inviteRejectedAt: inviteRejectedAt,
|
isModerator: isModerator,
|
||||||
isModerator: isModerator,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [Member]
|
/// Useful mapping functions for [Member]
|
||||||
extension MemberX on Member {
|
extension MemberX on Member {
|
||||||
/// Maps a [Member] into [MemberEntity]
|
/// Maps a [Member] into [MemberEntity]
|
||||||
MemberEntity toEntity({String cid}) {
|
MemberEntity toEntity({String cid}) => MemberEntity(
|
||||||
return MemberEntity(
|
userId: user?.id,
|
||||||
userId: user?.id,
|
banned: banned,
|
||||||
banned: banned,
|
shadowBanned: shadowBanned,
|
||||||
shadowBanned: shadowBanned,
|
channelCid: cid,
|
||||||
channelCid: cid,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
isModerator: isModerator,
|
||||||
isModerator: isModerator,
|
inviteRejectedAt: inviteRejectedAt,
|
||||||
inviteRejectedAt: inviteRejectedAt,
|
invited: invited,
|
||||||
invited: invited,
|
inviteAcceptedAt: inviteAcceptedAt,
|
||||||
inviteAcceptedAt: inviteAcceptedAt,
|
role: role,
|
||||||
role: role,
|
updatedAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,70 +12,66 @@ extension MessageEntityX on MessageEntity {
|
|||||||
List<Reaction> latestReactions,
|
List<Reaction> latestReactions,
|
||||||
List<Reaction> ownReactions,
|
List<Reaction> ownReactions,
|
||||||
Message quotedMessage,
|
Message quotedMessage,
|
||||||
}) {
|
}) =>
|
||||||
return Message(
|
Message(
|
||||||
shadowed: shadowed,
|
shadowed: shadowed,
|
||||||
latestReactions: latestReactions,
|
latestReactions: latestReactions,
|
||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
attachments: attachments?.map((it) {
|
attachments: attachments?.map((it) {
|
||||||
final json = jsonDecode(it);
|
final json = jsonDecode(it);
|
||||||
return Attachment.fromData(json);
|
return Attachment.fromData(json);
|
||||||
})?.toList(),
|
})?.toList(),
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
extraData: extraData,
|
extraData: extraData,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
id: id,
|
id: id,
|
||||||
type: type,
|
type: type,
|
||||||
status: status,
|
status: status,
|
||||||
command: command,
|
command: command,
|
||||||
parentId: parentId,
|
parentId: parentId,
|
||||||
quotedMessageId: quotedMessageId,
|
quotedMessageId: quotedMessageId,
|
||||||
quotedMessage: quotedMessage,
|
quotedMessage: quotedMessage,
|
||||||
reactionCounts: reactionCounts,
|
reactionCounts: reactionCounts,
|
||||||
reactionScores: reactionScores,
|
reactionScores: reactionScores,
|
||||||
replyCount: replyCount,
|
replyCount: replyCount,
|
||||||
showInChannel: showInChannel,
|
showInChannel: showInChannel,
|
||||||
text: messageText,
|
text: messageText,
|
||||||
user: user,
|
user: user,
|
||||||
deletedAt: deletedAt,
|
deletedAt: deletedAt,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
pinnedAt: pinnedAt,
|
pinnedAt: pinnedAt,
|
||||||
pinExpires: pinExpires,
|
pinExpires: pinExpires,
|
||||||
pinnedBy: pinnedBy,
|
pinnedBy: pinnedBy,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [Message]
|
/// Useful mapping functions for [Message]
|
||||||
extension MessageX on Message {
|
extension MessageX on Message {
|
||||||
/// Maps a [Message] into [MessageEntity]
|
/// Maps a [Message] into [MessageEntity]
|
||||||
MessageEntity toEntity({String cid}) {
|
MessageEntity toEntity({String cid}) => MessageEntity(
|
||||||
return MessageEntity(
|
id: id,
|
||||||
id: id,
|
attachments:
|
||||||
attachments: attachments?.map((it) {
|
attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||||
return jsonEncode(it.toData());
|
channelCid: cid,
|
||||||
})?.toList(),
|
type: type,
|
||||||
channelCid: cid,
|
parentId: parentId,
|
||||||
type: type,
|
quotedMessageId: quotedMessageId,
|
||||||
parentId: parentId,
|
command: command,
|
||||||
quotedMessageId: quotedMessageId,
|
createdAt: createdAt,
|
||||||
command: command,
|
shadowed: shadowed,
|
||||||
createdAt: createdAt,
|
showInChannel: showInChannel,
|
||||||
shadowed: shadowed,
|
replyCount: replyCount,
|
||||||
showInChannel: showInChannel,
|
reactionScores: reactionScores,
|
||||||
replyCount: replyCount,
|
reactionCounts: reactionCounts,
|
||||||
reactionScores: reactionScores,
|
status: status,
|
||||||
reactionCounts: reactionCounts,
|
updatedAt: updatedAt,
|
||||||
status: status,
|
extraData: extraData,
|
||||||
updatedAt: updatedAt,
|
userId: user?.id,
|
||||||
extraData: extraData,
|
deletedAt: deletedAt,
|
||||||
userId: user?.id,
|
messageText: text,
|
||||||
deletedAt: deletedAt,
|
pinned: pinned,
|
||||||
messageText: text,
|
pinnedAt: pinnedAt,
|
||||||
pinned: pinned,
|
pinExpires: pinExpires,
|
||||||
pinnedAt: pinnedAt,
|
pinnedByUserId: pinnedBy?.id,
|
||||||
pinExpires: pinExpires,
|
);
|
||||||
pinnedByUserId: pinnedBy?.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,70 +12,66 @@ extension PinnedMessageEntityX on PinnedMessageEntity {
|
|||||||
List<Reaction> latestReactions,
|
List<Reaction> latestReactions,
|
||||||
List<Reaction> ownReactions,
|
List<Reaction> ownReactions,
|
||||||
Message quotedMessage,
|
Message quotedMessage,
|
||||||
}) {
|
}) =>
|
||||||
return Message(
|
Message(
|
||||||
shadowed: shadowed,
|
shadowed: shadowed,
|
||||||
latestReactions: latestReactions,
|
latestReactions: latestReactions,
|
||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
attachments: attachments?.map((it) {
|
attachments: attachments?.map((it) {
|
||||||
final json = jsonDecode(it);
|
final json = jsonDecode(it);
|
||||||
return Attachment.fromData(json);
|
return Attachment.fromData(json);
|
||||||
})?.toList(),
|
})?.toList(),
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
extraData: extraData,
|
extraData: extraData,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
id: id,
|
id: id,
|
||||||
type: type,
|
type: type,
|
||||||
status: status,
|
status: status,
|
||||||
command: command,
|
command: command,
|
||||||
parentId: parentId,
|
parentId: parentId,
|
||||||
quotedMessageId: quotedMessageId,
|
quotedMessageId: quotedMessageId,
|
||||||
quotedMessage: quotedMessage,
|
quotedMessage: quotedMessage,
|
||||||
reactionCounts: reactionCounts,
|
reactionCounts: reactionCounts,
|
||||||
reactionScores: reactionScores,
|
reactionScores: reactionScores,
|
||||||
replyCount: replyCount,
|
replyCount: replyCount,
|
||||||
showInChannel: showInChannel,
|
showInChannel: showInChannel,
|
||||||
text: messageText,
|
text: messageText,
|
||||||
user: user,
|
user: user,
|
||||||
deletedAt: deletedAt,
|
deletedAt: deletedAt,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
pinnedAt: pinnedAt,
|
pinnedAt: pinnedAt,
|
||||||
pinExpires: pinExpires,
|
pinExpires: pinExpires,
|
||||||
pinnedBy: pinnedBy,
|
pinnedBy: pinnedBy,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [Message]
|
/// Useful mapping functions for [Message]
|
||||||
extension PMessageX on Message {
|
extension PMessageX on Message {
|
||||||
/// Maps a [Message] into [PinnedMessageEntity]
|
/// Maps a [Message] into [PinnedMessageEntity]
|
||||||
PinnedMessageEntity toPinnedEntity({String cid}) {
|
PinnedMessageEntity toPinnedEntity({String cid}) => PinnedMessageEntity(
|
||||||
return PinnedMessageEntity(
|
id: id,
|
||||||
id: id,
|
attachments:
|
||||||
attachments: attachments?.map((it) {
|
attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||||
return jsonEncode(it.toData());
|
channelCid: cid,
|
||||||
})?.toList(),
|
type: type,
|
||||||
channelCid: cid,
|
parentId: parentId,
|
||||||
type: type,
|
quotedMessageId: quotedMessageId,
|
||||||
parentId: parentId,
|
command: command,
|
||||||
quotedMessageId: quotedMessageId,
|
createdAt: createdAt,
|
||||||
command: command,
|
shadowed: shadowed,
|
||||||
createdAt: createdAt,
|
showInChannel: showInChannel,
|
||||||
shadowed: shadowed,
|
replyCount: replyCount,
|
||||||
showInChannel: showInChannel,
|
reactionScores: reactionScores,
|
||||||
replyCount: replyCount,
|
reactionCounts: reactionCounts,
|
||||||
reactionScores: reactionScores,
|
status: status,
|
||||||
reactionCounts: reactionCounts,
|
updatedAt: updatedAt,
|
||||||
status: status,
|
extraData: extraData,
|
||||||
updatedAt: updatedAt,
|
userId: user?.id,
|
||||||
extraData: extraData,
|
deletedAt: deletedAt,
|
||||||
userId: user?.id,
|
messageText: text,
|
||||||
deletedAt: deletedAt,
|
pinned: pinned,
|
||||||
messageText: text,
|
pinnedAt: pinnedAt,
|
||||||
pinned: pinned,
|
pinExpires: pinExpires,
|
||||||
pinnedAt: pinnedAt,
|
pinnedByUserId: pinnedBy?.id,
|
||||||
pinExpires: pinExpires,
|
);
|
||||||
pinnedByUserId: pinnedBy?.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,30 +4,26 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
/// Useful mapping functions for [ReactionEntity]
|
/// Useful mapping functions for [ReactionEntity]
|
||||||
extension ReactionEntityX on ReactionEntity {
|
extension ReactionEntityX on ReactionEntity {
|
||||||
/// Maps a [ReactionEntity] into [Reaction]
|
/// Maps a [ReactionEntity] into [Reaction]
|
||||||
Reaction toReaction({User user}) {
|
Reaction toReaction({User user}) => Reaction(
|
||||||
return Reaction(
|
extraData: extraData,
|
||||||
extraData: extraData,
|
type: type,
|
||||||
type: type,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
userId: userId,
|
||||||
userId: userId,
|
user: user,
|
||||||
user: user,
|
messageId: messageId,
|
||||||
messageId: messageId,
|
score: score,
|
||||||
score: score,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [Reaction]
|
/// Useful mapping functions for [Reaction]
|
||||||
extension ReactionX on Reaction {
|
extension ReactionX on Reaction {
|
||||||
/// Maps a [Reaction] into [ReactionEntity]
|
/// Maps a [Reaction] into [ReactionEntity]
|
||||||
ReactionEntity toEntity() {
|
ReactionEntity toEntity() => ReactionEntity(
|
||||||
return ReactionEntity(
|
extraData: extraData,
|
||||||
extraData: extraData,
|
type: type,
|
||||||
type: type,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
userId: userId,
|
||||||
userId: userId,
|
messageId: messageId,
|
||||||
messageId: messageId,
|
score: score,
|
||||||
score: score,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,20 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
/// Useful mapping functions for [ReadEntity]
|
/// Useful mapping functions for [ReadEntity]
|
||||||
extension ReadEntityX on ReadEntity {
|
extension ReadEntityX on ReadEntity {
|
||||||
/// Maps a [ReadEntity] into [Read]
|
/// Maps a [ReadEntity] into [Read]
|
||||||
Read toRead({User user}) {
|
Read toRead({User user}) => Read(
|
||||||
return Read(
|
user: user,
|
||||||
user: user,
|
lastRead: lastRead,
|
||||||
lastRead: lastRead,
|
unreadMessages: unreadMessages,
|
||||||
unreadMessages: unreadMessages,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [Read]
|
/// Useful mapping functions for [Read]
|
||||||
extension ReadX on Read {
|
extension ReadX on Read {
|
||||||
/// Maps a [Read] into [ReadEntity]
|
/// Maps a [Read] into [ReadEntity]
|
||||||
ReadEntity toEntity({String cid}) {
|
ReadEntity toEntity({String cid}) => ReadEntity(
|
||||||
return ReadEntity(
|
lastRead: lastRead,
|
||||||
lastRead: lastRead,
|
userId: user?.id,
|
||||||
userId: user?.id,
|
channelCid: cid,
|
||||||
channelCid: cid,
|
unreadMessages: unreadMessages,
|
||||||
unreadMessages: unreadMessages,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,29 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
|||||||
/// Useful mapping functions for [UserEntity]
|
/// Useful mapping functions for [UserEntity]
|
||||||
extension UserEntityX on UserEntity {
|
extension UserEntityX on UserEntity {
|
||||||
/// Maps a [UserEntity] into [User]
|
/// Maps a [UserEntity] into [User]
|
||||||
User toUser() {
|
User toUser() => User(
|
||||||
return User(
|
id: id,
|
||||||
id: id,
|
updatedAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
role: role,
|
||||||
role: role,
|
online: online,
|
||||||
online: online,
|
lastActive: lastActive,
|
||||||
lastActive: lastActive,
|
extraData: extraData,
|
||||||
extraData: extraData,
|
banned: banned,
|
||||||
banned: banned,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Useful mapping functions for [User]
|
/// Useful mapping functions for [User]
|
||||||
extension UserX on User {
|
extension UserX on User {
|
||||||
/// Maps a [User] into [UserEntity]
|
/// Maps a [User] into [UserEntity]
|
||||||
UserEntity toEntity() {
|
UserEntity toEntity() => UserEntity(
|
||||||
return UserEntity(
|
id: id,
|
||||||
id: id,
|
role: role,
|
||||||
role: role,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
updatedAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
lastActive: lastActive,
|
||||||
lastActive: lastActive,
|
online: online,
|
||||||
online: online,
|
banned: banned,
|
||||||
banned: banned,
|
extraData: extraData,
|
||||||
extraData: extraData,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import 'package:logging/logging.dart' show LogRecord;
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
import 'db/moor_chat_database.dart';
|
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||||
import 'db/shared/shared_db.dart';
|
import 'package:stream_chat_persistence/src/db/shared/shared_db.dart';
|
||||||
|
|
||||||
/// Various connection modes on which [StreamChatPersistenceClient] can work
|
/// Various connection modes on which [StreamChatPersistenceClient] can work
|
||||||
enum ConnectionMode {
|
enum ConnectionMode {
|
||||||
@@ -12,6 +15,12 @@ enum ConnectionMode {
|
|||||||
background,
|
background,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _levelEmojiMapper = {
|
||||||
|
Level.INFO: 'ℹ️',
|
||||||
|
Level.WARNING: '⚠️',
|
||||||
|
Level.SEVERE: '🚨',
|
||||||
|
};
|
||||||
|
|
||||||
/// A [MoorChatDatabase] based implementation of the [ChatPersistenceClient]
|
/// A [MoorChatDatabase] based implementation of the [ChatPersistenceClient]
|
||||||
class StreamChatPersistenceClient extends ChatPersistenceClient {
|
class StreamChatPersistenceClient extends ChatPersistenceClient {
|
||||||
/// Creates a new instance of the stream chat persistence client
|
/// Creates a new instance of the stream chat persistence client
|
||||||
@@ -19,18 +28,65 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
|||||||
/// Connection mode on which the client will work
|
/// Connection mode on which the client will work
|
||||||
ConnectionMode connectionMode = ConnectionMode.regular,
|
ConnectionMode connectionMode = ConnectionMode.regular,
|
||||||
Level logLevel = Level.WARNING,
|
Level logLevel = Level.WARNING,
|
||||||
}) : assert(connectionMode != null),
|
LogHandlerFunction logHandlerFunction,
|
||||||
assert(logLevel != null),
|
}) : assert(connectionMode != null, 'ConnectionMode cannot be null'),
|
||||||
|
assert(logLevel != null, 'LogLevel cannot be null'),
|
||||||
_connectionMode = connectionMode,
|
_connectionMode = connectionMode,
|
||||||
_logger = Logger.detached('💽')..level = logLevel;
|
_logger = Logger.detached('💽')..level = logLevel {
|
||||||
|
_logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function that has a parameter of type [LogRecord].
|
||||||
|
/// This is called on every new log record.
|
||||||
|
/// By default the client will use the handler returned by
|
||||||
|
/// [_getDefaultLogHandler].
|
||||||
|
/// Setting it you can handle the log messages directly instead of have them
|
||||||
|
/// written to stdout,
|
||||||
|
/// this is very convenient if you use an error tracking tool or if you want
|
||||||
|
/// to centralize your logs into one facility.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// myLogHandlerFunction = (LogRecord record) {
|
||||||
|
/// // do something with the record (ie. send it to Sentry or Fabric)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// final client = StreamChatPersistenceClient(
|
||||||
|
/// logHandlerFunction: myLogHandlerFunction,
|
||||||
|
/// );
|
||||||
|
///```
|
||||||
|
LogHandlerFunction logHandlerFunction;
|
||||||
|
|
||||||
|
/// [MoorChatDatabase] instance used by this client.
|
||||||
|
@visibleForTesting
|
||||||
|
MoorChatDatabase db;
|
||||||
|
|
||||||
MoorChatDatabase _db;
|
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
final ConnectionMode _connectionMode;
|
final ConnectionMode _connectionMode;
|
||||||
|
final _mutex = ReadWriteMutex();
|
||||||
|
|
||||||
|
void _defaultLogHandler(LogRecord record) {
|
||||||
|
print(
|
||||||
|
'(${record.time}) '
|
||||||
|
'${_levelEmojiMapper[record.level] ?? record.level.name} '
|
||||||
|
'${record.loggerName} ${record.message}',
|
||||||
|
);
|
||||||
|
if (record.stackTrace != null) print(record.stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> _readProtected<T>(Future<T> Function() f) async {
|
||||||
|
T ret;
|
||||||
|
await _mutex.protectRead(() async {
|
||||||
|
if (db == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ret = await f();
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connect(String userId) async {
|
Future<void> connect(String userId) async {
|
||||||
if (_db != null) {
|
if (db != null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'An instance of StreamChatDatabase is already connected.\n'
|
'An instance of StreamChatDatabase is already connected.\n'
|
||||||
'disconnect the previous instance before connecting again.',
|
'disconnect the previous instance before connecting again.',
|
||||||
@@ -39,211 +95,266 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
|||||||
switch (_connectionMode) {
|
switch (_connectionMode) {
|
||||||
case ConnectionMode.regular:
|
case ConnectionMode.regular:
|
||||||
_logger.info('Connecting on a regular isolate');
|
_logger.info('Connecting on a regular isolate');
|
||||||
_db = MoorChatDatabase(userId);
|
db = MoorChatDatabase(userId);
|
||||||
return;
|
return;
|
||||||
case ConnectionMode.background:
|
case ConnectionMode.background:
|
||||||
_logger.info('Connecting on background isolate');
|
_logger.info('Connecting on background isolate');
|
||||||
_db = await SharedDB.constructMoorChatDatabase(userId);
|
db = SharedDB.constructMoorChatDatabase(userId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Event> getConnectionInfo() {
|
Future<Event> getConnectionInfo() => _readProtected(() {
|
||||||
return _db.connectionEventDao.connectionEvent;
|
_logger.info('getConnectionInfo');
|
||||||
}
|
return db.connectionEventDao.connectionEvent;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateConnectionInfo(Event event) {
|
Future<void> updateConnectionInfo(Event event) => _readProtected(() {
|
||||||
return _db.connectionEventDao.updateConnectionEvent(event);
|
_logger.info('updateConnectionInfo');
|
||||||
}
|
return db.connectionEventDao.updateConnectionEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateLastSyncAt(DateTime lastSyncAt) {
|
Future<void> updateLastSyncAt(DateTime lastSyncAt) => _readProtected(() {
|
||||||
return _db.connectionEventDao.updateLastSyncAt(lastSyncAt);
|
_logger.info('updateLastSyncAt');
|
||||||
}
|
return db.connectionEventDao.updateLastSyncAt(lastSyncAt);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DateTime> getLastSyncAt() {
|
Future<DateTime> getLastSyncAt() => _readProtected(() {
|
||||||
return _db.connectionEventDao.lastSyncAt;
|
_logger.info('getLastSyncAt');
|
||||||
}
|
return db.connectionEventDao.lastSyncAt;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteChannels(List<String> cids) {
|
Future<void> deleteChannels(List<String> cids) => _readProtected(() {
|
||||||
return _db.channelDao.deleteChannelByCids(cids);
|
_logger.info('deleteChannels');
|
||||||
}
|
return db.channelDao.deleteChannelByCids(cids);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getChannelCids() => _db.channelDao.cids;
|
Future<List<String>> getChannelCids() => _readProtected(() {
|
||||||
|
_logger.info('getChannelCids');
|
||||||
|
return db.channelDao.cids;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteMessageByIds(List<String> messageIds) {
|
Future<void> deleteMessageByIds(List<String> messageIds) =>
|
||||||
return _db.messageDao.deleteMessageByIds(messageIds);
|
_readProtected(() {
|
||||||
}
|
_logger.info('deleteMessageByIds');
|
||||||
|
return db.messageDao.deleteMessageByIds(messageIds);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deletePinnedMessageByIds(List<String> messageIds) {
|
Future<void> deletePinnedMessageByIds(List<String> messageIds) =>
|
||||||
return _db.pinnedMessageDao.deleteMessageByIds(messageIds);
|
_readProtected(() {
|
||||||
}
|
_logger.info('deletePinnedMessageByIds');
|
||||||
|
return db.pinnedMessageDao.deleteMessageByIds(messageIds);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteMessageByCids(List<String> cids) {
|
Future<void> deleteMessageByCids(List<String> cids) => _readProtected(() {
|
||||||
return _db.messageDao.deleteMessageByCids(cids);
|
_logger.info('deleteMessageByCids');
|
||||||
}
|
return db.messageDao.deleteMessageByCids(cids);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deletePinnedMessageByCids(List<String> cids) {
|
Future<void> deletePinnedMessageByCids(List<String> cids) =>
|
||||||
return _db.pinnedMessageDao.deleteMessageByCids(cids);
|
_readProtected(() {
|
||||||
}
|
_logger.info('deletePinnedMessageByCids');
|
||||||
|
return db.pinnedMessageDao.deleteMessageByCids(cids);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Member>> getMembersByCid(String cid) {
|
Future<List<Member>> getMembersByCid(String cid) => _readProtected(() {
|
||||||
return _db.memberDao.getMembersByCid(cid);
|
_logger.info('getMembersByCid');
|
||||||
}
|
return db.memberDao.getMembersByCid(cid);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChannelModel> getChannelByCid(String cid) {
|
Future<ChannelModel> getChannelByCid(String cid) => _readProtected(() {
|
||||||
return _db.channelDao.getChannelByCid(cid);
|
_logger.info('getChannelByCid');
|
||||||
}
|
return db.channelDao.getChannelByCid(cid);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Message>> getMessagesByCid(
|
Future<List<Message>> getMessagesByCid(
|
||||||
String cid, {
|
String cid, {
|
||||||
PaginationParams messagePagination,
|
PaginationParams messagePagination,
|
||||||
}) {
|
}) =>
|
||||||
return _db.messageDao.getMessagesByCid(
|
_readProtected(() {
|
||||||
cid,
|
_logger.info('getMessagesByCid');
|
||||||
messagePagination: messagePagination,
|
return db.messageDao.getMessagesByCid(
|
||||||
);
|
cid,
|
||||||
}
|
messagePagination: messagePagination,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Message>> getPinnedMessagesByCid(
|
Future<List<Message>> getPinnedMessagesByCid(
|
||||||
String cid, {
|
String cid, {
|
||||||
PaginationParams messagePagination,
|
PaginationParams messagePagination,
|
||||||
}) {
|
}) =>
|
||||||
return _db.pinnedMessageDao.getMessagesByCid(
|
_readProtected(() {
|
||||||
cid,
|
_logger.info('getPinnedMessagesByCid');
|
||||||
messagePagination: messagePagination,
|
return db.pinnedMessageDao.getMessagesByCid(
|
||||||
);
|
cid,
|
||||||
}
|
messagePagination: messagePagination,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Read>> getReadsByCid(String cid) {
|
Future<List<Read>> getReadsByCid(String cid) => _readProtected(() {
|
||||||
return _db.readDao.getReadsByCid(cid);
|
_logger.info('getReadsByCid');
|
||||||
}
|
return db.readDao.getReadsByCid(cid);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, List<Message>>> getChannelThreads(String cid) async {
|
Future<Map<String, List<Message>>> getChannelThreads(String cid) async =>
|
||||||
final messages = await _db.messageDao.getThreadMessages(cid);
|
_readProtected(() async {
|
||||||
final messageByParentIdDictionary = <String, List<Message>>{};
|
_logger.info('getChannelThreads');
|
||||||
for (final message in messages) {
|
final messages = await db.messageDao.getThreadMessages(cid);
|
||||||
final parentId = message.parentId;
|
final messageByParentIdDictionary = <String, List<Message>>{};
|
||||||
messageByParentIdDictionary[parentId] = [
|
for (final message in messages) {
|
||||||
...messageByParentIdDictionary[parentId] ?? [],
|
final parentId = message.parentId;
|
||||||
message
|
messageByParentIdDictionary[parentId] = [
|
||||||
];
|
...messageByParentIdDictionary[parentId] ?? [],
|
||||||
}
|
message
|
||||||
return messageByParentIdDictionary;
|
];
|
||||||
}
|
}
|
||||||
|
return messageByParentIdDictionary;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Message>> getReplies(
|
Future<List<Message>> getReplies(
|
||||||
String parentId, {
|
String parentId, {
|
||||||
PaginationParams options,
|
PaginationParams options,
|
||||||
}) {
|
}) =>
|
||||||
return _db.messageDao.getThreadMessagesByParentId(
|
_readProtected(() async {
|
||||||
parentId,
|
_logger.info('getReplies');
|
||||||
options: options,
|
return db.messageDao.getThreadMessagesByParentId(
|
||||||
);
|
parentId,
|
||||||
}
|
options: options,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<ChannelState>> getChannelStates({
|
Future<List<ChannelState>> getChannelStates({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption<ChannelModel>> sort = const [],
|
List<SortOption<ChannelModel>> sort = const [],
|
||||||
PaginationParams paginationParams,
|
PaginationParams paginationParams,
|
||||||
}) async {
|
}) async =>
|
||||||
final channels = await _db.channelQueryDao.getChannels(
|
_readProtected(() async {
|
||||||
filter: filter,
|
_logger.info('getChannelStates');
|
||||||
sort: sort,
|
final channels = await db.channelQueryDao.getChannels(
|
||||||
paginationParams: paginationParams,
|
filter: filter,
|
||||||
);
|
sort: sort,
|
||||||
return Future.wait(channels.map((e) => getChannelStateByCid(e.cid)));
|
paginationParams: paginationParams,
|
||||||
}
|
);
|
||||||
|
return Future.wait(channels.map((e) => getChannelStateByCid(e.cid)));
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateChannelQueries(
|
Future<void> updateChannelQueries(
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<String> cids,
|
List<String> cids,
|
||||||
bool clearQueryCache,
|
bool clearQueryCache,
|
||||||
) {
|
) =>
|
||||||
return _db.channelQueryDao.updateChannelQueries(
|
_readProtected(() async {
|
||||||
filter,
|
_logger.info('updateChannelQueries');
|
||||||
cids,
|
return db.channelQueryDao.updateChannelQueries(
|
||||||
clearQueryCache,
|
filter,
|
||||||
);
|
cids,
|
||||||
}
|
clearQueryCache: clearQueryCache,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateChannels(List<ChannelModel> channels) {
|
Future<void> updateChannels(List<ChannelModel> channels) =>
|
||||||
return _db.channelDao.updateChannels(channels);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updateChannels');
|
||||||
|
return db.channelDao.updateChannels(channels);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateMembers(String cid, List<Member> members) {
|
Future<void> updateMembers(String cid, List<Member> members) =>
|
||||||
return _db.memberDao.updateMembers(cid, members);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updateMembers');
|
||||||
|
return db.memberDao.updateMembers(cid, members);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateMessages(String cid, List<Message> messages) {
|
Future<void> updateMessages(String cid, List<Message> messages) =>
|
||||||
return _db.messageDao.updateMessages(cid, messages);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updateMessages');
|
||||||
|
return db.messageDao.updateMessages(cid, messages);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updatePinnedMessages(String cid, List<Message> messages) {
|
Future<void> updatePinnedMessages(String cid, List<Message> messages) =>
|
||||||
return _db.pinnedMessageDao.updateMessages(cid, messages);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updatePinnedMessages');
|
||||||
|
return db.pinnedMessageDao.updateMessages(cid, messages);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateReactions(List<Reaction> reactions) {
|
Future<void> updateReactions(List<Reaction> reactions) =>
|
||||||
return _db.reactionDao.updateReactions(reactions);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updateReactions');
|
||||||
|
return db.reactionDao.updateReactions(reactions);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateReads(String cid, List<Read> reads) {
|
Future<void> updateReads(String cid, List<Read> reads) =>
|
||||||
return _db.readDao.updateReads(cid, reads);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('updateReads');
|
||||||
|
return db.readDao.updateReads(cid, reads);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateUsers(List<User> users) {
|
Future<void> updateUsers(List<User> users) => _readProtected(() async {
|
||||||
return _db.userDao.updateUsers(users);
|
_logger.info('updateUsers');
|
||||||
}
|
return db.userDao.updateUsers(users);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteReactionsByMessageId(List<String> messageIds) {
|
Future<void> deleteReactionsByMessageId(List<String> messageIds) =>
|
||||||
return _db.reactionDao.deleteReactionsByMessageIds(messageIds);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('deleteReactionsByMessageId');
|
||||||
|
return db.reactionDao.deleteReactionsByMessageIds(messageIds);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteMembersByCids(List<String> cids) {
|
Future<void> deleteMembersByCids(List<String> cids) =>
|
||||||
return _db.memberDao.deleteMemberByCids(cids);
|
_readProtected(() async {
|
||||||
}
|
_logger.info('deleteMembersByCids');
|
||||||
|
return db.memberDao.deleteMemberByCids(cids);
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> disconnect({bool flush = false}) async {
|
Future<void> updateChannelStates(List<ChannelState> channelStates) =>
|
||||||
if (_db != null) {
|
_readProtected(() async => db.transaction(() async {
|
||||||
_logger.info('Disconnecting');
|
await super.updateChannelStates(channelStates);
|
||||||
if (flush) {
|
}));
|
||||||
_logger.info('Flushing');
|
|
||||||
await _db.batch((batch) {
|
@override
|
||||||
_db.allTables.forEach((table) {
|
Future<void> disconnect({bool flush = false}) async =>
|
||||||
_db.delete(table).go();
|
_mutex.protectWrite(() async {
|
||||||
});
|
_logger.info('disconnect');
|
||||||
});
|
if (db != null) {
|
||||||
}
|
_logger.info('Disconnecting');
|
||||||
await _db.disconnect();
|
if (flush) {
|
||||||
_db = null;
|
_logger.info('Flushing');
|
||||||
}
|
await db.batch((batch) {
|
||||||
}
|
db.allTables.forEach((table) {
|
||||||
|
db.delete(table).go();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await db.disconnect();
|
||||||
|
db = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: stream_chat_persistence
|
name: stream_chat_persistence
|
||||||
homepage: https://github.com/GetStream/stream-chat-flutter
|
homepage: https://github.com/GetStream/stream-chat-flutter
|
||||||
description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter.
|
description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter.
|
||||||
version: 1.4.0-beta
|
version: 1.5.0
|
||||||
repository: https://github.com/GetStream/stream-chat-flutter
|
repository: https://github.com/GetStream/stream-chat-flutter
|
||||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||||
|
|
||||||
@@ -11,14 +11,17 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
logging: ^0.11.4
|
||||||
|
meta: ^1.2.4
|
||||||
moor: ^3.4.0
|
moor: ^3.4.0
|
||||||
|
mutex: ^2.0.0
|
||||||
path: ^1.7.0
|
path: ^1.7.0
|
||||||
path_provider: ^1.6.27
|
path_provider: ^1.6.27
|
||||||
sqlite3_flutter_libs: ^0.4.0+1
|
sqlite3_flutter_libs: ^0.4.0+1
|
||||||
stream_chat: ^1.4.0-beta
|
stream_chat: ^1.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.15.7
|
|
||||||
build_runner: ^1.11.0
|
build_runner: ^1.11.0
|
||||||
moor_generator: ^3.4.1
|
moor_generator: ^3.4.1
|
||||||
pedantic: ^1.9.2
|
pedantic: ^1.9.2
|
||||||
|
test: ^1.15.7
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||||
|
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('connect', () {
|
||||||
|
test('throws exception because already connected', () {
|
||||||
|
final streamChatPersistenceClient = StreamChatPersistenceClient(
|
||||||
|
connectionMode: ConnectionMode.background,
|
||||||
|
logLevel: Level.INFO,
|
||||||
|
)..db = MoorChatDatabase(
|
||||||
|
'test',
|
||||||
|
persistOnDisk: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => streamChatPersistenceClient.connect('test'),
|
||||||
|
throwsA(allOf(isException, predicate((e) {
|
||||||
|
return e.message ==
|
||||||
|
'An instance of StreamChatDatabase is already connected.\n'
|
||||||
|
'disconnect the previous instance before connecting again.';
|
||||||
|
}))),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user