From c88fcc84da2ff8c448e1dcbc90538f8e169556d6 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 2 Aug 2024 17:14:23 +0800 Subject: [PATCH] :sparkles: Show call participants --- lib/models/call.dart | 5 ++ lib/providers/content/attachment.dart | 1 - lib/screens/channel/channel_chat.dart | 60 +++---------- lib/translations/en_us.dart | 2 + lib/translations/zh_cn.dart | 2 + .../channel/channel_call_indicator.dart | 89 +++++++++++++++++++ .../chat/call/chat_call_indicator.dart | 28 +++--- pubspec.lock | 8 ++ pubspec.yaml | 1 + 9 files changed, 133 insertions(+), 63 deletions(-) create mode 100644 lib/widgets/channel/channel_call_indicator.dart diff --git a/lib/models/call.dart b/lib/models/call.dart index 31d3ff3..b93325a 100644 --- a/lib/models/call.dart +++ b/lib/models/call.dart @@ -10,6 +10,7 @@ class Call { String externalId; int founderId; int channelId; + List participants; Channel channel; Call({ @@ -21,6 +22,7 @@ class Call { required this.externalId, required this.founderId, required this.channelId, + required this.participants, required this.channel, }); @@ -34,6 +36,7 @@ class Call { externalId: json['external_id'], founderId: json['founder_id'], channelId: json['channel_id'], + participants: json['participants'] ?? List.empty(), channel: Channel.fromJson(json['channel']), ); @@ -46,6 +49,7 @@ class Call { 'external_id': externalId, 'founder_id': founderId, 'channel_id': channelId, + 'participants': participants, 'channel': channel.toJson(), }; } @@ -63,6 +67,7 @@ class ParticipantTrack { {required this.participant, required this.videoTrack, required this.isScreenShare}); + VideoTrack? videoTrack; Participant participant; bool isScreenShare; diff --git a/lib/providers/content/attachment.dart b/lib/providers/content/attachment.dart index 847e1d0..795585d 100644 --- a/lib/providers/content/attachment.dart +++ b/lib/providers/content/attachment.dart @@ -78,7 +78,6 @@ class AttachmentProvider extends GetConnect { }, ); if (resp.statusCode != 200) { - print(resp.data); throw Exception(resp.data); } diff --git a/lib/screens/channel/channel_chat.dart b/lib/screens/channel/channel_chat.dart index b67b224..a62af7e 100644 --- a/lib/screens/channel/channel_chat.dart +++ b/lib/screens/channel/channel_chat.dart @@ -11,7 +11,6 @@ import 'package:solian/models/channel.dart'; import 'package:solian/models/event.dart'; import 'package:solian/models/packet.dart'; import 'package:solian/providers/auth.dart'; -import 'package:solian/providers/call.dart'; import 'package:solian/providers/content/channel.dart'; import 'package:solian/providers/websocket.dart'; import 'package:solian/router.dart'; @@ -19,7 +18,7 @@ import 'package:solian/screens/channel/channel_detail.dart'; import 'package:solian/theme.dart'; import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_title.dart'; -import 'package:solian/widgets/chat/call/call_prejoin.dart'; +import 'package:solian/widgets/channel/channel_call_indicator.dart'; import 'package:solian/widgets/chat/call/chat_call_action.dart'; import 'package:solian/widgets/chat/chat_event.dart'; import 'package:solian/widgets/chat/chat_event_list.dart'; @@ -53,7 +52,7 @@ class _ChannelChatScreenState extends State { late final ChatEventController _chatController; - getChannel({String? alias}) async { + _getChannel({String? alias}) async { final ChannelProvider provider = Get.find(); setState(() => _isBusy = true); @@ -80,7 +79,7 @@ class _ChannelChatScreenState extends State { setState(() => _isBusy = false); } - getOngoingCall() async { + _getOngoingCall() async { final ChannelProvider provider = Get.find(); setState(() => _isBusy = true); @@ -100,7 +99,7 @@ class _ChannelChatScreenState extends State { setState(() => _isBusy = false); } - void listenMessages() { + void _listenMessages() { final WebSocketProvider provider = Get.find(); _subscription = provider.stream.stream.listen((event) { switch (event.method) { @@ -119,17 +118,6 @@ class _ChannelChatScreenState extends State { }); } - void showCallPrejoin() { - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) => ChatCallPrejoinPopup( - ongoingCall: _ongoingCall!, - channel: _channel!, - ), - ); - } - Event? _messageToReplying; Event? _messageToEditing; @@ -149,13 +137,12 @@ class _ChannelChatScreenState extends State { _chatController = ChatEventController(); _chatController.initialize(); - getChannel().then((_) { + _getOngoingCall(); + _getChannel().then((_) { _chatController.getEvents(_channel!, widget.realm); - listenMessages(); + _listenMessages(); }); - getOngoingCall(); - super.initState(); } @@ -183,8 +170,6 @@ class _ChannelChatScreenState extends State { ); } - final ChatCallProvider call = Get.find(); - return Scaffold( appBar: AppBar( leading: AppBarLeadingButton.adaptive(context), @@ -219,7 +204,7 @@ class _ChannelChatScreenState extends State { if (value == false) AppRouter.instance.pop(); if (value != null) { final resp = Channel.fromJson(value as Map); - getChannel(alias: resp.alias); + _getChannel(alias: resp.alias); } }); }, @@ -232,32 +217,9 @@ class _ChannelChatScreenState extends State { body: Column( children: [ if (_ongoingCall != null) - MaterialBanner( - padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), - leading: const Icon(Icons.call_received), - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - dividerColor: Colors.transparent, - content: Text('callOngoing'.tr), - actions: [ - Obx(() { - if (call.current.value == null) { - return TextButton( - onPressed: showCallPrejoin, - child: Text('callJoin'.tr), - ); - } else if (call.channel.value?.id == _channel?.id) { - return TextButton( - onPressed: () => call.gotoScreen(context), - child: Text('callResume'.tr), - ); - } else { - return TextButton( - onPressed: null, - child: Text('callJoin'.tr), - ); - } - }) - ], + ChannelCallIndicator( + channel: _channel!, + ongoingCall: _ongoingCall!, ), Expanded( child: ChatEventList( diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index 51b47f4..1cb257f 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -257,6 +257,8 @@ const i18nEnglish = { 'Are your sure to delete message @id? This action cannot be undone!', 'call': 'Call', 'callOngoing': 'A call is ongoing...', + 'callOngoingEmpty': 'A call is on hold...', + 'callOngoingParticipants': '@count people are calling...', 'callJoin': 'Join', 'callResume': 'Resume', 'callMicrophone': 'Microphone', diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index e3fc5fa..33b6e57 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -238,6 +238,8 @@ const i18nSimplifiedChinese = { 'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。', 'call': '通话', 'callOngoing': '一则通话正在进行中…', + 'callOngoingEmpty': '一则通话待机中…', + 'callOngoingParticipants': '@count 人正在进行通话…', 'callJoin': '加入', 'callResume': '恢复', 'callMicrophone': '麦克风', diff --git a/lib/widgets/channel/channel_call_indicator.dart b/lib/widgets/channel/channel_call_indicator.dart new file mode 100644 index 0000000..d065bcb --- /dev/null +++ b/lib/widgets/channel/channel_call_indicator.dart @@ -0,0 +1,89 @@ +import 'dart:convert'; + +import 'package:avatar_stack/avatar_stack.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:solian/models/account.dart'; +import 'package:solian/models/call.dart'; +import 'package:solian/models/channel.dart'; +import 'package:solian/platform.dart'; +import 'package:solian/providers/call.dart'; +import 'package:solian/widgets/chat/call/call_prejoin.dart'; + +class ChannelCallIndicator extends StatelessWidget { + final Channel channel; + final Call ongoingCall; + + const ChannelCallIndicator( + {super.key, required this.channel, required this.ongoingCall}); + + void _showCallPrejoin(BuildContext context) { + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) => ChatCallPrejoinPopup( + ongoingCall: ongoingCall, + channel: channel, + ), + ); + } + + @override + Widget build(BuildContext context) { + final ChatCallProvider call = Get.find(); + + return MaterialBanner( + padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), + leading: const Icon(Icons.call_received), + backgroundColor: Theme.of(context).colorScheme.surfaceContainer, + dividerColor: Colors.transparent, + content: Row( + children: [ + if (ongoingCall.participants.isEmpty) Text('callOngoingEmpty'.tr), + if (ongoingCall.participants.isNotEmpty) + Text('callOngoingParticipants'.trParams({ + 'count': ongoingCall.participants.length.toString(), + })), + const SizedBox(width: 6), + if (ongoingCall.participants.isNotEmpty) + Container( + height: 28, + constraints: const BoxConstraints(maxWidth: 120), + child: AvatarStack( + height: 28, + borderWidth: 0, + avatars: ongoingCall.participants.map((x) { + final userinfo = Account.fromJson(jsonDecode(x['metadata'])); + return PlatformInfo.canCacheImage + ? CachedNetworkImageProvider(userinfo.avatar) + as ImageProvider + : NetworkImage(userinfo.avatar) as ImageProvider; + }).toList(), + ), + ), + ], + ), + actions: [ + Obx(() { + if (call.current.value == null) { + return TextButton( + onPressed: () => _showCallPrejoin(context), + child: Text('callJoin'.tr), + ); + } else if (call.channel.value?.id == channel.id) { + return TextButton( + onPressed: () => call.gotoScreen(context), + child: Text('callResume'.tr), + ); + } else { + return TextButton( + onPressed: null, + child: Text('callJoin'.tr), + ); + } + }) + ], + ); + } +} diff --git a/lib/widgets/chat/call/chat_call_indicator.dart b/lib/widgets/chat/call/chat_call_indicator.dart index de62397..6167915 100644 --- a/lib/widgets/chat/call/chat_call_indicator.dart +++ b/lib/widgets/chat/call/chat_call_indicator.dart @@ -9,19 +9,21 @@ class ChatCallCurrentIndicator extends StatelessWidget { Widget build(BuildContext context) { final ChatCallProvider provider = Get.find(); - if (provider.current.value == null || provider.channel.value == null) { - return const SizedBox(); - } + return Obx(() { + if (provider.current.value == null || provider.channel.value == null) { + return const SizedBox(); + } - return ListTile( - tileColor: Theme.of(context).colorScheme.surfaceContainerHigh, - contentPadding: const EdgeInsets.symmetric(horizontal: 32), - leading: const Icon(Icons.call), - title: Text(provider.channel.value!.name), - subtitle: Text('callAlreadyOngoing'.tr), - onTap: () { - provider.gotoScreen(context); - }, - ); + return ListTile( + tileColor: Theme.of(context).colorScheme.surfaceContainerHigh, + contentPadding: const EdgeInsets.symmetric(horizontal: 32), + leading: const Icon(Icons.call), + title: Text(provider.channel.value!.name), + subtitle: Text('callAlreadyOngoing'.tr), + onTap: () { + provider.gotoScreen(context); + }, + ); + }); } } diff --git a/pubspec.lock b/pubspec.lock index 11fbe78..e58ded2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + avatar_stack: + dependency: "direct main" + description: + name: avatar_stack + sha256: e4a1576f7478add964bbb8aa5e530db39288fbbf81c30c4fb4b81162dd68aa49 + url: "https://pub.dev" + source: hosted + version: "1.2.0" badges: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index dc606b1..986fa1f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: image_cropper: ^8.0.1 markdown_toolbar: ^0.5.0 animations: ^2.0.11 + avatar_stack: ^1.2.0 dev_dependencies: flutter_test: