✨ Call button
This commit is contained in:
@ -129,7 +129,12 @@ class AuthProvider extends GetConnect {
|
||||
client.httpClient.addAuthenticator(requestAuthenticator);
|
||||
|
||||
final resp = await client.get('/api/users/me');
|
||||
_cacheUserProfileResponse = resp;
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception(resp.bodyString);
|
||||
} else {
|
||||
_cacheUserProfileResponse = resp;
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,24 @@ class ChannelProvider extends GetxController {
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<Response?> getChannelOngoingCall(String alias,
|
||||
{String realm = 'global'}) async {
|
||||
final AuthProvider auth = Get.find();
|
||||
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||
|
||||
final client = GetConnect(maxAuthRetries: 3);
|
||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||
|
||||
final resp = await client.get('/api/channels/$realm/$alias/calls/ongoing');
|
||||
if (resp.statusCode == 404) {
|
||||
return null;
|
||||
} else if (resp.statusCode != 200) {
|
||||
throw Exception(resp.bodyString);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<Response> listChannel({String scope = 'global'}) async {
|
||||
final AuthProvider auth = Get.find();
|
||||
if (!await auth.isAuthorized) throw Exception('unauthorized');
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/call.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/models/message.dart';
|
||||
import 'package:solian/models/packet.dart';
|
||||
@ -14,6 +15,7 @@ import 'package:solian/providers/content/channel.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/services.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/chat/call/chat_call_action.dart';
|
||||
import 'package:solian/widgets/chat/chat_message.dart';
|
||||
import 'package:solian/widgets/chat/chat_message_action.dart';
|
||||
import 'package:solian/widgets/chat/chat_message_input.dart';
|
||||
@ -39,6 +41,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
String? _overrideAlias;
|
||||
|
||||
Channel? _channel;
|
||||
Call? _ongoingCall;
|
||||
StreamSubscription<NetworkPackage>? _subscription;
|
||||
|
||||
final PagingController<int, Message> _pagingController =
|
||||
@ -72,6 +75,26 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
getOngoingCall() async {
|
||||
final ChannelProvider provider = Get.find();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final resp = await provider.getChannelOngoingCall(
|
||||
_overrideAlias ?? widget.alias,
|
||||
realm: widget.realm,
|
||||
);
|
||||
if (resp != null) {
|
||||
setState(() => _ongoingCall = Call.fromJson(resp.body));
|
||||
}
|
||||
} catch (e) {
|
||||
context.showErrorDialog(e);
|
||||
}
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
Future<void> getMessages(int pageKey) async {
|
||||
final AuthProvider auth = Get.find();
|
||||
if (!await auth.isAuthorized) return;
|
||||
@ -135,6 +158,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'calls.new':
|
||||
final payload = Call.fromJson(event.payload!);
|
||||
_ongoingCall = payload;
|
||||
break;
|
||||
case 'calls.end':
|
||||
_ongoingCall = null;
|
||||
break;
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
@ -150,7 +180,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
Message? _messageToReplying;
|
||||
Message? _messageToEditing;
|
||||
|
||||
Widget chatHistoryBuilder(context, item, index) {
|
||||
Widget buildHistory(context, item, index) {
|
||||
bool isMerged = false, hasMerged = false;
|
||||
if (index > 0) {
|
||||
hasMerged = checkMessageMergeable(
|
||||
@ -203,6 +233,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
listenMessages();
|
||||
_pagingController.addPageRequestListener(getMessages);
|
||||
});
|
||||
getOngoingCall();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -231,6 +262,14 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
title: Text(title),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
Builder(builder: (context) {
|
||||
if (_isBusy) return const SizedBox();
|
||||
return ChatCallButton(
|
||||
realm: _channel!.realm,
|
||||
channel: _channel!,
|
||||
ongoingCall: _ongoingCall,
|
||||
);
|
||||
}),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
@ -265,7 +304,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
clipBehavior: Clip.none,
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||
itemBuilder: chatHistoryBuilder,
|
||||
itemBuilder: buildHistory,
|
||||
noItemsFoundIndicatorBuilder: (_) => Container(),
|
||||
),
|
||||
).paddingOnly(bottom: 64),
|
||||
@ -295,6 +334,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_ongoingCall != null)
|
||||
MaterialBanner(
|
||||
padding: const EdgeInsets.only(left: 10, right: 20),
|
||||
leading: const Icon(Icons.call_received),
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||
content: Text('callOngoing'.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('callJoin'.tr),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -153,6 +153,8 @@ class SolianMessages extends Translations {
|
||||
'messageDeletionConfirm': 'Confirm message deletion',
|
||||
'messageDeletionConfirmCaption':
|
||||
'Are your sure to delete message @id? This action cannot be undone!',
|
||||
'callOngoing': 'A call is ongoing...',
|
||||
'callJoin': 'Join',
|
||||
},
|
||||
'zh_CN': {
|
||||
'hide': '隐藏',
|
||||
@ -294,6 +296,8 @@ class SolianMessages extends Translations {
|
||||
'messageActionList': '消息的操作',
|
||||
'messageDeletionConfirm': '确认删除消息',
|
||||
'messageDeletionConfirmCaption': '你确定要删除消息 @id 吗?该操作不可撤销。',
|
||||
'callOngoing': '一则通话正在进行中…',
|
||||
'callJoin': '加入',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
96
lib/widgets/chat/call/chat_call_action.dart
Normal file
96
lib/widgets/chat/call/chat_call_action.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/models/call.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/models/realm.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/services.dart';
|
||||
|
||||
class ChatCallButton extends StatefulWidget {
|
||||
final Realm? realm;
|
||||
final Channel channel;
|
||||
final Call? ongoingCall;
|
||||
final Function? onStarted;
|
||||
final Function? onEnded;
|
||||
|
||||
const ChatCallButton({
|
||||
super.key,
|
||||
required this.realm,
|
||||
required this.channel,
|
||||
required this.ongoingCall,
|
||||
this.onStarted,
|
||||
this.onEnded,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatCallButton> createState() => _ChatCallButtonState();
|
||||
}
|
||||
|
||||
class _ChatCallButtonState extends State<ChatCallButton> {
|
||||
bool _isBusy = false;
|
||||
|
||||
Future<void> makeCall() async {
|
||||
final AuthProvider auth = Get.find();
|
||||
if (!await auth.isAuthorized) return;
|
||||
|
||||
final client = GetConnect(maxAuthRetries: 3);
|
||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
final scope = (widget.realm?.alias.isNotEmpty ?? false)
|
||||
? widget.realm?.alias
|
||||
: 'global';
|
||||
final resp = await client.post(
|
||||
'/api/channels/$scope/${widget.channel.alias}/calls',
|
||||
{},
|
||||
);
|
||||
if (resp.statusCode == 200) {
|
||||
if (widget.onStarted != null) widget.onStarted!();
|
||||
} else {
|
||||
context.showErrorDialog(resp.bodyString);
|
||||
}
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
Future<void> endsCall() async {
|
||||
final AuthProvider auth = Get.find();
|
||||
if (!await auth.isAuthorized) return;
|
||||
|
||||
final client = GetConnect(maxAuthRetries: 3);
|
||||
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
final scope = (widget.realm?.alias.isNotEmpty ?? false)
|
||||
? widget.realm?.alias
|
||||
: 'global';
|
||||
final resp = await client
|
||||
.delete('/api/channels/${scope}/${widget.channel.alias}/calls/ongoing');
|
||||
if (resp.statusCode == 200) {
|
||||
if (widget.onEnded != null) widget.onEnded!();
|
||||
} else {
|
||||
context.showErrorDialog(resp.bodyString);
|
||||
}
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: _isBusy
|
||||
? null
|
||||
: widget.ongoingCall == null
|
||||
? makeCall
|
||||
: endsCall,
|
||||
icon: widget.ongoingCall == null
|
||||
? const Icon(Icons.call)
|
||||
: const Icon(Icons.call_end),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user