Merge pull request #583 from GetStream/release/2.1.0
chore(repo): 2.1.0
@@ -12,13 +12,16 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v2.1.0
|
- uses: amannn/action-semantic-pull-request@v3.4.0
|
||||||
with:
|
with:
|
||||||
scopes: |
|
scopes: |
|
||||||
llc
|
llc
|
||||||
persistence
|
persistence
|
||||||
core
|
core
|
||||||
ui
|
ui
|
||||||
|
doc
|
||||||
|
repo
|
||||||
|
localization
|
||||||
requireScope: true
|
requireScope: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -48,6 +48,9 @@ This package provides business logic to fetch common things required for integra
|
|||||||
### [stream_chat_flutter](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_flutter)
|
### [stream_chat_flutter](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_flutter)
|
||||||
This library includes both a low-level chat SDK and a set of reusable and customizable UI components.
|
This library includes both a low-level chat SDK and a set of reusable and customizable UI components.
|
||||||
|
|
||||||
|
### [stream_chat_localizations](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_localizations)
|
||||||
|
This library includes a set of localization files for the Flutter UI components.
|
||||||
|
|
||||||
## Flutter Chat Tutorial
|
## Flutter Chat Tutorial
|
||||||
|
|
||||||
The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/).
|
The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/).
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ scripts:
|
|||||||
|
|
||||||
analyze:
|
analyze:
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 4 --ignore="*example*" -- \
|
melos exec -c 5 --ignore="*example*" -- \
|
||||||
dart analyze --fatal-infos .
|
dart analyze --fatal-infos .
|
||||||
description: |
|
description: |
|
||||||
Run `dart analyze` in all packages.
|
Run `dart analyze` in all packages.
|
||||||
@@ -26,7 +26,7 @@ scripts:
|
|||||||
|
|
||||||
lint:pub:
|
lint:pub:
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 4 --no-private --ignore="*example*" -- \
|
melos exec -c 5 --no-private --ignore="*example*" -- \
|
||||||
pub publish --dry-run
|
pub publish --dry-run
|
||||||
description: |
|
description: |
|
||||||
Run `pub publish --dry-run` in all packages.
|
Run `pub publish --dry-run` in all packages.
|
||||||
@@ -56,7 +56,7 @@ scripts:
|
|||||||
dir-exists: test
|
dir-exists: test
|
||||||
|
|
||||||
test:flutter:
|
test:flutter:
|
||||||
run: melos exec -c 3 --fail-fast -- "flutter test --coverage"
|
run: melos exec -c 4 --fail-fast -- "flutter test --coverage"
|
||||||
description: Run Flutter tests for a specific package in this project.
|
description: Run Flutter tests for a specific package in this project.
|
||||||
select-package:
|
select-package:
|
||||||
flutter: true
|
flutter: true
|
||||||
@@ -64,7 +64,7 @@ scripts:
|
|||||||
|
|
||||||
coverage:ignore-file:
|
coverage:ignore-file:
|
||||||
run: |
|
run: |
|
||||||
melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
|
melos exec -c 5 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
|
||||||
description: Removes all the ignored files from the coverage report.
|
description: Removes all the ignored files from the coverage report.
|
||||||
select-package:
|
select-package:
|
||||||
dir-exists: coverage
|
dir-exists: coverage
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
🛑️ Removed
|
||||||
|
|
||||||
|
- The `MessageTranslation` class has been removed. Use the new `i18n` field in the `Message` class instead.
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- The `Message` class now has an `i18n` field for translations
|
||||||
|
- The `User` class now has a `language` field for the user's language preference.
|
||||||
|
|
||||||
|
🔄 Changed
|
||||||
|
|
||||||
|
- `client.user` is now deprecated in favor of `client.currentUser`.
|
||||||
|
- `client.userStream` is now deprecated in favor of `client.currentUserStream`.
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- [#563](https://github.com/GetStream/stream-chat-flutter/issues/563): `Channel.stopWatching()` not working
|
||||||
|
- [#575](https://github.com/GetStream/stream-chat-flutter/issues/575): Wrong `OwnUser.*`
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
🛑️ Breaking Changes from `1.5.3`
|
🛑️ Breaking Changes from `1.5.3`
|
||||||
|
|||||||
@@ -245,5 +245,5 @@ class _MessageViewState extends State<MessageView> {
|
|||||||
/// Helper extension for quickly retrieving
|
/// Helper extension for quickly retrieving
|
||||||
/// the current user id from a [StreamChatClient].
|
/// the current user id from a [StreamChatClient].
|
||||||
extension on StreamChatClient {
|
extension on StreamChatClient {
|
||||||
String get uid => state.user!.id;
|
String get uid => state.currentUser!.id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ class Channel {
|
|||||||
|
|
||||||
/// Returns true if the channel is muted
|
/// Returns true if the channel is muted
|
||||||
bool get isMuted =>
|
bool get isMuted =>
|
||||||
_client.state.user?.channelMutes
|
_client.state.currentUser?.channelMutes
|
||||||
.any((element) => element.channel.cid == cid) ==
|
.any((element) => element.channel.cid == cid) ==
|
||||||
true;
|
true;
|
||||||
|
|
||||||
/// Returns true if the channel is muted as a stream
|
/// Returns true if the channel is muted as a stream
|
||||||
Stream<bool>? get isMutedStream => _client.state.userStream
|
Stream<bool>? get isMutedStream => _client.state.currentUserStream
|
||||||
.map((event) =>
|
.map((event) =>
|
||||||
event!.channelMutes.any((element) => element.channel.cid == cid) ==
|
event!.channelMutes.any((element) => element.channel.cid == cid) ==
|
||||||
true)
|
true)
|
||||||
@@ -382,7 +382,7 @@ class Channel {
|
|||||||
// ignore: parameter_assignments
|
// ignore: parameter_assignments
|
||||||
message = message.copyWith(
|
message = message.copyWith(
|
||||||
createdAt: message.createdAt,
|
createdAt: message.createdAt,
|
||||||
user: _client.state.user,
|
user: _client.state.currentUser,
|
||||||
quotedMessage: quotedMessage,
|
quotedMessage: quotedMessage,
|
||||||
status: MessageSendingStatus.sending,
|
status: MessageSendingStatus.sending,
|
||||||
attachments: message.attachments.map(
|
attachments: message.attachments.map(
|
||||||
@@ -693,7 +693,7 @@ class Channel {
|
|||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
final messageId = message.id;
|
final messageId = message.id;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final user = _client.state.user;
|
final user = _client.state.currentUser;
|
||||||
|
|
||||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]];
|
final latestReactions = [...message.latestReactions ?? <Reaction>[]];
|
||||||
if (enforceUnique) {
|
if (enforceUnique) {
|
||||||
@@ -750,7 +750,7 @@ class 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.user;
|
final user = _client.state.currentUser;
|
||||||
|
|
||||||
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
||||||
if (reactionCounts.containsKey(type)) {
|
if (reactionCounts.containsKey(type)) {
|
||||||
@@ -1314,7 +1314,7 @@ class ChannelClientState {
|
|||||||
|
|
||||||
void _computeInitialUnread() {
|
void _computeInitialUnread() {
|
||||||
final userRead = channelState.read.firstWhereOrNull(
|
final userRead = channelState.read.firstWhereOrNull(
|
||||||
(r) => r.user.id == _channel._client.state.user?.id,
|
(r) => r.user.id == _channel._client.state.currentUser?.id,
|
||||||
);
|
);
|
||||||
if (userRead != null) {
|
if (userRead != null) {
|
||||||
unreadCount = userRead.unreadMessages;
|
unreadCount = userRead.unreadMessages;
|
||||||
@@ -1431,7 +1431,7 @@ class ChannelClientState {
|
|||||||
|
|
||||||
void _listenReactionDeleted() {
|
void _listenReactionDeleted() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
||||||
final userId = _channel.client.state.user!.id;
|
final userId = _channel.client.state.currentUser!.id;
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: [...event.message!.latestReactions!]
|
||||||
..removeWhere((it) => it.userId != userId),
|
..removeWhere((it) => it.userId != userId),
|
||||||
@@ -1442,7 +1442,7 @@ class ChannelClientState {
|
|||||||
|
|
||||||
void _listenReactions() {
|
void _listenReactions() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
||||||
final userId = _channel.client.state.user!.id;
|
final userId = _channel.client.state.currentUser!.id;
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: [...event.message!.latestReactions!]
|
||||||
..removeWhere((it) => it.userId != userId),
|
..removeWhere((it) => it.userId != userId),
|
||||||
@@ -1458,7 +1458,7 @@ class ChannelClientState {
|
|||||||
EventType.reactionUpdated,
|
EventType.reactionUpdated,
|
||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
final userId = _channel.client.state.user!.id;
|
final userId = _channel.client.state.currentUser!.id;
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: [...event.message!.latestReactions!]
|
||||||
..removeWhere((it) => it.userId != userId),
|
..removeWhere((it) => it.userId != userId),
|
||||||
@@ -1552,7 +1552,7 @@ class ChannelClientState {
|
|||||||
|
|
||||||
if (userReadIndex != null && userReadIndex != -1) {
|
if (userReadIndex != null && userReadIndex != -1) {
|
||||||
final userRead = readList.removeAt(userReadIndex);
|
final userRead = readList.removeAt(userReadIndex);
|
||||||
if (userRead.user.id == _channel._client.state.user!.id) {
|
if (userRead.user.id == _channel._client.state.currentUser!.id) {
|
||||||
unreadCount = 0;
|
unreadCount = 0;
|
||||||
}
|
}
|
||||||
readList.add(Read(
|
readList.add(Read(
|
||||||
@@ -1642,8 +1642,9 @@ class ChannelClientState {
|
|||||||
int get unreadCount => _unreadCountController.value;
|
int get unreadCount => _unreadCountController.value;
|
||||||
|
|
||||||
bool _countMessageAsUnread(Message message) {
|
bool _countMessageAsUnread(Message message) {
|
||||||
final userId = _channel.client.state.user?.id;
|
final userId = _channel.client.state.currentUser?.id;
|
||||||
final userIsMuted = _channel.client.state.user?.mutes.firstWhereOrNull(
|
final userIsMuted =
|
||||||
|
_channel.client.state.currentUser?.mutes.firstWhereOrNull(
|
||||||
(m) => m.user.id == message.user?.id,
|
(m) => m.user.id == message.user?.id,
|
||||||
) !=
|
) !=
|
||||||
null;
|
null;
|
||||||
@@ -1795,7 +1796,7 @@ class ChannelClientState {
|
|||||||
(event) {
|
(event) {
|
||||||
if (event.user != null) {
|
if (event.user != null) {
|
||||||
final user = event.user!;
|
final user = event.user!;
|
||||||
if (user.id != _channel.client.state.user?.id) {
|
if (user.id != _channel.client.state.currentUser?.id) {
|
||||||
_typings[user] = event;
|
_typings[user] = event;
|
||||||
_typingEventsController.add(_typings);
|
_typingEventsController.add(_typings);
|
||||||
}
|
}
|
||||||
@@ -1808,7 +1809,7 @@ class ChannelClientState {
|
|||||||
(event) {
|
(event) {
|
||||||
if (event.user != null) {
|
if (event.user != null) {
|
||||||
final user = event.user!;
|
final user = event.user!;
|
||||||
if (user.id != _channel.client.state.user?.id) {
|
if (user.id != _channel.client.state.currentUser?.id) {
|
||||||
_typings.remove(event.user);
|
_typings.remove(event.user);
|
||||||
_typingEventsController.add(_typings);
|
_typingEventsController.add(_typings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ import 'package:stream_chat/src/core/models/member.dart';
|
|||||||
import 'package:stream_chat/src/core/models/message.dart';
|
import 'package:stream_chat/src/core/models/message.dart';
|
||||||
import 'package:stream_chat/src/core/models/own_user.dart';
|
import 'package:stream_chat/src/core/models/own_user.dart';
|
||||||
import 'package:stream_chat/src/core/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||||
import 'package:stream_chat/src/core/util/utils.dart';
|
import 'package:stream_chat/src/core/util/utils.dart';
|
||||||
import 'package:stream_chat/src/db/chat_persistence_client.dart';
|
import 'package:stream_chat/src/db/chat_persistence_client.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/location.dart';
|
import 'package:stream_chat/src/location.dart';
|
||||||
import 'package:stream_chat/src/ws/connection_status.dart';
|
import 'package:stream_chat/src/ws/connection_status.dart';
|
||||||
import 'package:stream_chat/src/ws/websocket.dart';
|
import 'package:stream_chat/src/ws/websocket.dart';
|
||||||
|
import 'package:stream_chat/version.dart';
|
||||||
|
|
||||||
/// Handler function used for logging records. Function requires a single
|
/// Handler function used for logging records. Function requires a single
|
||||||
/// [LogRecord] as the only parameter.
|
/// [LogRecord] as the only parameter.
|
||||||
@@ -42,6 +44,10 @@ final _levelEmojiMapper = {
|
|||||||
Level.SEVERE: '🚨',
|
Level.SEVERE: '🚨',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final _userAgent = 'stream-chat-dart-client-'
|
||||||
|
'${CurrentPlatform.name}-'
|
||||||
|
'${PACKAGE_VERSION.split('+')[0]}';
|
||||||
|
|
||||||
/// The official Dart client for Stream Chat,
|
/// The official Dart client for Stream Chat,
|
||||||
/// a service for building chat applications.
|
/// a service for building chat applications.
|
||||||
/// This library can be used on any Dart project and on both mobile and web apps
|
/// This library can be used on any Dart project and on both mobile and web apps
|
||||||
@@ -80,6 +86,7 @@ class StreamChatClient {
|
|||||||
location: location,
|
location: location,
|
||||||
connectTimeout: connectTimeout,
|
connectTimeout: connectTimeout,
|
||||||
receiveTimeout: receiveTimeout,
|
receiveTimeout: receiveTimeout,
|
||||||
|
headers: {'X-Stream-Client': _userAgent},
|
||||||
);
|
);
|
||||||
|
|
||||||
_chatApi = chatApi ??
|
_chatApi = chatApi ??
|
||||||
@@ -99,6 +106,7 @@ class StreamChatClient {
|
|||||||
tokenManager: _tokenManager,
|
tokenManager: _tokenManager,
|
||||||
handler: handleEvent,
|
handler: handleEvent,
|
||||||
logger: detachedLogger('🔌'),
|
logger: detachedLogger('🔌'),
|
||||||
|
queryParameters: {'X-Stream-Client': _userAgent},
|
||||||
);
|
);
|
||||||
|
|
||||||
_retryPolicy = retryPolicy ??
|
_retryPolicy = retryPolicy ??
|
||||||
@@ -308,23 +316,21 @@ class StreamChatClient {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final ownUser = OwnUser.fromUser(user);
|
final ownUser = OwnUser.fromUser(user);
|
||||||
state.user = ownUser;
|
state.currentUser = ownUser;
|
||||||
|
|
||||||
if (!connectWebSocket) {
|
if (!connectWebSocket) return ownUser;
|
||||||
return ownUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_originalChatPersistenceClient != null) {
|
if (_originalChatPersistenceClient != null) {
|
||||||
_chatPersistenceClient = _originalChatPersistenceClient;
|
_chatPersistenceClient = _originalChatPersistenceClient;
|
||||||
await _chatPersistenceClient!.connect(ownUser.id);
|
await _chatPersistenceClient!.connect(ownUser.id);
|
||||||
}
|
}
|
||||||
final res = await openConnection();
|
final connectedUser = await openConnection();
|
||||||
return res;
|
return state.currentUser = connectedUser;
|
||||||
} catch (e, stk) {
|
} catch (e, stk) {
|
||||||
if (e is StreamWebSocketError && e.isRetriable) {
|
if (e is StreamWebSocketError && e.isRetriable) {
|
||||||
final event = await _chatPersistenceClient?.getConnectionInfo();
|
final event = await _chatPersistenceClient?.getConnectionInfo();
|
||||||
if (event != null) return event.me?.merge(ownUser) ?? ownUser;
|
if (event != null) return ownUser.merge(event.me);
|
||||||
}
|
}
|
||||||
logger.severe('error connecting user : ${ownUser.id}', e, stk);
|
logger.severe('error connecting user : ${ownUser.id}', e, stk);
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -334,12 +340,12 @@ class StreamChatClient {
|
|||||||
/// Creates a new WebSocket connection with the current user.
|
/// Creates a new WebSocket connection with the current user.
|
||||||
Future<OwnUser> openConnection() async {
|
Future<OwnUser> openConnection() async {
|
||||||
assert(
|
assert(
|
||||||
state.user != null,
|
state.currentUser != null,
|
||||||
'User is not set on client, '
|
'User is not set on client, '
|
||||||
'use `connectUser` or `connectAnonymousUser` instead',
|
'use `connectUser` or `connectAnonymousUser` instead',
|
||||||
);
|
);
|
||||||
|
|
||||||
final user = state.user!;
|
final user = state.currentUser!;
|
||||||
|
|
||||||
logger.info('Opening web-socket connection for ${user.id}');
|
logger.info('Opening web-socket connection for ${user.id}');
|
||||||
|
|
||||||
@@ -363,7 +369,7 @@ class StreamChatClient {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final event = await _ws.connect(user);
|
final event = await _ws.connect(user);
|
||||||
return event.me?.merge(user) ?? user;
|
return user.merge(event.me);
|
||||||
} catch (e, stk) {
|
} catch (e, stk) {
|
||||||
logger.severe('error connecting ws', e, stk);
|
logger.severe('error connecting ws', e, stk);
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -378,7 +384,7 @@ class StreamChatClient {
|
|||||||
void closeConnection() {
|
void closeConnection() {
|
||||||
if (wsConnectionStatus == ConnectionStatus.disconnected) return;
|
if (wsConnectionStatus == ConnectionStatus.disconnected) return;
|
||||||
|
|
||||||
logger.info('Closing web-socket connection for ${state.user?.id}');
|
logger.info('Closing web-socket connection for ${state.currentUser?.id}');
|
||||||
_wsConnectionStatus = ConnectionStatus.disconnected;
|
_wsConnectionStatus = ConnectionStatus.disconnected;
|
||||||
|
|
||||||
_connectionStatusSubscription?.cancel();
|
_connectionStatusSubscription?.cancel();
|
||||||
@@ -388,9 +394,6 @@ class StreamChatClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleHealthCheckEvent(Event event) {
|
void _handleHealthCheckEvent(Event event) {
|
||||||
final user = event.me;
|
|
||||||
if (user != null) state.user = user;
|
|
||||||
|
|
||||||
final connectionId = event.connectionId;
|
final connectionId = event.connectionId;
|
||||||
if (connectionId != null) {
|
if (connectionId != null) {
|
||||||
_connectionIdManager.setConnectionId(connectionId);
|
_connectionIdManager.setConnectionId(connectionId);
|
||||||
@@ -1290,7 +1293,7 @@ class StreamChatClient {
|
|||||||
/// If [flushChatPersistence] is true the client deletes all offline
|
/// If [flushChatPersistence] is true the client deletes all offline
|
||||||
/// user's data.
|
/// user's data.
|
||||||
Future<void> disconnectUser({bool flushChatPersistence = false}) async {
|
Future<void> disconnectUser({bool flushChatPersistence = false}) async {
|
||||||
logger.info('Disconnecting user : ${state.user?.id}');
|
logger.info('Disconnecting user : ${state.currentUser?.id}');
|
||||||
|
|
||||||
// resetting state
|
// resetting state
|
||||||
state.dispose();
|
state.dispose();
|
||||||
@@ -1331,22 +1334,6 @@ class ClientState {
|
|||||||
/// Creates a new instance listening to events and updating the state
|
/// Creates a new instance listening to events and updating the state
|
||||||
ClientState(this._client) {
|
ClientState(this._client) {
|
||||||
_subscriptions.addAll([
|
_subscriptions.addAll([
|
||||||
_client
|
|
||||||
.on()
|
|
||||||
.where((event) => event.me != null)
|
|
||||||
.map((e) => e.me)
|
|
||||||
.listen((user) {
|
|
||||||
_userController.add(user);
|
|
||||||
final totalUnreadCount = user?.totalUnreadCount;
|
|
||||||
if (totalUnreadCount != null) {
|
|
||||||
_totalUnreadCountController.add(totalUnreadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
final unreadChannels = user?.unreadChannels;
|
|
||||||
if (unreadChannels != null) {
|
|
||||||
_unreadChannelsController.add(unreadChannels);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
_client
|
_client
|
||||||
.on()
|
.on()
|
||||||
.map((event) => event.unreadChannels)
|
.map((event) => event.unreadChannels)
|
||||||
@@ -1386,8 +1373,8 @@ class ClientState {
|
|||||||
|
|
||||||
void _listenUserUpdated() {
|
void _listenUserUpdated() {
|
||||||
_subscriptions.add(_client.on(EventType.userUpdated).listen((event) {
|
_subscriptions.add(_client.on(EventType.userUpdated).listen((event) {
|
||||||
if (event.user!.id == user!.id) {
|
if (event.user!.id == currentUser!.id) {
|
||||||
user = OwnUser.fromJson(event.user!.toJson());
|
currentUser = OwnUser.fromJson(event.user!.toJson());
|
||||||
}
|
}
|
||||||
updateUser(event.user);
|
updateUser(event.user);
|
||||||
}));
|
}));
|
||||||
@@ -1409,9 +1396,10 @@ class ClientState {
|
|||||||
|
|
||||||
final StreamChatClient _client;
|
final StreamChatClient _client;
|
||||||
|
|
||||||
/// Update user information
|
/// Sets the user currently interacting with the client
|
||||||
set user(OwnUser? user) {
|
/// note: this fully overrides the [currentUser]
|
||||||
_userController.add(user);
|
set currentUser(OwnUser? user) {
|
||||||
|
_currentUserController.add(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update all the [users] with the provided [userList]
|
/// Update all the [users] with the provided [userList]
|
||||||
@@ -1428,10 +1416,24 @@ class ClientState {
|
|||||||
void updateUser(User? user) => updateUsers([user]);
|
void updateUser(User? user) => updateUsers([user]);
|
||||||
|
|
||||||
/// The current user
|
/// The current user
|
||||||
OwnUser? get user => _userController.valueOrNull;
|
OwnUser? get currentUser => _currentUserController.valueOrNull;
|
||||||
|
|
||||||
/// The current user as a stream
|
/// The current user as a stream
|
||||||
Stream<OwnUser?> get userStream => _userController.stream;
|
Stream<OwnUser?> get currentUserStream => _currentUserController.stream;
|
||||||
|
|
||||||
|
// coverage:ignore-start
|
||||||
|
|
||||||
|
/// The current user
|
||||||
|
@Deprecated('Use `.currentUser` instead, Will be removed in future releases')
|
||||||
|
OwnUser? get user => _currentUserController.valueOrNull;
|
||||||
|
|
||||||
|
/// The current user as a stream
|
||||||
|
@Deprecated(
|
||||||
|
'Use `.currentUserStream` instead, Will be removed in future releases',
|
||||||
|
)
|
||||||
|
Stream<OwnUser?> get userStream => _currentUserController.stream;
|
||||||
|
|
||||||
|
// coverage:ignore-end
|
||||||
|
|
||||||
/// The current user
|
/// The current user
|
||||||
Map<String, User> get users => _usersController.value;
|
Map<String, User> get users => _usersController.value;
|
||||||
@@ -1463,7 +1465,7 @@ class ClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _channelsController = BehaviorSubject<Map<String, Channel>>.seeded({});
|
final _channelsController = BehaviorSubject<Map<String, Channel>>.seeded({});
|
||||||
final _userController = BehaviorSubject<OwnUser?>();
|
final _currentUserController = BehaviorSubject<OwnUser?>();
|
||||||
final _usersController = BehaviorSubject<Map<String, User>>.seeded({});
|
final _usersController = BehaviorSubject<Map<String, User>>.seeded({});
|
||||||
final _unreadChannelsController = BehaviorSubject<int>.seeded(0);
|
final _unreadChannelsController = BehaviorSubject<int>.seeded(0);
|
||||||
final _totalUnreadCountController = BehaviorSubject<int>.seeded(0);
|
final _totalUnreadCountController = BehaviorSubject<int>.seeded(0);
|
||||||
@@ -1471,7 +1473,7 @@ class ClientState {
|
|||||||
/// Call this method to dispose this object
|
/// Call this method to dispose this object
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subscriptions.forEach((s) => s.cancel());
|
_subscriptions.forEach((s) => s.cancel());
|
||||||
_userController.close();
|
_currentUserController.close();
|
||||||
_unreadChannelsController.close();
|
_unreadChannelsController.close();
|
||||||
_totalUnreadCountController.close();
|
_totalUnreadCountController.close();
|
||||||
channels.values.forEach((c) => c.dispose());
|
channels.values.forEach((c) => c.dispose());
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ class ChannelApi {
|
|||||||
) async {
|
) async {
|
||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'${_getChannelUrl(channelId, channelType)}/stop-watching',
|
'${_getChannelUrl(channelId, channelType)}/stop-watching',
|
||||||
|
data: {},
|
||||||
);
|
);
|
||||||
return EmptyResponse.fromJson(response.data);
|
return EmptyResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class QueryChannelsResponse extends _BaseResponse {
|
|||||||
@JsonSerializable(createToJson: false)
|
@JsonSerializable(createToJson: false)
|
||||||
class TranslateMessageResponse extends _BaseResponse {
|
class TranslateMessageResponse extends _BaseResponse {
|
||||||
/// Translated message
|
/// Translated message
|
||||||
late TranslatedMessage message;
|
late Message message;
|
||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
static TranslateMessageResponse fromJson(Map<String, dynamic> json) =>
|
static TranslateMessageResponse fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ TranslateMessageResponse _$TranslateMessageResponseFromJson(
|
|||||||
Map<String, dynamic> json) {
|
Map<String, dynamic> json) {
|
||||||
return TranslateMessageResponse()
|
return TranslateMessageResponse()
|
||||||
..duration = json['duration'] as String?
|
..duration = json['duration'] as String?
|
||||||
..message =
|
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
|
||||||
TranslatedMessage.fromJson(json['message'] as Map<String, dynamic>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryMembersResponse _$QueryMembersResponseFromJson(Map<String, dynamic> json) {
|
QueryMembersResponse _$QueryMembersResponseFromJson(Map<String, dynamic> json) {
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.
|
|||||||
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
||||||
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
|
||||||
import 'package:stream_chat/src/location.dart';
|
import 'package:stream_chat/src/location.dart';
|
||||||
import 'package:stream_chat/version.dart';
|
|
||||||
|
|
||||||
part 'stream_http_client_options.dart';
|
part 'stream_http_client_options.dart';
|
||||||
|
|
||||||
@@ -33,11 +31,14 @@ class StreamHttpClient {
|
|||||||
..options.baseUrl = _options.baseUrl
|
..options.baseUrl = _options.baseUrl
|
||||||
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
|
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
|
||||||
..options.connectTimeout = _options.connectTimeout.inMilliseconds
|
..options.connectTimeout = _options.connectTimeout.inMilliseconds
|
||||||
..options.queryParameters = {'api_key': apiKey}
|
..options.queryParameters = {
|
||||||
|
'api_key': apiKey,
|
||||||
|
..._options.queryParameters,
|
||||||
|
}
|
||||||
..options.headers = {
|
..options.headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Stream-Client': _options.userAgent,
|
|
||||||
'Content-Encoding': 'application/gzip',
|
'Content-Encoding': 'application/gzip',
|
||||||
|
..._options.headers,
|
||||||
}
|
}
|
||||||
..interceptors.addAll([
|
..interceptors.addAll([
|
||||||
if (tokenManager != null) AuthInterceptor(this, tokenManager),
|
if (tokenManager != null) AuthInterceptor(this, tokenManager),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class StreamHttpClientOptions {
|
|||||||
this.location,
|
this.location,
|
||||||
this.connectTimeout = const Duration(seconds: 6),
|
this.connectTimeout = const Duration(seconds: 6),
|
||||||
this.receiveTimeout = const Duration(seconds: 6),
|
this.receiveTimeout = const Duration(seconds: 6),
|
||||||
|
this.queryParameters = const {},
|
||||||
|
this.headers = const {},
|
||||||
}) : _baseUrl = baseUrl ?? _defaultBaseURL;
|
}) : _baseUrl = baseUrl ?? _defaultBaseURL;
|
||||||
|
|
||||||
final String _baseUrl;
|
final String _baseUrl;
|
||||||
@@ -32,8 +34,20 @@ class StreamHttpClientOptions {
|
|||||||
/// received timeout, default to 6s
|
/// received timeout, default to 6s
|
||||||
final Duration receiveTimeout;
|
final Duration receiveTimeout;
|
||||||
|
|
||||||
/// Get the current user agent
|
/// Common query parameters.
|
||||||
String get userAgent => 'stream-chat-dart-client-'
|
///
|
||||||
'${CurrentPlatform.name}-'
|
/// List values use the default [ListFormat.multiCompatible].
|
||||||
'${PACKAGE_VERSION.split('+')[0]}';
|
///
|
||||||
|
/// The value can be overridden per parameter by adding a [MultiParam]
|
||||||
|
/// object wrapping the actual List value and the desired format.
|
||||||
|
final Map<String, Object?> queryParameters;
|
||||||
|
|
||||||
|
/// Http request headers.
|
||||||
|
/// The keys of initial headers will be converted to lowercase,
|
||||||
|
/// for example 'Content-Type' will be converted to 'content-type'.
|
||||||
|
///
|
||||||
|
/// The key of Header Map is case-insensitive
|
||||||
|
/// eg: content-type and Content-Type are
|
||||||
|
/// regard as the same key.
|
||||||
|
final Map<String, Object?> headers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:stream_chat/src/core/models/attachment.dart';
|
import 'package:stream_chat/src/core/models/attachment.dart';
|
||||||
import 'package:stream_chat/src/core/models/reaction.dart';
|
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
|
||||||
import 'package:stream_chat/src/core/models/user.dart';
|
import 'package:stream_chat/src/core/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
part 'message.g.dart';
|
part 'message.g.dart';
|
||||||
@@ -73,6 +73,7 @@ class Message extends Equatable {
|
|||||||
this.extraData = const {},
|
this.extraData = const {},
|
||||||
this.deletedAt,
|
this.deletedAt,
|
||||||
this.status = MessageSendingStatus.sent,
|
this.status = MessageSendingStatus.sent,
|
||||||
|
this.i18n,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
pinExpires = pinExpires?.toUtc(),
|
pinExpires = pinExpires?.toUtc(),
|
||||||
createdAt = createdAt ?? DateTime.now(),
|
createdAt = createdAt ?? DateTime.now(),
|
||||||
@@ -218,6 +219,10 @@ class Message extends Equatable {
|
|||||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||||
final DateTime? deletedAt;
|
final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// A Map of translations.
|
||||||
|
@JsonKey(includeIfNull: false)
|
||||||
|
final Map<String, String>? i18n;
|
||||||
|
|
||||||
/// Known top level fields.
|
/// Known top level fields.
|
||||||
/// Useful for [Serializer] methods.
|
/// Useful for [Serializer] methods.
|
||||||
static const topLevelFields = [
|
static const topLevelFields = [
|
||||||
@@ -248,6 +253,7 @@ class Message extends Equatable {
|
|||||||
'pinned_at',
|
'pinned_at',
|
||||||
'pin_expires',
|
'pin_expires',
|
||||||
'pinned_by',
|
'pinned_by',
|
||||||
|
'i18n',
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Serialize to json
|
/// Serialize to json
|
||||||
@@ -285,6 +291,7 @@ class Message extends Equatable {
|
|||||||
User? pinnedBy,
|
User? pinnedBy,
|
||||||
Map<String, Object?>? extraData,
|
Map<String, Object?>? extraData,
|
||||||
MessageSendingStatus? status,
|
MessageSendingStatus? status,
|
||||||
|
Map<String, String>? i18n,
|
||||||
}) {
|
}) {
|
||||||
assert(() {
|
assert(() {
|
||||||
if (pinExpires is! DateTime &&
|
if (pinExpires is! DateTime &&
|
||||||
@@ -324,6 +331,7 @@ class Message extends Equatable {
|
|||||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||||
pinExpires:
|
pinExpires:
|
||||||
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
|
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
|
||||||
|
i18n: i18n ?? this.i18n,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +366,7 @@ class Message extends Equatable {
|
|||||||
pinnedAt: other.pinnedAt,
|
pinnedAt: other.pinnedAt,
|
||||||
pinExpires: other.pinExpires,
|
pinExpires: other.pinExpires,
|
||||||
pinnedBy: other.pinnedBy,
|
pinnedBy: other.pinnedBy,
|
||||||
|
i18n: other.i18n,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -390,35 +399,6 @@ class Message extends Equatable {
|
|||||||
pinnedBy,
|
pinnedBy,
|
||||||
extraData,
|
extraData,
|
||||||
status,
|
status,
|
||||||
|
i18n,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A translated message
|
|
||||||
/// It has an additional property called [i18n]
|
|
||||||
@JsonSerializable()
|
|
||||||
class TranslatedMessage extends Message {
|
|
||||||
/// Constructor used for json serialization
|
|
||||||
TranslatedMessage(this.i18n) : super();
|
|
||||||
|
|
||||||
/// Create a new instance from a json
|
|
||||||
factory TranslatedMessage.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$TranslatedMessageFromJson(
|
|
||||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// A Map of
|
|
||||||
final Map<String, String>? i18n;
|
|
||||||
|
|
||||||
/// Known top level fields.
|
|
||||||
/// Useful for [Serializer] methods.
|
|
||||||
static final topLevelFields = [
|
|
||||||
'i18n',
|
|
||||||
...Message.topLevelFields,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Serialize to json
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
|
||||||
_$TranslatedMessageToJson(this),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ Message _$MessageFromJson(Map<String, dynamic> json) {
|
|||||||
deletedAt: json['deleted_at'] == null
|
deletedAt: json['deleted_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
i18n: (json['i18n'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,18 +110,6 @@ Map<String, dynamic> _$MessageToJson(Message instance) {
|
|||||||
val['pinned_by'] = readonly(instance.pinnedBy);
|
val['pinned_by'] = readonly(instance.pinnedBy);
|
||||||
val['extra_data'] = instance.extraData;
|
val['extra_data'] = instance.extraData;
|
||||||
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
||||||
|
writeNotNull('i18n', instance.i18n);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
TranslatedMessage _$TranslatedMessageFromJson(Map<String, dynamic> json) {
|
|
||||||
return TranslatedMessage(
|
|
||||||
(json['i18n'] as Map<String, dynamic>?)?.map(
|
|
||||||
(k, e) => MapEntry(k, e as String),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _$TranslatedMessageToJson(TranslatedMessage instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'i18n': instance.i18n,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class OwnUser extends User {
|
|||||||
Map<String, Object?> extraData = const {},
|
Map<String, Object?> extraData = const {},
|
||||||
bool banned = false,
|
bool banned = false,
|
||||||
List<String> teams = const [],
|
List<String> teams = const [],
|
||||||
|
String? language,
|
||||||
}) : super(
|
}) : super(
|
||||||
id: id,
|
id: id,
|
||||||
role: role,
|
role: role,
|
||||||
@@ -37,6 +38,7 @@ class OwnUser extends User {
|
|||||||
extraData: extraData,
|
extraData: extraData,
|
||||||
banned: banned,
|
banned: banned,
|
||||||
teams: teams,
|
teams: teams,
|
||||||
|
language: language,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
@@ -54,6 +56,7 @@ class OwnUser extends User {
|
|||||||
banned: user.banned,
|
banned: user.banned,
|
||||||
extraData: user.extraData,
|
extraData: user.extraData,
|
||||||
teams: user.teams,
|
teams: user.teams,
|
||||||
|
language: user.language,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates a copy of [OwnUser] with specified attributes overridden.
|
/// Creates a copy of [OwnUser] with specified attributes overridden.
|
||||||
@@ -73,6 +76,7 @@ class OwnUser extends User {
|
|||||||
List<Mute>? mutes,
|
List<Mute>? mutes,
|
||||||
int? totalUnreadCount,
|
int? totalUnreadCount,
|
||||||
int? unreadChannels,
|
int? unreadChannels,
|
||||||
|
String? language,
|
||||||
}) =>
|
}) =>
|
||||||
OwnUser(
|
OwnUser(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -89,15 +93,13 @@ class OwnUser extends User {
|
|||||||
mutes: mutes ?? this.mutes,
|
mutes: mutes ?? this.mutes,
|
||||||
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
|
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
|
||||||
unreadChannels: unreadChannels ?? this.unreadChannels,
|
unreadChannels: unreadChannels ?? this.unreadChannels,
|
||||||
|
language: language ?? this.language,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Returns a new [OwnUser] that is a combination of this ownUser
|
/// Returns a new [OwnUser] that is a combination of this ownUser
|
||||||
/// and the given [other] ownUser.
|
/// and the given [other] ownUser.
|
||||||
OwnUser merge(OwnUser? other) {
|
OwnUser merge(OwnUser? other) {
|
||||||
if (other == null) {
|
if (other == null) return this;
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return copyWith(
|
return copyWith(
|
||||||
banned: other.banned,
|
banned: other.banned,
|
||||||
channelMutes: other.channelMutes,
|
channelMutes: other.channelMutes,
|
||||||
@@ -113,6 +115,7 @@ class OwnUser extends User {
|
|||||||
totalUnreadCount: other.totalUnreadCount,
|
totalUnreadCount: other.totalUnreadCount,
|
||||||
unreadChannels: other.unreadChannels,
|
unreadChannels: other.unreadChannels,
|
||||||
updatedAt: other.updatedAt,
|
updatedAt: other.updatedAt,
|
||||||
|
language: other.language,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,5 +36,9 @@ OwnUser _$OwnUserFromJson(Map<String, dynamic> json) {
|
|||||||
online: json['online'] as bool? ?? false,
|
online: json['online'] as bool? ?? false,
|
||||||
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
|
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
|
||||||
banned: json['banned'] as bool? ?? false,
|
banned: json['banned'] as bool? ?? false,
|
||||||
|
teams:
|
||||||
|
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||||
|
[],
|
||||||
|
language: json['language'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class User extends Equatable {
|
|||||||
this.extraData = const {},
|
this.extraData = const {},
|
||||||
this.banned = false,
|
this.banned = false,
|
||||||
this.teams = const [],
|
this.teams = const [],
|
||||||
|
this.language,
|
||||||
}) : createdAt = createdAt ?? DateTime.now(),
|
}) : createdAt = createdAt ?? DateTime.now(),
|
||||||
updatedAt = updatedAt ?? DateTime.now();
|
updatedAt = updatedAt ?? DateTime.now();
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class User extends Equatable {
|
|||||||
'online',
|
'online',
|
||||||
'banned',
|
'banned',
|
||||||
'teams',
|
'teams',
|
||||||
|
'language',
|
||||||
];
|
];
|
||||||
|
|
||||||
/// User id
|
/// User id
|
||||||
@@ -82,8 +84,11 @@ class User extends Equatable {
|
|||||||
)
|
)
|
||||||
final Map<String, Object?> extraData;
|
final Map<String, Object?> extraData;
|
||||||
|
|
||||||
@override
|
/// The language this user prefers.
|
||||||
int get hashCode => id.hashCode;
|
///
|
||||||
|
/// Defaults to 'en'.
|
||||||
|
@JsonKey(includeIfNull: false)
|
||||||
|
final String? language;
|
||||||
|
|
||||||
/// Shortcut for user name
|
/// Shortcut for user name
|
||||||
String get name {
|
String get name {
|
||||||
@@ -98,11 +103,6 @@ class User extends Equatable {
|
|||||||
static List<String>? toIds(List<User>? users) =>
|
static List<String>? toIds(List<User>? users) =>
|
||||||
users?.map((u) => u.id).toList();
|
users?.map((u) => u.id).toList();
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is User && runtimeType == other.runtimeType && id == other.id;
|
|
||||||
|
|
||||||
/// Serialize to json
|
/// Serialize to json
|
||||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||||
_$UserToJson(this),
|
_$UserToJson(this),
|
||||||
@@ -119,6 +119,7 @@ class User extends Equatable {
|
|||||||
Map<String, Object?>? extraData,
|
Map<String, Object?>? extraData,
|
||||||
bool? banned,
|
bool? banned,
|
||||||
List<String>? teams,
|
List<String>? teams,
|
||||||
|
String? language,
|
||||||
}) =>
|
}) =>
|
||||||
User(
|
User(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -130,18 +131,9 @@ class User extends Equatable {
|
|||||||
extraData: extraData ?? this.extraData,
|
extraData: extraData ?? this.extraData,
|
||||||
banned: banned ?? this.banned,
|
banned: banned ?? this.banned,
|
||||||
teams: teams ?? this.teams,
|
teams: teams ?? this.teams,
|
||||||
|
language: language ?? this.language,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [id];
|
||||||
id,
|
|
||||||
role,
|
|
||||||
teams,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
lastActive,
|
|
||||||
online,
|
|
||||||
banned,
|
|
||||||
extraData,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ User _$UserFromJson(Map<String, dynamic> json) {
|
|||||||
teams:
|
teams:
|
||||||
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||||
[],
|
[],
|
||||||
|
language: json['language'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,5 +48,6 @@ Map<String, dynamic> _$UserToJson(User instance) {
|
|||||||
writeNotNull('online', readonly(instance.online));
|
writeNotNull('online', readonly(instance.online));
|
||||||
writeNotNull('banned', readonly(instance.banned));
|
writeNotNull('banned', readonly(instance.banned));
|
||||||
val['extra_data'] = instance.extraData;
|
val['extra_data'] = instance.extraData;
|
||||||
|
writeNotNull('language', instance.language);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,15 @@ class WebSocket with TimerHelper {
|
|||||||
this.reconnectionMonitorInterval = 10,
|
this.reconnectionMonitorInterval = 10,
|
||||||
this.healthCheckInterval = 20,
|
this.healthCheckInterval = 20,
|
||||||
this.reconnectionMonitorTimeout = 40,
|
this.reconnectionMonitorTimeout = 40,
|
||||||
|
this.queryParameters = const {},
|
||||||
}) : _logger = logger;
|
}) : _logger = logger;
|
||||||
|
|
||||||
///
|
///
|
||||||
final String apiKey;
|
final String apiKey;
|
||||||
|
|
||||||
|
/// Additional query parameters to be added to the websocket url
|
||||||
|
final Map<String, Object?> queryParameters;
|
||||||
|
|
||||||
/// WS base url
|
/// WS base url
|
||||||
final String baseUrl;
|
final String baseUrl;
|
||||||
|
|
||||||
@@ -156,6 +160,7 @@ class WebSocket with TimerHelper {
|
|||||||
'api_key': apiKey,
|
'api_key': apiKey,
|
||||||
'authorization': token.rawValue,
|
'authorization': token.rawValue,
|
||||||
'stream-auth-type': token.authType.raw,
|
'stream-auth-type': token.authType.raw,
|
||||||
|
...queryParameters,
|
||||||
};
|
};
|
||||||
final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws';
|
final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws';
|
||||||
final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), '');
|
final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), '');
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
|
|||||||
/// Current package version
|
/// Current package version
|
||||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||||
// ignore: constant_identifier_names
|
// ignore: constant_identifier_names
|
||||||
const PACKAGE_VERSION = '2.0.0';
|
const PACKAGE_VERSION = '2.1.0';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: stream_chat
|
name: stream_chat
|
||||||
homepage: https://getstream.io/
|
homepage: https://getstream.io/
|
||||||
description: The official Dart client for Stream Chat, a service for building chat applications.
|
description: The official Dart client for Stream Chat, a service for building chat applications.
|
||||||
version: 2.0.0
|
version: 2.1.0
|
||||||
repository: https://github.com/GetStream/stream-chat-flutter
|
repository: https://github.com/GetStream/stream-chat-flutter
|
||||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
|
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
|
||||||
"role": "test-role",
|
"role": "test-role",
|
||||||
"name": "John"
|
"name": "John",
|
||||||
|
"language": "en"
|
||||||
}
|
}
|
||||||
@@ -1610,9 +1610,12 @@ void main() {
|
|||||||
const messageId = 'test-message-id';
|
const messageId = 'test-message-id';
|
||||||
const language = 'hi'; // Hindi
|
const language = 'hi'; // Hindi
|
||||||
const translatedMessageText = 'नमस्ते';
|
const translatedMessageText = 'नमस्ते';
|
||||||
final translatedMessage = TranslatedMessage(const {
|
|
||||||
|
final translatedMessage = Message(
|
||||||
|
i18n: const {
|
||||||
language: translatedMessageText,
|
language: translatedMessageText,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
when(() => client.translateMessage(messageId, language)).thenAnswer(
|
when(() => client.translateMessage(messageId, language)).thenAnswer(
|
||||||
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ void main() {
|
|||||||
|
|
||||||
group('`.openConnection`', () {
|
group('`.openConnection`', () {
|
||||||
test('should throw if state does not contain user', () async {
|
test('should throw if state does not contain user', () async {
|
||||||
expect(client.state.user, isNull);
|
expect(client.state.currentUser, isNull);
|
||||||
try {
|
try {
|
||||||
await client.openConnection();
|
await client.openConnection();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -164,7 +164,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw if connection is already in progress', () async {
|
test('should throw if connection is already in progress', () async {
|
||||||
expect(client.state.user, isNull);
|
expect(client.state.currentUser, isNull);
|
||||||
try {
|
try {
|
||||||
await client.connectAnonymousUser();
|
await client.connectAnonymousUser();
|
||||||
await client.openConnection();
|
await client.openConnection();
|
||||||
@@ -179,7 +179,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw if connection is already available', () async {
|
test('should throw if connection is already available', () async {
|
||||||
expect(client.state.user, isNull);
|
expect(client.state.currentUser, isNull);
|
||||||
try {
|
try {
|
||||||
await client.connectAnonymousUser();
|
await client.connectAnonymousUser();
|
||||||
// waiting 300ms for `wsConnectionStatusStream` to emit
|
// waiting 300ms for `wsConnectionStatusStream` to emit
|
||||||
@@ -799,7 +799,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('`.disconnectUser` should reset state and user', () async {
|
test('`.disconnectUser` should reset state and user', () async {
|
||||||
expect(client.state.user, isNotNull);
|
expect(client.state.currentUser, isNotNull);
|
||||||
expect(client.wsConnectionStatus, ConnectionStatus.connected);
|
expect(client.wsConnectionStatus, ConnectionStatus.connected);
|
||||||
|
|
||||||
expectLater(
|
expectLater(
|
||||||
@@ -810,7 +810,7 @@ void main() {
|
|||||||
|
|
||||||
await client.disconnectUser();
|
await client.disconnectUser();
|
||||||
|
|
||||||
expect(client.state.user, isNull);
|
expect(client.state.currentUser, isNull);
|
||||||
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
|
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2109,9 +2109,11 @@ void main() {
|
|||||||
const messageId = 'test-message-id';
|
const messageId = 'test-message-id';
|
||||||
const language = 'hi'; // Hindi
|
const language = 'hi'; // Hindi
|
||||||
const translatedMessageText = 'नमस्ते';
|
const translatedMessageText = 'नमस्ते';
|
||||||
final translatedMessage = TranslatedMessage(const {
|
final translatedMessage = Message(
|
||||||
|
i18n: const {
|
||||||
language: translatedMessageText,
|
language: translatedMessageText,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
when(() => api.message.translateMessage(messageId, language)).thenAnswer(
|
when(() => api.message.translateMessage(messageId, language)).thenAnswer(
|
||||||
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
||||||
|
|||||||
@@ -595,14 +595,14 @@ void main() {
|
|||||||
|
|
||||||
final path = '${_getChannelUrl(channelId, channelType)}/stop-watching';
|
final path = '${_getChannelUrl(channelId, channelType)}/stop-watching';
|
||||||
|
|
||||||
when(() => client.post(path)).thenAnswer(
|
when(() => client.post(path, data: {})).thenAnswer(
|
||||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||||
|
|
||||||
final res = await channelApi.stopWatching(channelId, channelType);
|
final res = await channelApi.stopWatching(channelId, channelType);
|
||||||
|
|
||||||
expect(res, isNotNull);
|
expect(res, isNotNull);
|
||||||
|
|
||||||
verify(() => client.post(path)).called(1);
|
verify(() => client.post(path, data: {})).called(1);
|
||||||
verifyNoMoreInteractions(client);
|
verifyNoMoreInteractions(client);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,9 +371,11 @@ void main() {
|
|||||||
final path = '/messages/${message.id}/translate';
|
final path = '/messages/${message.id}/translate';
|
||||||
|
|
||||||
const translatedMessageText = 'नमस्ते';
|
const translatedMessageText = 'नमस्ते';
|
||||||
final translatedMessage = TranslatedMessage(const {
|
final translatedMessage = Message(
|
||||||
|
i18n: const {
|
||||||
language: translatedMessageText,
|
language: translatedMessageText,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
when(() => client.post(
|
when(() => client.post(
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ void main() {
|
|||||||
expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com');
|
expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com');
|
||||||
expect(options.connectTimeout, const Duration(seconds: 6));
|
expect(options.connectTimeout, const Duration(seconds: 6));
|
||||||
expect(options.receiveTimeout, const Duration(seconds: 6));
|
expect(options.receiveTimeout, const Duration(seconds: 6));
|
||||||
|
expect(options.queryParameters, const {});
|
||||||
|
expect(options.headers, const {});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should override all the default set params', () {
|
test('should override all the default set params', () {
|
||||||
@@ -16,11 +18,15 @@ void main() {
|
|||||||
baseUrl: 'base-url',
|
baseUrl: 'base-url',
|
||||||
connectTimeout: Duration(seconds: 3),
|
connectTimeout: Duration(seconds: 3),
|
||||||
receiveTimeout: Duration(seconds: 3),
|
receiveTimeout: Duration(seconds: 3),
|
||||||
|
headers: {'test': 'test'},
|
||||||
|
queryParameters: {'123': '123'},
|
||||||
);
|
);
|
||||||
expect(options.location, isNull);
|
expect(options.location, isNull);
|
||||||
expect(options.baseUrl, 'base-url');
|
expect(options.baseUrl, 'base-url');
|
||||||
expect(options.connectTimeout, const Duration(seconds: 3));
|
expect(options.connectTimeout, const Duration(seconds: 3));
|
||||||
expect(options.receiveTimeout, const Duration(seconds: 3));
|
expect(options.receiveTimeout, const Duration(seconds: 3));
|
||||||
|
expect(options.headers, {'test': 'test'});
|
||||||
|
expect(options.queryParameters, {'123': '123'});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('should create baseUrl according to provided location', () {
|
group('should create baseUrl according to provided location', () {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void main() {
|
|||||||
await client.get('path');
|
await client.get('path');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
verify(() => logger.info(any())).called(16);
|
verify(() => logger.info(any())).called(greaterThan(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loggingInterceptor should log error', () async {
|
test('loggingInterceptor should log error', () async {
|
||||||
@@ -108,7 +108,7 @@ void main() {
|
|||||||
await client.get('path');
|
await client.get('path');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
verify(() => logger.severe(any())).called(8);
|
verify(() => logger.severe(any())).called(greaterThan(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('`.lock` should lock the dio client', () async {
|
test('`.lock` should lock the dio client', () async {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ void main() {
|
|||||||
expect(message.pinnedAt, null);
|
expect(message.pinnedAt, null);
|
||||||
expect(message.pinExpires, null);
|
expect(message.pinExpires, null);
|
||||||
expect(message.pinnedBy, null);
|
expect(message.pinnedBy, null);
|
||||||
|
expect(message.i18n, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should serialize to json correctly', () {
|
test('should serialize to json correctly', () {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ void main() {
|
|||||||
expect(newUser.id, user.id);
|
expect(newUser.id, user.id);
|
||||||
expect(newUser.role, user.role);
|
expect(newUser.role, user.role);
|
||||||
expect(newUser.name, user.name);
|
expect(newUser.name, user.name);
|
||||||
|
expect(newUser.language, user.language);
|
||||||
|
|
||||||
newUser = user.copyWith(
|
newUser = user.copyWith(
|
||||||
id: 'test',
|
id: 'test',
|
||||||
@@ -41,6 +42,7 @@ void main() {
|
|||||||
expect(newUser.id, 'test');
|
expect(newUser.id, 'test');
|
||||||
expect(newUser.role, 'test');
|
expect(newUser.role, 'test');
|
||||||
expect(newUser.name, 'test');
|
expect(newUser.name, 'test');
|
||||||
|
expect(newUser.language, 'en');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class FakeChatApi extends Fake implements StreamChatApi {
|
|||||||
|
|
||||||
class FakeClientState extends Fake implements ClientState {
|
class FakeClientState extends Fake implements ClientState {
|
||||||
@override
|
@override
|
||||||
OwnUser? get user => OwnUser(id: 'test-user-id');
|
OwnUser? get currentUser => OwnUser(id: 'test-user-id');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int totalUnreadCount = 0;
|
int totalUnreadCount = 0;
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- Added `MessageListView.paginationLimit`
|
||||||
|
- `MessageText` renders message translation if available
|
||||||
|
- Allow the various ListView widgets to be themed via ThemeData classes
|
||||||
|
- Added `bottomRowBuilder` and `deletedBottomRowBuilder` that build a widget below a `MessageWidget`
|
||||||
|
|
||||||
|
🔄 Changed
|
||||||
|
|
||||||
|
- `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`.
|
||||||
|
- `StreamChat.of(context).userStream` is now deprecated in favor of `StreamChat.of(context).currentUserStream`.
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- Fix floating date divider not having a fixed size
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
🛑️ Breaking Changes from `1.5.4`
|
🛑️ Breaking Changes from `1.5.4`
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
|||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
@@ -41,7 +41,7 @@ android {
|
|||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.example"
|
applicationId "com.example.example"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.50'
|
ext.kotlin_version = '1.5.20'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
|
||||||
|
|
||||||
/// A chat-persisted StreamChatClient
|
|
||||||
final chatPersistentClient = StreamChatPersistenceClient(
|
|
||||||
logLevel: Level.INFO,
|
|
||||||
);
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -15,7 +12,7 @@ void main() async {
|
|||||||
final client = StreamChatClient(
|
final client = StreamChatClient(
|
||||||
's2dxdhpxd94g',
|
's2dxdhpxd94g',
|
||||||
logLevel: Level.INFO,
|
logLevel: Level.INFO,
|
||||||
)..chatPersistenceClient = chatPersistentClient;
|
);
|
||||||
|
|
||||||
/// Set the current user and connect the websocket. In a production
|
/// Set the current user and connect the websocket. In a production
|
||||||
/// scenario, this should be done using a backend to generate a user token
|
/// scenario, this should be done using a backend to generate a user token
|
||||||
@@ -72,6 +69,13 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) => MaterialApp(
|
Widget build(BuildContext context) => MaterialApp(
|
||||||
theme: ThemeData.light(),
|
theme: ThemeData.light(),
|
||||||
darkTheme: ThemeData.dark(),
|
darkTheme: ThemeData.dark(),
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('en'),
|
||||||
|
Locale('hi'),
|
||||||
|
Locale('fr'),
|
||||||
|
Locale('it'),
|
||||||
|
],
|
||||||
|
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||||
builder: (context, widget) => StreamChat(
|
builder: (context, widget) => StreamChat(
|
||||||
client: client,
|
client: client,
|
||||||
child: widget,
|
child: widget,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
: null,
|
: null,
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
sort: const [SortOption('last_message_at')],
|
sort: const [SortOption('last_message_at')],
|
||||||
pagination: const PaginationParams(
|
pagination: const PaginationParams(
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
sort: const [SortOption('last_message_at')],
|
sort: const [SortOption('last_message_at')],
|
||||||
pagination: const PaginationParams(
|
pagination: const PaginationParams(
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
channelPreviewBuilder: _channelPreviewBuilder,
|
channelPreviewBuilder: _channelPreviewBuilder,
|
||||||
// sort: [SortOption('last_message_at')],
|
// sort: [SortOption('last_message_at')],
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
sort: const [SortOption('last_message_at')],
|
sort: const [SortOption('last_message_at')],
|
||||||
pagination: const PaginationParams(
|
pagination: const PaginationParams(
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
sort: const [SortOption('last_message_at')],
|
sort: const [SortOption('last_message_at')],
|
||||||
pagination: const PaginationParams(
|
pagination: const PaginationParams(
|
||||||
@@ -115,7 +115,8 @@ class ChannelPage extends StatelessWidget {
|
|||||||
MessageWidget _,
|
MessageWidget _,
|
||||||
) {
|
) {
|
||||||
final message = details.message;
|
final message = details.message;
|
||||||
final isCurrentUser = StreamChat.of(context).user!.id == message.user!.id;
|
final isCurrentUser =
|
||||||
|
StreamChat.of(context).currentUser!.id == message.user!.id;
|
||||||
final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left;
|
final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left;
|
||||||
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
|
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: Filter.in_(
|
filter: Filter.in_(
|
||||||
'members',
|
'members',
|
||||||
[StreamChat.of(context).user!.id],
|
[StreamChat.of(context).currentUser!.id],
|
||||||
),
|
),
|
||||||
sort: const [SortOption('last_message_at')],
|
sort: const [SortOption('last_message_at')],
|
||||||
pagination: const PaginationParams(
|
pagination: const PaginationParams(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Flutter-related
|
||||||
|
**/Flutter/ephemeral/
|
||||||
|
**/Pods/
|
||||||
|
|
||||||
|
# Xcode-related
|
||||||
|
**/xcuserdata/
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
@@ -0,0 +1,635 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 51;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXAggregateTarget section */
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||||
|
isa = PBXAggregateTarget;
|
||||||
|
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||||
|
buildPhases = (
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = "Flutter Assemble";
|
||||||
|
productName = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
|
7A465D4E5940248C04D2D4E3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||||
|
remoteInfo = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Bundle Framework";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
2C2B248A2BB89C8B353A7D81 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||||
|
5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
7A465D4E5940248C04D2D4E3 /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Configs;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10E42044A3C60003C045 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33FAB671232836740065AC1E /* Runner */,
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||||
|
35E4E72D48C70FBCFEDFB30C /* Pods */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10ED2044A3C60003C045 /* example.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC11242044D66E0003C045 /* Resources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||||
|
);
|
||||||
|
name = Resources;
|
||||||
|
path = ..;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33FAB671232836740065AC1E /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */,
|
||||||
|
33CC11242044D66E0003C045 /* Resources */,
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
35E4E72D48C70FBCFEDFB30C /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
2C2B248A2BB89C8B353A7D81 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
C4CD72858CD59598795BB48E /* Pods-Runner.release.xcconfig */,
|
||||||
|
2BCA7399119839DE435DACD6 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5ED1F4FA50EB1433A201473C /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
72C2655B408A295073A1CEB6 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
|
F8C1BBEE8C9F4830ECBA88A2 /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 33CC10ED2044A3C60003C045 /* example.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastSwiftUpdateCheck = 0920;
|
||||||
|
LastUpgradeCheck = 0930;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
33CC10EC2044A3C60003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
SystemCapabilities = {
|
||||||
|
com.apple.Sandbox = {
|
||||||
|
enabled = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
33CC111A2044C6BA0003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 33CC10E42044A3C60003C045;
|
||||||
|
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */,
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||||
|
};
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
Flutter/ephemeral/tripwire,
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||||
|
};
|
||||||
|
72C2655B408A295073A1CEB6 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
F8C1BBEE8C9F4830ECBA88A2 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||||
|
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F52044A3C60003C045 /* Base */,
|
||||||
|
);
|
||||||
|
name = MainMenu.xib;
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */,
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */,
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */,
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */,
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1000"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
@NSApplicationMain
|
||||||
|
class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_16.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_32.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_32.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_64.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_128.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_256.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_256.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_512.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_512.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_1024.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,339 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||||
|
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
|
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||||
|
<items>
|
||||||
|
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
||||||
|
<items>
|
||||||
|
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||||
|
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||||
|
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||||
|
<menuItem title="Services" id="NMo-om-nkz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
|
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
|
<connections>
|
||||||
|
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||||
|
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
|
<connections>
|
||||||
|
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||||
|
<connections>
|
||||||
|
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||||
|
<connections>
|
||||||
|
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||||
|
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||||
|
<connections>
|
||||||
|
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||||
|
<connections>
|
||||||
|
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||||
|
<connections>
|
||||||
|
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||||
|
<connections>
|
||||||
|
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||||
|
<menuItem title="Find" id="4EN-yA-p0u">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||||
|
<connections>
|
||||||
|
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||||
|
<connections>
|
||||||
|
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||||
|
<connections>
|
||||||
|
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||||
|
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||||
|
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="View" id="H8h-7b-M4v">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||||
|
<connections>
|
||||||
|
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||||
|
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
<point key="canvasLocation" x="142" y="-258"/>
|
||||||
|
</menu>
|
||||||
|
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||||
|
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
</window>
|
||||||
|
</objects>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Application-level settings for the Runner target.
|
||||||
|
//
|
||||||
|
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
||||||
|
// future. If not, the values below would default to using the project name when this becomes a
|
||||||
|
// 'flutter create' template.
|
||||||
|
|
||||||
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
|
PRODUCT_NAME = example
|
||||||
|
|
||||||
|
// The application's bundle identifier
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.example
|
||||||
|
|
||||||
|
// The copyright displayed in application information
|
||||||
|
PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||||
|
#include "Warnings.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||||
|
#include "Warnings.xcconfig"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES
|
||||||
|
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||||
|
CLANG_WARN_PRAGMA_PACK = YES
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES
|
||||||
|
CLANG_WARN_COMMA = YES
|
||||||
|
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
||||||
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
||||||
|
GCC_WARN_SHADOW = YES
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
class MainFlutterWindow: NSWindow {
|
||||||
|
override func awakeFromNib() {
|
||||||
|
let flutterViewController = FlutterViewController.init()
|
||||||
|
let windowFrame = self.frame
|
||||||
|
self.contentViewController = flutterViewController
|
||||||
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
|
super.awakeFromNib()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -33,6 +33,8 @@ dependencies:
|
|||||||
# path: ../../stream_chat_flutter_core
|
# path: ../../stream_chat_flutter_core
|
||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
path: ../
|
path: ../
|
||||||
|
stream_chat_localizations:
|
||||||
|
path: ../../stream_chat_localizations
|
||||||
stream_chat_persistence:
|
stream_chat_persistence:
|
||||||
path: ../../stream_chat_persistence
|
path: ../../stream_chat_persistence
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
|
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Widget to build in progress
|
/// Widget to build in progress
|
||||||
typedef InProgressBuilder = Widget Function(BuildContext, int, int);
|
typedef InProgressBuilder = Widget Function(BuildContext, int, int);
|
||||||
@@ -226,7 +227,7 @@ class _FailedState extends StatelessWidget {
|
|||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'UPLOAD ERROR',
|
context.translations.uploadErrorLabel,
|
||||||
style: theme.textTheme.footnote.copyWith(
|
style: theme.textTheme.footnote.copyWith(
|
||||||
color: theme.colorTheme.barsBg,
|
color: theme.colorTheme.barsBg,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
|
|||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
// ignore: always_use_package_imports
|
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||||
import 'attachment_widget.dart';
|
|
||||||
|
|
||||||
/// Widget for displaying file attachments
|
/// Widget for displaying file attachments
|
||||||
class FileAttachment extends AttachmentWidget {
|
class FileAttachment extends AttachmentWidget {
|
||||||
@@ -76,7 +76,7 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
attachment.title ?? 'File',
|
attachment.title ?? context.translations.fileText,
|
||||||
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -286,7 +286,10 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
progressIndicatorColor: theme.colorTheme.accentPrimary,
|
progressIndicatorColor: theme.colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
success: () => Text(fileSize(size), style: textStyle),
|
success: () => Text(fileSize(size), style: textStyle),
|
||||||
failed: (_) => Text('UPLOAD ERROR', style: textStyle),
|
failed: (_) => Text(
|
||||||
|
context.translations.uploadErrorLabel,
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/visible_footnote.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Widget for showing a GIF attachment
|
/// Widget for showing a GIF attachment
|
||||||
class GiphyAttachment extends AttachmentWidget {
|
class GiphyAttachment extends AttachmentWidget {
|
||||||
@@ -71,9 +73,9 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.giphyIcon(),
|
StreamSvgIcon.giphyIcon(),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Text(
|
Text(
|
||||||
'Giphy',
|
context.translations.giphyLabel,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
if (attachment.title != null)
|
if (attachment.title != null)
|
||||||
@@ -134,7 +136,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Cancel',
|
context.translations.cancelLabel.toLowerCase(),
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyBold
|
.bodyBold
|
||||||
@@ -166,7 +168,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Shuffle',
|
context.translations.shuffleLabel,
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyBold
|
.bodyBold
|
||||||
@@ -199,7 +201,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Send',
|
context.translations.sendLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
@@ -216,36 +218,11 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Align(
|
const Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: Row(
|
child: VisibleFootnote(),
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
StreamSvgIcon.eye(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textHighEmphasis
|
|
||||||
.withOpacity(0.5),
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Only visible to you',
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.footnote
|
|
||||||
.copyWith(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.textHighEmphasis
|
|
||||||
.withOpacity(0.5)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -339,7 +316,7 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'GIPHY',
|
context.translations.giphyLabel.toUpperCase(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
StreamChatTheme.of(context).colorTheme.barsBg,
|
StreamChatTheme.of(context).colorTheme.barsBg,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
child: _buildPage(context),
|
child: _buildPage(context),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPage(context) {
|
Widget _buildPage(BuildContext context) {
|
||||||
final theme = StreamChatTheme.of(context);
|
final theme = StreamChatTheme.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
@@ -69,7 +69,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_buildButton(
|
_buildButton(
|
||||||
context,
|
context,
|
||||||
'Reply',
|
context.translations.replyLabel,
|
||||||
StreamSvgIcon.iconCurveLineLeftUp(
|
StreamSvgIcon.iconCurveLineLeftUp(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: theme.colorTheme.textLowEmphasis,
|
color: theme.colorTheme.textLowEmphasis,
|
||||||
@@ -80,7 +80,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_buildButton(
|
_buildButton(
|
||||||
context,
|
context,
|
||||||
'Show in Chat',
|
context.translations.showInChatLabel,
|
||||||
StreamSvgIcon.eye(
|
StreamSvgIcon.eye(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: theme.colorTheme.textHighEmphasis,
|
color: theme.colorTheme.textHighEmphasis,
|
||||||
@@ -89,8 +89,9 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
_buildButton(
|
_buildButton(
|
||||||
context,
|
context,
|
||||||
// ignore: lines_longer_than_80_chars
|
message.attachments[currentIndex].type == 'video'
|
||||||
'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}',
|
? context.translations.saveVideoLabel
|
||||||
|
: context.translations.saveImageLabel,
|
||||||
StreamSvgIcon.iconSave(
|
StreamSvgIcon.iconSave(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: theme.colorTheme.textLowEmphasis,
|
color: theme.colorTheme.textLowEmphasis,
|
||||||
@@ -138,10 +139,11 @@ class AttachmentActionsModal extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (StreamChat.of(context).user?.id == message.user?.id)
|
if (StreamChat.of(context).currentUser?.id ==
|
||||||
|
message.user?.id)
|
||||||
_buildButton(
|
_buildButton(
|
||||||
context,
|
context,
|
||||||
'Delete',
|
context.translations.deleteLabel.capitalize(),
|
||||||
StreamSvgIcon.delete(
|
StreamSvgIcon.delete(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: theme.colorTheme.accentError,
|
color: theme.colorTheme.accentError,
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class ChannelAvatar extends StatelessWidget {
|
|||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
final currentUser = streamChat.user!;
|
final currentUser = streamChat.currentUser!;
|
||||||
final otherMembers = channel.state!.members
|
final otherMembers = channel.state!.members
|
||||||
.where((it) => it.userId != currentUser.id)
|
.where((it) => it.userId != currentUser.id)
|
||||||
.toList(growable: false);
|
.toList(growable: false);
|
||||||
@@ -149,7 +149,7 @@ class ChannelAvatar extends StatelessWidget {
|
|||||||
// our own space, no other members
|
// our own space, no other members
|
||||||
if (otherMembers.isEmpty) {
|
if (otherMembers.isEmpty) {
|
||||||
return BetterStreamBuilder<User>(
|
return BetterStreamBuilder<User>(
|
||||||
stream: streamChat.client.state.userStream.map((it) => it!),
|
stream: streamChat.client.state.currentUserStream.map((it) => it!),
|
||||||
initialData: currentUser,
|
initialData: currentUser,
|
||||||
builder: (context, user) => UserAvatar(
|
builder: (context, user) => UserAvatar(
|
||||||
borderRadius: borderRadius ?? previewTheme?.borderRadius,
|
borderRadius: borderRadius ?? previewTheme?.borderRadius,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/channel_info.dart';
|
import 'package:stream_chat_flutter/src/channel_info.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Bottom Sheet with options
|
/// Bottom Sheet with options
|
||||||
class ChannelBottomSheet extends StatefulWidget {
|
class ChannelBottomSheet extends StatefulWidget {
|
||||||
@@ -27,8 +28,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
|
|
||||||
final members = channel.state?.members ?? [];
|
final members = channel.state?.members ?? [];
|
||||||
|
|
||||||
final userAsMember =
|
final userAsMember = members
|
||||||
members.firstWhere((e) => e.user?.id == _streamChatState.user?.id);
|
.firstWhere((e) => e.user?.id == _streamChatState.currentUser?.id);
|
||||||
final isOwner = userAsMember.role == 'owner';
|
final isOwner = userAsMember.role == 'owner';
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -149,7 +150,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'View Info',
|
title: context.translations.viewInfoLabel,
|
||||||
onTap: widget.onViewInfoTap,
|
onTap: widget.onViewInfoTap,
|
||||||
),
|
),
|
||||||
if (!channel.isDistinct)
|
if (!channel.isDistinct)
|
||||||
@@ -160,7 +161,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'Leave Group',
|
title: context.translations.leaveGroupLabel,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showActions = false;
|
_showActions = false;
|
||||||
@@ -179,7 +180,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
color: _streamChatThemeData.colorTheme.accentError,
|
color: _streamChatThemeData.colorTheme.accentError,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'Delete Conversation',
|
title: context.translations.deleteConversationLabel,
|
||||||
titleColor: _streamChatThemeData.colorTheme.accentError,
|
titleColor: _streamChatThemeData.colorTheme.accentError,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -198,7 +199,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
color: _streamChatThemeData.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'Cancel',
|
title: context.translations.cancelLabel,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
@@ -219,10 +220,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
Future<void> _showDeleteDialog() async {
|
Future<void> _showDeleteDialog() async {
|
||||||
final res = await showConfirmationDialog(
|
final res = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Delete Conversation',
|
title: context.translations.deleteConversationLabel,
|
||||||
okText: 'DELETE',
|
okText: context.translations.deleteLabel,
|
||||||
question: 'Are you sure you want to delete this conversation?',
|
question: context.translations.deleteConversationQuestion,
|
||||||
cancelText: 'CANCEL',
|
cancelText: context.translations.cancelLabel,
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: _streamChatThemeData.colorTheme.accentError,
|
color: _streamChatThemeData.colorTheme.accentError,
|
||||||
),
|
),
|
||||||
@@ -237,17 +238,17 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
|||||||
Future<void> _showLeaveDialog() async {
|
Future<void> _showLeaveDialog() async {
|
||||||
final res = await showConfirmationDialog(
|
final res = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Leave conversation',
|
title: context.translations.leaveConversationLabel,
|
||||||
okText: 'LEAVE',
|
okText: context.translations.leaveLabel,
|
||||||
question: 'Are you sure you want to leave this conversation?',
|
question: context.translations.leaveConversationQuestion,
|
||||||
cancelText: 'CANCEL',
|
cancelText: context.translations.cancelLabel,
|
||||||
icon: StreamSvgIcon.userRemove(
|
icon: StreamSvgIcon.userRemove(
|
||||||
color: _streamChatThemeData.colorTheme.accentError,
|
color: _streamChatThemeData.colorTheme.accentError,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
final channel = _streamChannelState.channel;
|
final channel = _streamChannelState.channel;
|
||||||
final user = _streamChatState.user;
|
final user = _streamChatState.currentUser;
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
await channel.removeMembers([user.id]);
|
await channel.removeMembers([user.id]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/src/info_tile.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/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
@@ -121,14 +122,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ConnectionStatus.connected:
|
case ConnectionStatus.connected:
|
||||||
statusString = 'Connected';
|
statusString = context.translations.connectedLabel;
|
||||||
showStatus = false;
|
showStatus = false;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.connecting:
|
case ConnectionStatus.connecting:
|
||||||
statusString = 'Reconnecting...';
|
statusString = context.translations.reconnectingLabel;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.disconnected:
|
case ConnectionStatus.disconnected:
|
||||||
statusString = 'Disconnected';
|
statusString = context.translations.disconnectedLabel;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart' show IterableExtension;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Widget which shows channel info
|
/// Widget which shows channel info
|
||||||
class ChannelInfo extends StatelessWidget {
|
class ChannelInfo extends StatelessWidget {
|
||||||
@@ -55,10 +56,13 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
) {
|
) {
|
||||||
Widget? alternativeWidget;
|
Widget? alternativeWidget;
|
||||||
|
|
||||||
if (channel.memberCount != null && channel.memberCount! > 2) {
|
final memberCount = channel.memberCount;
|
||||||
var text = '${channel.memberCount} Members';
|
if (memberCount != null && memberCount > 2) {
|
||||||
|
var text = context.translations.membersCountText(memberCount);
|
||||||
final watcherCount = channel.state?.watcherCount ?? 0;
|
final watcherCount = channel.state?.watcherCount ?? 0;
|
||||||
if (watcherCount > 0) text += ' $watcherCount Online';
|
if (watcherCount > 0) {
|
||||||
|
text += ' ${context.translations.watchersCountText(watcherCount)}';
|
||||||
|
}
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
text,
|
text,
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
@@ -67,7 +71,7 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
.subtitle,
|
.subtitle,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final userId = StreamChat.of(context).user?.id;
|
final userId = StreamChat.of(context).currentUser?.id;
|
||||||
final otherMember = members?.firstWhereOrNull(
|
final otherMember = members?.firstWhereOrNull(
|
||||||
(element) => element.userId != userId,
|
(element) => element.userId != userId,
|
||||||
);
|
);
|
||||||
@@ -75,12 +79,13 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
if (otherMember != null) {
|
if (otherMember != null) {
|
||||||
if (otherMember.user?.online == true) {
|
if (otherMember.user?.online == true) {
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
'Online',
|
context.translations.userOnlineText,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}',
|
'${context.translations.userLastOnlineText} '
|
||||||
|
'${Jiffy(otherMember.user?.lastActive).fromNow()}',
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -111,7 +116,7 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
'Searching for Network',
|
context.translations.searchingForNetworkText,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -125,7 +130,7 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Offline...',
|
context.translations.offlineLabel,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -141,7 +146,7 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
..closeConnection()
|
..closeConnection()
|
||||||
..openConnection(),
|
..openConnection(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Try Again',
|
context.translations.tryAgainLabel,
|
||||||
style: textStyle?.copyWith(
|
style: textStyle?.copyWith(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
|
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Widget builder for title
|
/// Widget builder for title
|
||||||
typedef TitleBuilder = Widget Function(
|
typedef TitleBuilder = Widget Function(
|
||||||
@@ -95,7 +96,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _client = client ?? StreamChat.of(context).client;
|
final _client = client ?? StreamChat.of(context).client;
|
||||||
final user = _client.state.user;
|
final user = _client.state.currentUser;
|
||||||
return ConnectionStatusBuilder(
|
return ConnectionStatusBuilder(
|
||||||
statusBuilder: (context, status) {
|
statusBuilder: (context, status) {
|
||||||
var statusString = '';
|
var statusString = '';
|
||||||
@@ -103,21 +104,20 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ConnectionStatus.connected:
|
case ConnectionStatus.connected:
|
||||||
statusString = 'Connected';
|
statusString = context.translations.connectedLabel;
|
||||||
showStatus = false;
|
showStatus = false;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.connecting:
|
case ConnectionStatus.connecting:
|
||||||
statusString = 'Reconnecting...';
|
statusString = context.translations.reconnectingLabel;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.disconnected:
|
case ConnectionStatus.disconnected:
|
||||||
statusString = 'Disconnected';
|
statusString = context.translations.disconnectedLabel;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
return InfoTile(
|
return InfoTile(
|
||||||
// ignore: avoid_bool_literals_in_conditional_expressions
|
showMessage: showConnectionStateTile && showStatus,
|
||||||
showMessage: showConnectionStateTile ? showStatus : false,
|
|
||||||
message: statusString,
|
message: statusString,
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
textTheme: Theme.of(context).textTheme,
|
textTheme: Theme.of(context).textTheme,
|
||||||
@@ -207,7 +207,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
Widget _buildConnectedTitleState(BuildContext context) {
|
Widget _buildConnectedTitleState(BuildContext context) {
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
return Text(
|
return Text(
|
||||||
'Stream Chat',
|
context.translations.streamChatLabel,
|
||||||
style: chatThemeData.textTheme.headlineBold.copyWith(
|
style: chatThemeData.textTheme.headlineBold.copyWith(
|
||||||
color: chatThemeData.colorTheme.textHighEmphasis,
|
color: chatThemeData.colorTheme.textHighEmphasis,
|
||||||
),
|
),
|
||||||
@@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
'Searching for Network',
|
context.translations.searchingForNetworkText,
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
.channelListHeaderTheme
|
.channelListHeaderTheme
|
||||||
.title
|
.title
|
||||||
@@ -247,7 +247,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Offline...',
|
context.translations.offlineLabel,
|
||||||
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -258,7 +258,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
..closeConnection()
|
..closeConnection()
|
||||||
..openConnection(),
|
..openConnection(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Try Again',
|
context.translations.tryAgainLabel,
|
||||||
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
|
|||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Callback called when tapping on a channel
|
/// Callback called when tapping on a channel
|
||||||
typedef ChannelTapCallback = void Function(Channel, Widget?);
|
typedef ChannelTapCallback = void Function(Channel, Widget?);
|
||||||
@@ -231,10 +232,21 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return LazyLoadScrollView(
|
child = LazyLoadScrollView(
|
||||||
onEndOfPage: () => _channelListController.paginateData!(),
|
onEndOfPage: () => _channelListController.paginateData!(),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final backgroundColor = ChannelListViewTheme.of(context).backgroundColor;
|
||||||
|
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
return ColoredBox(
|
||||||
|
color: backgroundColor,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView(BuildContext context, List<Channel> channels) {
|
Widget _buildListView(BuildContext context, List<Channel> channels) {
|
||||||
@@ -290,7 +302,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Let’s start chatting!',
|
context.translations.letsStartChattingLabel,
|
||||||
style: chatThemeData.textTheme.headline,
|
style: chatThemeData.textTheme.headline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -300,7 +312,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
horizontal: 52,
|
horizontal: 52,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'How about sending your first message to a friend?',
|
context.translations.sendingFirstMessageLabel,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: chatThemeData.textTheme.body.copyWith(
|
style: chatThemeData.textTheme.body.copyWith(
|
||||||
color: chatThemeData.colorTheme.textLowEmphasis,
|
color: chatThemeData.colorTheme.textLowEmphasis,
|
||||||
@@ -319,7 +331,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: widget.onStartChatPressed,
|
onPressed: widget.onStartChatPressed,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Start a chat',
|
context.translations.startAChatLabel,
|
||||||
style: chatThemeData.textTheme.bodyBold.copyWith(
|
style: chatThemeData.textTheme.bodyBold.copyWith(
|
||||||
color: chatThemeData.colorTheme.accentPrimary,
|
color: chatThemeData.colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
@@ -455,9 +467,9 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text.rich(
|
Text.rich(
|
||||||
const TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
WidgetSpan(
|
const WidgetSpan(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
right: 2,
|
right: 2,
|
||||||
@@ -465,14 +477,14 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: Icon(Icons.error_outline),
|
child: Icon(Icons.error_outline),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(text: 'Error loading channels'),
|
TextSpan(text: context.translations.loadingChannelsError),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.headline6,
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => _channelListController.loadData!(),
|
onPressed: () => _channelListController.loadData!(),
|
||||||
child: const Text('Retry'),
|
child: Text(context.translations.retryLabel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -541,7 +553,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
'owner',
|
'owner',
|
||||||
].contains(channel.state!.members
|
].contains(channel.state!.members
|
||||||
.firstWhereOrNull(
|
.firstWhereOrNull(
|
||||||
(m) => m.userId == channel.client.state.user?.id)
|
(m) => m.userId == channel.client.state.currentUser?.id)
|
||||||
?.role))
|
?.role))
|
||||||
IconSlideAction(
|
IconSlideAction(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
@@ -550,17 +562,16 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
),
|
),
|
||||||
onTap: widget.onDeletePressed != null
|
onTap: widget.onDeletePressed != null
|
||||||
? () {
|
? () {
|
||||||
widget.onDeletePressed!(channel);
|
widget.onDeletePressed?.call(channel);
|
||||||
}
|
}
|
||||||
: () async {
|
: () async {
|
||||||
final res = await showConfirmationDialog(
|
final res = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Delete Conversation',
|
title: context.translations.deleteConversationLabel,
|
||||||
okText: 'DELETE',
|
|
||||||
question:
|
question:
|
||||||
// ignore: lines_longer_than_80_chars
|
context.translations.deleteConversationQuestion,
|
||||||
'Are you sure you want to delete this conversation?',
|
okText: context.translations.deleteLabel,
|
||||||
cancelText: 'CANCEL',
|
cancelText: context.translations.cancelLabel,
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: chatThemeData.colorTheme.accentError,
|
color: chatThemeData.colorTheme.accentError,
|
||||||
),
|
),
|
||||||
@@ -571,10 +582,6 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: chatThemeData.colorTheme.appBg,
|
|
||||||
),
|
|
||||||
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
||||||
ChannelPreview(
|
ChannelPreview(
|
||||||
onLongPress: widget.onChannelLongPress,
|
onLongPress: widget.onChannelLongPress,
|
||||||
@@ -583,7 +590,6 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,7 +668,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Error loading channels',
|
context.translations.loadingChannelsError,
|
||||||
style: theme.textTheme.body.copyWith(
|
style: theme.textTheme.body.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// It shows the current [Channel] name using a [Text] widget.
|
/// It shows the current [Channel] name using a [Text] widget.
|
||||||
///
|
///
|
||||||
@@ -44,10 +45,12 @@ class ChannelName extends StatelessWidget {
|
|||||||
) =>
|
) =>
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
var title = 'No title';
|
var title = context.translations.noTitleText;
|
||||||
if (extraData['name'] == null) {
|
if (extraData['name'] != null) {
|
||||||
final otherMembers =
|
title = extraData['name'];
|
||||||
members?.where((member) => member.userId != client.user!.id);
|
} else {
|
||||||
|
final otherMembers = members
|
||||||
|
?.where((member) => member.userId != client.currentUser!.id);
|
||||||
if (otherMembers?.length == 1) {
|
if (otherMembers?.length == 1) {
|
||||||
if (otherMembers!.first.user != null) {
|
if (otherMembers!.first.user != null) {
|
||||||
title = otherMembers.first.user!.name;
|
title = otherMembers.first.user!.name;
|
||||||
@@ -71,8 +74,6 @@ class ChannelName extends StatelessWidget {
|
|||||||
title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
|
title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
|
||||||
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
title = extraData['name'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:jiffy/jiffy.dart';
|
|||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
@@ -101,7 +102,7 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
if (members?.isEmpty == true ||
|
if (members?.isEmpty == true ||
|
||||||
members?.any((Member e) =>
|
members?.any((Member e) =>
|
||||||
e.user!.id ==
|
e.user!.id ==
|
||||||
channel.client.state.user?.id) !=
|
channel.client.state.currentUser?.id) !=
|
||||||
true) {
|
true) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
@@ -124,7 +125,7 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
(m) => !m.isDeleted && m.shadowed != true,
|
(m) => !m.isDeleted && m.shadowed != true,
|
||||||
);
|
);
|
||||||
if (lastMessage?.user?.id ==
|
if (lastMessage?.user?.id ==
|
||||||
streamChatState.user?.id) {
|
streamChatState.currentUser?.id) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 4),
|
padding: const EdgeInsets.only(right: 4),
|
||||||
child: SendingIndicator(
|
child: SendingIndicator(
|
||||||
@@ -133,7 +134,8 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
isMessageRead: channel.state!.read
|
isMessageRead: channel.state!.read
|
||||||
?.where((element) =>
|
?.where((element) =>
|
||||||
element.user.id !=
|
element.user.id !=
|
||||||
channel.client.state.user!.id)
|
channel
|
||||||
|
.client.state.currentUser!.id)
|
||||||
.where((element) => element.lastRead
|
.where((element) => element.lastRead
|
||||||
.isAfter(lastMessage.createdAt))
|
.isAfter(lastMessage.createdAt))
|
||||||
.isNotEmpty ==
|
.isNotEmpty ==
|
||||||
@@ -172,7 +174,7 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
startOfDay
|
startOfDay
|
||||||
.subtract(const Duration(days: 1))
|
.subtract(const Duration(days: 1))
|
||||||
.millisecondsSinceEpoch) {
|
.millisecondsSinceEpoch) {
|
||||||
stringDate = 'Yesterday';
|
stringDate = context.translations.yesterdayLabel;
|
||||||
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
|
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
|
||||||
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
|
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
|
||||||
} else {
|
} else {
|
||||||
@@ -197,7 +199,7 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
' Channel is muted',
|
' ${context.translations.channelIsMutedText}',
|
||||||
style: chatThemeData.channelPreviewTheme.subtitle,
|
style: chatThemeData.channelPreviewTheme.subtitle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.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/extension.dart';
|
||||||
|
|
||||||
/// It shows a date divider depending on the date difference
|
/// It shows a date divider depending on the date difference
|
||||||
class DateDivider extends StatelessWidget {
|
class DateDivider extends StatelessWidget {
|
||||||
@@ -24,10 +25,10 @@ class DateDivider extends StatelessWidget {
|
|||||||
|
|
||||||
String dayInfo;
|
String dayInfo;
|
||||||
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
|
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
|
||||||
dayInfo = 'Today';
|
dayInfo = context.translations.todayLabel;
|
||||||
} else if (Jiffy(createdAt)
|
} else if (Jiffy(createdAt)
|
||||||
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
|
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
|
||||||
dayInfo = 'Yesterday';
|
dayInfo = context.translations.yesterdayLabel;
|
||||||
} else if (Jiffy(createdAt).isAfter(
|
} else if (Jiffy(createdAt).isAfter(
|
||||||
now.subtract(const Duration(days: 7)),
|
now.subtract(const Duration(days: 7)),
|
||||||
Units.DAY,
|
Units.DAY,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/extension.dart';
|
||||||
|
|
||||||
/// Widget to display deleted message
|
/// Widget to display deleted message
|
||||||
class DeletedMessage extends StatelessWidget {
|
class DeletedMessage extends StatelessWidget {
|
||||||
@@ -49,7 +50,7 @@ class DeletedMessage extends StatelessWidget {
|
|||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Message deleted',
|
context.translations.messageDeletedLabel,
|
||||||
style: messageTheme.messageText?.copyWith(
|
style: messageTheme.messageText?.copyWith(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
color: messageTheme.createdAt?.color,
|
color: messageTheme.createdAt?.color,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:characters/characters.dart';
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
|
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/localization/translations.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
final _emojiChars = Emoji.chars();
|
final _emojiChars = Emoji.chars();
|
||||||
@@ -9,7 +10,8 @@ final _emojiChars = Emoji.chars();
|
|||||||
/// String extension
|
/// String extension
|
||||||
extension StringExtension on String {
|
extension StringExtension on String {
|
||||||
/// Returns the capitalized string
|
/// Returns the capitalized string
|
||||||
String capitalize() => '${this[0].toUpperCase()}${substring(1)}';
|
String capitalize() =>
|
||||||
|
'${this[0].toUpperCase()}${substring(1).toLowerCase()}';
|
||||||
|
|
||||||
/// Returns whether the string contains only emoji's or not.
|
/// Returns whether the string contains only emoji's or not.
|
||||||
///
|
///
|
||||||
@@ -103,6 +105,11 @@ extension BuildContextX on BuildContext {
|
|||||||
// ignore: public_member_api_docs
|
// ignore: public_member_api_docs
|
||||||
double get textScaleFactor =>
|
double get textScaleFactor =>
|
||||||
MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0;
|
MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0;
|
||||||
|
|
||||||
|
/// Retrieves current translations according to locale
|
||||||
|
/// Defaults to [DefaultTranslations]
|
||||||
|
Translations get translations =>
|
||||||
|
StreamChatLocalizations.of(this) ?? DefaultTranslations.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extension on [BorderRadius]
|
/// Extension on [BorderRadius]
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import 'dart:io';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:stream_chat_flutter/src/gallery_footer.dart';
|
import 'package:stream_chat_flutter/src/gallery_footer.dart';
|
||||||
import 'package:stream_chat_flutter/src/gallery_header.dart';
|
import 'package:stream_chat_flutter/src/gallery_header.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Return action for coming back from pages
|
/// Return action for coming back from pages
|
||||||
enum ReturnActionType {
|
enum ReturnActionType {
|
||||||
@@ -182,9 +182,10 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
children: [
|
children: [
|
||||||
GalleryHeader(
|
GalleryHeader(
|
||||||
userName: widget.userName,
|
userName: widget.userName,
|
||||||
sentAt:
|
sentAt: context.translations.sentAtText(
|
||||||
// ignore: lines_longer_than_80_chars
|
date: widget.message.createdAt,
|
||||||
'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}',
|
time: widget.message.createdAt,
|
||||||
|
),
|
||||||
onBackPressed: () {
|
onBackPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@@ -197,7 +198,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (widget.message.type != 'ephemeral')
|
if (!widget.message.isEphemeral)
|
||||||
GalleryFooter(
|
GalleryFooter(
|
||||||
currentPage: _currentPage,
|
currentPage: _currentPage,
|
||||||
totalPages: widget.mediaAttachments.length,
|
totalPages: widget.mediaAttachments.length,
|
||||||
@@ -222,22 +223,6 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
String getDay(DateTime dateTime) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
if (DateTime(dateTime.year, dateTime.month, dateTime.day) ==
|
|
||||||
DateTime(now.year, now.month, now.day)) {
|
|
||||||
return 'today';
|
|
||||||
} else if (DateTime(now.year, now.month, now.day)
|
|
||||||
.difference(dateTime)
|
|
||||||
.inHours <
|
|
||||||
24) {
|
|
||||||
return 'yesterday';
|
|
||||||
} else {
|
|
||||||
return 'on ${Jiffy(dateTime).MMMd}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
for (final package in videoPackages.values) {
|
for (final package in videoPackages.values) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
|||||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Footer widget for media display
|
/// Footer widget for media display
|
||||||
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||||
@@ -135,7 +136,9 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'${widget.currentPage + 1} of ${widget.totalPages}',
|
'${widget.currentPage + 1} '
|
||||||
|
'${context.translations.ofText} '
|
||||||
|
'${widget.totalPages}',
|
||||||
style: galleryFooterThemeData.titleTextStyle,
|
style: galleryFooterThemeData.titleTextStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -191,7 +194,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Photos',
|
context.translations.photosLabel,
|
||||||
style:
|
style:
|
||||||
galleryFooterThemeData.bottomSheetPhotosTextStyle,
|
galleryFooterThemeData.bottomSheetPhotosTextStyle,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
backgroundColor: galleryHeaderThemeData.backgroundColor,
|
backgroundColor: galleryHeaderThemeData.backgroundColor,
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
if (message.type != 'ephemeral')
|
if (!message.isEphemeral)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.iconMenuPoint(
|
icon: StreamSvgIcon.iconMenuPoint(
|
||||||
color: galleryHeaderThemeData.iconMenuPointColor,
|
color: galleryHeaderThemeData.iconMenuPointColor,
|
||||||
@@ -78,7 +78,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: message.type != 'ephemeral'
|
title: !message.isEphemeral
|
||||||
? InkWell(
|
? InkWell(
|
||||||
onTap: onTitleTap,
|
onTap: onTitleTap,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Fallback user avatar with a polygon gradient overlayed with text
|
||||||
|
class GradientAvatar extends StatefulWidget {
|
||||||
|
/// Constructor for [GradientAvatar]
|
||||||
|
const GradientAvatar({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.userId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// Name of user to shorten and display
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// ID of user to be used for key
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_GradientAvatarState createState() => _GradientAvatarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GradientAvatarState extends State<GradientAvatar> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Center(
|
||||||
|
child: RepaintBoundary(
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: DemoPainter(
|
||||||
|
widget.userId,
|
||||||
|
getShortenedName(widget.name),
|
||||||
|
DefaultTextStyle.of(context).style.fontFamily ?? 'Roboto',
|
||||||
|
),
|
||||||
|
child: const SizedBox.expand(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
String getShortenedName(String name) {
|
||||||
|
var parts = name.split(' ')..removeWhere((e) => e == '');
|
||||||
|
|
||||||
|
if (parts.length > 2) {
|
||||||
|
parts = parts.take(2).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
result = result + parts[i][0].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Painter for bg polygon gradient
|
||||||
|
class DemoPainter extends CustomPainter {
|
||||||
|
/// Constructor for [DemoPainter]
|
||||||
|
DemoPainter(
|
||||||
|
this.userId,
|
||||||
|
this.username,
|
||||||
|
this.fontFamily,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Init grid row count
|
||||||
|
static const int rowCount = 5;
|
||||||
|
|
||||||
|
/// Init grid column count
|
||||||
|
static const int columnCount = 5;
|
||||||
|
|
||||||
|
/// User ID used for key
|
||||||
|
String userId;
|
||||||
|
|
||||||
|
/// User name to display
|
||||||
|
String username;
|
||||||
|
|
||||||
|
/// Font family to use
|
||||||
|
String fontFamily;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final rowUnit = size.width / columnCount;
|
||||||
|
final columnUnit = size.height / rowCount;
|
||||||
|
final rand = Random(userId.length);
|
||||||
|
|
||||||
|
final squares = <Offset4>[];
|
||||||
|
final points = <Offset>{};
|
||||||
|
final gradient = colorGradients[rand.nextInt(colorGradients.length)];
|
||||||
|
|
||||||
|
for (var i = 0; i < rowCount; i++) {
|
||||||
|
for (var j = 0; j < columnCount; j++) {
|
||||||
|
final off1 = Offset(rowUnit * j, columnUnit * i);
|
||||||
|
final off2 = Offset(rowUnit * (j + 1), columnUnit * i);
|
||||||
|
final off3 = Offset(rowUnit * (j + 1), columnUnit * (i + 1));
|
||||||
|
final off4 = Offset(rowUnit * j, columnUnit * (i + 1));
|
||||||
|
|
||||||
|
points.addAll([off1, off2, off3, off4]);
|
||||||
|
|
||||||
|
final pointsList = points.toList();
|
||||||
|
|
||||||
|
final p1 = pointsList.indexOf(off1);
|
||||||
|
final p2 = pointsList.indexOf(off2);
|
||||||
|
final p3 = pointsList.indexOf(off3);
|
||||||
|
final p4 = pointsList.indexOf(off4);
|
||||||
|
|
||||||
|
squares.add(
|
||||||
|
Offset4(p1, p2, p3, p4, i, j, rowCount, columnCount, gradient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final list = transformPoints(points, size);
|
||||||
|
squares.forEach((e) => e.draw(canvas, list));
|
||||||
|
|
||||||
|
final smallerSide = size.width > size.height ? size.width : size.height;
|
||||||
|
|
||||||
|
final textSize = smallerSide / 3;
|
||||||
|
|
||||||
|
final dxShift = (username.length == 2 ? 1.45 : 0.9) * textSize / 2;
|
||||||
|
final dyShift = (username.length == 2 ? 1.0 : 1.65) * textSize / 2;
|
||||||
|
|
||||||
|
final fontSize = username.length == 2 ? textSize : textSize * 1.5;
|
||||||
|
|
||||||
|
TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: username,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: fontFamily,
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textDirection: TextDirection.ltr)
|
||||||
|
..layout(maxWidth: size.width)
|
||||||
|
..paint(
|
||||||
|
canvas,
|
||||||
|
Offset(
|
||||||
|
(size.width / 2) - dxShift,
|
||||||
|
(size.height / 2) - dyShift,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
|
||||||
|
/// Transforms initial grid into a polygon grid
|
||||||
|
List<Offset> transformPoints(Set<Offset> points, Size size) {
|
||||||
|
final transformedList = <Offset>[];
|
||||||
|
final orgList = points.toList();
|
||||||
|
final rand = Random(userId.length);
|
||||||
|
|
||||||
|
for (var i = 0; i < points.length; i++) {
|
||||||
|
final orgDx = orgList[i].dx;
|
||||||
|
final orgDy = orgList[i].dy;
|
||||||
|
|
||||||
|
if (orgDx == 0 ||
|
||||||
|
orgDy == 0 ||
|
||||||
|
orgDx == size.width ||
|
||||||
|
orgDy == size.height) {
|
||||||
|
transformedList.add(Offset(orgDx, orgDy));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final sign1 = 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 dy = 0.6 * sign2 * rand.nextInt(size.height ~/ rowCount);
|
||||||
|
|
||||||
|
transformedList.add(Offset(orgDx + dx, orgDy + dy));
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Class for storing and drawing four points of a polygon
|
||||||
|
class Offset4 {
|
||||||
|
/// Constructor for [Offset4]
|
||||||
|
Offset4(
|
||||||
|
this.p1,
|
||||||
|
this.p2,
|
||||||
|
this.p3,
|
||||||
|
this.p4,
|
||||||
|
this.row,
|
||||||
|
this.column,
|
||||||
|
this.rowSize,
|
||||||
|
this.colSize,
|
||||||
|
this.gradient,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Point 1
|
||||||
|
int p1;
|
||||||
|
|
||||||
|
/// Point 2
|
||||||
|
int p2;
|
||||||
|
|
||||||
|
/// Point 3
|
||||||
|
int p3;
|
||||||
|
|
||||||
|
/// Point 4
|
||||||
|
int p4;
|
||||||
|
|
||||||
|
/// Position of polygon on grid
|
||||||
|
int row;
|
||||||
|
|
||||||
|
/// Position of polygon on grid
|
||||||
|
int column;
|
||||||
|
|
||||||
|
/// Max row size
|
||||||
|
int rowSize;
|
||||||
|
|
||||||
|
/// Max col size
|
||||||
|
int colSize;
|
||||||
|
|
||||||
|
/// Gradient to be applied to polygon
|
||||||
|
List<Color> gradient;
|
||||||
|
|
||||||
|
/// Draw the polygon on canvas
|
||||||
|
void draw(Canvas canvas, List<Offset> points) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = Color.fromARGB(255, Random().nextInt(255),
|
||||||
|
Random().nextInt(255), Random().nextInt(255))
|
||||||
|
..shader = ui.Gradient.linear(
|
||||||
|
points[p1],
|
||||||
|
points[p3],
|
||||||
|
gradient,
|
||||||
|
);
|
||||||
|
|
||||||
|
final backgroundPath = Path()
|
||||||
|
..moveTo(points[p1].dx, points[p1].dy)
|
||||||
|
..lineTo(points[p2].dx, points[p2].dy)
|
||||||
|
..lineTo(points[p3].dx, points[p3].dy)
|
||||||
|
..lineTo(points[p4].dx, points[p4].dy)
|
||||||
|
..lineTo(points[p1].dx, points[p1].dy)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(backgroundPath, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gradient list for polygons
|
||||||
|
const colorGradients = [
|
||||||
|
[Color(0xffffafbd), Color(0xffffc3a0)],
|
||||||
|
[Color(0xff2193b0), Color(0xff6dd5ed)],
|
||||||
|
[Color(0xffcc2b5e), Color(0xff753a88)],
|
||||||
|
[Color(0xffee9ca7), Color(0xffffdde1)],
|
||||||
|
[Color(0xff42275a), Color(0xff734b6d)],
|
||||||
|
[Color(0xffde6262), Color(0xffffb88c)],
|
||||||
|
[Color(0xff56ab2f), Color(0xffa8e063)],
|
||||||
|
[Color(0xff614385), Color(0xff516395)],
|
||||||
|
[Color(0xffeacda3), Color(0xffd6ae7b)],
|
||||||
|
[Color(0xff02aab0), Color(0xff00cdac)],
|
||||||
|
];
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:stream_chat_flutter/src/localization/translations.dart'
|
||||||
|
show Translations;
|
||||||
|
|
||||||
|
/// Defines the localized resource values used by the StreamChatFlutter widgets.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [GlobalStreamChatLocalizations], which provides stream chat localizations
|
||||||
|
/// for many languages.
|
||||||
|
abstract class StreamChatLocalizations implements Translations {
|
||||||
|
/// The `StreamChatLocalizations` from the closest [Localizations] instance
|
||||||
|
/// that encloses the given context.
|
||||||
|
///
|
||||||
|
/// If no [StreamChatLocalizations] are available in the given `context`, this
|
||||||
|
/// method returns null.
|
||||||
|
///
|
||||||
|
/// This method is just a convenient shorthand for:
|
||||||
|
/// `Localizations.of<StreamChatLocalizations>(
|
||||||
|
/// context,
|
||||||
|
/// StreamChatLocalizations
|
||||||
|
/// )`.
|
||||||
|
///
|
||||||
|
/// References to the localized resources defined by this class are typically
|
||||||
|
/// written in terms of this method. For example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// tooltip: StreamChatLocalizations.of(context).streamChatLabel,
|
||||||
|
/// ```
|
||||||
|
static StreamChatLocalizations? of(BuildContext context) =>
|
||||||
|
Localizations.of<StreamChatLocalizations>(
|
||||||
|
context,
|
||||||
|
StreamChatLocalizations,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,667 @@
|
|||||||
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/connection_status_builder.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_input.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
||||||
|
show User;
|
||||||
|
|
||||||
|
/// Translation strings for the stream chat widgets
|
||||||
|
abstract class Translations {
|
||||||
|
/// The error shown when [launchURL] fails
|
||||||
|
String get launchUrlError;
|
||||||
|
|
||||||
|
/// The error shown when loading users fails
|
||||||
|
String get loadingUsersError;
|
||||||
|
|
||||||
|
/// The label for "retry" button
|
||||||
|
String get retryLabel;
|
||||||
|
|
||||||
|
/// The label for showing no users
|
||||||
|
String get noUsersLabel;
|
||||||
|
|
||||||
|
/// The text for showing user is online
|
||||||
|
String get userOnlineText;
|
||||||
|
|
||||||
|
/// The text for showing the last online of the user
|
||||||
|
String get userLastOnlineText;
|
||||||
|
|
||||||
|
/// The text shown when [users] starts typing
|
||||||
|
String userTypingText(Iterable<User> users);
|
||||||
|
|
||||||
|
/// The label for "thread reply"
|
||||||
|
String get threadReplyLabel;
|
||||||
|
|
||||||
|
/// The text for showing if the message is only visible to you
|
||||||
|
String get onlyVisibleToYouText;
|
||||||
|
|
||||||
|
/// The text for showing the thread reply count
|
||||||
|
String threadReplyCountText(int count);
|
||||||
|
|
||||||
|
/// The text for showing the attachments upload progress
|
||||||
|
String attachmentsUploadProgressText({
|
||||||
|
required int remaining,
|
||||||
|
required int total,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The text for showing who pinned the message
|
||||||
|
String pinnedByUserText({
|
||||||
|
required User pinnedBy,
|
||||||
|
required User currentUser,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The text for showing there are empty messages
|
||||||
|
String get emptyMessagesText;
|
||||||
|
|
||||||
|
/// The text for showing generic error
|
||||||
|
String get genericErrorText;
|
||||||
|
|
||||||
|
/// The error shown when loading messages fails
|
||||||
|
String get loadingMessagesError;
|
||||||
|
|
||||||
|
/// The text for showing the result count in [MessageSearchListView]
|
||||||
|
String resultCountText(int count);
|
||||||
|
|
||||||
|
/// The text for showing the message is deleted
|
||||||
|
String get messageDeletedText;
|
||||||
|
|
||||||
|
/// The label for message deleted
|
||||||
|
String get messageDeletedLabel;
|
||||||
|
|
||||||
|
/// The label for message reactions
|
||||||
|
String get messageReactionsLabel;
|
||||||
|
|
||||||
|
/// The text for showing there are no chats
|
||||||
|
String get emptyChatMessagesText;
|
||||||
|
|
||||||
|
/// The text for showing the thread separator in case [MessageListView]
|
||||||
|
/// contains a parent message
|
||||||
|
String threadSeparatorText(int replyCount);
|
||||||
|
|
||||||
|
/// The label for "connected" in [ConnectionStatusBuilder]
|
||||||
|
String get connectedLabel;
|
||||||
|
|
||||||
|
/// The label for "disconnected" in [ConnectionStatusBuilder]
|
||||||
|
String get disconnectedLabel;
|
||||||
|
|
||||||
|
/// The label for "reconnecting" in [ConnectionStatusBuilder]
|
||||||
|
String get reconnectingLabel;
|
||||||
|
|
||||||
|
/// The label for also send as direct message "checkbox"" in [MessageInput]
|
||||||
|
String get alsoSendAsDirectMessageLabel;
|
||||||
|
|
||||||
|
/// The label for search Gif
|
||||||
|
String get searchGifLabel;
|
||||||
|
|
||||||
|
/// The label for add a comment or send in case of
|
||||||
|
/// attachments inside [MessageInput]
|
||||||
|
String get addACommentOrSendLabel;
|
||||||
|
|
||||||
|
/// The label for write a message in [MessageInput]
|
||||||
|
String get writeAMessageLabel;
|
||||||
|
|
||||||
|
/// The label for instant commands in [MessageInput]
|
||||||
|
String get instantCommandsLabel;
|
||||||
|
|
||||||
|
/// The error shown in case the fi"le is too large even after compression
|
||||||
|
/// while uploading via [MessageInput]
|
||||||
|
String fileTooLargeAfterCompressionError(double limitInMB);
|
||||||
|
|
||||||
|
/// The error shown in case the file is too large
|
||||||
|
/// while uploading via [MessageInput]
|
||||||
|
String fileTooLargeError(double limitInMB);
|
||||||
|
|
||||||
|
/// The text for showing the query while searching for emojis
|
||||||
|
String emojiMatchingQueryText(String query);
|
||||||
|
|
||||||
|
/// The label for "add a file"
|
||||||
|
String get addAFileLabel;
|
||||||
|
|
||||||
|
/// The label for "upload a photo"
|
||||||
|
String get uploadAPhotoLabel;
|
||||||
|
|
||||||
|
/// The label for "upload a video"
|
||||||
|
String get uploadAVideoLabel;
|
||||||
|
|
||||||
|
/// The label for "photo from camera"
|
||||||
|
String get photoFromCameraLabel;
|
||||||
|
|
||||||
|
/// The label for "video from camera"
|
||||||
|
String get videoFromCameraLabel;
|
||||||
|
|
||||||
|
/// The label for "upload a file"
|
||||||
|
String get uploadAFileLabel;
|
||||||
|
|
||||||
|
/// The error shown when something went wrong
|
||||||
|
String get somethingWentWrongError;
|
||||||
|
|
||||||
|
/// The label for "OK"
|
||||||
|
String get okLabel;
|
||||||
|
|
||||||
|
/// The label for "add more files"
|
||||||
|
String get addMoreFilesLabel;
|
||||||
|
|
||||||
|
/// The message shown for asking photo and video access permission
|
||||||
|
String get enablePhotoAndVideoAccessMessage;
|
||||||
|
|
||||||
|
/// The message shown for asking gallery access permission
|
||||||
|
String get allowGalleryAccessMessage;
|
||||||
|
|
||||||
|
/// The label for "flag message"
|
||||||
|
String get flagMessageLabel;
|
||||||
|
|
||||||
|
/// The question asked while showing flag message dialog
|
||||||
|
String get flagMessageQuestion;
|
||||||
|
|
||||||
|
/// The label for "Flag"
|
||||||
|
String get flagLabel;
|
||||||
|
|
||||||
|
/// The label for "Cancel"
|
||||||
|
String get cancelLabel;
|
||||||
|
|
||||||
|
/// The label for successful message flag
|
||||||
|
String get flagMessageSuccessfulLabel;
|
||||||
|
|
||||||
|
/// The text for showing the message if successfully flagged
|
||||||
|
String get flagMessageSuccessfulText;
|
||||||
|
|
||||||
|
/// The label for "delete message"
|
||||||
|
String get deleteMessageLabel;
|
||||||
|
|
||||||
|
/// The question asked while showing delete message dialog
|
||||||
|
String get deleteMessageQuestion;
|
||||||
|
|
||||||
|
/// The label for "Delete"
|
||||||
|
String get deleteLabel;
|
||||||
|
|
||||||
|
/// The text for showing the operation could not be completed
|
||||||
|
String get operationCouldNotBeCompletedText;
|
||||||
|
|
||||||
|
/// The label for "Reply"
|
||||||
|
String get replyLabel;
|
||||||
|
|
||||||
|
/// The text for showing pin/un-pin functionality in [MessageWidget]
|
||||||
|
/// based on [pinned]
|
||||||
|
String togglePinUnpinText({required bool pinned});
|
||||||
|
|
||||||
|
/// The text for showing delete/retry-delete based on [isDeleteFailed]
|
||||||
|
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed});
|
||||||
|
|
||||||
|
/// The label for "copy message"
|
||||||
|
String get copyMessageLabel;
|
||||||
|
|
||||||
|
/// The label for "edit message"
|
||||||
|
String get editMessageLabel;
|
||||||
|
|
||||||
|
/// The text for showing resend/resend-edited message
|
||||||
|
/// based on [isUpdateFailed]
|
||||||
|
String toggleResendOrResendEditedMessage({required bool isUpdateFailed});
|
||||||
|
|
||||||
|
/// The label for "Photos"
|
||||||
|
String get photosLabel;
|
||||||
|
|
||||||
|
/// The text for showing on which [date] and [time] the message was sent
|
||||||
|
String sentAtText({required DateTime date, required DateTime time});
|
||||||
|
|
||||||
|
/// The label for "Today"
|
||||||
|
String get todayLabel;
|
||||||
|
|
||||||
|
/// The label for "Yesterday"
|
||||||
|
String get yesterdayLabel;
|
||||||
|
|
||||||
|
/// The text for showing the channel is muted
|
||||||
|
String get channelIsMutedText;
|
||||||
|
|
||||||
|
/// The text for showing there is no title
|
||||||
|
String get noTitleText;
|
||||||
|
|
||||||
|
/// The label for "let's start chatting"
|
||||||
|
String get letsStartChattingLabel;
|
||||||
|
|
||||||
|
/// The label for sending the first message
|
||||||
|
String get sendingFirstMessageLabel;
|
||||||
|
|
||||||
|
/// The label for "start a chat"
|
||||||
|
String get startAChatLabel;
|
||||||
|
|
||||||
|
/// The error shown when loading channel fails
|
||||||
|
String get loadingChannelsError;
|
||||||
|
|
||||||
|
/// The label for "Delete conversation"
|
||||||
|
String get deleteConversationLabel;
|
||||||
|
|
||||||
|
/// The question asked while showing delete conversation dialog
|
||||||
|
String get deleteConversationQuestion;
|
||||||
|
|
||||||
|
/// The label for "Stream Chat"
|
||||||
|
String get streamChatLabel;
|
||||||
|
|
||||||
|
/// The text for showing searching for network
|
||||||
|
String get searchingForNetworkText;
|
||||||
|
|
||||||
|
/// The label for "Offline"
|
||||||
|
String get offlineLabel;
|
||||||
|
|
||||||
|
/// The label for "Try again"
|
||||||
|
String get tryAgainLabel;
|
||||||
|
|
||||||
|
/// The text for showing the members count based on [count]
|
||||||
|
String membersCountText(int count);
|
||||||
|
|
||||||
|
/// The text for showing the watchers count based on [count]
|
||||||
|
String watchersCountText(int count);
|
||||||
|
|
||||||
|
/// The label for "View Info"
|
||||||
|
String get viewInfoLabel;
|
||||||
|
|
||||||
|
/// The label for "Leave Group"
|
||||||
|
String get leaveGroupLabel;
|
||||||
|
|
||||||
|
/// The label for "Leave"
|
||||||
|
String get leaveLabel;
|
||||||
|
|
||||||
|
/// The label for "Leave conversation"
|
||||||
|
String get leaveConversationLabel;
|
||||||
|
|
||||||
|
/// The question asked while showing leave conversation dialog
|
||||||
|
String get leaveConversationQuestion;
|
||||||
|
|
||||||
|
/// The label for "Show in chat"
|
||||||
|
String get showInChatLabel;
|
||||||
|
|
||||||
|
/// The label for "Save Image"
|
||||||
|
String get saveImageLabel;
|
||||||
|
|
||||||
|
/// The label for "Save Video"
|
||||||
|
String get saveVideoLabel;
|
||||||
|
|
||||||
|
/// The label for "Upload Error"
|
||||||
|
String get uploadErrorLabel;
|
||||||
|
|
||||||
|
/// The label for "Giphy"
|
||||||
|
String get giphyLabel;
|
||||||
|
|
||||||
|
/// The label for "Shuffle"
|
||||||
|
String get shuffleLabel;
|
||||||
|
|
||||||
|
/// The label for "Send"
|
||||||
|
String get sendLabel;
|
||||||
|
|
||||||
|
/// The label for "With"
|
||||||
|
String get withText;
|
||||||
|
|
||||||
|
/// The text shown for "In"
|
||||||
|
String get inText;
|
||||||
|
|
||||||
|
/// The text shown for "You"
|
||||||
|
String get youText;
|
||||||
|
|
||||||
|
/// The text shown for "Of"
|
||||||
|
String get ofText;
|
||||||
|
|
||||||
|
/// The text shown for "File"
|
||||||
|
String get fileText;
|
||||||
|
|
||||||
|
/// The label for "Reply to message"
|
||||||
|
String get replyToMessageLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default implementation of Translation strings for the stream chat widgets
|
||||||
|
class DefaultTranslations implements Translations {
|
||||||
|
const DefaultTranslations._();
|
||||||
|
|
||||||
|
/// Singleton instance of [DefaultTranslations]
|
||||||
|
static const instance = DefaultTranslations._();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get launchUrlError => 'Cannot launch the url';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingUsersError => 'Error loading users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noUsersLabel => 'There are no users currently';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get retryLabel => 'Retry';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userLastOnlineText => 'Last online';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userOnlineText => 'Online';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String userTypingText(Iterable<User> users) {
|
||||||
|
if (users.isEmpty) return '';
|
||||||
|
final first = users.first;
|
||||||
|
if (users.length == 1) {
|
||||||
|
return '${first.name} is typing';
|
||||||
|
}
|
||||||
|
return '${first.name} and ${users.length - 1} more are typing';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get threadReplyLabel => 'Thread Reply';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onlyVisibleToYouText => 'Only visible to you';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String threadReplyCountText(int count) => '$count Thread Replies';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String attachmentsUploadProgressText({
|
||||||
|
required int remaining,
|
||||||
|
required int total,
|
||||||
|
}) =>
|
||||||
|
'Uploading $remaining/$total ...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String pinnedByUserText({
|
||||||
|
required User pinnedBy,
|
||||||
|
required User currentUser,
|
||||||
|
}) {
|
||||||
|
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
|
||||||
|
if (pinnedByCurrentUser) return 'Pinned by You';
|
||||||
|
return 'Pinned by ${pinnedBy.name}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get emptyMessagesText => 'There are no messages currently';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get genericErrorText => 'Something went wrong';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingMessagesError => 'Error loading messages';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String resultCountText(int count) => '$count results';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageDeletedText => 'This message is deleted.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageDeletedLabel => 'Message deleted';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageReactionsLabel => 'Message Reactions';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get emptyChatMessagesText => 'No chats here yet...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String threadSeparatorText(int replyCount) {
|
||||||
|
if (replyCount == 1) return '1 Reply';
|
||||||
|
return '$replyCount Replies';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectedLabel => 'Connected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get disconnectedLabel => 'Disconnected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reconnectingLabel => 'Reconnecting...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addACommentOrSendLabel => 'Add a comment or send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGifLabel => 'Search GIFs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get writeAMessageLabel => 'Write a message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get instantCommandsLabel => 'Instant Commands';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fileTooLargeAfterCompressionError(double limitInMB) =>
|
||||||
|
'The file is too large to upload. '
|
||||||
|
'The file size limit is $limitInMB MB. '
|
||||||
|
'We tried compressing it, but it was not enough.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fileTooLargeError(double limitInMB) =>
|
||||||
|
'The file is too large to upload. The file size limit is $limitInMB MB.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addAFileLabel => 'Add a file';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get photoFromCameraLabel => 'Photo from camera';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAFileLabel => 'Upload a file';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAPhotoLabel => 'Upload a photo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAVideoLabel => 'Upload a video';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get videoFromCameraLabel => 'Video from camera';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get okLabel => 'OK';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get somethingWentWrongError => 'Something went wrong';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addMoreFilesLabel => 'Add more files';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enablePhotoAndVideoAccessMessage =>
|
||||||
|
'Please enable access to your photos'
|
||||||
|
'\nand videos so you can share them with friends.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowGalleryAccessMessage => 'Allow access to your gallery';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageLabel => 'Flag Message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageQuestion =>
|
||||||
|
'Do you want to send a copy of this message to a'
|
||||||
|
'\nmoderator for further investigation?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagLabel => 'FLAG';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelLabel => 'CANCEL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageSuccessfulLabel => 'Message flagged';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageSuccessfulText =>
|
||||||
|
'The message has been reported to a moderator.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteLabel => 'DELETE';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteMessageLabel => 'Delete Message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteMessageQuestion =>
|
||||||
|
'Are you sure you want to permanently delete this\nmessage?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get operationCouldNotBeCompletedText =>
|
||||||
|
'The operation couldn\'t be completed.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get replyLabel => 'Reply';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String togglePinUnpinText({required bool pinned}) {
|
||||||
|
if (pinned) return 'Unpin from Conversation';
|
||||||
|
return 'Pin to Conversation';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
|
||||||
|
if (isDeleteFailed) return 'Retry Deleting Message';
|
||||||
|
return 'Delete Message';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get copyMessageLabel => 'Copy Message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editMessageLabel => 'Edit Message';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
|
||||||
|
if (isUpdateFailed) return 'Resend Edited Message';
|
||||||
|
return 'Resend';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get photosLabel => 'Photos';
|
||||||
|
|
||||||
|
String _getDay(DateTime dateTime) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final yesterday = DateTime(now.year, now.month, now.day - 1);
|
||||||
|
|
||||||
|
final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||||
|
|
||||||
|
if (date == today) {
|
||||||
|
return 'today';
|
||||||
|
} else if (date == yesterday) {
|
||||||
|
return 'yesterday';
|
||||||
|
} else {
|
||||||
|
return 'on ${Jiffy(date).MMMd}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sentAtText({required DateTime date, required DateTime time}) =>
|
||||||
|
'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get todayLabel => 'Today';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get yesterdayLabel => 'Yesterday';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelIsMutedText => 'Channel is muted';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noTitleText => 'No title';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get letsStartChattingLabel => 'Let’s start chatting!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendingFirstMessageLabel =>
|
||||||
|
'How about sending your first message to a friend?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get startAChatLabel => 'Start a chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingChannelsError => 'Error loading channels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteConversationLabel => 'Delete Conversation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteConversationQuestion =>
|
||||||
|
'Are you sure you want to delete this conversation?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get streamChatLabel => 'Stream Chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchingForNetworkText => 'Searching for Network';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get offlineLabel => 'Offline...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tryAgainLabel => 'Try Again';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String membersCountText(int count) {
|
||||||
|
if (count == 1) return '1 Member';
|
||||||
|
return '$count Members';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String watchersCountText(int count) {
|
||||||
|
if (count == 1) return '1 Online';
|
||||||
|
return '$count Online';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewInfoLabel => 'View Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveGroupLabel => 'Leave Group';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveLabel => 'LEAVE';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveConversationLabel => 'Leave conversation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveConversationQuestion =>
|
||||||
|
'Are you sure you want to leave this conversation?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showInChatLabel => 'Show in Chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saveImageLabel => 'Save Image';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saveVideoLabel => 'Save Video';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadErrorLabel => 'UPLOAD ERROR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get giphyLabel => 'Giphy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get shuffleLabel => 'Shuffle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendLabel => 'Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get withText => 'with';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get inText => 'in';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get youText => 'You';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ofText => 'of';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fileText => 'File';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get replyToMessageLabel => 'Reply to Message';
|
||||||
|
}
|
||||||
@@ -101,7 +101,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
Widget _showMessageOptionsModal() {
|
Widget _showMessageOptionsModal() {
|
||||||
final mediaQueryData = MediaQuery.of(context);
|
final mediaQueryData = MediaQuery.of(context);
|
||||||
final size = mediaQueryData.size;
|
final size = mediaQueryData.size;
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).currentUser;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = 2 * size.width / 3;
|
||||||
var messageTextLength = widget.message.text!.length;
|
var messageTextLength = widget.message.text!.length;
|
||||||
@@ -268,16 +268,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
final streamChatThemeData = StreamChatTheme.of(context);
|
final streamChatThemeData = StreamChatTheme.of(context);
|
||||||
final answer = await showConfirmationDialog(
|
final answer = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Flag Message',
|
title: context.translations.flagMessageLabel,
|
||||||
icon: StreamSvgIcon.flag(
|
icon: StreamSvgIcon.flag(
|
||||||
color: streamChatThemeData.colorTheme.accentError,
|
color: streamChatThemeData.colorTheme.accentError,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
question:
|
question: context.translations.flagMessageQuestion,
|
||||||
// ignore: lines_longer_than_80_chars
|
okText: context.translations.flagLabel,
|
||||||
'Do you want to send a copy of this message to a\nmoderator for further investigation?',
|
cancelText: context.translations.cancelLabel,
|
||||||
okText: 'FLAG',
|
|
||||||
cancelText: 'CANCEL',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final theme = streamChatThemeData;
|
final theme = streamChatThemeData;
|
||||||
@@ -290,9 +288,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
color: theme.colorTheme.accentError,
|
color: theme.colorTheme.accentError,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
details: 'The message has been reported to a moderator.',
|
details: context.translations.flagMessageSuccessfulText,
|
||||||
title: 'Message flagged',
|
title: context.translations.flagMessageSuccessfulLabel,
|
||||||
okText: 'OK',
|
okText: context.translations.okLabel,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err is StreamChatNetworkError &&
|
if (err is StreamChatNetworkError &&
|
||||||
@@ -303,9 +301,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
color: theme.colorTheme.accentError,
|
color: theme.colorTheme.accentError,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
details: 'The message has been reported to a moderator.',
|
details: context.translations.flagMessageSuccessfulText,
|
||||||
title: 'Message flagged',
|
title: context.translations.flagMessageSuccessfulLabel,
|
||||||
okText: 'OK',
|
okText: context.translations.okLabel,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_showErrorAlert();
|
_showErrorAlert();
|
||||||
@@ -335,14 +333,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
});
|
});
|
||||||
final answer = await showConfirmationDialog(
|
final answer = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Delete message',
|
title: context.translations.deleteMessageLabel,
|
||||||
icon: StreamSvgIcon.flag(
|
icon: StreamSvgIcon.flag(
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentError,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
question: 'Are you sure you want to permanently delete this\nmessage?',
|
question: context.translations.deleteMessageQuestion,
|
||||||
okText: 'DELETE',
|
okText: context.translations.deleteLabel,
|
||||||
cancelText: 'CANCEL',
|
cancelText: context.translations.cancelLabel,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (answer == true) {
|
if (answer == true) {
|
||||||
@@ -366,9 +364,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
color: StreamChatTheme.of(context).colorTheme.accentError,
|
color: StreamChatTheme.of(context).colorTheme.accentError,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
details: 'The operation couldn\'t be completed.',
|
details: context.translations.operationCouldNotBeCompletedText,
|
||||||
title: 'Something went wrong',
|
title: context.translations.somethingWentWrongError,
|
||||||
okText: 'OK',
|
okText: context.translations.okLabel,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +388,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'Reply',
|
context.translations.replyLabel,
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -412,7 +410,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'Flag Message',
|
context.translations.flagMessageLabel,
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -435,7 +433,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation',
|
context.translations.togglePinUnpinText(
|
||||||
|
pinned: widget.message.pinned,
|
||||||
|
),
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -458,7 +458,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message',
|
context.translations.toggleDeleteRetryDeleteMessageText(
|
||||||
|
isDeleteFailed: isDeleteFailed,
|
||||||
|
),
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.body
|
.body
|
||||||
@@ -487,7 +489,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'Copy Message',
|
context.translations.copyMessageLabel,
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -512,7 +514,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'Edit Message',
|
context.translations.editMessageLabel,
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -544,7 +546,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
isUpdateFailed ? 'Resend Edited Message' : 'Resend',
|
context.translations.toggleResendOrResendEditedMessage(
|
||||||
|
isUpdateFailed: isUpdateFailed,
|
||||||
|
),
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -588,9 +592,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
color: streamChatThemeData.colorTheme.disabled,
|
color: streamChatThemeData.colorTheme.disabled,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'Edit Message',
|
context.translations.editMessageLabel,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
@@ -636,7 +640,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(
|
Text(
|
||||||
'Thread Reply',
|
context.translations.threadReplyLabel,
|
||||||
style: streamChatThemeData.textTheme.body,
|
style: streamChatThemeData.textTheme.body,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -357,9 +357,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
color: _streamChatTheme.colorTheme.disabled,
|
color: _streamChatTheme.colorTheme.disabled,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'Reply to Message',
|
context.translations.replyToMessageLabel,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
@@ -461,7 +461,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Also send as direct message',
|
context.translations.alsoSendAsDirectMessageLabel,
|
||||||
style: _streamChatTheme.textTheme.footnote.copyWith(
|
style: _streamChatTheme.textTheme.footnote.copyWith(
|
||||||
color: _streamChatTheme.colorTheme.textHighEmphasis
|
color: _streamChatTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(0.5),
|
.withOpacity(0.5),
|
||||||
@@ -586,7 +586,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
style: _streamChatTheme.messageInputTheme.inputTextStyle,
|
style: _streamChatTheme.messageInputTheme.inputTextStyle,
|
||||||
autofocus: widget.autofocus,
|
autofocus: widget.autofocus,
|
||||||
textAlignVertical: TextAlignVertical.center,
|
textAlignVertical: TextAlignVertical.center,
|
||||||
decoration: _getInputDecoration(),
|
decoration: _getInputDecoration(context),
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -598,11 +598,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDecoration _getInputDecoration() {
|
InputDecoration _getInputDecoration(BuildContext context) {
|
||||||
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
|
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
|
||||||
return InputDecoration(
|
return InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hintText: _getHint(),
|
hintText: _getHint(context),
|
||||||
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
|
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
|
||||||
color: _streamChatTheme.colorTheme.textLowEmphasis,
|
color: _streamChatTheme.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
@@ -751,14 +751,14 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getHint() {
|
String _getHint(BuildContext context) {
|
||||||
if (_commandEnabled && _chosenCommand!.name == 'giphy') {
|
if (_commandEnabled && _chosenCommand!.name == 'giphy') {
|
||||||
return 'Search GIFs';
|
return context.translations.searchGifLabel;
|
||||||
}
|
}
|
||||||
if (_attachments.isNotEmpty) {
|
if (_attachments.isNotEmpty) {
|
||||||
return 'Add a comment or send';
|
return context.translations.addACommentOrSendLabel;
|
||||||
}
|
}
|
||||||
return 'Write a message';
|
return context.translations.writeAMessageLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkEmoji(String s, BuildContext context) {
|
void _checkEmoji(String s, BuildContext context) {
|
||||||
@@ -881,7 +881,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Instant Commands',
|
context.translations.instantCommandsLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _streamChatTheme.colorTheme.textHighEmphasis
|
color: _streamChatTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5),
|
.withOpacity(.5),
|
||||||
@@ -1144,8 +1144,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
|
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
|
||||||
_showErrorAlert(
|
_showErrorAlert(
|
||||||
// ignore: lines_longer_than_80_chars
|
context.translations.fileTooLargeAfterCompressionError(
|
||||||
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
|
widget.maxAttachmentSize / (1024 * 1024),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1156,9 +1157,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
path: mediaInfo.path,
|
path: mediaInfo.path,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_showErrorAlert(
|
_showErrorAlert(context.translations.fileTooLargeError(
|
||||||
'The file is too large to upload. The file size limit is 20MB.',
|
widget.maxAttachmentSize / (1024 * 1024),
|
||||||
);
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1426,7 +1427,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Emoji matching "$query"',
|
context.translations.emojiMatchingQueryText(
|
||||||
|
query,
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _streamChatTheme
|
color: _streamChatTheme
|
||||||
.colorTheme.textHighEmphasis
|
.colorTheme.textHighEmphasis
|
||||||
@@ -1776,17 +1779,17 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
builder: (_) => Column(
|
builder: (_) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Add a file',
|
context.translations.addAFileLabel,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.image),
|
leading: const Icon(Icons.image),
|
||||||
title: const Text('Upload a photo'),
|
title: Text(context.translations.uploadAPhotoLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pickFile(DefaultAttachmentTypes.image);
|
pickFile(DefaultAttachmentTypes.image);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -1794,7 +1797,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.video_library),
|
leading: const Icon(Icons.video_library),
|
||||||
title: const Text('Upload a video'),
|
title: Text(context.translations.uploadAVideoLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pickFile(DefaultAttachmentTypes.video);
|
pickFile(DefaultAttachmentTypes.video);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -1803,7 +1806,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.camera_alt),
|
leading: const Icon(Icons.camera_alt),
|
||||||
title: const Text('Photo from camera'),
|
title: Text(context.translations.photoFromCameraLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pickFile(DefaultAttachmentTypes.image, true);
|
pickFile(DefaultAttachmentTypes.image, true);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -1812,7 +1815,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.videocam),
|
leading: const Icon(Icons.videocam),
|
||||||
title: const Text('Video from camera'),
|
title: Text(context.translations.videoFromCameraLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pickFile(DefaultAttachmentTypes.video, true);
|
pickFile(DefaultAttachmentTypes.video, true);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -1820,7 +1823,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.insert_drive_file),
|
leading: const Icon(Icons.insert_drive_file),
|
||||||
title: const Text('Upload a file'),
|
title: Text(context.translations.uploadAFileLabel),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pickFile(DefaultAttachmentTypes.file);
|
pickFile(DefaultAttachmentTypes.file);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -1923,8 +1926,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
|
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
|
||||||
_showErrorAlert(
|
_showErrorAlert(
|
||||||
// ignore: lines_longer_than_80_chars
|
context.translations.fileTooLargeAfterCompressionError(
|
||||||
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
|
widget.maxAttachmentSize / (1024 * 1024),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1935,9 +1939,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
path: mediaInfo.path,
|
path: mediaInfo.path,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_showErrorAlert(
|
_showErrorAlert(context.translations.fileTooLargeError(
|
||||||
'The file is too large to upload. The file size limit is 20MB.',
|
widget.maxAttachmentSize / (1024 * 1024),
|
||||||
);
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2117,7 +2121,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
height: 26,
|
height: 26,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Something went wrong',
|
context.translations.somethingWentWrongError,
|
||||||
style: _streamChatTheme.textTheme.headlineBold,
|
style: _streamChatTheme.textTheme.headlineBold,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -2146,7 +2150,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'OK',
|
context.translations.okLabel,
|
||||||
style: _streamChatTheme.textTheme.bodyBold.copyWith(
|
style: _streamChatTheme.textTheme.bodyBold.copyWith(
|
||||||
color: _streamChatTheme.colorTheme.accentPrimary),
|
color: _streamChatTheme.colorTheme.accentPrimary),
|
||||||
),
|
),
|
||||||
@@ -2257,10 +2261,10 @@ class _PickerWidget extends StatefulWidget {
|
|||||||
final StreamChatThemeData streamChatTheme;
|
final StreamChatThemeData streamChatTheme;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
__PickerWidgetState createState() => __PickerWidgetState();
|
_PickerWidgetState createState() => _PickerWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class __PickerWidgetState extends State<_PickerWidget> {
|
class _PickerWidgetState extends State<_PickerWidget> {
|
||||||
Future<bool>? requestPermission;
|
Future<bool>? requestPermission;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -2292,7 +2296,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
|
|||||||
color: widget.streamChatTheme.colorTheme.inputBg,
|
color: widget.streamChatTheme.colorTheme.inputBg,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Add more files',
|
context.translations.addMoreFilesLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -2324,8 +2328,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
|
|||||||
color: widget.streamChatTheme.colorTheme.disabled,
|
color: widget.streamChatTheme.colorTheme.disabled,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
// ignore: lines_longer_than_80_chars
|
context.translations.enablePhotoAndVideoAccessMessage,
|
||||||
'Please enable access to your photos \nand videos so you can share them with friends.',
|
|
||||||
style: widget.streamChatTheme.textTheme.body.copyWith(
|
style: widget.streamChatTheme.textTheme.body.copyWith(
|
||||||
color:
|
color:
|
||||||
widget.streamChatTheme.colorTheme.textLowEmphasis),
|
widget.streamChatTheme.colorTheme.textLowEmphasis),
|
||||||
@@ -2334,7 +2337,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Allow access to your gallery',
|
context.translations.allowGalleryAccessMessage,
|
||||||
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
|
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
|
||||||
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -166,11 +166,23 @@ class MessageListView extends StatefulWidget {
|
|||||||
this.showFloatingDateDivider = true,
|
this.showFloatingDateDivider = true,
|
||||||
this.threadSeparatorBuilder,
|
this.threadSeparatorBuilder,
|
||||||
this.messageListController,
|
this.messageListController,
|
||||||
|
this.reverse = true,
|
||||||
|
this.paginationLimit = 20,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Function used to build a custom message widget
|
/// Function used to build a custom message widget
|
||||||
final MessageBuilder? messageBuilder;
|
final MessageBuilder? messageBuilder;
|
||||||
|
|
||||||
|
/// Whether the view scrolls in the reading direction.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
///
|
||||||
|
/// See [ScrollView.reverse].
|
||||||
|
final bool reverse;
|
||||||
|
|
||||||
|
/// Limit used during pagination
|
||||||
|
final int paginationLimit;
|
||||||
|
|
||||||
/// Function used to build a custom system message widget
|
/// Function used to build a custom system message widget
|
||||||
final SystemMessageBuilder? systemMessageBuilder;
|
final SystemMessageBuilder? systemMessageBuilder;
|
||||||
|
|
||||||
@@ -323,11 +335,13 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
bool _inBetweenList = false;
|
bool _inBetweenList = false;
|
||||||
|
|
||||||
late final _defaultController = MessageListController();
|
late final _defaultController = MessageListController();
|
||||||
|
|
||||||
MessageListController get _messageListController =>
|
MessageListController get _messageListController =>
|
||||||
widget.messageListController ?? _defaultController;
|
widget.messageListController ?? _defaultController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => MessageListCore(
|
Widget build(BuildContext context) => MessageListCore(
|
||||||
|
paginationLimit: widget.paginationLimit,
|
||||||
messageFilter: widget.messageFilter,
|
messageFilter: widget.messageFilter,
|
||||||
loadingBuilder: widget.loadingBuilder ??
|
loadingBuilder: widget.loadingBuilder ??
|
||||||
(context) => const Center(
|
(context) => const Center(
|
||||||
@@ -336,7 +350,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
emptyBuilder: widget.emptyBuilder ??
|
emptyBuilder: widget.emptyBuilder ??
|
||||||
(context) => Center(
|
(context) => Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'No chats here yet...',
|
context.translations.emptyChatMessagesText,
|
||||||
style: _streamTheme.textTheme.footnote.copyWith(
|
style: _streamTheme.textTheme.footnote.copyWith(
|
||||||
color: _streamTheme.colorTheme.textHighEmphasis
|
color: _streamTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
@@ -349,7 +363,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
errorBuilder: widget.errorBuilder ??
|
errorBuilder: widget.errorBuilder ??
|
||||||
(BuildContext context, Object error) => Center(
|
(BuildContext context, Object error) => Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Something went wrong',
|
context.translations.genericErrorText,
|
||||||
style: _streamTheme.textTheme.footnote.copyWith(
|
style: _streamTheme.textTheme.footnote.copyWith(
|
||||||
color: _streamTheme.colorTheme.textHighEmphasis
|
color: _streamTheme.colorTheme.textHighEmphasis
|
||||||
.withOpacity(.5)),
|
.withOpacity(.5)),
|
||||||
@@ -386,7 +400,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
1 // parent message
|
1 // parent message
|
||||||
;
|
;
|
||||||
|
|
||||||
return Stack(
|
final child = Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
ConnectionStatusBuilder(
|
ConnectionStatusBuilder(
|
||||||
@@ -395,14 +409,14 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
var showStatus = true;
|
var showStatus = true;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ConnectionStatus.connected:
|
case ConnectionStatus.connected:
|
||||||
statusString = 'Connected';
|
statusString = context.translations.connectedLabel;
|
||||||
showStatus = false;
|
showStatus = false;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.connecting:
|
case ConnectionStatus.connecting:
|
||||||
statusString = 'Reconnecting...';
|
statusString = context.translations.reconnectingLabel;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.disconnected:
|
case ConnectionStatus.disconnected:
|
||||||
statusString = 'Disconnected';
|
statusString = context.translations.disconnectedLabel;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +459,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
initialAlignment: initialAlignment ?? 0,
|
initialAlignment: initialAlignment ?? 0,
|
||||||
physics: widget.scrollPhysics,
|
physics: widget.scrollPhysics,
|
||||||
itemScrollController: _scrollController,
|
itemScrollController: _scrollController,
|
||||||
reverse: true,
|
reverse: widget.reverse,
|
||||||
addAutomaticKeepAlives: false,
|
addAutomaticKeepAlives: false,
|
||||||
itemCount: itemCount,
|
itemCount: itemCount,
|
||||||
|
|
||||||
@@ -528,7 +542,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
},
|
},
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i == itemCount - 1) {
|
if (i == itemCount - 1) {
|
||||||
if (widget.parentMessage == null) return const Offstage();
|
if (widget.parentMessage == null) {
|
||||||
|
return const Offstage();
|
||||||
|
}
|
||||||
return buildParentMessage(widget.parentMessage!);
|
return buildParentMessage(widget.parentMessage!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,6 +600,17 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
_buildFloatingDateDivider(itemCount),
|
_buildFloatingDateDivider(itemCount),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final backgroundColor = MessageListViewTheme.of(context).backgroundColor;
|
||||||
|
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
return ColoredBox(
|
||||||
|
color: backgroundColor,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildThreadSeparator() {
|
Widget _buildThreadSeparator() {
|
||||||
@@ -591,7 +618,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
return widget.threadSeparatorBuilder!.call(context);
|
return widget.threadSeparatorBuilder!.call(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
final replyCount = widget.parentMessage!.replyCount;
|
final replyCount = widget.parentMessage!.replyCount!;
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: _streamTheme.colorTheme.bgGradient,
|
gradient: _streamTheme.colorTheme.bgGradient,
|
||||||
@@ -599,7 +626,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
|
context.translations.threadSeparatorText(replyCount),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
|
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
|
||||||
),
|
),
|
||||||
@@ -608,7 +635,10 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Positioned _buildFloatingDateDivider(int itemCount) => Positioned(
|
Positioned _buildFloatingDateDivider(int itemCount) => Positioned(
|
||||||
top: 20,
|
top: widget.reverse ? 20 : null,
|
||||||
|
bottom: widget.reverse ? null : 20,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
child: BetterStreamBuilder<Iterable<ItemPosition>>(
|
||||||
initialData: _itemPositionListener.itemPositions.value,
|
initialData: _itemPositionListener.itemPositions.value,
|
||||||
stream: _itemPositionStream,
|
stream: _itemPositionStream,
|
||||||
@@ -640,7 +670,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _paginateData(
|
Future<void> _paginateData(
|
||||||
StreamChannelState? channel, QueryDirection direction) =>
|
StreamChannelState? channel,
|
||||||
|
QueryDirection direction,
|
||||||
|
) =>
|
||||||
_messageListController.paginateData!(direction: direction);
|
_messageListController.paginateData!(direction: direction);
|
||||||
|
|
||||||
int? _getTopElementIndex(Iterable<ItemPosition> values) {
|
int? _getTopElementIndex(Iterable<ItemPosition> values) {
|
||||||
@@ -672,7 +704,8 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
final unreadCount = snapshot.data!.item2;
|
final unreadCount = snapshot.data!.item2;
|
||||||
final showUnreadCount = unreadCount > 0 &&
|
final showUnreadCount = unreadCount > 0 &&
|
||||||
streamChannel!.channel.state!.members.any((e) =>
|
streamChannel!.channel.state!.members.any((e) =>
|
||||||
e.userId == streamChannel!.channel.client.state.user!.id);
|
e.userId ==
|
||||||
|
streamChannel!.channel.client.state.currentUser!.id);
|
||||||
return Positioned(
|
return Positioned(
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
right: 8,
|
right: 8,
|
||||||
@@ -700,7 +733,11 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: StreamSvgIcon.down(
|
child: widget.reverse
|
||||||
|
? StreamSvgIcon.down(
|
||||||
|
color: _streamTheme.colorTheme.textHighEmphasis,
|
||||||
|
)
|
||||||
|
: StreamSvgIcon.up(
|
||||||
color: _streamTheme.colorTheme.textHighEmphasis,
|
color: _streamTheme.colorTheme.textHighEmphasis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -774,9 +811,10 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
Widget buildParentMessage(
|
Widget buildParentMessage(
|
||||||
Message message,
|
Message message,
|
||||||
) {
|
) {
|
||||||
final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
|
final isMyMessage =
|
||||||
|
message.user!.id == StreamChat.of(context).currentUser!.id;
|
||||||
final isOnlyEmoji = message.text!.isOnlyEmoji;
|
final isOnlyEmoji = message.text!.isOnlyEmoji;
|
||||||
final currentUser = StreamChat.of(context).user;
|
final currentUser = StreamChat.of(context).currentUser;
|
||||||
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
||||||
final currentUserMember =
|
final currentUserMember =
|
||||||
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
||||||
@@ -861,7 +899,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final userId = StreamChat.of(context).user!.id;
|
final userId = StreamChat.of(context).currentUser!.id;
|
||||||
final isMyMessage = message.user!.id == userId;
|
final isMyMessage = message.user!.id == userId;
|
||||||
final nextMessage = index - 1 >= 0 ? messages[index - 1] : null;
|
final nextMessage = index - 1 >= 0 ? messages[index - 1] : null;
|
||||||
final isNextUserSame =
|
final isNextUserSame =
|
||||||
@@ -924,7 +962,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
final currentUser = StreamChat.of(context).user;
|
final currentUser = StreamChat.of(context).currentUser;
|
||||||
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
final members = StreamChannel.of(context).channel.state?.members ?? [];
|
||||||
final currentUserMember =
|
final currentUserMember =
|
||||||
members.firstWhere((e) => e.user!.id == currentUser!.id);
|
members.firstWhere((e) => e.user!.id == currentUser!.id);
|
||||||
@@ -1130,7 +1168,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
_topPaginationActive = false;
|
_topPaginationActive = false;
|
||||||
}
|
}
|
||||||
if (event.message!.user!.id ==
|
if (event.message!.user!.id ==
|
||||||
streamChannel!.channel.client.state.user!.id) {
|
streamChannel!.channel.client.state.currentUser!.id) {
|
||||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||||
_scrollController?.jumpTo(
|
_scrollController?.jumpTo(
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -1212,8 +1250,8 @@ class _LoadingIndicator extends StatelessWidget {
|
|||||||
initialData: false,
|
initialData: false,
|
||||||
errorBuilder: (context, error) => Container(
|
errorBuilder: (context, error) => Container(
|
||||||
color: streamTheme.colorTheme.accentError.withOpacity(.2),
|
color: streamTheme.colorTheme.accentError.withOpacity(.2),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Text('Error loading messages'),
|
child: Text(context.translations.loadingMessagesError),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
builder: (context, data) {
|
builder: (context, data) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart';
|
|||||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Modal widget for displaying message reactions
|
/// Modal widget for displaying message reactions
|
||||||
class MessageReactionsModal extends StatelessWidget {
|
class MessageReactionsModal extends StatelessWidget {
|
||||||
@@ -42,7 +43,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).currentUser;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = 2 * size.width / 3;
|
||||||
var messageTextLength = message.text!.length;
|
var messageTextLength = message.text!.length;
|
||||||
@@ -154,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Message Reactions',
|
context.translations.messageReactionsLabel,
|
||||||
style: chatThemeData.textTheme.headlineBold,
|
style: chatThemeData.textTheme.headlineBold,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// It shows the current [Message] preview.
|
/// It shows the current [Message] preview.
|
||||||
///
|
///
|
||||||
@@ -49,12 +50,14 @@ class MessageSearchItem extends StatelessWidget {
|
|||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
user.id == StreamChat.of(context).user?.id ? 'You' : user.name,
|
user.id == StreamChat.of(context).currentUser?.id
|
||||||
|
? context.translations.youText
|
||||||
|
: user.name,
|
||||||
style: chatThemeData.channelPreviewTheme.title,
|
style: chatThemeData.channelPreviewTheme.title,
|
||||||
),
|
),
|
||||||
if (channelName != null) ...[
|
if (channelName != null) ...[
|
||||||
Text(
|
Text(
|
||||||
' in ',
|
' ${context.translations.inText} ',
|
||||||
style: chatThemeData.channelPreviewTheme.title?.copyWith(
|
style: chatThemeData.channelPreviewTheme.title?.copyWith(
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
@@ -98,7 +101,7 @@ class MessageSearchItem extends StatelessWidget {
|
|||||||
Widget _buildSubtitle(BuildContext context, Message message) {
|
Widget _buildSubtitle(BuildContext context, Message message) {
|
||||||
var text = message.text;
|
var text = message.text;
|
||||||
if (message.isDeleted) {
|
if (message.isDeleted) {
|
||||||
text = 'This message was deleted.';
|
text = context.translations.messageDeletedText;
|
||||||
} else if (message.attachments.isNotEmpty) {
|
} else if (message.attachments.isNotEmpty) {
|
||||||
final parts = <String>[
|
final parts = <String>[
|
||||||
...message.attachments.map((e) {
|
...message.attachments.map((e) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
|
|||||||
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Callback called when tapping on a user
|
/// Callback called when tapping on a user
|
||||||
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
|
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
|
||||||
@@ -140,11 +141,13 @@ class MessageSearchListView extends StatefulWidget {
|
|||||||
|
|
||||||
class _MessageSearchListViewState extends State<MessageSearchListView> {
|
class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||||
late final _defaultController = MessageSearchListController();
|
late final _defaultController = MessageSearchListController();
|
||||||
|
|
||||||
MessageSearchListController get _messageSearchListController =>
|
MessageSearchListController get _messageSearchListController =>
|
||||||
widget.messageSearchListController ?? _defaultController;
|
widget.messageSearchListController ?? _defaultController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => MessageSearchListCore(
|
Widget build(BuildContext context) {
|
||||||
|
final messageSearchListCore = MessageSearchListCore(
|
||||||
filters: widget.filters,
|
filters: widget.filters,
|
||||||
sortOptions: widget.sortOptions,
|
sortOptions: widget.sortOptions,
|
||||||
messageQuery: widget.messageQuery,
|
messageQuery: widget.messageQuery,
|
||||||
@@ -160,8 +163,8 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minHeight: viewportConstraints.maxHeight,
|
minHeight: viewportConstraints.maxHeight,
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Text('There are no messages currently'),
|
child: Text(context.translations.emptyMessagesText),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -175,7 +178,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
showMessage: widget.showErrorTile,
|
showMessage: widget.showErrorTile,
|
||||||
tileAnchor: Alignment.topCenter,
|
tileAnchor: Alignment.topCenter,
|
||||||
childAnchor: Alignment.topCenter,
|
childAnchor: Alignment.topCenter,
|
||||||
message: 'An error occurred.',
|
message: context.translations.genericErrorText,
|
||||||
child: Container(),
|
child: Container(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -197,6 +200,19 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
childBuilder: widget.childBuilder ?? _buildListView,
|
childBuilder: widget.childBuilder ?? _buildListView,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final backgroundColor =
|
||||||
|
MessageSearchListViewTheme.of(context).backgroundColor;
|
||||||
|
|
||||||
|
if (backgroundColor != null) {
|
||||||
|
return ColoredBox(
|
||||||
|
color: backgroundColor,
|
||||||
|
child: messageSearchListCore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageSearchListCore;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _separatorBuilder(BuildContext context, int index) => Container(
|
Widget _separatorBuilder(BuildContext context, int index) => Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: StreamChatTheme.of(context).colorTheme.borders,
|
color: StreamChatTheme.of(context).colorTheme.borders,
|
||||||
@@ -226,10 +242,10 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
.colorTheme
|
.colorTheme
|
||||||
.accentError
|
.accentError
|
||||||
.withOpacity(.2),
|
.withOpacity(.2),
|
||||||
child: const Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('Error loading messages'),
|
child: Text(context.translations.loadingMessagesError),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -292,7 +308,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${items.length} results',
|
context.translations.resultCountText(items.length),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: chatThemeData.colorTheme.textLowEmphasis,
|
color: chatThemeData.colorTheme.textLowEmphasis,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -29,11 +29,19 @@ class MessageText extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n');
|
final streamChat = StreamChat.of(context);
|
||||||
|
assert(streamChat.currentUser != null, '');
|
||||||
|
return BetterStreamBuilder<String>(
|
||||||
|
stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'),
|
||||||
|
initialData: streamChat.currentUser!.language ?? 'en',
|
||||||
|
builder: (context, language) {
|
||||||
|
final translatedText =
|
||||||
|
message.i18n?['${language}_text'] ?? message.text;
|
||||||
|
final messageText =
|
||||||
|
_replaceMentions(translatedText ?? '').replaceAll('\n', '\n\n');
|
||||||
final themeData = Theme.of(context);
|
final themeData = Theme.of(context);
|
||||||
return MarkdownBody(
|
return MarkdownBody(
|
||||||
data: text,
|
data: messageText,
|
||||||
onTapLink: (
|
onTapLink: (
|
||||||
String link,
|
String link,
|
||||||
String? href,
|
String? href,
|
||||||
@@ -43,15 +51,10 @@ class MessageText extends StatelessWidget {
|
|||||||
final mentionedUser = message.mentionedUsers.firstWhereOrNull(
|
final mentionedUser = message.mentionedUsers.firstWhereOrNull(
|
||||||
(u) => '@${u.name}' == link,
|
(u) => '@${u.name}' == link,
|
||||||
);
|
);
|
||||||
if (mentionedUser == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onMentionTap != null) {
|
if (mentionedUser == null) return;
|
||||||
onMentionTap!(mentionedUser);
|
|
||||||
} else {
|
onMentionTap?.call(mentionedUser);
|
||||||
print('tap on ${mentionedUser.name}');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (onLinkTap != null) {
|
if (onLinkTap != null) {
|
||||||
onLinkTap!(link);
|
onLinkTap!(link);
|
||||||
@@ -75,14 +78,17 @@ class MessageText extends StatelessWidget {
|
|||||||
p: messageTheme.messageText,
|
p: messageTheme.messageText,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _replaceMentions(String text) {
|
String _replaceMentions(String text) {
|
||||||
message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) {
|
var messageTextToRender = text;
|
||||||
// ignore: parameter_assignments
|
for (final user in message.mentionedUsers.toSet()) {
|
||||||
text = text.replaceAll(
|
final userName = user.name;
|
||||||
|
messageTextToRender = messageTextToRender.replaceAll(
|
||||||
'@$userName', '[@$userName](@${userName.replaceAll(' ', '')})');
|
'@$userName', '[@$userName](@${userName.replaceAll(' ', '')})');
|
||||||
});
|
}
|
||||||
return text;
|
return messageTextToRender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ class MessageWidget extends StatefulWidget {
|
|||||||
this.userAvatarBuilder,
|
this.userAvatarBuilder,
|
||||||
this.editMessageInputBuilder,
|
this.editMessageInputBuilder,
|
||||||
this.textBuilder,
|
this.textBuilder,
|
||||||
|
this.bottomRowBuilder,
|
||||||
|
this.deletedBottomRowBuilder,
|
||||||
this.onReturnAction,
|
this.onReturnAction,
|
||||||
Map<String, AttachmentBuilder>? customAttachmentBuilders,
|
Map<String, AttachmentBuilder>? customAttachmentBuilders,
|
||||||
this.readList,
|
this.readList,
|
||||||
@@ -275,6 +277,12 @@ class MessageWidget extends StatefulWidget {
|
|||||||
/// Function called on long press
|
/// Function called on long press
|
||||||
final void Function(BuildContext, Message)? onMessageActions;
|
final void Function(BuildContext, Message)? onMessageActions;
|
||||||
|
|
||||||
|
/// Widget builder for building a bottom row below the message
|
||||||
|
final Widget Function(BuildContext, Message)? bottomRowBuilder;
|
||||||
|
|
||||||
|
/// Widget builder for building a bottom row below a deleted message
|
||||||
|
final Widget Function(BuildContext, Message)? deletedBottomRowBuilder;
|
||||||
|
|
||||||
/// Widget builder for building user avatar
|
/// Widget builder for building user avatar
|
||||||
final Widget Function(BuildContext, User)? userAvatarBuilder;
|
final Widget Function(BuildContext, User)? userAvatarBuilder;
|
||||||
|
|
||||||
@@ -410,6 +418,8 @@ class MessageWidget extends StatefulWidget {
|
|||||||
Widget Function(BuildContext, Message)? editMessageInputBuilder,
|
Widget Function(BuildContext, Message)? editMessageInputBuilder,
|
||||||
Widget Function(BuildContext, Message)? textBuilder,
|
Widget Function(BuildContext, Message)? textBuilder,
|
||||||
Widget Function(BuildContext, Message)? usernameBuilder,
|
Widget Function(BuildContext, Message)? usernameBuilder,
|
||||||
|
Widget Function(BuildContext, Message)? bottomRowBuilder,
|
||||||
|
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||||
void Function(BuildContext, Message)? onMessageActions,
|
void Function(BuildContext, Message)? onMessageActions,
|
||||||
Message? message,
|
Message? message,
|
||||||
MessageTheme? messageTheme,
|
MessageTheme? messageTheme,
|
||||||
@@ -463,6 +473,9 @@ class MessageWidget extends StatefulWidget {
|
|||||||
editMessageInputBuilder ?? this.editMessageInputBuilder,
|
editMessageInputBuilder ?? this.editMessageInputBuilder,
|
||||||
textBuilder: textBuilder ?? this.textBuilder,
|
textBuilder: textBuilder ?? this.textBuilder,
|
||||||
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
||||||
|
bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
|
||||||
|
deletedBottomRowBuilder:
|
||||||
|
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
|
||||||
onMessageActions: onMessageActions ?? this.onMessageActions,
|
onMessageActions: onMessageActions ?? this.onMessageActions,
|
||||||
message: message ?? this.message,
|
message: message ?? this.message,
|
||||||
messageTheme: messageTheme ?? this.messageTheme,
|
messageTheme: messageTheme ?? this.messageTheme,
|
||||||
@@ -782,7 +795,11 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
bottom:
|
bottom:
|
||||||
isPinned && widget.showPinHighlight ? 6.0 : 0.0,
|
isPinned && widget.showPinHighlight ? 6.0 : 0.0,
|
||||||
),
|
),
|
||||||
child: _bottomRow,
|
child: widget.bottomRowBuilder?.call(
|
||||||
|
context,
|
||||||
|
widget.message,
|
||||||
|
) ??
|
||||||
|
_bottomRow,
|
||||||
),
|
),
|
||||||
if (isFailedState)
|
if (isFailedState)
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -810,7 +827,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildQuotedMessage() {
|
Widget _buildQuotedMessage() {
|
||||||
final isMyMessage = widget.message.user?.id == _streamChat.user?.id;
|
final isMyMessage = widget.message.user?.id == _streamChat.currentUser?.id;
|
||||||
final onTap = widget.message.quotedMessage?.isDeleted != true &&
|
final onTap = widget.message.quotedMessage?.isDeleted != true &&
|
||||||
widget.onQuotedMessageTap != null
|
widget.onQuotedMessageTap != null
|
||||||
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
|
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
|
||||||
@@ -830,22 +847,11 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
|
|
||||||
Widget get _bottomRow {
|
Widget get _bottomRow {
|
||||||
if (isDeleted) {
|
if (isDeleted) {
|
||||||
final chatThemeData = _streamChatTheme;
|
return widget.deletedBottomRowBuilder?.call(
|
||||||
return Row(
|
context,
|
||||||
mainAxisSize: MainAxisSize.min,
|
widget.message,
|
||||||
children: [
|
) ??
|
||||||
StreamSvgIcon.eye(
|
const Offstage();
|
||||||
color: chatThemeData.colorTheme.textLowEmphasis,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'Only visible to you',
|
|
||||||
style: chatThemeData.textTheme.footnote
|
|
||||||
.copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
@@ -854,9 +860,9 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
|
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
|
||||||
final replyCount = widget.message.replyCount;
|
final replyCount = widget.message.replyCount;
|
||||||
|
|
||||||
var msg = 'Thread Reply';
|
var msg = context.translations.threadReplyLabel;
|
||||||
if (showThreadReplyIndicator && replyCount! > 1) {
|
if (showThreadReplyIndicator && replyCount! > 1) {
|
||||||
msg = '$replyCount Thread Replies';
|
msg = context.translations.threadReplyCountText(replyCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: prefer_function_declarations_over_variables
|
// ignore: prefer_function_declarations_over_variables
|
||||||
@@ -993,7 +999,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
Widget _buildReactionIndicator(
|
Widget _buildReactionIndicator(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) {
|
) {
|
||||||
final ownId = _streamChat.user!.id;
|
final ownId = _streamChat.currentUser!.id;
|
||||||
final reactionsMap = <String, Reaction>{};
|
final reactionsMap = <String, Reaction>{};
|
||||||
widget.message.latestReactions?.forEach((element) {
|
widget.message.latestReactions?.forEach((element) {
|
||||||
if (!reactionsMap.containsKey(element.type) ||
|
if (!reactionsMap.containsKey(element.type) ||
|
||||||
@@ -1054,8 +1060,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
showReactionPickerIndicator: widget.showReactions &&
|
showReactionPickerIndicator: widget.showReactions &&
|
||||||
(widget.message.status == MessageSendingStatus.sent),
|
(widget.message.status == MessageSendingStatus.sent),
|
||||||
showPinHighlight: false,
|
showPinHighlight: false,
|
||||||
showUserAvatar:
|
showUserAvatar: widget.message.user!.id ==
|
||||||
widget.message.user!.id == channel.client.state.user!.id
|
channel.client.state.currentUser!.id
|
||||||
? DisplayWidget.gone
|
? DisplayWidget.gone
|
||||||
: DisplayWidget.show,
|
: DisplayWidget.show,
|
||||||
),
|
),
|
||||||
@@ -1118,7 +1124,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
(widget.message.status == MessageSendingStatus.sent),
|
(widget.message.status == MessageSendingStatus.sent),
|
||||||
showPinHighlight: false,
|
showPinHighlight: false,
|
||||||
showUserAvatar:
|
showUserAvatar:
|
||||||
widget.message.user!.id == channel.client.state.user!.id
|
widget.message.user!.id == channel.client.state.currentUser!.id
|
||||||
? DisplayWidget.gone
|
? DisplayWidget.gone
|
||||||
: DisplayWidget.show,
|
: DisplayWidget.show,
|
||||||
),
|
),
|
||||||
@@ -1201,7 +1207,10 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Text(
|
return Text(
|
||||||
'Uploading $uploadRemaining/$totalAttachments ...',
|
context.translations.attachmentsUploadProgressText(
|
||||||
|
remaining: uploadRemaining,
|
||||||
|
total: totalAttachments,
|
||||||
|
),
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1275,8 +1284,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPinnedMessage(Message message) {
|
Widget _buildPinnedMessage(Message message) {
|
||||||
final pinnedBy = message.pinnedBy;
|
final pinnedBy = message.pinnedBy!;
|
||||||
final pinnedByMe = _streamChat.user!.id == pinnedBy!.id;
|
final currentUser = _streamChat.currentUser!;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
|
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
|
||||||
@@ -1290,7 +1299,10 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}',
|
context.translations.pinnedByUserText(
|
||||||
|
pinnedBy: pinnedBy,
|
||||||
|
currentUser: currentUser,
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: _streamChatTheme.colorTheme.textLowEmphasis,
|
color: _streamChatTheme.colorTheme.textLowEmphasis,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
final userId = StreamChat.of(context).user?.id;
|
final userId = StreamChat.of(context).currentUser?.id;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 4,
|
horizontal: 4,
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ class StreamChatState extends State<StreamChat> {
|
|||||||
data: materialTheme.copyWith(
|
data: materialTheme.copyWith(
|
||||||
primaryIconTheme: streamTheme.primaryIconTheme,
|
primaryIconTheme: streamTheme.primaryIconTheme,
|
||||||
accentColor: streamTheme.colorTheme.accentPrimary,
|
accentColor: streamTheme.colorTheme.accentPrimary,
|
||||||
scaffoldBackgroundColor: streamTheme.colorTheme.barsBg,
|
|
||||||
),
|
),
|
||||||
child: StreamChatCore(
|
child: StreamChatCore(
|
||||||
client: client,
|
client: client,
|
||||||
@@ -127,21 +126,34 @@ class StreamChatState extends State<StreamChat> {
|
|||||||
return defaultTheme.merge(themeData);
|
return defaultTheme.merge(themeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coverage:ignore-start
|
||||||
|
|
||||||
/// The current user
|
/// The current user
|
||||||
User? get user => widget.client.state.user;
|
@Deprecated('Use `.currentUser` instead, Will be removed in future releases')
|
||||||
|
User? get user => widget.client.state.currentUser;
|
||||||
|
|
||||||
/// The current user as a stream
|
/// The current user as a stream
|
||||||
Stream<User?> get userStream => widget.client.state.userStream;
|
@Deprecated(
|
||||||
|
'Use `.currentUserStream` instead, Will be removed in future releases',
|
||||||
|
)
|
||||||
|
Stream<User?> get userStream => widget.client.state.currentUserStream;
|
||||||
|
|
||||||
@override
|
// coverage:ignore-end
|
||||||
void initState() {
|
|
||||||
super.initState();
|
/// The current user
|
||||||
}
|
User? get currentUser => widget.client.state.currentUser;
|
||||||
|
|
||||||
|
/// The current user as a stream
|
||||||
|
Stream<User?> get currentUserStream => widget.client.state.currentUserStream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
final locale = ui.window.locale;
|
final locale = ui.window.locale;
|
||||||
Jiffy.locale(locale.languageCode);
|
final languageCode = locale.languageCode;
|
||||||
|
final availableLocales = Jiffy.getAllAvailableLocales();
|
||||||
|
if (availableLocales.contains(languageCode)) {
|
||||||
|
Jiffy.locale(languageCode);
|
||||||
|
}
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||