From f7604ebf34043495ec727f66b39cc4917827fa73 Mon Sep 17 00:00:00 2001 From: Jganenokk Date: Wed, 15 Jul 2026 21:00:09 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BA=D0=B0=D0=BA=D0=BE=D0=B5=20=D1=82?= =?UTF-8?q?=D0=BE=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=85=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=20=D0=B2=20=D0=BD=D0=B5=D0=B7=D0=BD=D0=B0=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D1=8B=D0=B5=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backend/api.dart | 8 ++- lib/backend/modules/chats.dart | 30 ++++++++++-- .../chats/chat/view/composer_input.dart | 49 +++++++++++++++++++ lib/frontend/screens/chats/chat_screen.dart | 42 ++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) diff --git a/lib/backend/api.dart b/lib/backend/api.dart index fa39b16..237750d 100644 --- a/lib/backend/api.dart +++ b/lib/backend/api.dart @@ -266,7 +266,11 @@ class Api { } /// Отправляет запрос и ждёт ответ от сервера. - Future sendRequest(int opcode, Map payload) async { + Future sendRequest( + int opcode, + Map payload, { + bool silent = false, + }) async { final session = _session; if (session == null) { throw StateError('Нет соединения (${Opcode.name(opcode)})'); @@ -296,7 +300,7 @@ class Api { throw ex; } final text = _serverErrorText(packet.payload); - if (text != null) _errorController.add(text); + if (text != null && !silent) _errorController.add(text); final err = PacketError( messageFromErrorPayload(packet.payload), errorKey: resp.errorKey, diff --git a/lib/backend/modules/chats.dart b/lib/backend/modules/chats.dart index 1d32c05..b2da53c 100644 --- a/lib/backend/modules/chats.dart +++ b/lib/backend/modules/chats.dart @@ -399,7 +399,7 @@ class ChatsModule { 'chatId': chatId, 'messageId': msgIdNum, 'mark': mark, - }); + }, silent: true); } catch (_) {} } @@ -424,7 +424,7 @@ class ChatsModule { 'chatId': chatId, 'messageId': msgIdNum, 'mark': mark, - }); + }, silent: true); } catch (_) {} } @@ -1348,12 +1348,36 @@ class ChatsModule { await api.sendRequest(Opcode.chatSubscribe, { 'chatId': chatId, 'subscribe': subscribe, - }); + }, silent: true); } catch (e) { logger.w('subscribeChat failed: $e'); } } + Future<({CachedChat chat, int? subscribersCount})> joinChannel( + Api api, + String link, + int accountId, + ) async { + final packet = await api.sendRequest(Opcode.chatJoin, { + 'link': link, + }, silent: true); + if (!packet.isOk) { + throw PacketError(messageFromErrorPayload(packet.payload)); + } + final payload = packet.payload; + final chatMap = payload is Map ? payload['chat'] : null; + if (chatMap is! Map) { + throw const PacketError('Не удалось подписаться'); + } + final cached = await cacheServerChat(chatMap, accountId); + if (cached == null) { + throw const PacketError('Не удалось подписаться'); + } + final count = chatMap['participantsCount']; + return (chat: cached, subscribersCount: count is int ? count : null); + } + Future ensureChatCached(Api api, int accountId, int chatId) async { final rows = await AppDatabase.loadChat(accountId, chatId); if (rows.isNotEmpty) return true; diff --git a/lib/frontend/screens/chats/chat/view/composer_input.dart b/lib/frontend/screens/chats/chat/view/composer_input.dart index e3ae06a..bf42001 100644 --- a/lib/frontend/screens/chats/chat/view/composer_input.dart +++ b/lib/frontend/screens/chats/chat/view/composer_input.dart @@ -46,6 +46,9 @@ class ComposerInputBar extends StatelessWidget { required this.contextMenuBuilder, required this.isMuted, required this.onToggleMute, + this.channelSubscribed = true, + this.channelSubscribing = false, + this.onSubscribe, }); final String chatType; @@ -73,6 +76,9 @@ class ComposerInputBar extends StatelessWidget { final Widget Function(BuildContext, EditableTextState) contextMenuBuilder; final bool isMuted; final VoidCallback onToggleMute; + final bool channelSubscribed; + final bool channelSubscribing; + final VoidCallback? onSubscribe; @override Widget build(BuildContext context) { @@ -80,6 +86,49 @@ class ComposerInputBar extends StatelessWidget { final mutedIcon = cs.onSurfaceVariant.withValues(alpha: 0.85); if (chatType == "CHANNEL") { + if (!channelSubscribed) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), + child: GlossyPill( + onTap: channelSubscribing ? null : onSubscribe, + color: cs.primary, + borderRadius: BorderRadius.circular(28), + padding: const EdgeInsets.symmetric(vertical: 16), + depth: 8, + borderSide: BorderSide( + color: cs.outlineVariant.withValues(alpha: 0.5), + width: 0.5, + ), + child: SizedBox( + width: double.infinity, + child: Center( + child: channelSubscribing + ? SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + color: cs.onPrimary, + ), + ) + : Text( + 'Подписаться', + style: TextStyle( + color: cs.onPrimary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ); + } return SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), diff --git a/lib/frontend/screens/chats/chat_screen.dart b/lib/frontend/screens/chats/chat_screen.dart index a2440d1..5b65ffa 100644 --- a/lib/frontend/screens/chats/chat_screen.dart +++ b/lib/frontend/screens/chats/chat_screen.dart @@ -435,6 +435,8 @@ class _ChatScreenState extends State late AnimationController _shimmerController; Timer? _shimmerStartTimer; bool _previewChat = false; + bool _subscribing = false; + String? _channelLink; bool _forwardRequestDone = false; final ChatController _chatController = ChatController(); @@ -642,6 +644,10 @@ class _ChatScreenState extends State if (widget.chatType != 'CHAT' && widget.chatType != 'CHANNEL') return; final info = await chats.getChatInfo(api, widget.chatId); if (!mounted) return; + if (widget.chatType == 'CHANNEL') { + final link = info?['link']; + if (link is String && link.isNotEmpty) _channelLink = link; + } final count = info?['participantsCount'] as int?; if (count != null && count != _participantsCount) { _participantsCount = count; @@ -2166,6 +2172,9 @@ class _ChatScreenState extends State _formatContextMenu(_messageController, ctx, state), isMuted: chat?.isMuted ?? false, onToggleMute: _toggleChatMute, + channelSubscribed: !_previewChat, + channelSubscribing: _subscribing, + onSubscribe: _subscribeChannel, ), StickerPanelView( stickers: _stickers, @@ -2754,6 +2763,39 @@ class _ChatScreenState extends State ); } + Future _subscribeChannel() async { + if (_subscribing) return; + setState(() => _subscribing = true); + try { + var link = _channelLink; + if (link == null || link.isEmpty) { + final info = await chats.getChatInfo(api, widget.chatId); + link = info?['link'] as String?; + } + if (link == null || link.isEmpty) { + throw const PacketError('Не удалось получить ссылку канала'); + } + final result = await chats.joinChannel(api, link, _myId); + if (!mounted) return; + setState(() { + _previewChat = false; + _subscribing = false; + chat = result.chat; + _participantsCount = + result.subscribersCount ?? ((_participantsCount ?? 0) + 1); + }); + _recomputeHeaderStatus(); + showCustomNotification(context, 'Вы подписались на канал'); + } catch (e) { + if (!mounted) return; + setState(() => _subscribing = false); + showCustomNotification( + context, + e is PacketError ? e.message : 'Не удалось подписаться', + ); + } + } + Future _toggleChatMute() async { final current = chat; if (current == null) return;