✨ Show call participants
This commit is contained in:
parent
11fb79623e
commit
c88fcc84da
@ -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;
|
||||
|
@ -78,7 +78,6 @@ class AttachmentProvider extends GetConnect {
|
||||
},
|
||||
);
|
||||
if (resp.statusCode != 200) {
|
||||
print(resp.data);
|
||||
throw Exception(resp.data);
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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',
|
||||
|
@ -238,6 +238,8 @@ const i18nSimplifiedChinese = {
|
||||
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
|
||||
'call': '通话',
|
||||
'callOngoing': '一则通话正在进行中…',
|
||||
'callOngoingEmpty': '一则通话待机中…',
|
||||
'callOngoingParticipants': '@count 人正在进行通话…',
|
||||
'callJoin': '加入',
|
||||
'callResume': '恢复',
|
||||
'callMicrophone': '麦克风',
|
||||
|
89
lib/widgets/channel/channel_call_indicator.dart
Normal file
89
lib/widgets/channel/channel_call_indicator.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
})
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user