✨ Show call participants
This commit is contained in:
parent
11fb79623e
commit
c88fcc84da
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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',
|
||||||
|
@ -238,6 +238,8 @@ const i18nSimplifiedChinese = {
|
|||||||
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
|
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
|
||||||
'call': '通话',
|
'call': '通话',
|
||||||
'callOngoing': '一则通话正在进行中…',
|
'callOngoing': '一则通话正在进行中…',
|
||||||
|
'callOngoingEmpty': '一则通话待机中…',
|
||||||
|
'callOngoingParticipants': '@count 人正在进行通话…',
|
||||||
'callJoin': '加入',
|
'callJoin': '加入',
|
||||||
'callResume': '恢复',
|
'callResume': '恢复',
|
||||||
'callMicrophone': '麦克风',
|
'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) {
|
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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user