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;
int founderId;
int channelId;
List<dynamic> 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;

View File

@ -78,7 +78,6 @@ class AttachmentProvider extends GetConnect {
},
);
if (resp.statusCode != 200) {
print(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/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<ChannelChatScreen> {
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<ChannelChatScreen> {
setState(() => _isBusy = false);
}
getOngoingCall() async {
_getOngoingCall() async {
final ChannelProvider provider = Get.find();
setState(() => _isBusy = true);
@ -100,7 +99,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
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<ChannelChatScreen> {
});
}
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<ChannelChatScreen> {
_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<ChannelChatScreen> {
);
}
final ChatCallProvider call = Get.find();
return Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),
@ -219,7 +204,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
if (value == false) AppRouter.instance.pop();
if (value != null) {
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(
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(

View File

@ -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',

View File

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

View File

@ -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:

View File

@ -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: