Merge branch 'develop' of https://github.com/GetStream/stream-chat-flutter into mark-read-fix
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
name: Dart Code Metrics
|
||||||
|
|
||||||
|
env:
|
||||||
|
flutter_version: "2.5.0"
|
||||||
|
folders: "lib, test"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: dart-code-metrics
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Git Checkout"
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: "Cache Flutter dependencies"
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /opt/hostedtoolcache/flutter
|
||||||
|
key: ${{ env.flutter_version }}-flutter
|
||||||
|
|
||||||
|
- name: "Install Flutter"
|
||||||
|
uses: subosito/flutter-action@v1
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.flutter_version }}
|
||||||
|
|
||||||
|
- name: "Install Tools"
|
||||||
|
run: flutter pub global activate melos 1.0.0-dev.3
|
||||||
|
- name: "Bootstrap Workspace"
|
||||||
|
run: melos bootstrap
|
||||||
|
|
||||||
|
- name: "Stream Chat Metrics"
|
||||||
|
uses: dart-code-checker/dart-code-metrics-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
relative_path: 'packages/stream_chat'
|
||||||
|
folders: ${{ env.folders }}
|
||||||
|
|
||||||
|
- name: "Stream Chat Flutter Core Metrics"
|
||||||
|
uses: dart-code-checker/dart-code-metrics-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
relative_path: 'packages/stream_chat_flutter_core'
|
||||||
|
folders: ${{ env.folders }}
|
||||||
|
|
||||||
|
- name: "Stream Chat Flutter Metrics"
|
||||||
|
uses: dart-code-checker/dart-code-metrics-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
relative_path: 'packages/stream_chat_flutter'
|
||||||
|
folders: ${{ env.folders }}
|
||||||
|
|
||||||
|
- name: "Stream Chat Localizations Metrics"
|
||||||
|
uses: dart-code-checker/dart-code-metrics-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
relative_path: 'packages/stream_chat_localizations'
|
||||||
|
folders: ${{ env.folders }}
|
||||||
|
|
||||||
|
- name: "Stream Chat Persistence Metrics"
|
||||||
|
uses: dart-code-checker/dart-code-metrics-action@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
relative_path: 'packages/stream_chat_persistence'
|
||||||
|
folders: ${{ env.folders }}
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
flutter-version: ${{ env.flutter_version }}
|
flutter-version: ${{ env.flutter_version }}
|
||||||
- name: "Install Tools"
|
- name: "Install Tools"
|
||||||
run: |
|
run: |
|
||||||
flutter pub global activate melos
|
flutter pub global activate melos 1.0.0-dev.3
|
||||||
- name: "Bootstrap Workspace"
|
- name: "Bootstrap Workspace"
|
||||||
run: melos bootstrap
|
run: melos bootstrap
|
||||||
- name: "Dart Analyze"
|
- name: "Dart Analyze"
|
||||||
|
|||||||
+35
-2
@@ -1,7 +1,9 @@
|
|||||||
analyzer:
|
analyzer:
|
||||||
|
plugins:
|
||||||
|
- dart_code_metrics
|
||||||
exclude:
|
exclude:
|
||||||
- packages/*/lib/**/*.g.dart
|
- packages/*/lib/**/*.g.dart
|
||||||
- packages/*/lib/src/emoji
|
- packages/*/lib/src/emoji/**
|
||||||
- packages/*/lib/**/*.freezed.dart
|
- packages/*/lib/**/*.freezed.dart
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
@@ -143,4 +145,35 @@ linter:
|
|||||||
- cast_nullable_to_non_nullable
|
- cast_nullable_to_non_nullable
|
||||||
- unnecessary_null_checks
|
- unnecessary_null_checks
|
||||||
- tighten_type_of_initializing_formals
|
- tighten_type_of_initializing_formals
|
||||||
- null_check_on_nullable_type_parameter
|
- null_check_on_nullable_type_parameter
|
||||||
|
|
||||||
|
# https://dartcodemetrics.dev/docs/getting-started/introduction
|
||||||
|
dart_code_metrics:
|
||||||
|
rules:
|
||||||
|
# Dart Specific
|
||||||
|
- binary-expression-operand-order
|
||||||
|
- double-literal-format
|
||||||
|
- prefer-match-file-name:
|
||||||
|
exclude:
|
||||||
|
- packages/*/test/**
|
||||||
|
- packages/*/example/**
|
||||||
|
- packages/**/util/**
|
||||||
|
- packages/**/utils.dart
|
||||||
|
- packages/stream_chat/lib/src/client/client.dart
|
||||||
|
- packages/stream_chat/lib/src/core/api/responses.dart
|
||||||
|
- packages/stream_chat/lib/src/core/api/requests.dart
|
||||||
|
- packages/stream_chat/lib/src/core/platform_detector/**
|
||||||
|
- packages/stream_chat_persistence/lib/src/db/shared/**
|
||||||
|
- packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart
|
||||||
|
- no-boolean-literal-compare
|
||||||
|
- no-equal-then-else
|
||||||
|
- no-empty-block:
|
||||||
|
exclude:
|
||||||
|
- packages/*/test/**
|
||||||
|
- prefer-trailing-comma:
|
||||||
|
exclude:
|
||||||
|
- packages/*/test/**
|
||||||
|
|
||||||
|
# Flutter specific
|
||||||
|
- always-remove-listener
|
||||||
|
- avoid-unnecessary-setstate
|
||||||
+18
-3
@@ -11,6 +11,10 @@ scripts:
|
|||||||
run: melos run analyze && melos run format
|
run: melos run analyze && melos run format
|
||||||
description: Run all static analysis checks
|
description: Run all static analysis checks
|
||||||
|
|
||||||
|
analyze:all:
|
||||||
|
run: melos run analyze && melos run metrics
|
||||||
|
description: Run all
|
||||||
|
|
||||||
analyze:
|
analyze:
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 5 --ignore="*example*" -- \
|
melos exec -c 5 --ignore="*example*" -- \
|
||||||
@@ -24,6 +28,14 @@ scripts:
|
|||||||
description: |
|
description: |
|
||||||
Run `flutter format --set-exit-if-changed .` in all packages.
|
Run `flutter format --set-exit-if-changed .` in all packages.
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
run: |
|
||||||
|
melos exec -c 1 --ignore="*example*" -- \
|
||||||
|
flutter pub run dart_code_metrics:metrics analyze lib
|
||||||
|
description: |
|
||||||
|
Run `dart_code_metrics` in all packages.
|
||||||
|
- Note: you can also rely on your IDEs Dart Analysis / Issues window.
|
||||||
|
|
||||||
lint:pub:
|
lint:pub:
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 5 --no-private --ignore="*example*" -- \
|
melos exec -c 5 --no-private --ignore="*example*" -- \
|
||||||
@@ -71,10 +83,13 @@ scripts:
|
|||||||
|
|
||||||
docs:
|
docs:
|
||||||
run: |
|
run: |
|
||||||
npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli &&
|
npm install -g https://github.com/GetStream/stream-chat-docusaurus-cli &&
|
||||||
npx stream-chat-docusaurus -i -s
|
npx stream-chat-docusaurus -i -s
|
||||||
description: Runs the docusaurus documentation locally.
|
description: Runs the docusaurus documentation locally.
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
flutter: '>=1.22.4 <2.0.0'
|
flutter: '>=1.17.0 <2.0.0'
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: example
|
name: stream_chat_example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
@@ -11,8 +11,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.0
|
cupertino_icons: ^1.0.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat:
|
stream_chat: ^2.2.1
|
||||||
path: ../
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ class Channel {
|
|||||||
state!.addMessage(message);
|
state!.addMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) {
|
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
||||||
final attachmentsUploadCompleter = Completer<Message>();
|
final attachmentsUploadCompleter = Completer<Message>();
|
||||||
_messageAttachmentsUploadCompleter[message.id] =
|
_messageAttachmentsUploadCompleter[message.id] =
|
||||||
attachmentsUploadCompleter;
|
attachmentsUploadCompleter;
|
||||||
@@ -849,12 +849,12 @@ class Channel {
|
|||||||
..update(type, (value) {
|
..update(type, (value) {
|
||||||
if (enforceUnique) return value;
|
if (enforceUnique) return value;
|
||||||
return value + 1;
|
return value + 1;
|
||||||
}, ifAbsent: () => 1),
|
}, ifAbsent: () => 1), // ignore: prefer-trailing-comma
|
||||||
reactionScores: {...message.reactionScores ?? <String, int>{}}
|
reactionScores: {...message.reactionScores ?? <String, int>{}}
|
||||||
..update(type, (value) {
|
..update(type, (value) {
|
||||||
if (enforceUnique) return value;
|
if (enforceUnique) return value;
|
||||||
return value + 1;
|
return value + 1;
|
||||||
}, ifAbsent: () => 1),
|
}, ifAbsent: () => 1), // ignore: prefer-trailing-comma
|
||||||
latestReactions: latestReactions,
|
latestReactions: latestReactions,
|
||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
@@ -878,7 +878,9 @@ class Channel {
|
|||||||
|
|
||||||
/// Delete a reaction from this channel.
|
/// Delete a reaction from this channel.
|
||||||
Future<EmptyResponse> deleteReaction(
|
Future<EmptyResponse> deleteReaction(
|
||||||
Message message, Reaction reaction) async {
|
Message message,
|
||||||
|
Reaction reaction,
|
||||||
|
) async {
|
||||||
final type = reaction.type;
|
final type = reaction.type;
|
||||||
final user = _client.state.currentUser;
|
final user = _client.state.currentUser;
|
||||||
|
|
||||||
@@ -1336,7 +1338,7 @@ class Channel {
|
|||||||
type,
|
type,
|
||||||
clearHistory: clearHistory,
|
clearHistory: clearHistory,
|
||||||
);
|
);
|
||||||
if (clearHistory == true) {
|
if (clearHistory) {
|
||||||
state!.truncate();
|
state!.truncate();
|
||||||
final cid = _cid;
|
final cid = _cid;
|
||||||
if (cid != null) {
|
if (cid != null) {
|
||||||
@@ -1501,28 +1503,27 @@ class ChannelClientState {
|
|||||||
final expiredAttachmentMessagesId = channelState.messages
|
final expiredAttachmentMessagesId = channelState.messages
|
||||||
.where((m) =>
|
.where((m) =>
|
||||||
!_updatedMessagesIds.contains(m.id) &&
|
!_updatedMessagesIds.contains(m.id) &&
|
||||||
m.attachments.isNotEmpty == true &&
|
m.attachments.isNotEmpty &&
|
||||||
m.attachments.any((e) {
|
m.attachments.any((e) {
|
||||||
final url = e.imageUrl ?? e.assetUrl;
|
final url = e.imageUrl ?? e.assetUrl;
|
||||||
if (url == null || !url.contains('')) {
|
if (url == null || !url.contains('')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (!uri.host.endsWith('stream-io-cdn.com') ||
|
if (!uri.host.endsWith('stream-io-cdn.com') ||
|
||||||
uri.queryParameters['Expires'] == null) {
|
uri.queryParameters['Expires'] == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final secondsFromEpoch =
|
final secondsFromEpoch =
|
||||||
int.parse(uri.queryParameters['Expires']!);
|
int.parse(uri.queryParameters['Expires']!);
|
||||||
final expiration = DateTime.fromMillisecondsSinceEpoch(
|
final expiration =
|
||||||
secondsFromEpoch * 1000);
|
DateTime.fromMillisecondsSinceEpoch(secondsFromEpoch * 1000);
|
||||||
return expiration.isBefore(DateTime.now());
|
return expiration.isBefore(DateTime.now());
|
||||||
}) ==
|
}))
|
||||||
true)
|
|
||||||
.map((e) => e.id)
|
.map((e) => e.id)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (expiredAttachmentMessagesId.isNotEmpty == true) {
|
if (expiredAttachmentMessagesId.isNotEmpty) {
|
||||||
await _channel._initializedCompleter.future;
|
await _channel._initializedCompleter.future;
|
||||||
_updatedMessagesIds.addAll(expiredAttachmentMessagesId);
|
_updatedMessagesIds.addAll(expiredAttachmentMessagesId);
|
||||||
_channel.getMessagesById(expiredAttachmentMessagesId);
|
_channel.getMessagesById(expiredAttachmentMessagesId);
|
||||||
@@ -1546,7 +1547,8 @@ class ChannelClientState {
|
|||||||
final user = e.user;
|
final user = e.user;
|
||||||
updateChannelState(channelState.copyWith(
|
updateChannelState(channelState.copyWith(
|
||||||
members: List.from(
|
members: List.from(
|
||||||
channelState.members..removeWhere((m) => m.userId == user!.id)),
|
channelState.members..removeWhere((m) => m.userId == user!.id),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1647,7 +1649,7 @@ class ChannelClientState {
|
|||||||
);
|
);
|
||||||
addMessage(message);
|
addMessage(message);
|
||||||
|
|
||||||
if (message.pinned == true) {
|
if (message.pinned) {
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
pinnedMessages: [
|
pinnedMessages: [
|
||||||
..._channelState.pinnedMessages,
|
..._channelState.pinnedMessages,
|
||||||
@@ -1794,9 +1796,8 @@ class ChannelClientState {
|
|||||||
channelStateStream.map((cs) => cs.pinnedMessages.toList());
|
channelStateStream.map((cs) => cs.pinnedMessages.toList());
|
||||||
|
|
||||||
/// Get channel last message.
|
/// Get channel last message.
|
||||||
Message? get lastMessage => _channelState.messages.isNotEmpty == true
|
Message? get lastMessage =>
|
||||||
? _channelState.messages.last
|
_channelState.messages.isNotEmpty ? _channelState.messages.last : null;
|
||||||
: null;
|
|
||||||
|
|
||||||
/// Get channel last message.
|
/// Get channel last message.
|
||||||
Stream<Message?> get lastMessageStream =>
|
Stream<Message?> get lastMessageStream =>
|
||||||
@@ -1859,8 +1860,8 @@ class ChannelClientState {
|
|||||||
(m) => m.user.id == message.user?.id,
|
(m) => m.user.id == message.user?.id,
|
||||||
) !=
|
) !=
|
||||||
null;
|
null;
|
||||||
return message.silent != true &&
|
return !message.silent &&
|
||||||
message.shadowed != true &&
|
!message.shadowed &&
|
||||||
message.user?.id != userId &&
|
message.user?.id != userId &&
|
||||||
!userIsMuted;
|
!userIsMuted;
|
||||||
}
|
}
|
||||||
@@ -1898,9 +1899,7 @@ class ChannelClientState {
|
|||||||
...updatedState.messages,
|
...updatedState.messages,
|
||||||
..._channelState.messages
|
..._channelState.messages
|
||||||
.where((m) =>
|
.where((m) =>
|
||||||
updatedState.messages
|
!updatedState.messages.any((newMessage) => newMessage.id == m.id))
|
||||||
.any((newMessage) => newMessage.id == m.id) !=
|
|
||||||
true)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
]..sort(_sortByCreatedAt);
|
]..sort(_sortByCreatedAt);
|
||||||
|
|
||||||
@@ -1908,9 +1907,7 @@ class ChannelClientState {
|
|||||||
...updatedState.watchers,
|
...updatedState.watchers,
|
||||||
..._channelState.watchers
|
..._channelState.watchers
|
||||||
.where((w) =>
|
.where((w) =>
|
||||||
updatedState.watchers
|
!updatedState.watchers.any((newWatcher) => newWatcher.id == w.id))
|
||||||
.any((newWatcher) => newWatcher.id == w.id) !=
|
|
||||||
true)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1922,9 +1919,7 @@ class ChannelClientState {
|
|||||||
...updatedState.read,
|
...updatedState.read,
|
||||||
..._channelState.read
|
..._channelState.read
|
||||||
.where((r) =>
|
.where((r) =>
|
||||||
updatedState.read
|
!updatedState.read.any((newRead) => newRead.user.id == r.user.id))
|
||||||
.any((newRead) => newRead.user.id == r.user.id) !=
|
|
||||||
true)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -2031,7 +2026,7 @@ class ChannelClientState {
|
|||||||
.on()
|
.on()
|
||||||
.where((event) =>
|
.where((event) =>
|
||||||
event.user != null &&
|
event.user != null &&
|
||||||
members.any((m) => m.userId == event.user!.id) == true)
|
members.any((m) => m.userId == event.user!.id))
|
||||||
.listen(
|
.listen(
|
||||||
(event) {
|
(event) {
|
||||||
final newMembers = List<Member>.from(members);
|
final newMembers = List<Member>.from(members);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class ChannelApi {
|
|||||||
if (messageLimit != null) 'message_limit': messageLimit,
|
if (messageLimit != null) 'message_limit': messageLimit,
|
||||||
|
|
||||||
// pagination
|
// pagination
|
||||||
...paginationParams.toJson()
|
...paginationParams.toJson(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ enum PushProvider {
|
|||||||
firebase,
|
firebase,
|
||||||
|
|
||||||
/// Send notifications using Apple's Push Notification service
|
/// Send notifications using Apple's Push Notification service
|
||||||
apn
|
apn,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper extension for [PushProvider]
|
/// Helper extension for [PushProvider]
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ enum ChatErrorCode {
|
|||||||
internalSystemError,
|
internalSystemError,
|
||||||
|
|
||||||
/// No access to requested channels
|
/// No access to requested channels
|
||||||
noAccessToChannels
|
noAccessToChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _errorCodeWithDescription = {
|
const _errorCodeWithDescription = {
|
||||||
@@ -98,14 +98,20 @@ const _errorCodeWithDescription = {
|
|||||||
MapEntry(1000, 'Unauthorised, token not defined'),
|
MapEntry(1000, 'Unauthorised, token not defined'),
|
||||||
ChatErrorCode.inputError:
|
ChatErrorCode.inputError:
|
||||||
MapEntry(4, 'Wrong data/parameter is sent to the API'),
|
MapEntry(4, 'Wrong data/parameter is sent to the API'),
|
||||||
ChatErrorCode.duplicateUsername: MapEntry(6,
|
ChatErrorCode.duplicateUsername: MapEntry(
|
||||||
'Duplicate username is sent while enforce_unique_usernames is enabled'),
|
6,
|
||||||
|
'Duplicate username is sent while enforce_unique_usernames is enabled',
|
||||||
|
),
|
||||||
ChatErrorCode.messageTooLong: MapEntry(20, 'Message is too long'),
|
ChatErrorCode.messageTooLong: MapEntry(20, 'Message is too long'),
|
||||||
ChatErrorCode.eventNotSupported: MapEntry(18, 'Event is not supported'),
|
ChatErrorCode.eventNotSupported: MapEntry(18, 'Event is not supported'),
|
||||||
ChatErrorCode.channelFeatureNotSupported: MapEntry(19,
|
ChatErrorCode.channelFeatureNotSupported: MapEntry(
|
||||||
'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)'),
|
19,
|
||||||
ChatErrorCode.multipleNestling: MapEntry(21,
|
'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)',
|
||||||
'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads'),
|
),
|
||||||
|
ChatErrorCode.multipleNestling: MapEntry(
|
||||||
|
21,
|
||||||
|
'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads',
|
||||||
|
),
|
||||||
ChatErrorCode.customCommandEndpointCall:
|
ChatErrorCode.customCommandEndpointCall:
|
||||||
MapEntry(45, 'Custom Command handler returned an error'),
|
MapEntry(45, 'Custom Command handler returned an error'),
|
||||||
ChatErrorCode.customCommandEndpointMissing:
|
ChatErrorCode.customCommandEndpointMissing:
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class StreamWebSocketError extends StreamChatError {
|
|||||||
|
|
||||||
///
|
///
|
||||||
factory StreamWebSocketError.fromWebSocketChannelError(
|
factory StreamWebSocketError.fromWebSocketChannelError(
|
||||||
WebSocketChannelException error) {
|
WebSocketChannelException error,
|
||||||
|
) {
|
||||||
final message = error.message ?? '';
|
final message = error.message ?? '';
|
||||||
return StreamWebSocketError(message);
|
return StreamWebSocketError(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,8 +105,11 @@ class LoggingInterceptor extends Interceptor {
|
|||||||
final formDataMap = <String, dynamic>{}
|
final formDataMap = <String, dynamic>{}
|
||||||
..addEntries(data.fields)
|
..addEntries(data.fields)
|
||||||
..addEntries(data.files);
|
..addEntries(data.files);
|
||||||
_printMapAsTable(_logPrintRequest, formDataMap,
|
_printMapAsTable(
|
||||||
header: 'Form data | ${data.boundary}');
|
_logPrintRequest,
|
||||||
|
formDataMap,
|
||||||
|
header: 'Form data | ${data.boundary}',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_printBlock(_logPrintRequest, data.toString());
|
_printBlock(_logPrintRequest, data.toString());
|
||||||
}
|
}
|
||||||
@@ -201,14 +204,19 @@ class LoggingInterceptor extends Interceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _printRequestHeader(
|
void _printRequestHeader(
|
||||||
void Function(Object) logPrint, RequestOptions options) {
|
void Function(Object) logPrint,
|
||||||
|
RequestOptions options,
|
||||||
|
) {
|
||||||
final uri = options.uri;
|
final uri = options.uri;
|
||||||
final method = options.method;
|
final method = options.method;
|
||||||
_printBoxed(logPrint, header: 'Request ║ $method ', text: uri.toString());
|
_printBoxed(logPrint, header: 'Request ║ $method ', text: uri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _printLine(void Function(Object) logPrint,
|
void _printLine(
|
||||||
[String pre = '', String suf = '╝']) =>
|
void Function(Object) logPrint, [
|
||||||
|
String pre = '',
|
||||||
|
String suf = '╝',
|
||||||
|
]) =>
|
||||||
logPrint('$pre${'═' * maxWidth}$suf');
|
logPrint('$pre${'═' * maxWidth}$suf');
|
||||||
|
|
||||||
void _printKV(void Function(Object) logPrint, String? key, Object? v) {
|
void _printKV(void Function(Object) logPrint, String? key, Object? v) {
|
||||||
@@ -227,8 +235,10 @@ class LoggingInterceptor extends Interceptor {
|
|||||||
final lines = (msg.length / maxWidth).ceil();
|
final lines = (msg.length / maxWidth).ceil();
|
||||||
for (var i = 0; i < lines; ++i) {
|
for (var i = 0; i < lines; ++i) {
|
||||||
logPrint((i >= 0 ? '║ ' : '') +
|
logPrint((i >= 0 ? '║ ' : '') +
|
||||||
msg.substring(i * maxWidth,
|
msg.substring(
|
||||||
math.min<int>(i * maxWidth + maxWidth, msg.length)));
|
i * maxWidth,
|
||||||
|
math.min<int>(i * maxWidth + maxWidth, msg.length),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,8 +311,13 @@ class LoggingInterceptor extends Interceptor {
|
|||||||
if (compact) {
|
if (compact) {
|
||||||
logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}');
|
logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}');
|
||||||
} else {
|
} else {
|
||||||
_printPrettyMap(logPrint, e,
|
_printPrettyMap(
|
||||||
tabs: tabs + 1, isListItem: true, isLast: isLast);
|
logPrint,
|
||||||
|
e,
|
||||||
|
tabs: tabs + 1,
|
||||||
|
isListItem: true,
|
||||||
|
isLast: isLast,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}');
|
logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}');
|
||||||
|
|||||||
@@ -56,12 +56,15 @@ class Attachment extends Equatable {
|
|||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory Attachment.fromJson(Map<String, dynamic> json) =>
|
factory Attachment.fromJson(Map<String, dynamic> json) =>
|
||||||
_$AttachmentFromJson(
|
_$AttachmentFromJson(
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||||
|
);
|
||||||
|
|
||||||
/// Create a new instance from a db data
|
/// Create a new instance from a db data
|
||||||
factory Attachment.fromData(Map<String, dynamic> json) =>
|
factory Attachment.fromData(Map<String, dynamic> json) =>
|
||||||
_$AttachmentFromJson(Serializer.moveToExtraDataFromRoot(
|
_$AttachmentFromJson(Serializer.moveToExtraDataFromRoot(
|
||||||
json, topLevelFields + dbSpecificTopLevelFields));
|
json,
|
||||||
|
topLevelFields + dbSpecificTopLevelFields,
|
||||||
|
));
|
||||||
|
|
||||||
///The attachment type based on the URL resource. This can be: audio,
|
///The attachment type based on the URL resource. This can be: audio,
|
||||||
///image or video
|
///image or video
|
||||||
|
|||||||
@@ -11,54 +11,6 @@ part 'attachment_file.freezed.dart';
|
|||||||
|
|
||||||
part 'attachment_file.g.dart';
|
part 'attachment_file.g.dart';
|
||||||
|
|
||||||
/// Union class to hold various [UploadState] of a attachment.
|
|
||||||
@freezed
|
|
||||||
class UploadState with _$UploadState {
|
|
||||||
/// Preparing state of the union
|
|
||||||
const factory UploadState.preparing() = Preparing;
|
|
||||||
|
|
||||||
/// InProgress state of the union
|
|
||||||
const factory UploadState.inProgress({
|
|
||||||
required int uploaded,
|
|
||||||
required int total,
|
|
||||||
}) = InProgress;
|
|
||||||
|
|
||||||
/// Success state of the union
|
|
||||||
const factory UploadState.success() = Success;
|
|
||||||
|
|
||||||
/// Failed state of the union
|
|
||||||
const factory UploadState.failed({required String error}) = Failed;
|
|
||||||
|
|
||||||
/// Creates a new instance from a json
|
|
||||||
factory UploadState.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$UploadStateFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper extension for UploadState
|
|
||||||
extension UploadStateX on UploadState? {
|
|
||||||
/// Returns true if state is [Preparing]
|
|
||||||
bool get isPreparing => this is Preparing;
|
|
||||||
|
|
||||||
/// Returns true if state is [InProgress]
|
|
||||||
bool get isInProgress => this is InProgress;
|
|
||||||
|
|
||||||
/// Returns true if state is [Success]
|
|
||||||
bool get isSuccess => this is Success;
|
|
||||||
|
|
||||||
/// Returns true if state is [Failed]
|
|
||||||
bool get isFailed => this is Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List? _fromString(String? bytes) {
|
|
||||||
if (bytes == null) return null;
|
|
||||||
return Uint8List.fromList(bytes.codeUnits);
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _toString(Uint8List? bytes) {
|
|
||||||
if (bytes == null) return null;
|
|
||||||
return String.fromCharCodes(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The class that contains the information about an attachment file
|
/// The class that contains the information about an attachment file
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class AttachmentFile {
|
class AttachmentFile {
|
||||||
@@ -135,3 +87,51 @@ class AttachmentFile {
|
|||||||
return multiPartFile;
|
return multiPartFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Union class to hold various [UploadState] of a attachment.
|
||||||
|
@freezed
|
||||||
|
class UploadState with _$UploadState {
|
||||||
|
/// Preparing state of the union
|
||||||
|
const factory UploadState.preparing() = Preparing;
|
||||||
|
|
||||||
|
/// InProgress state of the union
|
||||||
|
const factory UploadState.inProgress({
|
||||||
|
required int uploaded,
|
||||||
|
required int total,
|
||||||
|
}) = InProgress;
|
||||||
|
|
||||||
|
/// Success state of the union
|
||||||
|
const factory UploadState.success() = Success;
|
||||||
|
|
||||||
|
/// Failed state of the union
|
||||||
|
const factory UploadState.failed({required String error}) = Failed;
|
||||||
|
|
||||||
|
/// Creates a new instance from a json
|
||||||
|
factory UploadState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadStateFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper extension for UploadState
|
||||||
|
extension UploadStateX on UploadState? {
|
||||||
|
/// Returns true if state is [Preparing]
|
||||||
|
bool get isPreparing => this is Preparing;
|
||||||
|
|
||||||
|
/// Returns true if state is [InProgress]
|
||||||
|
bool get isInProgress => this is InProgress;
|
||||||
|
|
||||||
|
/// Returns true if state is [Success]
|
||||||
|
bool get isSuccess => this is Success;
|
||||||
|
|
||||||
|
/// Returns true if state is [Failed]
|
||||||
|
bool get isFailed => this is Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List? _fromString(String? bytes) {
|
||||||
|
if (bytes == null) return null;
|
||||||
|
return Uint8List.fromList(bytes.codeUnits);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _toString(Uint8List? bytes) {
|
||||||
|
if (bytes == null) return null;
|
||||||
|
return String.fromCharCodes(bytes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class ChannelModel {
|
|||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory ChannelModel.fromJson(Map<String, dynamic> json) =>
|
factory ChannelModel.fromJson(Map<String, dynamic> json) =>
|
||||||
_$ChannelModelFromJson(
|
_$ChannelModelFromJson(
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||||
|
);
|
||||||
|
|
||||||
/// The id of this channel
|
/// The id of this channel
|
||||||
final String id;
|
final String id;
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ class Message extends Equatable {
|
|||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||||
|
);
|
||||||
|
|
||||||
/// The message ID. This is either created by Stream or set client side when
|
/// The message ID. This is either created by Stream or set client side when
|
||||||
/// the message is added.
|
/// the message is added.
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class OwnUser extends User {
|
|||||||
|
|
||||||
/// Create a new instance from json.
|
/// Create a new instance from json.
|
||||||
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||||
|
);
|
||||||
|
|
||||||
/// Create a new instance from [User] object.
|
/// Create a new instance from [User] object.
|
||||||
factory OwnUser.fromUser(User user) => OwnUser(
|
factory OwnUser.fromUser(User user) => OwnUser(
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ dependencies:
|
|||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.0.1
|
build_runner: ^2.0.1
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
freezed: ^0.14.1+3
|
freezed: ^0.14.1+3
|
||||||
json_serializable: ^5.0.2
|
json_serializable: ^5.0.2
|
||||||
mocktail: ^0.1.1
|
mocktail: ^0.1.1
|
||||||
test: ^1.18.2
|
test: ^1.17.12
|
||||||
@@ -291,7 +291,7 @@ void main() {
|
|||||||
(index) => Attachment(
|
(index) => Attachment(
|
||||||
id: 'test-attachment-id-$index',
|
id: 'test-attachment-id-$index',
|
||||||
type: index.isEven ? 'image' : 'file',
|
type: index.isEven ? 'image' : 'file',
|
||||||
file: AttachmentFile(size: 33 * index, path: 'test-file-path'),
|
file: AttachmentFile(size: index * 33, path: 'test-file-path'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ void main() {
|
|||||||
(index) => Attachment(
|
(index) => Attachment(
|
||||||
id: 'test-attachment-id-$index',
|
id: 'test-attachment-id-$index',
|
||||||
type: index.isEven ? 'image' : 'file',
|
type: index.isEven ? 'image' : 'file',
|
||||||
file: AttachmentFile(size: 33 * index, path: 'test-file-path'),
|
file: AttachmentFile(size: index * 33, path: 'test-file-path'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Future<void> main() async {
|
|||||||
|
|
||||||
await client.connectUser(
|
await client.connectUser(
|
||||||
User(id: 'super-band-9'),
|
User(id: 'super-band-9'),
|
||||||
'''eyJ0eXAiO«iJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||||
);
|
);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: example
|
name: stream_chat_flutter_example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
@@ -27,17 +27,9 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
# stream_chat:
|
stream_chat_flutter: ^2.2.1
|
||||||
# path: ../../stream_chat
|
stream_chat_localizations: ^1.1.0
|
||||||
# stream_chat_flutter_core:
|
stream_chat_persistence: ^2.2.0
|
||||||
# path: ../../stream_chat_flutter_core
|
|
||||||
stream_chat_flutter:
|
|
||||||
path: ../
|
|
||||||
stream_chat_localizations:
|
|
||||||
path: ../../stream_chat_localizations
|
|
||||||
stream_chat_persistence:
|
|
||||||
path: ../../stream_chat_persistence
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
+3
-3
@@ -141,7 +141,7 @@ class _PreparingState extends StatelessWidget {
|
|||||||
uploaded: 0,
|
uploaded: 0,
|
||||||
total: double.maxFinite.toInt(),
|
total: double.maxFinite.toInt(),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ class _InProgressState extends StatelessWidget {
|
|||||||
uploaded: sent,
|
uploaded: sent,
|
||||||
total: total,
|
total: total,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ class _FailedState extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class AttachmentError extends StatelessWidget {
|
|||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.accentError
|
.accentError
|
||||||
.withOpacity(.1),
|
.withOpacity(0.1),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.error_outline,
|
Icons.error_outline,
|
||||||
|
|||||||
@@ -253,7 +253,8 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
if (message.status == MessageSendingStatus.sent) {
|
if (message.status == MessageSendingStatus.sent) {
|
||||||
trailingWidget = IconButton(
|
trailingWidget = IconButton(
|
||||||
icon: StreamSvgIcon.cloudDownload(
|
icon: StreamSvgIcon.cloudDownload(
|
||||||
color: theme.colorTheme.textHighEmphasis),
|
color: theme.colorTheme.textHighEmphasis,
|
||||||
|
),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
splashRadius: 16,
|
splashRadius: 16,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
.textHighEmphasis
|
.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(0.5),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -99,12 +99,14 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
() {
|
() {
|
||||||
final attachment = message.attachments[currentIndex];
|
final attachment = message.attachments[currentIndex];
|
||||||
final isImage = attachment.type == 'image';
|
final isImage = attachment.type == 'image';
|
||||||
final Future<String?> Function(Attachment,
|
final Future<String?> Function(
|
||||||
{void Function(int, int) progressCallback})
|
Attachment, {
|
||||||
saveFile = fileDownloader ?? _downloadAttachment;
|
void Function(int, int) progressCallback,
|
||||||
final Future<String?> Function(Attachment,
|
}) saveFile = fileDownloader ?? _downloadAttachment;
|
||||||
{void Function(int, int) progressCallback})
|
final Future<String?> Function(
|
||||||
saveImage = imageDownloader ?? _downloadAttachment;
|
Attachment, {
|
||||||
|
void Function(int, int) progressCallback,
|
||||||
|
}) saveImage = imageDownloader ?? _downloadAttachment;
|
||||||
final downloader = isImage ? saveImage : saveFile;
|
final downloader = isImage ? saveImage : saveFile;
|
||||||
|
|
||||||
final progressNotifier =
|
final progressNotifier =
|
||||||
@@ -183,7 +185,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/unread_indicator.dart';
|
|||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
/// Back button implementation
|
/// Back button implementation
|
||||||
|
// ignore: prefer-match-file-name
|
||||||
class StreamBackButton extends StatelessWidget {
|
class StreamBackButton extends StatelessWidget {
|
||||||
/// Constructor for creating back button
|
/// Constructor for creating back button
|
||||||
const StreamBackButton({
|
const StreamBackButton({
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
UserAvatar(
|
UserAvatar(
|
||||||
user: members
|
user: members
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(e) => e.user?.id != userAsMember.user?.id)
|
(e) => e.user?.id != userAsMember.user?.id,
|
||||||
|
)
|
||||||
.user!,
|
.user!,
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxHeight: 64,
|
maxHeight: 64,
|
||||||
@@ -93,7 +94,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
Text(
|
Text(
|
||||||
members
|
members
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(e) => e.user?.id != userAsMember.user?.id)
|
(e) => e.user?.id != userAsMember.user?.id,
|
||||||
|
)
|
||||||
.user
|
.user
|
||||||
?.name ??
|
?.name ??
|
||||||
'',
|
'',
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
onPressed: onNewChatButtonTap,
|
onPressed: onNewChatButtonTap,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
title: Column(
|
title: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -565,7 +565,8 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
'owner',
|
'owner',
|
||||||
].contains(channel.state!.members
|
].contains(channel.state!.members
|
||||||
.firstWhereOrNull(
|
.firstWhereOrNull(
|
||||||
(m) => m.userId == channel.client.state.currentUser?.id)
|
(m) => m.userId == channel.client.state.currentUser?.id,
|
||||||
|
)
|
||||||
?.role))
|
?.role))
|
||||||
IconSlideAction(
|
IconSlideAction(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
|
|||||||
@@ -72,85 +72,82 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||||
final streamChatState = StreamChat.of(context);
|
final streamChatState = StreamChat.of(context);
|
||||||
return BetterStreamBuilder<bool>(
|
return BetterStreamBuilder<bool>(
|
||||||
stream: channel.isMutedStream,
|
stream: channel.isMutedStream,
|
||||||
initialData: channel.isMuted,
|
initialData: channel.isMuted,
|
||||||
builder: (context, data) => AnimatedOpacity(
|
builder: (context, data) => AnimatedOpacity(
|
||||||
opacity: data ? 0.5 : 1,
|
opacity: data ? 0.5 : 1,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
),
|
),
|
||||||
onTap: () => onTap?.call(channel),
|
onTap: () => onTap?.call(channel),
|
||||||
onLongPress: () => onLongPress?.call(channel),
|
onLongPress: () => onLongPress?.call(channel),
|
||||||
leading: leading ?? ChannelAvatar(onTap: onImageTap),
|
leading: leading ?? ChannelAvatar(onTap: onImageTap),
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: title ??
|
child: title ??
|
||||||
ChannelName(
|
ChannelName(
|
||||||
textStyle: channelPreviewTheme.titleStyle,
|
textStyle: channelPreviewTheme.titleStyle,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
BetterStreamBuilder<List<Member>>(
|
|
||||||
stream: channel.state?.membersStream,
|
|
||||||
initialData: channel.state?.members,
|
|
||||||
comparator: const ListEquality().equals,
|
|
||||||
builder: (context, members) {
|
|
||||||
if (members.isEmpty ||
|
|
||||||
members.any((Member e) =>
|
|
||||||
e.user!.id ==
|
|
||||||
channel.client.state.currentUser?.id) !=
|
|
||||||
true) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
return UnreadIndicator(
|
|
||||||
cid: channel.cid,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Flexible(child: subtitle ?? _buildSubtitle(context)),
|
|
||||||
sendingIndicator ??
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final lastMessage =
|
|
||||||
channel.state?.messages.lastWhereOrNull(
|
|
||||||
(m) => !m.isDeleted && m.shadowed != true,
|
|
||||||
);
|
|
||||||
if (lastMessage?.user?.id ==
|
|
||||||
streamChatState.currentUser?.id) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 4),
|
|
||||||
child: SendingIndicator(
|
|
||||||
message: lastMessage!,
|
|
||||||
size: channelPreviewTheme.indicatorIconSize,
|
|
||||||
isMessageRead: channel.state!.read
|
|
||||||
.where((element) =>
|
|
||||||
element.user.id !=
|
|
||||||
channel
|
|
||||||
.client.state.currentUser!.id)
|
|
||||||
.where((element) => element.lastRead
|
|
||||||
.isAfter(lastMessage.createdAt))
|
|
||||||
.isNotEmpty ==
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
trailing ?? _buildDate(context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
));
|
BetterStreamBuilder<List<Member>>(
|
||||||
|
stream: channel.state?.membersStream,
|
||||||
|
initialData: channel.state?.members,
|
||||||
|
comparator: const ListEquality().equals,
|
||||||
|
builder: (context, members) {
|
||||||
|
if (members.isEmpty ||
|
||||||
|
!members.any((Member e) =>
|
||||||
|
e.user!.id == channel.client.state.currentUser?.id)) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
return UnreadIndicator(
|
||||||
|
cid: channel.cid,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(child: subtitle ?? _buildSubtitle(context)),
|
||||||
|
sendingIndicator ??
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final lastMessage =
|
||||||
|
channel.state?.messages.lastWhereOrNull(
|
||||||
|
(m) => !m.isDeleted && !m.shadowed,
|
||||||
|
);
|
||||||
|
if (lastMessage?.user?.id ==
|
||||||
|
streamChatState.currentUser?.id) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4),
|
||||||
|
child: SendingIndicator(
|
||||||
|
message: lastMessage!,
|
||||||
|
size: channelPreviewTheme.indicatorIconSize,
|
||||||
|
isMessageRead: channel.state!.read
|
||||||
|
.where((element) =>
|
||||||
|
element.user.id !=
|
||||||
|
channel.client.state.currentUser!.id)
|
||||||
|
.where((element) => element.lastRead
|
||||||
|
.isAfter(lastMessage.createdAt))
|
||||||
|
.isNotEmpty,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
trailing ?? _buildDate(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDate(BuildContext context) => BetterStreamBuilder<DateTime>(
|
Widget _buildDate(BuildContext context) => BetterStreamBuilder<DateTime>(
|
||||||
@@ -246,10 +243,11 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
lastMessage.mentionedUsers,
|
lastMessage.mentionedUsers,
|
||||||
lastMessage.attachments,
|
lastMessage.attachments,
|
||||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||||
color: channelPreviewTheme.subtitleStyle?.color,
|
color: channelPreviewTheme.subtitleStyle?.color,
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
? FontStyle.italic
|
? FontStyle.italic
|
||||||
: FontStyle.normal),
|
: FontStyle.normal,
|
||||||
|
),
|
||||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||||
color: channelPreviewTheme.subtitleStyle?.color,
|
color: channelPreviewTheme.subtitleStyle?.color,
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ class CommandsOverlay extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints.loose(size),
|
constraints: BoxConstraints.loose(size),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _streamChatTheme.colorTheme.barsBg,
|
color: _streamChatTheme.colorTheme.barsBg,
|
||||||
borderRadius: BorderRadius.circular(8)),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@@ -72,9 +73,9 @@ class CommandsOverlay extends StatelessWidget {
|
|||||||
context.translations.instantCommandsLabel,
|
context.translations.instantCommandsLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _streamChatTheme.colorTheme.textHighEmphasis
|
color: _streamChatTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -102,7 +103,8 @@ class CommandsOverlay extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: c.name.capitalize(),
|
text: c.name.capitalize(),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' /${c.name} ${c.args}',
|
text: ' /${c.name} ${c.args}',
|
||||||
@@ -132,7 +134,9 @@ class CommandsOverlay extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommandIcon(
|
Widget _buildCommandIcon(
|
||||||
StreamChatThemeData _streamChatTheme, String iconType) {
|
StreamChatThemeData _streamChatTheme,
|
||||||
|
String iconType,
|
||||||
|
) {
|
||||||
switch (iconType) {
|
switch (iconType) {
|
||||||
case 'giphy':
|
case 'giphy':
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
|
|||||||
@@ -21,25 +21,16 @@ class DateDivider extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final createdAt = Jiffy(dateTime);
|
final createdAt = Jiffy(dateTime);
|
||||||
final now = DateTime.now();
|
final now = Jiffy(DateTime.now());
|
||||||
|
|
||||||
String dayInfo;
|
var dayInfo = createdAt.MMMd;
|
||||||
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
|
if (createdAt.isSame(now, Units.DAY)) {
|
||||||
dayInfo = context.translations.todayLabel;
|
dayInfo = context.translations.todayLabel;
|
||||||
} else if (Jiffy(createdAt)
|
} else if (createdAt.isSame(now.subtract(days: 1), Units.DAY)) {
|
||||||
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
|
|
||||||
dayInfo = context.translations.yesterdayLabel;
|
dayInfo = context.translations.yesterdayLabel;
|
||||||
} else if (Jiffy(createdAt).isAfter(
|
} else if (createdAt.isAfter(now.subtract(days: 7), Units.DAY)) {
|
||||||
now.subtract(const Duration(days: 7)),
|
|
||||||
Units.DAY,
|
|
||||||
)) {
|
|
||||||
dayInfo = createdAt.EEEE;
|
dayInfo = createdAt.EEEE;
|
||||||
} else if (Jiffy(createdAt).isAfter(
|
} else if (createdAt.isAfter(now.subtract(years: 1), Units.DAY)) {
|
||||||
Jiffy(now).subtract(years: 1),
|
|
||||||
Units.DAY,
|
|
||||||
)) {
|
|
||||||
dayInfo = createdAt.MMMd;
|
|
||||||
} else {
|
|
||||||
dayInfo = createdAt.MMMd;
|
dayInfo = createdAt.MMMd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ class EmojiOverlay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _streamChatTheme.colorTheme.textHighEmphasis
|
color: _streamChatTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ extension FlipBorder on BorderRadius {
|
|||||||
topLeft: topRight,
|
topLeft: topRight,
|
||||||
topRight: topLeft,
|
topRight: topLeft,
|
||||||
bottomLeft: bottomRight,
|
bottomLeft: bottomRight,
|
||||||
bottomRight: bottomLeft)
|
bottomRight: bottomLeft,
|
||||||
|
)
|
||||||
: this;
|
: this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
await Future.wait(videoPackages.values.map(
|
await Future.wait(videoPackages.values.map(
|
||||||
(it) => it.initialize(),
|
(it) => it.initialize(),
|
||||||
));
|
));
|
||||||
setState(() {});
|
setState(() {}); // ignore: no-empty-block
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -96,83 +96,83 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _controller,
|
animation: _controller,
|
||||||
builder: (context, snapshot) => PageView.builder(
|
builder: (context, snapshot) => PageView.builder(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
onPageChanged: (val) {
|
onPageChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
_currentPage = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final attachment = widget.mediaAttachments[index];
|
||||||
|
if (attachment.type == 'image' ||
|
||||||
|
attachment.type == 'giphy') {
|
||||||
|
final imageUrl = attachment.imageUrl ??
|
||||||
|
attachment.assetUrl ??
|
||||||
|
attachment.thumbUrl;
|
||||||
|
return PhotoView(
|
||||||
|
loadingBuilder: (context, image) => const Offstage(),
|
||||||
|
imageProvider: (imageUrl == null &&
|
||||||
|
attachment.localUri != null &&
|
||||||
|
attachment.file?.bytes != null)
|
||||||
|
? Image.memory(attachment.file!.bytes!).image
|
||||||
|
: CachedNetworkImageProvider(imageUrl!),
|
||||||
|
maxScale: PhotoViewComputedScale.covered,
|
||||||
|
minScale: PhotoViewComputedScale.contained,
|
||||||
|
heroAttributes: PhotoViewHeroAttributes(
|
||||||
|
tag: widget.mediaAttachments,
|
||||||
|
),
|
||||||
|
backgroundDecoration: BoxDecoration(
|
||||||
|
color: ColorTween(
|
||||||
|
begin: ChannelHeaderTheme.of(context).color,
|
||||||
|
end: Colors.black,
|
||||||
|
).lerp(_controller.value),
|
||||||
|
),
|
||||||
|
onTapUp: (a, b, c) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentPage = val;
|
_optionsShown = !_optionsShown;
|
||||||
});
|
});
|
||||||
},
|
if (_controller.isCompleted) {
|
||||||
itemBuilder: (context, index) {
|
_controller.reverse();
|
||||||
final attachment = widget.mediaAttachments[index];
|
} else {
|
||||||
if (attachment.type == 'image' ||
|
_controller.forward();
|
||||||
attachment.type == 'giphy') {
|
|
||||||
final imageUrl = attachment.imageUrl ??
|
|
||||||
attachment.assetUrl ??
|
|
||||||
attachment.thumbUrl;
|
|
||||||
return PhotoView(
|
|
||||||
loadingBuilder: (context, image) =>
|
|
||||||
const Offstage(),
|
|
||||||
imageProvider: (imageUrl == null &&
|
|
||||||
attachment.localUri != null &&
|
|
||||||
attachment.file?.bytes != null)
|
|
||||||
? Image.memory(attachment.file!.bytes!).image
|
|
||||||
: CachedNetworkImageProvider(imageUrl!),
|
|
||||||
maxScale: PhotoViewComputedScale.covered,
|
|
||||||
minScale: PhotoViewComputedScale.contained,
|
|
||||||
heroAttributes: PhotoViewHeroAttributes(
|
|
||||||
tag: widget.mediaAttachments,
|
|
||||||
),
|
|
||||||
backgroundDecoration: BoxDecoration(
|
|
||||||
color: ColorTween(
|
|
||||||
begin: ChannelHeaderTheme.of(context).color,
|
|
||||||
end: Colors.black,
|
|
||||||
).lerp(_controller.value),
|
|
||||||
),
|
|
||||||
onTapUp: (a, b, c) {
|
|
||||||
setState(() {
|
|
||||||
_optionsShown = !_optionsShown;
|
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
|
||||||
_controller.forward();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (attachment.type == 'video') {
|
|
||||||
final controller = videoPackages[attachment.id]!;
|
|
||||||
if (!controller.initialized) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_optionsShown = !_optionsShown;
|
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
|
||||||
_controller.forward();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 50,
|
|
||||||
),
|
|
||||||
child: Chewie(
|
|
||||||
controller: controller.chewieController!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return Container();
|
|
||||||
},
|
},
|
||||||
itemCount: widget.mediaAttachments.length,
|
);
|
||||||
)),
|
} else if (attachment.type == 'video') {
|
||||||
|
final controller = videoPackages[attachment.id]!;
|
||||||
|
if (!controller.initialized) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_optionsShown = !_optionsShown;
|
||||||
|
});
|
||||||
|
if (_controller.isCompleted) {
|
||||||
|
_controller.reverse();
|
||||||
|
} else {
|
||||||
|
_controller.forward();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 50,
|
||||||
|
),
|
||||||
|
child: Chewie(
|
||||||
|
controller: controller.chewieController!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
itemCount: widget.mediaAttachments.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
opacity: _optionsShown ? 1.0 : 0.0,
|
opacity: _optionsShown ? 1.0 : 0.0,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
@@ -67,19 +66,6 @@ class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GalleryFooterState extends State<GalleryFooter> {
|
class _GalleryFooterState extends State<GalleryFooter> {
|
||||||
final TextEditingController _messageController = TextEditingController();
|
|
||||||
final FocusNode _messageFocusNode = FocusNode();
|
|
||||||
|
|
||||||
final List<Channel> _selectedChannels = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_messageFocusNode.addListener(() {
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const showShareButton = !kIsWeb;
|
const showShareButton = !kIsWeb;
|
||||||
@@ -143,8 +129,9 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
context.translations.galleryPaginationText(
|
context.translations.galleryPaginationText(
|
||||||
currentPage: widget.currentPage,
|
currentPage: widget.currentPage,
|
||||||
totalPages: widget.totalPages),
|
totalPages: widget.totalPages,
|
||||||
|
),
|
||||||
style: galleryFooterThemeData.titleTextStyle,
|
style: galleryFooterThemeData.titleTextStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -267,6 +254,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
@@ -280,7 +268,6 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
user: widget.message.user!,
|
user: widget.message.user!,
|
||||||
constraints:
|
constraints:
|
||||||
@@ -301,25 +288,4 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends the current message
|
|
||||||
Future sendMessage() async {
|
|
||||||
final text = _messageController.text.trim();
|
|
||||||
|
|
||||||
final attachments = widget.message.attachments;
|
|
||||||
|
|
||||||
_messageController.clear();
|
|
||||||
|
|
||||||
for (final channel in _selectedChannels) {
|
|
||||||
final message = Message(
|
|
||||||
text: text,
|
|
||||||
attachments: [attachments[widget.currentPage]],
|
|
||||||
);
|
|
||||||
|
|
||||||
await channel.sendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedChannels.clear();
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ class DemoPainter extends CustomPainter {
|
|||||||
final p4 = pointsList.indexOf(off4);
|
final p4 = pointsList.indexOf(off4);
|
||||||
|
|
||||||
squares.add(
|
squares.add(
|
||||||
Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient));
|
Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,17 +124,18 @@ class DemoPainter extends CustomPainter {
|
|||||||
final fontSize = username.length == 2 ? textSize : textSize * 1.5;
|
final fontSize = username.length == 2 ? textSize : textSize * 1.5;
|
||||||
|
|
||||||
TextPainter(
|
TextPainter(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: username,
|
text: username,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: fontFamily,
|
fontFamily: fontFamily,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.white.withOpacity(0.7),
|
color: Colors.white.withOpacity(0.7),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
textDirection: TextDirection.ltr)
|
textAlign: TextAlign.center,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)
|
||||||
..layout(maxWidth: size.width)
|
..layout(maxWidth: size.width)
|
||||||
..paint(
|
..paint(
|
||||||
canvas,
|
canvas,
|
||||||
@@ -168,8 +170,8 @@ class DemoPainter extends CustomPainter {
|
|||||||
final sign1 = rand.nextInt(2) == 1 ? 1 : -1;
|
final sign1 = rand.nextInt(2) == 1 ? 1 : -1;
|
||||||
final sign2 = rand.nextInt(2) == 1 ? 1 : -1;
|
final sign2 = rand.nextInt(2) == 1 ? 1 : -1;
|
||||||
|
|
||||||
final dx = 0.6 * sign1 * rand.nextInt(size.width ~/ columnCount);
|
final dx = sign1 * 0.6 * rand.nextInt(size.width ~/ columnCount);
|
||||||
final dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount);
|
final dy = sign2 * 0.6 * rand.nextInt(size.height ~/ rowCount);
|
||||||
|
|
||||||
transformedList.add(Offset(orgDx + dx, orgDy + dy));
|
transformedList.add(Offset(orgDx + dx, orgDy + dy));
|
||||||
}
|
}
|
||||||
@@ -223,8 +225,12 @@ class Offset4 {
|
|||||||
/// Draw the polygon on canvas
|
/// Draw the polygon on canvas
|
||||||
void draw(Canvas canvas, List<Offset> points) {
|
void draw(Canvas canvas, List<Offset> points) {
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..color = Color.fromARGB(255, Random().nextInt(255),
|
..color = Color.fromARGB(
|
||||||
Random().nextInt(255), Random().nextInt(255))
|
255,
|
||||||
|
Random().nextInt(255),
|
||||||
|
Random().nextInt(255),
|
||||||
|
Random().nextInt(255),
|
||||||
|
)
|
||||||
..shader = ui.Gradient.linear(
|
..shader = ui.Gradient.linear(
|
||||||
points[p1],
|
points[p1],
|
||||||
points[p3],
|
points[p3],
|
||||||
|
|||||||
@@ -300,8 +300,10 @@ abstract class Translations {
|
|||||||
String get youText;
|
String get youText;
|
||||||
|
|
||||||
/// Gallery footer pagination text
|
/// Gallery footer pagination text
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages});
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
});
|
||||||
|
|
||||||
/// The text shown for "File"
|
/// The text shown for "File"
|
||||||
String get fileText;
|
String get fileText;
|
||||||
@@ -665,8 +667,10 @@ class DefaultTranslations implements Translations {
|
|||||||
String get youText => 'You';
|
String get youText => 'You';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} of $totalPages';
|
'${currentPage + 1} of $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -144,9 +144,9 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
_getMedia();
|
_getMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getMedia() async {
|
Future<void> _getMedia() async {
|
||||||
final assetList = await PhotoManager.getAssetPathList().then((value) {
|
final assetList = await PhotoManager.getAssetPathList().then((value) {
|
||||||
if (value.isNotEmpty == true) {
|
if (value.isNotEmpty) {
|
||||||
return value.singleWhere((element) => element.isAll);
|
return value.singleWhere((element) => element.isAll);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -178,7 +178,9 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ImageStreamCompleter load(
|
ImageStreamCompleter load(
|
||||||
MediaThumbnailProvider key, DecoderCallback decode) =>
|
MediaThumbnailProvider key,
|
||||||
|
DecoderCallback decode,
|
||||||
|
) =>
|
||||||
MultiFrameImageStreamCompleter(
|
MultiFrameImageStreamCompleter(
|
||||||
codec: _loadAsync(key, decode),
|
codec: _loadAsync(key, decode),
|
||||||
scale: 1,
|
scale: 1,
|
||||||
@@ -188,7 +190,9 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Future<ui.Codec> _loadAsync(
|
Future<ui.Codec> _loadAsync(
|
||||||
MediaThumbnailProvider key, DecoderCallback decode) async {
|
MediaThumbnailProvider key,
|
||||||
|
DecoderCallback decode,
|
||||||
|
) async {
|
||||||
assert(key == this, 'Checks MediaThumbnailProvider');
|
assert(key == this, 'Checks MediaThumbnailProvider');
|
||||||
final bytes = await media.thumbData;
|
final bytes = await media.thumbData;
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
final size = mediaQueryData.size;
|
final size = mediaQueryData.size;
|
||||||
final user = StreamChat.of(context).currentUser;
|
final user = StreamChat.of(context).currentUser;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = size.width * 2 / 3;
|
||||||
var messageTextLength = widget.message.text!.length;
|
var messageTextLength = widget.message.text!.length;
|
||||||
if (widget.message.quotedMessage != null) {
|
if (widget.message.quotedMessage != null) {
|
||||||
var quotedMessageLength =
|
var quotedMessageLength =
|
||||||
@@ -119,7 +119,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
final roughSentenceSize = messageTextLength *
|
final roughSentenceSize = messageTextLength *
|
||||||
(widget.messageTheme.messageTextStyle?.fontSize ?? 1) *
|
(widget.messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||||
1.2;
|
1.2;
|
||||||
final divFactor = widget.message.attachments.isNotEmpty == true
|
final divFactor = widget.message.attachments.isNotEmpty
|
||||||
? 1
|
? 1
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||||
|
|
||||||
@@ -142,14 +142,15 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
(widget.message.status == MessageSendingStatus.sent))
|
(widget.message.status == MessageSendingStatus.sent))
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment(
|
alignment: Alignment(
|
||||||
user?.id == widget.message.user?.id
|
user?.id == widget.message.user?.id
|
||||||
? (divFactor >= 1.0
|
? (divFactor >= 1.0
|
||||||
? -0.2 - shiftFactor
|
? -0.2 - shiftFactor
|
||||||
: (1.2 - divFactor))
|
: (1.2 - divFactor))
|
||||||
: (divFactor >= 1.0
|
: (divFactor >= 1.0
|
||||||
? 0.2 + shiftFactor
|
? shiftFactor + 0.2
|
||||||
: -(1.2 - divFactor)),
|
: -(1.2 - divFactor)),
|
||||||
0),
|
0,
|
||||||
|
),
|
||||||
child: ReactionPicker(
|
child: ReactionPicker(
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
),
|
),
|
||||||
@@ -194,7 +195,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
.map((action) => _buildCustomAction(
|
.map((action) => _buildCustomAction(
|
||||||
context,
|
context,
|
||||||
action,
|
action,
|
||||||
))
|
)),
|
||||||
].insertBetween(
|
].insertBetween(
|
||||||
Container(
|
Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import 'package:stream_chat_flutter/src/emoji_overlay.dart';
|
|||||||
import 'package:stream_chat_flutter/src/extension.dart';
|
import 'package:stream_chat_flutter/src/extension.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/overlays.dart';
|
import 'package:stream_chat_flutter/src/multi_overlay.dart';
|
||||||
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
|
import 'package:stream_chat_flutter/src/quoted_message_widget.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';
|
||||||
@@ -373,11 +373,13 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
_parseExistingMessage(widget.editMessage ?? widget.initialMessage!);
|
_parseExistingMessage(widget.editMessage ?? widget.initialMessage!);
|
||||||
}
|
}
|
||||||
textEditingController.addListener(_onChangedDebounced);
|
textEditingController.addListener(_onChangedDebounced);
|
||||||
_focusNode.addListener(() {
|
_focusNode.addListener(_focusNodeListener);
|
||||||
if (_focusNode.hasFocus) {
|
}
|
||||||
_openFilePickerSection = false;
|
|
||||||
}
|
void _focusNodeListener() {
|
||||||
});
|
if (_focusNode.hasFocus) {
|
||||||
|
_openFilePickerSection = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int _timeOut = 0;
|
int _timeOut = 0;
|
||||||
@@ -536,7 +538,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
? null
|
? null
|
||||||
: Border.all(
|
: Border.all(
|
||||||
color: _streamChatTheme.colorTheme.textHighEmphasis
|
color: _streamChatTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(0.5),
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
@@ -640,7 +642,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
secondChild: widget.disableAttachments &&
|
secondChild: widget.disableAttachments &&
|
||||||
!widget.showCommandsButton &&
|
!widget.showCommandsButton &&
|
||||||
widget.actions.isNotEmpty != true
|
!widget.actions.isNotEmpty
|
||||||
? const Offstage()
|
? const Offstage()
|
||||||
: Wrap(
|
: Wrap(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -706,7 +708,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
decoration: _getInputDecoration(context),
|
decoration: _getInputDecoration(context),
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -827,6 +829,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
if (value.isNotEmpty) {
|
if (value.isNotEmpty) {
|
||||||
|
// ignore: no-empty-block
|
||||||
channel.keyStroke(widget.parentMessage?.id).catchError((e) {});
|
channel.keyStroke(widget.parentMessage?.id).catchError((e) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,8 +1031,10 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
_attachments.isNotEmpty)
|
_attachments.isNotEmpty)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
pickFile(DefaultAttachmentTypes.image,
|
pickFile(
|
||||||
camera: true);
|
DefaultAttachmentTypes.image,
|
||||||
|
camera: true,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -1042,8 +1047,10 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
_attachments.isNotEmpty)
|
_attachments.isNotEmpty)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
pickFile(DefaultAttachmentTypes.video,
|
pickFile(
|
||||||
camera: true);
|
DefaultAttachmentTypes.video,
|
||||||
|
camera: true,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1195,8 +1202,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
textEditingController.value = TextEditingValue(
|
textEditingController.value = TextEditingValue(
|
||||||
text: rejoin +
|
text: rejoin +
|
||||||
textEditingController.text
|
textEditingController.text.substring(
|
||||||
.substring(textEditingController.selection.start),
|
textEditingController.selection.start,
|
||||||
|
),
|
||||||
selection: TextSelection.collapsed(
|
selection: TextSelection.collapsed(
|
||||||
offset: rejoin.length,
|
offset: rejoin.length,
|
||||||
),
|
),
|
||||||
@@ -1254,8 +1262,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Widget _buildReplyToMessage() {
|
Widget _buildReplyToMessage() {
|
||||||
if (!_hasQuotedMessage) return const Offstage();
|
if (!_hasQuotedMessage) return const Offstage();
|
||||||
final containsUrl = widget.quotedMessage!.attachments
|
final containsUrl = widget.quotedMessage!.attachments
|
||||||
.any((element) => element.titleLink != null) ==
|
.any((element) => element.titleLink != null);
|
||||||
true;
|
|
||||||
return QuotedMessageWidget(
|
return QuotedMessageWidget(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
showBorder: !containsUrl,
|
showBorder: !containsUrl,
|
||||||
@@ -1360,7 +1367,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
setState(() => _attachments.remove(attachment.id));
|
setState(() => _attachments.remove(attachment.id));
|
||||||
},
|
},
|
||||||
fillColor:
|
fillColor:
|
||||||
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.5),
|
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: StreamSvgIcon.close(
|
child: StreamSvgIcon.close(
|
||||||
size: 24,
|
size: 24,
|
||||||
@@ -1839,10 +1846,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
backgroundColor: _streamChatTheme.colorTheme.barsBg,
|
backgroundColor: _streamChatTheme.colorTheme.barsBg,
|
||||||
context: context,
|
context: context,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
builder: (context) => Column(
|
builder: (context) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -1875,7 +1883,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color:
|
color:
|
||||||
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(.08),
|
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.08),
|
||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@@ -1908,6 +1916,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
textEditingController.dispose();
|
textEditingController.dispose();
|
||||||
|
_focusNode.removeListener(_focusNodeListener);
|
||||||
_stopSlowMode();
|
_stopSlowMode();
|
||||||
_onChangedDebounced.cancel();
|
_onChangedDebounced.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -2018,7 +2027,8 @@ class _PickerWidgetState extends State<_PickerWidget> {
|
|||||||
Text(
|
Text(
|
||||||
context.translations.enablePhotoAndVideoAccessMessage,
|
context.translations.enablePhotoAndVideoAccessMessage,
|
||||||
style: widget.streamChatTheme.textTheme.body.copyWith(
|
style: widget.streamChatTheme.textTheme.body.copyWith(
|
||||||
color: widget.streamChatTheme.colorTheme.textLowEmphasis),
|
color: widget.streamChatTheme.colorTheme.textLowEmphasis,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ typedef OnMessageTap = void Function(Message);
|
|||||||
typedef ReplyTapCallback = void Function(Message);
|
typedef ReplyTapCallback = void Function(Message);
|
||||||
|
|
||||||
/// Class for message details
|
/// Class for message details
|
||||||
|
// ignore: prefer-match-file-name
|
||||||
class MessageDetails {
|
class MessageDetails {
|
||||||
/// Constructor for creating [MessageDetails]
|
/// Constructor for creating [MessageDetails]
|
||||||
MessageDetails(
|
MessageDetails(
|
||||||
@@ -358,8 +359,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
context.translations.emptyChatMessagesText,
|
context.translations.emptyChatMessagesText,
|
||||||
style: _streamTheme.textTheme.footnote.copyWith(
|
style: _streamTheme.textTheme.footnote.copyWith(
|
||||||
color: _streamTheme.colorTheme.textHighEmphasis
|
color: _streamTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
messageListBuilder: widget.messageListBuilder ??
|
messageListBuilder: widget.messageListBuilder ??
|
||||||
@@ -371,8 +373,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
context.translations.genericErrorText,
|
context.translations.genericErrorText,
|
||||||
style: _streamTheme.textTheme.footnote.copyWith(
|
style: _streamTheme.textTheme.footnote.copyWith(
|
||||||
color: _streamTheme.colorTheme.textHighEmphasis
|
color: _streamTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -383,7 +386,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
|
|
||||||
if (_messageListLength != null) {
|
if (_messageListLength != null) {
|
||||||
if (_bottomPaginationActive || (_inBetweenList && _upToDate)) {
|
if (_bottomPaginationActive || (_inBetweenList && _upToDate)) {
|
||||||
if (_itemPositionListener.itemPositions.value.isNotEmpty == true) {
|
if (_itemPositionListener.itemPositions.value.isNotEmpty) {
|
||||||
final first = _itemPositionListener.itemPositions.value.first;
|
final first = _itemPositionListener.itemPositions.value.first;
|
||||||
final diff = newMessagesListLength - _messageListLength!;
|
final diff = newMessagesListLength - _messageListLength!;
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
@@ -674,7 +677,8 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
||||||
initialData: _itemPositionListener.itemPositions.value,
|
initialData: _itemPositionListener.itemPositions.value,
|
||||||
stream: _valueListenableToStreamAdapter(
|
stream: _valueListenableToStreamAdapter(
|
||||||
_itemPositionListener.itemPositions),
|
_itemPositionListener.itemPositions,
|
||||||
|
),
|
||||||
comparator: (a, b) {
|
comparator: (a, b) {
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -984,7 +988,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
|
|
||||||
final allRead = readList.length >= (channel.memberCount ?? 0) - 1;
|
final allRead = readList.length >= (channel.memberCount ?? 0) - 1;
|
||||||
final hasFileAttachment =
|
final hasFileAttachment =
|
||||||
message.attachments.any((it) => it.type == 'file') == true;
|
message.attachments.any((it) => it.type == 'file');
|
||||||
|
|
||||||
final isThreadMessage =
|
final isThreadMessage =
|
||||||
message.parentId != null && message.showInChannel == true;
|
message.parentId != null && message.showInChannel == true;
|
||||||
@@ -1016,7 +1020,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
|
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
|
||||||
|
|
||||||
final hasUrlAttachment =
|
final hasUrlAttachment =
|
||||||
message.attachments.any((it) => it.titleLink != null) == true;
|
message.attachments.any((it) => it.titleLink != null);
|
||||||
|
|
||||||
final borderSide =
|
final borderSide =
|
||||||
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
|
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
|
||||||
@@ -1259,10 +1263,11 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
if (widget.onThreadTap != null) {
|
if (widget.onThreadTap != null) {
|
||||||
_onThreadTap = (Message message) {
|
_onThreadTap = (Message message) {
|
||||||
widget.onThreadTap!(
|
widget.onThreadTap!(
|
||||||
message,
|
message,
|
||||||
widget.threadBuilder != null
|
widget.threadBuilder != null
|
||||||
? widget.threadBuilder!(context, message)
|
? widget.threadBuilder!(context, message)
|
||||||
: null);
|
: null,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
} else if (widget.threadBuilder != null) {
|
} else if (widget.threadBuilder != null) {
|
||||||
_onThreadTap = (Message message) {
|
_onThreadTap = (Message message) {
|
||||||
@@ -1271,7 +1276,8 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => BetterStreamBuilder<Message>(
|
builder: (_) => BetterStreamBuilder<Message>(
|
||||||
stream: streamChannel!.channel.state!.messagesStream.map(
|
stream: streamChannel!.channel.state!.messagesStream.map(
|
||||||
(messages) => messages.firstWhere((m) => m.id == message.id)),
|
(messages) => messages.firstWhere((m) => m.id == message.id),
|
||||||
|
),
|
||||||
initialData: message,
|
initialData: message,
|
||||||
builder: (_, data) => StreamChannel(
|
builder: (_, data) => StreamChannel(
|
||||||
channel: streamChannel!.channel,
|
channel: streamChannel!.channel,
|
||||||
@@ -1320,7 +1326,7 @@ class _LoadingIndicator extends StatelessWidget {
|
|||||||
stream: stream,
|
stream: stream,
|
||||||
initialData: false,
|
initialData: false,
|
||||||
errorBuilder: (context, error) => Container(
|
errorBuilder: (context, error) => Container(
|
||||||
color: streamTheme.colorTheme.accentError.withOpacity(.2),
|
color: streamTheme.colorTheme.accentError.withOpacity(0.2),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(context.translations.loadingMessagesError),
|
child: Text(context.translations.loadingMessagesError),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
final user = StreamChat.of(context).currentUser;
|
final user = StreamChat.of(context).currentUser;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = size.width * 2 / 3;
|
||||||
var messageTextLength = message.text!.length;
|
var messageTextLength = message.text!.length;
|
||||||
if (message.quotedMessage != null) {
|
if (message.quotedMessage != null) {
|
||||||
var quotedMessageLength = message.quotedMessage!.text!.length + 40;
|
var quotedMessageLength = message.quotedMessage!.text!.length + 40;
|
||||||
if (message.quotedMessage!.attachments.isNotEmpty == true) {
|
if (message.quotedMessage!.attachments.isNotEmpty) {
|
||||||
quotedMessageLength += 40;
|
quotedMessageLength += 40;
|
||||||
}
|
}
|
||||||
if (quotedMessageLength > messageTextLength) {
|
if (quotedMessageLength > messageTextLength) {
|
||||||
@@ -60,7 +60,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
final roughSentenceSize = messageTextLength *
|
final roughSentenceSize = messageTextLength *
|
||||||
(messageTheme.messageTextStyle?.fontSize ?? 1) *
|
(messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||||
1.2;
|
1.2;
|
||||||
final divFactor = message.attachments.isNotEmpty == true
|
final divFactor = message.attachments.isNotEmpty
|
||||||
? 1
|
? 1
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||||
|
|
||||||
@@ -80,14 +80,15 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
(message.status == MessageSendingStatus.sent))
|
(message.status == MessageSendingStatus.sent))
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment(
|
alignment: Alignment(
|
||||||
user!.id == message.user!.id
|
user!.id == message.user!.id
|
||||||
? (divFactor >= 1.0
|
? (divFactor >= 1.0
|
||||||
? -0.2 - shiftFactor
|
? -0.2 - shiftFactor
|
||||||
: (1.2 - divFactor))
|
: (1.2 - divFactor))
|
||||||
: (divFactor >= 1.0
|
: (divFactor >= 1.0
|
||||||
? 0.2 + shiftFactor
|
? shiftFactor + 0.2
|
||||||
: -(1.2 - divFactor)),
|
: -(1.2 - divFactor)),
|
||||||
0),
|
0,
|
||||||
|
),
|
||||||
child: ReactionPicker(
|
child: ReactionPicker(
|
||||||
message: message,
|
message: message,
|
||||||
),
|
),
|
||||||
@@ -102,7 +103,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
user,
|
user,
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -146,11 +146,12 @@ class MessageSearchItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextSpan _getDisplayText(
|
TextSpan _getDisplayText(
|
||||||
String text,
|
String text,
|
||||||
List<User> mentions,
|
List<User> mentions,
|
||||||
List<Attachment> attachments,
|
List<Attachment> attachments,
|
||||||
TextStyle? normalTextStyle,
|
TextStyle? normalTextStyle,
|
||||||
TextStyle? mentionsTextStyle) {
|
TextStyle? mentionsTextStyle,
|
||||||
|
) {
|
||||||
final textList = text.split(' ');
|
final textList = text.split(' ');
|
||||||
final resList = <TextSpan>[];
|
final resList = <TextSpan>[];
|
||||||
for (final e in textList) {
|
for (final e in textList) {
|
||||||
|
|||||||
@@ -217,7 +217,9 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget _listItemBuilder(
|
Widget _listItemBuilder(
|
||||||
BuildContext context, GetMessageResponse getMessageResponse) {
|
BuildContext context,
|
||||||
|
GetMessageResponse getMessageResponse,
|
||||||
|
) {
|
||||||
if (widget.itemBuilder != null) {
|
if (widget.itemBuilder != null) {
|
||||||
return widget.itemBuilder!(context, getMessageResponse);
|
return widget.itemBuilder!(context, getMessageResponse);
|
||||||
}
|
}
|
||||||
@@ -231,33 +233,34 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
return StreamBuilder<bool>(
|
return StreamBuilder<bool>(
|
||||||
stream: messageSearchBloc.queryMessagesLoading,
|
stream: messageSearchBloc.queryMessagesLoading,
|
||||||
initialData: false,
|
initialData: false,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.accentError
|
|
||||||
.withOpacity(.2),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Center(
|
|
||||||
child: Text(context.translations.loadingMessagesError),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 100,
|
color: StreamChatTheme.of(context)
|
||||||
padding: const EdgeInsets.all(32),
|
.colorTheme
|
||||||
child: Center(
|
.accentError
|
||||||
child: snapshot.data!
|
.withOpacity(0.2),
|
||||||
? const CircularProgressIndicator()
|
child: Padding(
|
||||||
: Container(),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Center(
|
||||||
|
child: Text(context.translations.loadingMessagesError),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
return Container(
|
||||||
|
height: 100,
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Center(
|
||||||
|
child: snapshot.data!
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: Container(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView(List<GetMessageResponse> data) {
|
Widget _buildListView(List<GetMessageResponse> data) {
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ class MessageText extends StatelessWidget {
|
|||||||
for (final user in message.mentionedUsers.toSet()) {
|
for (final user in message.mentionedUsers.toSet()) {
|
||||||
final userName = user.name;
|
final userName = user.name;
|
||||||
messageTextToRender = messageTextToRender.replaceAll(
|
messageTextToRender = messageTextToRender.replaceAll(
|
||||||
'@$userName', '[@$userName](@${userName.replaceAll(' ', '')})');
|
'@$userName',
|
||||||
|
'[@$userName](@${userName.replaceAll(' ', '')})',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return messageTextToRender;
|
return messageTextToRender;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -575,8 +575,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed;
|
bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed;
|
||||||
|
|
||||||
bool get isGiphy =>
|
bool get isGiphy =>
|
||||||
widget.message.attachments.any((element) => element.type == 'giphy') ==
|
widget.message.attachments.any((element) => element.type == 'giphy');
|
||||||
true;
|
|
||||||
|
|
||||||
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
|
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
|
||||||
|
|
||||||
@@ -596,7 +595,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
isDeleted;
|
isDeleted;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => widget.message.attachments.isNotEmpty == true;
|
bool get wantKeepAlive => widget.message.attachments.isNotEmpty;
|
||||||
|
|
||||||
late StreamChatThemeData _streamChatTheme;
|
late StreamChatThemeData _streamChatTheme;
|
||||||
late StreamChatState _streamChat;
|
late StreamChatState _streamChat;
|
||||||
@@ -674,7 +673,10 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
child: PortalEntry(
|
child: PortalEntry(
|
||||||
portal: Container(
|
portal: Container(
|
||||||
transform: Matrix4.translationValues(
|
transform: Matrix4.translationValues(
|
||||||
widget.reverse ? 12 : -12, 0, 0),
|
widget.reverse ? 12 : -12,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
),
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxWidth: 22 * 6.0,
|
maxWidth: 22 * 6.0,
|
||||||
),
|
),
|
||||||
@@ -704,13 +706,15 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
? Container(
|
? Container(
|
||||||
// ignore: lines_longer_than_80_chars
|
// ignore: lines_longer_than_80_chars
|
||||||
margin: EdgeInsets.symmetric(
|
margin: EdgeInsets.symmetric(
|
||||||
horizontal:
|
horizontal:
|
||||||
// ignore: lines_longer_than_80_chars
|
// ignore: lines_longer_than_80_chars
|
||||||
widget.showUserAvatar ==
|
widget.showUserAvatar ==
|
||||||
// ignore: lines_longer_than_80_chars
|
// ignore: lines_longer_than_80_chars
|
||||||
DisplayWidget.gone
|
DisplayWidget
|
||||||
? 0
|
.gone
|
||||||
: 4.0),
|
? 0
|
||||||
|
: 4.0,
|
||||||
|
),
|
||||||
child: DeletedMessage(
|
child: DeletedMessage(
|
||||||
borderRadiusGeometry: widget
|
borderRadiusGeometry: widget
|
||||||
.borderRadiusGeometry,
|
.borderRadiusGeometry,
|
||||||
@@ -794,7 +798,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
widget.message.user != null) ...[
|
widget.message.user != null) ...[
|
||||||
_buildUserAvatar(),
|
_buildUserAvatar(),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (showBottomRow)
|
if (showBottomRow)
|
||||||
@@ -856,7 +860,11 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
: chatThemeData.ownMessageTheme,
|
: chatThemeData.ownMessageTheme,
|
||||||
reverse: widget.reverse,
|
reverse: widget.reverse,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0),
|
right: 8,
|
||||||
|
left: 8,
|
||||||
|
top: 8,
|
||||||
|
bottom: hasNonUrlAttachments ? 8 : 0,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,64 +1062,64 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
barrierColor: _streamChatTheme.colorTheme.overlay,
|
barrierColor: _streamChatTheme.colorTheme.overlay,
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageActionsModal(
|
child: MessageActionsModal(
|
||||||
messageWidget: widget.copyWith(
|
messageWidget: widget.copyWith(
|
||||||
key: const Key('MessageWidget'),
|
key: const Key('MessageWidget'),
|
||||||
message: widget.message.copyWith(
|
message: widget.message.copyWith(
|
||||||
text: (widget.message.text?.length ?? 0) > 200
|
text: (widget.message.text?.length ?? 0) > 200
|
||||||
? '${widget.message.text!.substring(0, 200)}...'
|
? '${widget.message.text!.substring(0, 200)}...'
|
||||||
: widget.message.text,
|
: widget.message.text,
|
||||||
),
|
),
|
||||||
showReactions: false,
|
showReactions: false,
|
||||||
showUsername: false,
|
showUsername: false,
|
||||||
showTimestamp: false,
|
showTimestamp: false,
|
||||||
translateUserAvatar: false,
|
translateUserAvatar: false,
|
||||||
showSendingIndicator: false,
|
showSendingIndicator: false,
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
showReactionPickerIndicator: widget.showReactions &&
|
showReactionPickerIndicator: widget.showReactions &&
|
||||||
(widget.message.status == MessageSendingStatus.sent),
|
(widget.message.status == MessageSendingStatus.sent),
|
||||||
showPinHighlight: false,
|
showPinHighlight: false,
|
||||||
showUserAvatar: widget.message.user!.id ==
|
showUserAvatar:
|
||||||
channel.client.state.currentUser!.id
|
widget.message.user!.id == channel.client.state.currentUser!.id
|
||||||
? DisplayWidget.gone
|
? DisplayWidget.gone
|
||||||
: DisplayWidget.show,
|
: DisplayWidget.show,
|
||||||
),
|
),
|
||||||
onCopyTap: (message) =>
|
onCopyTap: (message) =>
|
||||||
Clipboard.setData(ClipboardData(text: message.text)),
|
Clipboard.setData(ClipboardData(text: message.text)),
|
||||||
messageTheme: widget.messageTheme,
|
messageTheme: widget.messageTheme,
|
||||||
reverse: widget.reverse,
|
reverse: widget.reverse,
|
||||||
showDeleteMessage: widget.showDeleteMessage || isDeleteFailed,
|
showDeleteMessage: widget.showDeleteMessage || isDeleteFailed,
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||||
onReplyTap: widget.onReplyTap,
|
onReplyTap: widget.onReplyTap,
|
||||||
onThreadReplyTap: widget.onThreadTap,
|
onThreadReplyTap: widget.onThreadTap,
|
||||||
showResendMessage: widget.showResendMessage &&
|
showResendMessage:
|
||||||
(isSendFailed || isUpdateFailed),
|
widget.showResendMessage && (isSendFailed || isUpdateFailed),
|
||||||
showCopyMessage: widget.showCopyMessage &&
|
showCopyMessage: widget.showCopyMessage &&
|
||||||
!isFailedState &&
|
!isFailedState &&
|
||||||
widget.message.text?.trim().isNotEmpty == true,
|
widget.message.text?.trim().isNotEmpty == true,
|
||||||
showEditMessage: widget.showEditMessage &&
|
showEditMessage: widget.showEditMessage &&
|
||||||
!isDeleteFailed &&
|
!isDeleteFailed &&
|
||||||
widget.message.attachments
|
!widget.message.attachments
|
||||||
.any((element) => element.type == 'giphy') !=
|
.any((element) => element.type == 'giphy'),
|
||||||
true,
|
showReactions: widget.showReactions,
|
||||||
showReactions: widget.showReactions,
|
showReplyMessage: widget.showReplyMessage &&
|
||||||
showReplyMessage: widget.showReplyMessage &&
|
!isFailedState &&
|
||||||
!isFailedState &&
|
widget.onReplyTap != null,
|
||||||
widget.onReplyTap != null,
|
showThreadReplyMessage: widget.showThreadReplyMessage &&
|
||||||
showThreadReplyMessage: widget.showThreadReplyMessage &&
|
!isFailedState &&
|
||||||
!isFailedState &&
|
widget.onThreadTap != null,
|
||||||
widget.onThreadTap != null,
|
showFlagButton: widget.showFlagButton,
|
||||||
showFlagButton: widget.showFlagButton,
|
showPinButton: widget.showPinButton,
|
||||||
showPinButton: widget.showPinButton,
|
customActions: widget.customActions,
|
||||||
customActions: widget.customActions,
|
),
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMessageReactionsModalBottomSheet(BuildContext context) {
|
void _showMessageReactionsModalBottomSheet(BuildContext context) {
|
||||||
@@ -1291,8 +1299,9 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
? widget.messageTheme.copyWith(
|
? widget.messageTheme.copyWith(
|
||||||
messageTextStyle:
|
messageTextStyle:
|
||||||
widget.messageTheme.messageTextStyle!.copyWith(
|
widget.messageTheme.messageTextStyle!.copyWith(
|
||||||
fontSize: 42,
|
fontSize: 42,
|
||||||
))
|
),
|
||||||
|
)
|
||||||
: widget.messageTheme,
|
: widget.messageTheme,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1326,7 +1335,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1432,8 +1441,12 @@ class _ThreadReplyPainter extends CustomPainter {
|
|||||||
|
|
||||||
final path = Path()
|
final path = Path()
|
||||||
..moveTo(reverse ? size.width : 0, 0)
|
..moveTo(reverse ? size.width : 0, 0)
|
||||||
..quadraticBezierTo(reverse ? size.width : 0, size.height * 0.38,
|
..quadraticBezierTo(
|
||||||
reverse ? size.width : 0, size.height * 0.50)
|
reverse ? size.width : 0,
|
||||||
|
size.height * 0.38,
|
||||||
|
reverse ? size.width : 0,
|
||||||
|
size.height * 0.5,
|
||||||
|
)
|
||||||
..quadraticBezierTo(
|
..quadraticBezierTo(
|
||||||
reverse ? size.width : 0,
|
reverse ? size.width : 0,
|
||||||
size.height,
|
size.height,
|
||||||
|
|||||||
+17
-17
@@ -2,23 +2,6 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
|
|
||||||
/// Class that contains the parameters for building an overlay entry
|
|
||||||
class OverlayOptions {
|
|
||||||
/// Constructs a new overlay options object
|
|
||||||
/// [visible] - the visibility of the overlay
|
|
||||||
/// [widget] - the widget to be displayed
|
|
||||||
OverlayOptions({
|
|
||||||
required this.visible,
|
|
||||||
required this.widget,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// the visibility of the overlay
|
|
||||||
final bool visible;
|
|
||||||
|
|
||||||
/// the widget to be displayed
|
|
||||||
final Widget widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Widget that renders a single overlay widget from a list of [overlayOptions]
|
/// Widget that renders a single overlay widget from a list of [overlayOptions]
|
||||||
/// It shows the first one that is visible
|
/// It shows the first one that is visible
|
||||||
class MultiOverlay extends StatelessWidget {
|
class MultiOverlay extends StatelessWidget {
|
||||||
@@ -61,3 +44,20 @@ class MultiOverlay extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Class that contains the parameters for building an overlay entry
|
||||||
|
class OverlayOptions {
|
||||||
|
/// Constructs a new overlay options object
|
||||||
|
/// [visible] - the visibility of the overlay
|
||||||
|
/// [widget] - the widget to be displayed
|
||||||
|
OverlayOptions({
|
||||||
|
required this.visible,
|
||||||
|
required this.widget,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// the visibility of the overlay
|
||||||
|
final bool visible;
|
||||||
|
|
||||||
|
/// the widget to be displayed
|
||||||
|
final Widget widget;
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_controller = VideoPlayerController.network(widget.attachment.assetUrl!)
|
_controller = VideoPlayerController.network(widget.attachment.assetUrl!)
|
||||||
..initialize().then((_) {
|
..initialize().then((_) {
|
||||||
|
// ignore: no-empty-block
|
||||||
setState(() {}); //when your thumbnail will show.
|
setState(() {}); //when your thumbnail will show.
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -95,10 +96,10 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
/// Callback for tap on widget
|
/// Callback for tap on widget
|
||||||
final GestureTapCallback? onTap;
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
bool get _hasAttachments => message.attachments.isNotEmpty == true;
|
bool get _hasAttachments => message.attachments.isNotEmpty;
|
||||||
|
|
||||||
bool get _containsLinkAttachment =>
|
bool get _containsLinkAttachment =>
|
||||||
message.attachments.any((element) => element.titleLink != null) == true;
|
message.attachments.any((element) => element.titleLink != null);
|
||||||
|
|
||||||
bool get _containsText => message.text?.isNotEmpty == true;
|
bool get _containsText => message.text?.isNotEmpty == true;
|
||||||
|
|
||||||
@@ -140,12 +141,14 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
messageTheme: isOnlyEmoji && _containsText
|
messageTheme: isOnlyEmoji && _containsText
|
||||||
? messageTheme.copyWith(
|
? messageTheme.copyWith(
|
||||||
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
))
|
),
|
||||||
|
)
|
||||||
: messageTheme.copyWith(
|
: messageTheme.copyWith(
|
||||||
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
].insertBetween(const SizedBox(width: 8));
|
].insertBetween(const SizedBox(width: 8));
|
||||||
@@ -186,7 +189,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
image: CachedNetworkImageProvider(
|
image: CachedNetworkImageProvider(
|
||||||
attachment.imageUrl!,
|
attachment.thumbUrl!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -275,7 +278,8 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
height: 32,
|
height: 32,
|
||||||
width: 32,
|
width: 32,
|
||||||
child: getFileTypeImage(
|
child: getFileTypeImage(
|
||||||
attachment.extraData['mime_type'] as String?),
|
attachment.extraData['mime_type'] as String?,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: (!highlightOwnReactions || reaction.user?.id == userId)
|
color: (!highlightOwnReactions || reaction.user?.id == userId)
|
||||||
? chatThemeData.colorTheme.accentPrimary
|
? chatThemeData.colorTheme.accentPrimary
|
||||||
: chatThemeData.colorTheme.textHighEmphasis.withOpacity(.5),
|
: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,11 @@ class _ReactionPickerState extends State<ReactionPicker>
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: reactionIcons
|
children: reactionIcons
|
||||||
.map<Widget>((reactionIcon) {
|
.map<Widget>((reactionIcon) {
|
||||||
final ownReactionIndex = widget.message.ownReactions
|
final ownReactionIndex =
|
||||||
?.indexWhere(
|
widget.message.ownReactions?.indexWhere(
|
||||||
(reaction) => reaction.type == reactionIcon.type) ??
|
(reaction) => reaction.type == reactionIcon.type,
|
||||||
-1;
|
) ??
|
||||||
|
-1;
|
||||||
final index = reactionIcons.indexOf(reactionIcon);
|
final index = reactionIcons.indexOf(reactionIcon);
|
||||||
|
|
||||||
final child = reactionIcon.builder(
|
final child = reactionIcon.builder(
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ class StreamChat extends StatefulWidget {
|
|||||||
|
|
||||||
if (streamChatState == null) {
|
if (streamChatState == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'You must have a StreamChat widget at the top of your widget tree');
|
'You must have a StreamChat widget at the top of your widget tree',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamChatState;
|
return streamChatState;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class StreamChatThemeData {
|
|||||||
) {
|
) {
|
||||||
final accentColor = colorTheme.accentPrimary;
|
final accentColor = colorTheme.accentPrimary;
|
||||||
final iconTheme =
|
final iconTheme =
|
||||||
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(.5));
|
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5));
|
||||||
final channelHeaderTheme = ChannelHeaderThemeData(
|
final channelHeaderTheme = ChannelHeaderThemeData(
|
||||||
avatarTheme: AvatarThemeData(
|
avatarTheme: AvatarThemeData(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -174,7 +174,7 @@ class StreamChatThemeData {
|
|||||||
color: const Color(0xff7A7A7A),
|
color: const Color(0xff7A7A7A),
|
||||||
),
|
),
|
||||||
lastMessageAtStyle: textTheme.footnote.copyWith(
|
lastMessageAtStyle: textTheme.footnote.copyWith(
|
||||||
color: colorTheme.textHighEmphasis.withOpacity(.5),
|
color: colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
indicatorIconSize: 16,
|
indicatorIconSize: 16,
|
||||||
);
|
);
|
||||||
@@ -280,7 +280,7 @@ class StreamChatThemeData {
|
|||||||
return StreamSvgIcon.loveReaction(
|
return StreamSvgIcon.loveReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(.5),
|
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -292,7 +292,7 @@ class StreamChatThemeData {
|
|||||||
return StreamSvgIcon.thumbsUpReaction(
|
return StreamSvgIcon.thumbsUpReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(.5),
|
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -304,7 +304,7 @@ class StreamChatThemeData {
|
|||||||
return StreamSvgIcon.thumbsDownReaction(
|
return StreamSvgIcon.thumbsDownReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(.5),
|
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -316,7 +316,7 @@ class StreamChatThemeData {
|
|||||||
return StreamSvgIcon.lolReaction(
|
return StreamSvgIcon.lolReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(.5),
|
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -328,7 +328,7 @@ class StreamChatThemeData {
|
|||||||
return StreamSvgIcon.wutReaction(
|
return StreamSvgIcon.wutReaction(
|
||||||
color: highlighted
|
color: highlighted
|
||||||
? theme.colorTheme.accentPrimary
|
? theme.colorTheme.accentPrimary
|
||||||
: theme.primaryIconTheme.color!.withOpacity(.5),
|
: theme.primaryIconTheme.color!.withOpacity(0.5),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// A style that overrides the default appearance of various avatar widgets.
|
/// A style that overrides the default appearance of various avatar widgets.
|
||||||
|
// ignore: prefer-match-file-name
|
||||||
class AvatarThemeData with Diagnosticable {
|
class AvatarThemeData with Diagnosticable {
|
||||||
/// Creates an [AvatarThemeData].
|
/// Creates an [AvatarThemeData].
|
||||||
const AvatarThemeData({
|
const AvatarThemeData({
|
||||||
|
|||||||
@@ -25,13 +25,33 @@ class ColorTheme {
|
|||||||
stops: [0, 1],
|
stops: [0, 1],
|
||||||
),
|
),
|
||||||
this.borderTop = const Effect(
|
this.borderTop = const Effect(
|
||||||
sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0, alpha: 0.08),
|
sigmaX: 0,
|
||||||
|
sigmaY: -1,
|
||||||
|
color: Color(0xff000000),
|
||||||
|
blur: 0,
|
||||||
|
alpha: 0.08,
|
||||||
|
),
|
||||||
this.borderBottom = const Effect(
|
this.borderBottom = const Effect(
|
||||||
sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0, alpha: 0.08),
|
sigmaX: 0,
|
||||||
|
sigmaY: 1,
|
||||||
|
color: Color(0xff000000),
|
||||||
|
blur: 0,
|
||||||
|
alpha: 0.08,
|
||||||
|
),
|
||||||
this.shadowIconButton = const Effect(
|
this.shadowIconButton = const Effect(
|
||||||
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4),
|
sigmaX: 0,
|
||||||
|
sigmaY: 2,
|
||||||
|
color: Color(0xff000000),
|
||||||
|
alpha: 0.5,
|
||||||
|
blur: 4,
|
||||||
|
),
|
||||||
this.modalShadow = const Effect(
|
this.modalShadow = const Effect(
|
||||||
sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8),
|
sigmaX: 0,
|
||||||
|
sigmaY: 0,
|
||||||
|
color: Color(0xff000000),
|
||||||
|
alpha: 1,
|
||||||
|
blur: 8,
|
||||||
|
),
|
||||||
}) : brightness = Brightness.light;
|
}) : brightness = Brightness.light;
|
||||||
|
|
||||||
/// Initialise with dark theme
|
/// Initialise with dark theme
|
||||||
|
|||||||
@@ -151,11 +151,20 @@ class GalleryFooterThemeData with Diagnosticable {
|
|||||||
bottomSheetBarrierColor:
|
bottomSheetBarrierColor:
|
||||||
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
|
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
|
||||||
bottomSheetBackgroundColor: Color.lerp(
|
bottomSheetBackgroundColor: Color.lerp(
|
||||||
a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, t),
|
a.bottomSheetBackgroundColor,
|
||||||
|
b.bottomSheetBackgroundColor,
|
||||||
|
t,
|
||||||
|
),
|
||||||
bottomSheetPhotosTextStyle: TextStyle.lerp(
|
bottomSheetPhotosTextStyle: TextStyle.lerp(
|
||||||
a.bottomSheetPhotosTextStyle, b.bottomSheetPhotosTextStyle, t),
|
a.bottomSheetPhotosTextStyle,
|
||||||
|
b.bottomSheetPhotosTextStyle,
|
||||||
|
t,
|
||||||
|
),
|
||||||
bottomSheetCloseIconColor: Color.lerp(
|
bottomSheetCloseIconColor: Color.lerp(
|
||||||
a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t),
|
a.bottomSheetCloseIconColor,
|
||||||
|
b.bottomSheetCloseIconColor,
|
||||||
|
t,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Merges one [GalleryFooterThemeData] with another.
|
/// Merges one [GalleryFooterThemeData] with another.
|
||||||
@@ -208,10 +217,16 @@ class GalleryFooterThemeData with Diagnosticable {
|
|||||||
..add(ColorProperty('gridIconButtonColor', gridIconButtonColor))
|
..add(ColorProperty('gridIconButtonColor', gridIconButtonColor))
|
||||||
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor))
|
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor))
|
||||||
..add(ColorProperty(
|
..add(ColorProperty(
|
||||||
'bottomSheetBackgroundColor', bottomSheetBackgroundColor))
|
'bottomSheetBackgroundColor',
|
||||||
|
bottomSheetBackgroundColor,
|
||||||
|
))
|
||||||
..add(DiagnosticsProperty(
|
..add(DiagnosticsProperty(
|
||||||
'bottomSheetPhotosTextStyle', bottomSheetPhotosTextStyle))
|
'bottomSheetPhotosTextStyle',
|
||||||
|
bottomSheetPhotosTextStyle,
|
||||||
|
))
|
||||||
..add(ColorProperty(
|
..add(ColorProperty(
|
||||||
'bottomSheetCloseIconColor', bottomSheetCloseIconColor));
|
'bottomSheetCloseIconColor',
|
||||||
|
bottomSheetCloseIconColor,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||||
|
|
||||||
/// Class for getting message theme
|
/// Class for getting message theme
|
||||||
|
// ignore: prefer-match-file-name
|
||||||
class MessageThemeData with Diagnosticable {
|
class MessageThemeData with Diagnosticable {
|
||||||
/// Creates a [MessageThemeData].
|
/// Creates a [MessageThemeData].
|
||||||
const MessageThemeData({
|
const MessageThemeData({
|
||||||
@@ -105,7 +106,10 @@ class MessageThemeData with Diagnosticable {
|
|||||||
messageTextStyle:
|
messageTextStyle:
|
||||||
TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t),
|
TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t),
|
||||||
reactionsBackgroundColor: Color.lerp(
|
reactionsBackgroundColor: Color.lerp(
|
||||||
a.reactionsBackgroundColor, b.reactionsBackgroundColor, t),
|
a.reactionsBackgroundColor,
|
||||||
|
b.reactionsBackgroundColor,
|
||||||
|
t,
|
||||||
|
),
|
||||||
reactionsBorderColor:
|
reactionsBorderColor:
|
||||||
Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t),
|
Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t),
|
||||||
reactionsMaskColor:
|
reactionsMaskColor:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class TypingIndicator extends StatelessWidget {
|
|||||||
.map((e) => e.key)),
|
.map((e) => e.key)),
|
||||||
builder: (context, data) => AnimatedSwitcher(
|
builder: (context, data) => AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: data.isNotEmpty == true
|
child: data.isNotEmpty
|
||||||
? Padding(
|
? Padding(
|
||||||
key: const Key('main'),
|
key: const Key('main'),
|
||||||
padding: padding,
|
padding: padding,
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ class UserAvatar extends StatelessWidget {
|
|||||||
streamChatTheme
|
streamChatTheme
|
||||||
.ownMessageTheme.avatarTheme?.borderRadius,
|
.ownMessageTheme.avatarTheme?.borderRadius,
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: imageProvider, fit: BoxFit.cover),
|
image: imageProvider,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -90,12 +90,13 @@ class UserItem extends StatelessWidget {
|
|||||||
Widget _buildLastActive(BuildContext context) {
|
Widget _buildLastActive(BuildContext context) {
|
||||||
final chatTheme = StreamChatTheme.of(context);
|
final chatTheme = StreamChatTheme.of(context);
|
||||||
return Text(
|
return Text(
|
||||||
user.online == true
|
user.online
|
||||||
? context.translations.userOnlineText
|
? context.translations.userOnlineText
|
||||||
: '${context.translations.userLastOnlineText} '
|
: '${context.translations.userLastOnlineText} '
|
||||||
'${Jiffy(user.lastActive).fromNow()}',
|
'${Jiffy(user.lastActive).fromNow()}',
|
||||||
style: chatTheme.textTheme.footnote.copyWith(
|
style: chatTheme.textTheme.footnote.copyWith(
|
||||||
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)),
|
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class UserListView extends StatefulWidget {
|
|||||||
this.listBuilder,
|
this.listBuilder,
|
||||||
this.userListController,
|
this.userListController,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
crossAxisCount == 1 || groupAlphabetically == false,
|
crossAxisCount == 1 || !groupAlphabetically,
|
||||||
'Cannot group alphabetically when crossAxisCount > 1',
|
'Cannot group alphabetically when crossAxisCount > 1',
|
||||||
),
|
),
|
||||||
limit = limit ?? pagination?.limit ?? 30,
|
limit = limit ?? pagination?.limit ?? 30,
|
||||||
@@ -407,33 +407,34 @@ class _UserListViewState extends State<UserListView>
|
|||||||
|
|
||||||
Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) =>
|
Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) =>
|
||||||
StreamBuilder<bool>(
|
StreamBuilder<bool>(
|
||||||
stream: usersProvider.queryUsersLoading,
|
stream: usersProvider.queryUsersLoading,
|
||||||
initialData: false,
|
initialData: false,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.accentError
|
|
||||||
.withOpacity(.2),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Center(
|
|
||||||
child: Text(context.translations.loadingUsersError),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 100,
|
color: StreamChatTheme.of(context)
|
||||||
padding: const EdgeInsets.all(32),
|
.colorTheme
|
||||||
child: Center(
|
.accentError
|
||||||
child: snapshot.data!
|
.withOpacity(0.2),
|
||||||
? const CircularProgressIndicator()
|
child: Padding(
|
||||||
: Container(),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Center(
|
||||||
|
child: Text(context.translations.loadingUsersError),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
return Container(
|
||||||
|
height: 100,
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
child: Center(
|
||||||
|
child: snapshot.data!
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: Container(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Widget _separatorBuilder(context, i) => Container(
|
Widget _separatorBuilder(context, i) => Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|
||||||
|
|
||||||
/// Displays a list of users who reacted
|
|
||||||
class UserReactionDisplay extends StatelessWidget {
|
|
||||||
/// Constructor for creating a [UserReactionDisplay]
|
|
||||||
const UserReactionDisplay({
|
|
||||||
Key? key,
|
|
||||||
required this.reactionToEmoji,
|
|
||||||
required this.message,
|
|
||||||
this.size = 30,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
/// Reaction map
|
|
||||||
final Map<String, String> reactionToEmoji;
|
|
||||||
|
|
||||||
/// Message which is reacted to
|
|
||||||
final Message message;
|
|
||||||
|
|
||||||
/// Size of Icon
|
|
||||||
final double size;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Container(
|
|
||||||
color: Colors.black87,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: reactionToEmoji.keys.map((reactionType) {
|
|
||||||
final firstUserReaction = message.latestReactions!
|
|
||||||
.firstWhere((element) => element.type == reactionType,
|
|
||||||
//ignore: unnecessary_parenthesis
|
|
||||||
orElse: (() => null) as Reaction Function()?);
|
|
||||||
|
|
||||||
if (firstUserReaction.user == null) {
|
|
||||||
return IconButton(
|
|
||||||
iconSize: size,
|
|
||||||
icon: Container(),
|
|
||||||
onPressed: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IconButton(
|
|
||||||
iconSize: size,
|
|
||||||
icon: UserAvatar(
|
|
||||||
user: firstUserReaction.user!,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: size - 5,
|
|
||||||
maxWidth: size - 5,
|
|
||||||
),
|
|
||||||
onTap: (user) {},
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -29,78 +29,82 @@ Future<bool?> showConfirmationDialog(
|
|||||||
}) {
|
}) {
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
backgroundColor: chatThemeData.colorTheme.barsBg,
|
backgroundColor: chatThemeData.colorTheme.barsBg,
|
||||||
context: context,
|
context: context,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
)),
|
),
|
||||||
builder: (context) {
|
),
|
||||||
final effect = chatThemeData.colorTheme.borderTop;
|
builder: (context) {
|
||||||
return SafeArea(
|
final effect = chatThemeData.colorTheme.borderTop;
|
||||||
child: Column(
|
return SafeArea(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
const SizedBox(height: 26),
|
children: [
|
||||||
if (icon != null) icon,
|
const SizedBox(height: 26),
|
||||||
const SizedBox(height: 26),
|
if (icon != null) icon,
|
||||||
|
const SizedBox(height: 26),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: chatThemeData.textTheme.headlineBold,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 7),
|
||||||
|
if (question != null)
|
||||||
Text(
|
Text(
|
||||||
title,
|
question,
|
||||||
style: chatThemeData.textTheme.headlineBold,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 7),
|
const SizedBox(height: 36),
|
||||||
if (question != null)
|
Container(
|
||||||
Text(
|
color: effect.color!.withOpacity(effect.alpha ?? 1),
|
||||||
question,
|
height: 1,
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
),
|
Row(
|
||||||
const SizedBox(height: 36),
|
children: [
|
||||||
Container(
|
if (cancelText != null)
|
||||||
color: effect.color!.withOpacity(effect.alpha ?? 1),
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
if (cancelText != null)
|
|
||||||
Flexible(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(false);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
cancelText,
|
|
||||||
style: chatThemeData.textTheme.bodyBold.copyWith(
|
|
||||||
color: chatThemeData.colorTheme.textHighEmphasis
|
|
||||||
.withOpacity(0.5)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context, true);
|
Navigator.of(context).pop(false);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
okText,
|
cancelText,
|
||||||
style: chatThemeData.textTheme.bodyBold.copyWith(
|
style: chatThemeData.textTheme.bodyBold.copyWith(
|
||||||
color: chatThemeData.colorTheme.accentError),
|
color: chatThemeData.colorTheme.textHighEmphasis
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
Flexible(
|
||||||
),
|
child: Container(
|
||||||
],
|
alignment: Alignment.center,
|
||||||
),
|
child: TextButton(
|
||||||
);
|
onPressed: () {
|
||||||
});
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
okText,
|
||||||
|
style: chatThemeData.textTheme.bodyBold.copyWith(
|
||||||
|
color: chatThemeData.colorTheme.accentError,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows info dialog
|
/// Shows info dialog
|
||||||
@@ -119,10 +123,11 @@ Future<bool?> showInfoDialog(
|
|||||||
theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg,
|
theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg,
|
||||||
context: context,
|
context: context,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16),
|
topRight: Radius.circular(16),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
builder: (context) => SafeArea(
|
builder: (context) => SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -147,8 +152,8 @@ Future<bool?> showInfoDialog(
|
|||||||
height: 36,
|
height: 36,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: theme?.colorTheme.textHighEmphasis.withOpacity(.08) ??
|
color: theme?.colorTheme.textHighEmphasis.withOpacity(0.08) ??
|
||||||
chatThemeData.colorTheme.textHighEmphasis.withOpacity(.08),
|
chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.08),
|
||||||
height: 1,
|
height: 1,
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import 'package:video_compress/video_compress.dart';
|
|||||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
class IVideoService {
|
// ignore: prefer-match-file-name
|
||||||
IVideoService._();
|
class _IVideoService {
|
||||||
|
_IVideoService._();
|
||||||
|
|
||||||
/// Singleton instance of [IVideoService]
|
/// Singleton instance of [_IVideoService]
|
||||||
static final IVideoService instance = IVideoService._();
|
static final _IVideoService instance = _IVideoService._();
|
||||||
final _lock = Lock();
|
final _lock = Lock();
|
||||||
|
|
||||||
/// compress video from [path]
|
/// compress video from [path]
|
||||||
@@ -64,6 +65,6 @@ class IVideoService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get instance of [IVideoService]
|
/// Get instance of [_IVideoService]
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
IVideoService get VideoService => IVideoService.instance;
|
_IVideoService get VideoService => _IVideoService.instance;
|
||||||
|
|||||||
@@ -55,10 +55,9 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
golden_toolkit: ^0.10.0
|
golden_toolkit: ^0.10.0
|
||||||
mocktail: ^0.1.2
|
mocktail: ^0.1.2
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
pedantic: ^1.11.0
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ final _channelPreviewThemeControl = ChannelPreviewThemeData(
|
|||||||
color: const Color(0xff7A7A7A),
|
color: const Color(0xff7A7A7A),
|
||||||
),
|
),
|
||||||
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
||||||
color: ColorTheme.light().textHighEmphasis.withOpacity(.5),
|
color: ColorTheme.light().textHighEmphasis.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
indicatorIconSize: 16,
|
indicatorIconSize: 16,
|
||||||
);
|
);
|
||||||
@@ -83,7 +83,7 @@ final _channelPreviewThemeControlMidLerp = ChannelPreviewThemeData(
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
||||||
color: const Color(0x807f7f7f).withOpacity(.5),
|
color: const Color(0x807f7f7f).withOpacity(0.5),
|
||||||
),
|
),
|
||||||
indicatorIconSize: 16,
|
indicatorIconSize: 16,
|
||||||
);
|
);
|
||||||
@@ -102,7 +102,7 @@ final _channelPreviewThemeControlDark = ChannelPreviewThemeData(
|
|||||||
color: const Color(0xff7A7A7A),
|
color: const Color(0xff7A7A7A),
|
||||||
),
|
),
|
||||||
lastMessageAtStyle: TextTheme.dark().footnote.copyWith(
|
lastMessageAtStyle: TextTheme.dark().footnote.copyWith(
|
||||||
color: ColorTheme.dark().textHighEmphasis.withOpacity(.5),
|
color: ColorTheme.dark().textHighEmphasis.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
indicatorIconSize: 16,
|
indicatorIconSize: 16,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: example
|
name: stream_chat_flutter_core_example
|
||||||
description: Example app for testing stream_chat_flutter_core
|
description: Example app for testing stream_chat_flutter_core
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
@@ -26,9 +26,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter_core:
|
stream_chat_flutter_core: ^2.2.1
|
||||||
path: ../
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class _BetterStreamBuilderState<T extends Object>
|
|||||||
if (widget.errorBuilder != null && error != _lastError) {
|
if (widget.errorBuilder != null && error != _lastError) {
|
||||||
_lastError = error;
|
_lastError = error;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
// ignore: no-empty-block
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ class _BetterStreamBuilderState<T extends Object>
|
|||||||
if (!isEqual) {
|
if (!isEqual) {
|
||||||
_lastEvent = event;
|
_lastEvent = event;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {}); // ignore: no-empty-block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
_paginationEnded = false;
|
_paginationEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!clear && _paginationEnded) ||
|
if ((!clear && _paginationEnded) || _queryChannelsLoadingController.value) {
|
||||||
_queryChannelsLoadingController.value == true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +220,8 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
.listen((e) {
|
.listen((e) {
|
||||||
final channel = e.channel;
|
final channel = e.channel;
|
||||||
_channelsController.add(List.from(
|
_channelsController.add(List.from(
|
||||||
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
|
(channels ?? [])..removeWhere((c) => c.cid == channel?.cid),
|
||||||
|
));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
|||||||
_paginationEnded = false;
|
_paginationEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!clear && _paginationEnded) ||
|
if ((!clear && _paginationEnded) || _queryMessagesLoadingController.value) {
|
||||||
_queryMessagesLoadingController.value == true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class StreamChannelState extends State<StreamChannel> {
|
|||||||
bool preferOffline = false,
|
bool preferOffline = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (_topPaginationEnded ||
|
if (_topPaginationEnded ||
|
||||||
_queryTopMessagesController.value == true ||
|
_queryTopMessagesController.value ||
|
||||||
channel.state == null) {
|
channel.state == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -120,9 +120,9 @@ class StreamChannelState extends State<StreamChannel> {
|
|||||||
bool preferOffline = false,
|
bool preferOffline = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (_bottomPaginationEnded ||
|
if (_bottomPaginationEnded ||
|
||||||
_queryBottomMessagesController.value == true ||
|
_queryBottomMessagesController.value ||
|
||||||
channel.state == null ||
|
channel.state == null ||
|
||||||
channel.state!.isUpToDate == true) return;
|
channel.state!.isUpToDate) return;
|
||||||
_queryBottomMessagesController.add(true);
|
_queryBottomMessagesController.add(true);
|
||||||
|
|
||||||
if (channel.state!.messages.isEmpty) {
|
if (channel.state!.messages.isEmpty) {
|
||||||
@@ -164,7 +164,7 @@ class StreamChannelState extends State<StreamChannel> {
|
|||||||
bool preferOffline = false,
|
bool preferOffline = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (_topPaginationEnded ||
|
if (_topPaginationEnded ||
|
||||||
_queryTopMessagesController.value == true ||
|
_queryTopMessagesController.value ||
|
||||||
channel.state == null) return;
|
channel.state == null) return;
|
||||||
_queryTopMessagesController.add(true);
|
_queryTopMessagesController.add(true);
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ class UsersBlocState extends State<UsersBloc>
|
|||||||
_paginationEnded = false;
|
_paginationEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!clear && _paginationEnded) ||
|
if ((!clear && _paginationEnded) || _queryUsersLoadingController.value) {
|
||||||
_queryUsersLoadingController.value == true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ dependencies:
|
|||||||
stream_chat: ^3.1.1
|
stream_chat: ^3.1.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
fake_async: ^1.2.0
|
fake_async: ^1.2.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: example
|
name: stream_chat_localizations_example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
@@ -11,12 +11,8 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_localizations:
|
stream_chat_flutter: ^2.2.1
|
||||||
path: ../
|
stream_chat_localizations: ^1.1.0
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
stream_chat_flutter:
|
|
||||||
path: ../../stream_chat_flutter
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const kStreamChatSupportedLanguages = {
|
|||||||
'it',
|
'it',
|
||||||
'es',
|
'es',
|
||||||
'ja',
|
'ja',
|
||||||
'ko'
|
'ko',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
|
/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
|
||||||
|
|||||||
@@ -350,8 +350,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations {
|
|||||||
String get youText => 'You';
|
String get youText => 'You';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} of $totalPages';
|
'${currentPage + 1} of $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -355,8 +355,10 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
|
|||||||
String get youText => 'Usted';
|
String get youText => 'Usted';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} de $totalPages';
|
'${currentPage + 1} de $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -354,8 +354,10 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
|||||||
String get youText => 'Vous';
|
String get youText => 'Vous';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} de $totalPages';
|
'${currentPage + 1} de $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -349,8 +349,10 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations {
|
|||||||
String get youText => 'आप';
|
String get youText => 'आप';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} ऑफ़ $totalPages';
|
'${currentPage + 1} ऑफ़ $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -351,8 +351,10 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.''';
|
|||||||
String get youText => 'te';
|
String get youText => 'te';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} di $totalPages';
|
'${currentPage + 1} di $totalPages';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -334,9 +334,12 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations {
|
|||||||
String get youText => '당신';
|
String get youText => '당신';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String galleryPaginationText(
|
String galleryPaginationText({
|
||||||
{required int currentPage, required int totalPages}) =>
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
'${currentPage + 1} / $totalPages';
|
'${currentPage + 1} / $totalPages';
|
||||||
|
|
||||||
//3 / 11
|
//3 / 11
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ dependencies:
|
|||||||
stream_chat_flutter: ^3.0.0
|
stream_chat_flutter: ^3.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: example
|
name: stream_chat_persistence_example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
@@ -11,12 +11,8 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.3
|
cupertino_icons: ^1.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_persistence:
|
stream_chat: ^2.2.1
|
||||||
path: ../
|
stream_chat_persistence: ^2.2.0
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
stream_chat:
|
|
||||||
path: ../../stream_chat
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -62,8 +62,10 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
Future<Message?> getMessageById(String id) async =>
|
Future<Message?> getMessageById(String id) async =>
|
||||||
await (select(messages).join([
|
await (select(messages).join([
|
||||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(_pinnedByUsers,
|
leftOuterJoin(
|
||||||
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(messages.id.equals(id)))
|
..where(messages.id.equals(id)))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
@@ -74,8 +76,10 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
Future<List<Message>> getThreadMessages(String cid) async =>
|
Future<List<Message>> getThreadMessages(String cid) async =>
|
||||||
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(_pinnedByUsers,
|
leftOuterJoin(
|
||||||
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(messages.channelCid.equals(cid))
|
..where(messages.channelCid.equals(cid))
|
||||||
..where(messages.parentId.isNotNull())
|
..where(messages.parentId.isNotNull())
|
||||||
@@ -92,7 +96,9 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
final msgList = await Future.wait(await (select(messages).join([
|
final msgList = await Future.wait(await (select(messages).join([
|
||||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(messages.parentId.isNotNull())
|
..where(messages.parentId.isNotNull())
|
||||||
..where(messages.parentId.equals(parentId))
|
..where(messages.parentId.equals(parentId))
|
||||||
@@ -134,7 +140,9 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
final msgList = await Future.wait(await (select(messages).join([
|
final msgList = await Future.wait(await (select(messages).join([
|
||||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(messages.channelCid.equals(cid))
|
..where(messages.channelCid.equals(cid))
|
||||||
..where(
|
..where(
|
||||||
|
|||||||
@@ -64,8 +64,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
Future<Message?> getMessageById(String id) async =>
|
Future<Message?> getMessageById(String id) async =>
|
||||||
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(
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.id.equals(id)))
|
..where(pinnedMessages.id.equals(id)))
|
||||||
.map(_messageFromJoinRow)
|
.map(_messageFromJoinRow)
|
||||||
@@ -76,8 +78,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
Future<List<Message>> getThreadMessages(String cid) async =>
|
Future<List<Message>> getThreadMessages(String cid) async =>
|
||||||
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(
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.channelCid.equals(cid))
|
..where(pinnedMessages.channelCid.equals(cid))
|
||||||
..where(pinnedMessages.parentId.isNotNull())
|
..where(pinnedMessages.parentId.isNotNull())
|
||||||
@@ -93,8 +97,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}) async {
|
}) async {
|
||||||
final msgList = await Future.wait(await (select(pinnedMessages).join([
|
final msgList = await Future.wait(await (select(pinnedMessages).join([
|
||||||
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(_pinnedByUsers,
|
leftOuterJoin(
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.parentId.isNotNull())
|
..where(pinnedMessages.parentId.isNotNull())
|
||||||
..where(pinnedMessages.parentId.equals(parentId))
|
..where(pinnedMessages.parentId.equals(parentId))
|
||||||
@@ -134,8 +140,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}) async {
|
}) async {
|
||||||
final msgList = await Future.wait(await (select(pinnedMessages).join([
|
final msgList = await Future.wait(await (select(pinnedMessages).join([
|
||||||
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||||
leftOuterJoin(_pinnedByUsers,
|
leftOuterJoin(
|
||||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
_pinnedByUsers,
|
||||||
|
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(pinnedMessages.channelCid.equals(cid))
|
..where(pinnedMessages.channelCid.equals(cid))
|
||||||
..where(pinnedMessages.parentId.isNull() |
|
..where(pinnedMessages.parentId.isNull() |
|
||||||
|
|||||||
@@ -10,29 +10,32 @@ export 'shared/shared_db.dart';
|
|||||||
part 'moor_chat_database.g.dart';
|
part 'moor_chat_database.g.dart';
|
||||||
|
|
||||||
/// A chat database implemented using moor
|
/// A chat database implemented using moor
|
||||||
@UseMoor(tables: [
|
@UseMoor(
|
||||||
Channels,
|
tables: [
|
||||||
Messages,
|
Channels,
|
||||||
PinnedMessages,
|
Messages,
|
||||||
PinnedMessageReactions,
|
PinnedMessages,
|
||||||
Reactions,
|
PinnedMessageReactions,
|
||||||
Users,
|
Reactions,
|
||||||
Members,
|
Users,
|
||||||
Reads,
|
Members,
|
||||||
ChannelQueries,
|
Reads,
|
||||||
ConnectionEvents,
|
ChannelQueries,
|
||||||
], daos: [
|
ConnectionEvents,
|
||||||
UserDao,
|
],
|
||||||
ChannelDao,
|
daos: [
|
||||||
MessageDao,
|
UserDao,
|
||||||
PinnedMessageDao,
|
ChannelDao,
|
||||||
PinnedMessageReactionDao,
|
MessageDao,
|
||||||
MemberDao,
|
PinnedMessageDao,
|
||||||
ReactionDao,
|
PinnedMessageReactionDao,
|
||||||
ReadDao,
|
MemberDao,
|
||||||
ChannelQueryDao,
|
ReactionDao,
|
||||||
ConnectionEventDao,
|
ReadDao,
|
||||||
])
|
ChannelQueryDao,
|
||||||
|
ConnectionEventDao,
|
||||||
|
],
|
||||||
|
)
|
||||||
class MoorChatDatabase extends _$MoorChatDatabase {
|
class MoorChatDatabase extends _$MoorChatDatabase {
|
||||||
/// Creates a new moor chat database instance
|
/// Creates a new moor chat database instance
|
||||||
MoorChatDatabase(
|
MoorChatDatabase(
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
|||||||
final parentId = message.parentId!;
|
final parentId = message.parentId!;
|
||||||
messageByParentIdDictionary[parentId] = [
|
messageByParentIdDictionary[parentId] = [
|
||||||
...messageByParentIdDictionary[parentId] ?? [],
|
...messageByParentIdDictionary[parentId] ?? [],
|
||||||
message
|
message,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return messageByParentIdDictionary;
|
return messageByParentIdDictionary;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -22,8 +23,8 @@ dependencies:
|
|||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.0.1
|
build_runner: ^2.0.1
|
||||||
|
dart_code_metrics: ^4.4.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
mocktail: ^0.1.1
|
mocktail: ^0.1.1
|
||||||
moor_generator: ^4.2.1
|
moor_generator: ^4.2.1
|
||||||
pedantic: ^1.11.0
|
|
||||||
@@ -82,9 +82,9 @@ void main() {
|
|||||||
cid: cids[index],
|
cid: cids[index],
|
||||||
createdBy: users[index],
|
createdBy: users[index],
|
||||||
config: ChannelConfig(),
|
config: ChannelConfig(),
|
||||||
extraData: {'test_custom_field': 3 + index},
|
extraData: {'test_custom_field': index + 3},
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
memberCount: 3 + index,
|
memberCount: index + 3,
|
||||||
lastMessageAt: now.add(Duration(hours: index)),
|
lastMessageAt: now.add(Duration(hours: index)),
|
||||||
),
|
),
|
||||||
).reversed.toList(growable: false);
|
).reversed.toList(growable: false);
|
||||||
|
|||||||
Reference in New Issue
Block a user