Show call participants

This commit is contained in:
LittleSheep 2024-08-02 17:14:23 +08:00
parent 11fb79623e
commit c88fcc84da
9 changed files with 133 additions and 63 deletions

View File

@ -10,6 +10,7 @@ class Call {
String externalId; String externalId;
int founderId; int founderId;
int channelId; int channelId;
List<dynamic> participants;
Channel channel; Channel channel;
Call({ Call({
@ -21,6 +22,7 @@ class Call {
required this.externalId, required this.externalId,
required this.founderId, required this.founderId,
required this.channelId, required this.channelId,
required this.participants,
required this.channel, required this.channel,
}); });
@ -34,6 +36,7 @@ class Call {
externalId: json['external_id'], externalId: json['external_id'],
founderId: json['founder_id'], founderId: json['founder_id'],
channelId: json['channel_id'], channelId: json['channel_id'],
participants: json['participants'] ?? List.empty(),
channel: Channel.fromJson(json['channel']), channel: Channel.fromJson(json['channel']),
); );
@ -46,6 +49,7 @@ class Call {
'external_id': externalId, 'external_id': externalId,
'founder_id': founderId, 'founder_id': founderId,
'channel_id': channelId, 'channel_id': channelId,
'participants': participants,
'channel': channel.toJson(), 'channel': channel.toJson(),
}; };
} }
@ -63,6 +67,7 @@ class ParticipantTrack {
{required this.participant, {required this.participant,
required this.videoTrack, required this.videoTrack,
required this.isScreenShare}); required this.isScreenShare});
VideoTrack? videoTrack; VideoTrack? videoTrack;
Participant participant; Participant participant;
bool isScreenShare; bool isScreenShare;

View File

@ -78,7 +78,6 @@ class AttachmentProvider extends GetConnect {
}, },
); );
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
print(resp.data);
throw Exception(resp.data); throw Exception(resp.data);
} }

View File

@ -11,7 +11,6 @@ import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart'; import 'package:solian/models/event.dart';
import 'package:solian/models/packet.dart'; import 'package:solian/models/packet.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/call.dart';
import 'package:solian/providers/content/channel.dart'; import 'package:solian/providers/content/channel.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/router.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/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.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/call/chat_call_action.dart';
import 'package:solian/widgets/chat/chat_event.dart'; import 'package:solian/widgets/chat/chat_event.dart';
import 'package:solian/widgets/chat/chat_event_list.dart'; import 'package:solian/widgets/chat/chat_event_list.dart';
@ -53,7 +52,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
late final ChatEventController _chatController; late final ChatEventController _chatController;
getChannel({String? alias}) async { _getChannel({String? alias}) async {
final ChannelProvider provider = Get.find(); final ChannelProvider provider = Get.find();
setState(() => _isBusy = true); setState(() => _isBusy = true);
@ -80,7 +79,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
setState(() => _isBusy = false); setState(() => _isBusy = false);
} }
getOngoingCall() async { _getOngoingCall() async {
final ChannelProvider provider = Get.find(); final ChannelProvider provider = Get.find();
setState(() => _isBusy = true); setState(() => _isBusy = true);
@ -100,7 +99,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
setState(() => _isBusy = false); setState(() => _isBusy = false);
} }
void listenMessages() { void _listenMessages() {
final WebSocketProvider provider = Get.find(); final WebSocketProvider provider = Get.find();
_subscription = provider.stream.stream.listen((event) { _subscription = provider.stream.stream.listen((event) {
switch (event.method) { switch (event.method) {
@ -119,17 +118,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
}); });
} }
void showCallPrejoin() {
showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => ChatCallPrejoinPopup(
ongoingCall: _ongoingCall!,
channel: _channel!,
),
);
}
Event? _messageToReplying; Event? _messageToReplying;
Event? _messageToEditing; Event? _messageToEditing;
@ -149,13 +137,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_chatController = ChatEventController(); _chatController = ChatEventController();
_chatController.initialize(); _chatController.initialize();
getChannel().then((_) { _getOngoingCall();
_getChannel().then((_) {
_chatController.getEvents(_channel!, widget.realm); _chatController.getEvents(_channel!, widget.realm);
listenMessages(); _listenMessages();
}); });
getOngoingCall();
super.initState(); super.initState();
} }
@ -183,8 +170,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
); );
} }
final ChatCallProvider call = Get.find();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
@ -219,7 +204,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
if (value == false) AppRouter.instance.pop(); if (value == false) AppRouter.instance.pop();
if (value != null) { if (value != null) {
final resp = Channel.fromJson(value as Map<String, dynamic>); final resp = Channel.fromJson(value as Map<String, dynamic>);
getChannel(alias: resp.alias); _getChannel(alias: resp.alias);
} }
}); });
}, },
@ -232,32 +217,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
body: Column( body: Column(
children: [ children: [
if (_ongoingCall != null) if (_ongoingCall != null)
MaterialBanner( ChannelCallIndicator(
padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), channel: _channel!,
leading: const Icon(Icons.call_received), ongoingCall: _ongoingCall!,
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),
);
}
})
],
), ),
Expanded( Expanded(
child: ChatEventList( child: ChatEventList(

View File

@ -257,6 +257,8 @@ const i18nEnglish = {
'Are your sure to delete message @id? This action cannot be undone!', 'Are your sure to delete message @id? This action cannot be undone!',
'call': 'Call', 'call': 'Call',
'callOngoing': 'A call is ongoing...', 'callOngoing': 'A call is ongoing...',
'callOngoingEmpty': 'A call is on hold...',
'callOngoingParticipants': '@count people are calling...',
'callJoin': 'Join', 'callJoin': 'Join',
'callResume': 'Resume', 'callResume': 'Resume',
'callMicrophone': 'Microphone', 'callMicrophone': 'Microphone',

View File

@ -238,6 +238,8 @@ const i18nSimplifiedChinese = {
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。', 'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
'call': '通话', 'call': '通话',
'callOngoing': '一则通话正在进行中…', 'callOngoing': '一则通话正在进行中…',
'callOngoingEmpty': '一则通话待机中…',
'callOngoingParticipants': '@count 人正在进行通话…',
'callJoin': '加入', 'callJoin': '加入',
'callResume': '恢复', 'callResume': '恢复',
'callMicrophone': '麦克风', 'callMicrophone': '麦克风',

View File

@ -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),
);
}
})
],
);
}
}

View File

@ -9,19 +9,21 @@ class ChatCallCurrentIndicator extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ChatCallProvider provider = Get.find(); final ChatCallProvider provider = Get.find();
if (provider.current.value == null || provider.channel.value == null) { return Obx(() {
return const SizedBox(); if (provider.current.value == null || provider.channel.value == null) {
} return const SizedBox();
}
return ListTile( return ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerHigh, tileColor: Theme.of(context).colorScheme.surfaceContainerHigh,
contentPadding: const EdgeInsets.symmetric(horizontal: 32), contentPadding: const EdgeInsets.symmetric(horizontal: 32),
leading: const Icon(Icons.call), leading: const Icon(Icons.call),
title: Text(provider.channel.value!.name), title: Text(provider.channel.value!.name),
subtitle: Text('callAlreadyOngoing'.tr), subtitle: Text('callAlreadyOngoing'.tr),
onTap: () { onTap: () {
provider.gotoScreen(context); provider.gotoScreen(context);
}, },
); );
});
} }
} }

View File

@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" 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: badges:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -67,6 +67,7 @@ dependencies:
image_cropper: ^8.0.1 image_cropper: ^8.0.1
markdown_toolbar: ^0.5.0 markdown_toolbar: ^0.5.0
animations: ^2.0.11 animations: ^2.0.11
avatar_stack: ^1.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: