diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c71469..4d3eba26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## v4.0.2 (Jun 23, 2023) +- Improved stability. + ## v4.0.1 (Jun 14, 2023) - Improved stability. diff --git a/README.md b/README.md index 1f4fb843..d416c61c 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ Find out more about Sendbird Chat for Flutter on [the documentation](https://st. ## Requirements The minimum requirements for the Chat SDK for Flutter are: -- Dart 2.17.0 or later -- Flutter 2.11.0 or later +- Dart 2.19.0 or later +- Flutter 3.7.0 or later > **Note**: Sendbird server supports Transport Layer Security (TLS) from version 1.0 up to 1.3. For example, in the server regions where TLS 1.3 isn’t available, lower versions, sequentially from 1.2 to 1.0, will be supported for secure data transmission. @@ -48,7 +48,7 @@ Before installing Sendbird Chat SDK, you need to create a Sendbird application o ```yaml dependencies: - sendbird_chat_sdk: ^4.0.0 + sendbird_chat_sdk: ^4.0.2 ``` - Run `flutter pub get` command in your project directory. diff --git a/analysis_options.yaml b/analysis_options.yaml deleted file mode 100644 index 445027a8..00000000 --- a/analysis_options.yaml +++ /dev/null @@ -1,26 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - - -## Defines a default set of lint rules enforced for -## projects at Google. For details and rationale, -## see https://github.com/dart-lang/pedantic#enabled-lints. -#include: package:pedantic/analysis_options.yaml -# -## For lint rules and documentation, see http://dart-lang.github.io/linter/lints. -## Uncomment to specify additional rules. -#linter: -# rules: -# prefer_final_fields: false -# unawaited_futures: false -# -#analyzer: -# exclude: -# - example -# - lib/**/*.g.dart - -analyzer: - exclude: - - lib/**/*.g.dart diff --git a/lib/src/internal/main/chat/chat.dart b/lib/src/internal/main/chat/chat.dart index 7bcd5916..5358f236 100644 --- a/lib/src/internal/main/chat/chat.dart +++ b/lib/src/internal/main/chat/chat.dart @@ -49,7 +49,7 @@ part 'chat_event_handler.dart'; part 'chat_push.dart'; part 'chat_user.dart'; -const sdkVersion = '4.0.1'; +const sdkVersion = '4.0.2'; // Internal implementation for main class. Do not directly access this class. class Chat with WidgetsBindingObserver { @@ -170,43 +170,39 @@ class Chat with WidgetsBindingObserver { if (!isTest) { Connectivity().onConnectivityChanged.asBroadcastStream( - onCancel: (controller) => { - _connectivityResult = ConnectivityResult.none, - controller.pause(), - }, - onListen: (subscription) { - subscription.onData((data) async { - switch (data) { - case ConnectivityResult.none: - break; - case ConnectivityResult.mobile: - if (_connectivityResult == ConnectivityResult.none && - chatContext.sessionKey != null) { - await connectionManager.reconnect(reset: true); - } - break; - case ConnectivityResult.wifi: - if (_connectivityResult == ConnectivityResult.none && - chatContext.sessionKey != null) { - await connectionManager.reconnect(reset: true); - } - break; - case ConnectivityResult.bluetooth: - case ConnectivityResult.ethernet: - if (_connectivityResult == ConnectivityResult.none && - chatContext.sessionKey != null) { - await connectionManager.reconnect(reset: true); - } - break; - case ConnectivityResult.vpn: - break; - default: - break; - } - _connectivityResult = data; - }); - }, - ); + onCancel: (controller) => { + _connectivityResult = ConnectivityResult.none, + controller.pause(), + }, + onListen: (subscription) { + subscription.onData((data) async { + switch (data) { + case ConnectivityResult.none: + sbLog.d(StackTrace.current, 'ConnectivityResult.none'); + channelCache.markAsDirtyAll(); // Check + break; + case ConnectivityResult.mobile: + case ConnectivityResult.wifi: + case ConnectivityResult.ethernet: + case ConnectivityResult.vpn: + case ConnectivityResult.other: + sbLog.d( + StackTrace.current, '${data.toString()} => reconnect()'); + if (_connectivityResult == ConnectivityResult.none && + chatContext.sessionKey != null) { + await connectionManager.reconnect(reset: true); + } + break; + case ConnectivityResult.bluetooth: + sbLog.d(StackTrace.current, 'ConnectivityResult.bluetooth'); + break; + default: + sbLog.d(StackTrace.current, data.toString()); + break; + } + _connectivityResult = data; + }); + }); } } diff --git a/lib/src/internal/main/chat_manager/command_manager.dart b/lib/src/internal/main/chat_manager/command_manager.dart index 91515dbf..0feb167d 100644 --- a/lib/src/internal/main/chat_manager/command_manager.dart +++ b/lib/src/internal/main/chat_manager/command_manager.dart @@ -120,8 +120,10 @@ class CommandManager { } Future processCommand(Command cmd) async { - sbLog.d(StackTrace.current, - '\n-[cmd] ${cmd.cmd}\n-[payload] ${jsonEncoder.convert(cmd.payload)}'); + if (cmd.payload['cmd'] == null || cmd.payload['cmd'] != 'PONG') { + sbLog.d(StackTrace.current, + '\n-[cmd] ${cmd.cmd}\n-[payload] ${jsonEncoder.convert(cmd.payload)}'); + } final unreadCountPayload = cmd.payload['unread_cnt']; if (unreadCountPayload != null) { @@ -180,7 +182,9 @@ class CommandManager { } else if (cmd.isVote) { await _processVote(cmd); } else { - sbLog.i(StackTrace.current, 'Pass command: ${cmd.cmd}'); + if (cmd.cmd != 'PONG') { + sbLog.i(StackTrace.current, 'Pass command: ${cmd.cmd}'); + } } } diff --git a/lib/src/internal/main/logger/sendbird_logger.dart b/lib/src/internal/main/logger/sendbird_logger.dart index cf016a9e..6be5739c 100644 --- a/lib/src/internal/main/logger/sendbird_logger.dart +++ b/lib/src/internal/main/logger/sendbird_logger.dart @@ -10,7 +10,9 @@ class SendbirdLogger { Logger logger; static final _instance = SendbirdLogger._(); - SendbirdLogger._() : logger = Logger(level: Level.nothing); + SendbirdLogger._() : logger = Logger(level: Level.nothing) { + _setLogLevel(Level.nothing); + } factory SendbirdLogger() => _instance; void setLogLevel(LogLevel level) { diff --git a/lib/src/internal/main/utils/async/async_queue.dart b/lib/src/internal/main/utils/async/async_queue.dart index 7e4514dc..f37bff8e 100644 --- a/lib/src/internal/main/utils/async/async_queue.dart +++ b/lib/src/internal/main/utils/async/async_queue.dart @@ -57,6 +57,7 @@ class AsyncQueue { } } catch (e) { sbLog.e(StackTrace.current, 'e: $e'); + rethrow; } } } diff --git a/lib/src/internal/network/websocket/websocket_client.dart b/lib/src/internal/network/websocket/websocket_client.dart index b7cd1acc..f0157c54 100644 --- a/lib/src/internal/network/websocket/websocket_client.dart +++ b/lib/src/internal/network/websocket/websocket_client.dart @@ -107,10 +107,12 @@ class WebSocketClient { } void send(String data) { + if (!_isConnected) { + throw WebSocketFailedException(); + } + try { - if (_isConnected) { - _webSocketChannel?.sink.add(data); - } + _webSocketChannel?.sink.add(data); } catch (e) { sbLog.e(StackTrace.current, 'e: $e'); throw WebSocketFailedException(message: e.toString()); diff --git a/lib/src/public/core/channel/base_channel/base_channel.dart b/lib/src/public/core/channel/base_channel/base_channel.dart index a5b3db66..139d0627 100644 --- a/lib/src/public/core/channel/base_channel/base_channel.dart +++ b/lib/src/public/core/channel/base_channel/base_channel.dart @@ -211,5 +211,8 @@ abstract class BaseChannel implements Cacheable { customType = others.customType; isFrozen = others.isFrozen; isEphemeral = others.isEphemeral; + + fromCache = others.fromCache; + dirty = others.dirty; } } diff --git a/lib/src/public/core/channel/base_channel/base_channel_message.dart b/lib/src/public/core/channel/base_channel/base_channel_message.dart index fa73a5e8..460ef805 100644 --- a/lib/src/public/core/channel/base_channel/base_channel_message.dart +++ b/lib/src/public/core/channel/base_channel/base_channel_message.dart @@ -96,7 +96,7 @@ extension BaseChannelMessage on BaseChannel { }, (e, s) { sbLog.e(StackTrace.current, 'e: $e'); - if (e is AckTimeoutException) { + if (e is SendbirdException) { pendingUserMessage ..errorCode = e.code ?? SendbirdError.unknownError ..sendingStatus = SendingStatus.failed; @@ -176,17 +176,19 @@ extension BaseChannelMessage on BaseChannel { final pendingFileMessage = FileMessage.fromParams(params: params, channel: this)..set(chat); + pendingFileMessage.sendingStatus = SendingStatus.pending; pendingFileMessage.sender = Sender.fromUser(chat.chatContext.currentUser, this); - final queue = chat.getMessageQueue(channelUrl); - final task = AsyncSimpleTask( - () async { - UploadResponse? uploadResponse; + bool isCanceled = false; + runZonedGuarded(() async { + final queue = chat.getMessageQueue(channelUrl); + final task = AsyncSimpleTask( + () async { + UploadResponse? uploadResponse; - if (params.fileInfo.hasBinary) { - try { + if (params.fileInfo.hasBinary) { uploadResponse = await chat.apiClient .send(ChannelFileUploadRequest(chat, channelUrl: channelUrl, @@ -199,52 +201,44 @@ extension BaseChannelMessage on BaseChannel { if (handler != null) { handler( pendingFileMessage..sendingStatus = SendingStatus.failed, - SendbirdException( - message: 'Upload timeout', - code: SendbirdError.fileUploadTimeout, - ), + SendbirdException(code: SendbirdError.fileUploadTimeout), ); } throw SendbirdException(code: SendbirdError.fileUploadTimeout); }, ); - } catch (e) { - sbLog.e(StackTrace.current, 'e: $e'); - rethrow; } - } - - String? fileUrl = uploadResponse?.url; - int? fileSize = uploadResponse?.fileSize; - if (fileUrl != null) params.fileInfo.fileUrl = fileUrl; - if (fileSize != null) params.fileInfo.fileSize = fileSize; - - final cmd = Command.buildFileMessage( - channelUrl: channelUrl, - params: params, - requestId: pendingFileMessage.requestId, - requireAuth: uploadResponse?.requireAuth, - thumbnails: uploadResponse?.thumbnails, - ); - final message = BaseMessage.getMessageFromJsonWithChat( - chat, - cmd.payload, - channelType: channelType, - commandType: cmd.cmd, - ); + String? fileUrl = uploadResponse?.url; + int? fileSize = uploadResponse?.fileSize; + if (fileUrl != null) params.fileInfo.fileUrl = fileUrl; + if (fileSize != null) params.fileInfo.fileSize = fileSize; - if (chat.chatContext.currentUser == null) { - final error = ConnectionRequiredException(); - message - ..errorCode = error.code - ..sendingStatus = SendingStatus.failed; - if (handler != null) handler(message, error); - return message; - } - - if (chat.connectionManager.isConnected()) { - runZonedGuarded(() { + final cmd = Command.buildFileMessage( + channelUrl: channelUrl, + params: params, + requestId: pendingFileMessage.requestId, + requireAuth: uploadResponse?.requireAuth, + thumbnails: uploadResponse?.thumbnails, + ); + + final message = BaseMessage.getMessageFromJsonWithChat( + chat, + cmd.payload, + channelType: channelType, + commandType: cmd.cmd, + ); + + if (chat.chatContext.currentUser == null) { + final error = ConnectionRequiredException(); + message + ..errorCode = error.code + ..sendingStatus = SendingStatus.failed; + if (handler != null) handler(message, error); + return message; + } + + if (chat.connectionManager.isConnected()) { chat.commandManager.sendCommand(cmd).then((result) { if (result == null) return; @@ -258,42 +252,44 @@ extension BaseChannelMessage on BaseChannel { chat.collectionManager.onMessageSentByMe(message); if (handler != null) handler(message, null); }); - }, (e, s) { - sbLog.e(StackTrace.current, 'e: $e'); - - if (e is AckTimeoutException) { - pendingFileMessage - ..errorCode = e.code ?? SendbirdError.unknownError - ..sendingStatus = SendingStatus.failed; - if (handler != null) handler(pendingFileMessage, e); - } - }); - } else { - final request = ChannelFileMessageSendRequest( - chat, - channelType: channelType, - channelUrl: channelUrl, - params: params, - thumbnails: uploadResponse?.thumbnails, - requireAuth: uploadResponse?.requireAuth, - ); - final message = await chat.apiClient.send(request); - - chat.collectionManager.onMessageSentByMe(message); - if (handler != null) handler(message, null); - } - }, - onCancel: () { - if (handler != null) { - handler(pendingFileMessage, OperationCanceledException()); - } - }, - ); + } else { + final request = ChannelFileMessageSendRequest( + chat, + channelType: channelType, + channelUrl: channelUrl, + params: params, + thumbnails: uploadResponse?.thumbnails, + requireAuth: uploadResponse?.requireAuth, + ); + final message = await chat.apiClient.send(request); + + chat.collectionManager.onMessageSentByMe(message); + if (handler != null) handler(message, null); + } + }, + onCancel: () { + isCanceled = true; + if (handler != null) { + handler(pendingFileMessage, OperationCanceledException()); + } + }, + ); + + queue.enqueue(task); - queue.enqueue(task); + chat.setUploadTask(pendingFileMessage.requestId!, task); + chat.setMessageQueue(channelUrl, queue); + }, (e, s) { + sbLog.e(StackTrace.current, 'e: $e'); + if (isCanceled) return; - chat.setUploadTask(pendingFileMessage.requestId!, task); - chat.setMessageQueue(channelUrl, queue); + if (e is SendbirdException) { + pendingFileMessage + ..errorCode = e.code ?? SendbirdError.unknownError + ..sendingStatus = SendingStatus.failed; + if (handler != null) handler(pendingFileMessage, e); + } + }); return pendingFileMessage; } diff --git a/lib/src/public/core/message/file_message.dart b/lib/src/public/core/message/file_message.dart index 0a1be45b..ca93d4be 100644 --- a/lib/src/public/core/message/file_message.dart +++ b/lib/src/public/core/message/file_message.dart @@ -148,19 +148,25 @@ class FileMessage extends BaseMessage { required BaseChannel channel, }) { final message = FileMessage( + channelType: channel.channelType, + channelUrl: channel.channelUrl, + messageId: 0, requestId: const Uuid().v1(), + // BaseMessageCreateParams + data: params.data, + customType: params.customType, + mentionType: params.mentionType, + metaArrays: params.metaArrays, + parentMessageId: params.parentMessageId, + replyToChannel: params.replyToChannel, + // FileMessageCreateParams url: params.fileInfo.fileUrl ?? '', - messageId: 0, file: params.fileInfo.file, name: params.fileInfo.fileName, + type: params.fileInfo.mimeType, size: params.fileInfo.fileSize ?? 0, - channelType: channel.channelType, - channelUrl: channel.channelUrl, - mentionType: params.mentionType, createdAt: DateTime.now().millisecondsSinceEpoch, requireAuth: false, - replyToChannel: params.replyToChannel, - // Check: Is needed to add other fields. ); return message; } diff --git a/pubspec.yaml b/pubspec.yaml index 8bef5407..b5dd889e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,13 @@ name: sendbird_chat_sdk description: With Sendbird Chat for Flutter, you can easily build an in-app chat with all the essential messaging features. -version: 4.0.1 +version: 4.0.2 homepage: https://sendbird.com repository: https://github.com/sendbird/sendbird-chat-sdk-flutter documentation: https://sendbird.com/docs/chat/v4/flutter/getting-started/send-first-message environment: - sdk: '>=2.17.0 <4.0.0' - flutter: '>=2.11.0' + sdk: '>=2.19.0 <4.0.0' + flutter: '>=3.7.0' platforms: android: @@ -17,24 +17,24 @@ platforms: dependencies: flutter: sdk: flutter - collection: ^1.17.1 + collection: ^1.17.0 uuid: ^3.0.7 json_annotation: ^4.8.1 encrypt: ^5.0.1 shared_preferences: ^2.1.2 - http: ^1.0.0 + http: ^0.13.6 logger: ^1.4.0 mime: ^1.0.4 - connectivity_plus: ^4.0.1 + connectivity_plus: ^2.3.9 http_parser: ^4.0.2 web_socket_channel: ^2.4.0 - universal_io: ^2.2.2 + universal_io: ^2.2.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.1 - test: ^1.21.4 + test: ^1.22.0 stack_trace: ^1.11.0 - json_serializable: ^6.7.0 - build_runner: ^2.4.5 + json_serializable: ^6.6.2 + build_runner: ^2.3.3