✨ Chat call basis
This commit is contained in:
parent
a761b80499
commit
15c8c0fe8f
@ -75,6 +75,9 @@
|
|||||||
"chatChannelDescriptionLabel": "Channel Description",
|
"chatChannelDescriptionLabel": "Channel Description",
|
||||||
"chatChannelLeaveConfirm": "Are you sure you want to leave this channel? Your message will be stored, but if you rejoin this channel later, you will lose your control of your previous messages.",
|
"chatChannelLeaveConfirm": "Are you sure you want to leave this channel? Your message will be stored, but if you rejoin this channel later, you will lose your control of your previous messages.",
|
||||||
"chatChannelDeleteConfirm": "Are you sure you want to delete this channel? All messages in this channel will be gone forever. This operation cannot be revert!",
|
"chatChannelDeleteConfirm": "Are you sure you want to delete this channel? All messages in this channel will be gone forever. This operation cannot be revert!",
|
||||||
|
"chatCall": "Call",
|
||||||
|
"chatCallOngoing": "A call is ongoing",
|
||||||
|
"chatCallJoin": "Join",
|
||||||
"chatMessagePlaceholder": "Write a message...",
|
"chatMessagePlaceholder": "Write a message...",
|
||||||
"chatMessageEditNotify": "You are about editing a message.",
|
"chatMessageEditNotify": "You are about editing a message.",
|
||||||
"chatMessageReplyNotify": "You are about replying a message.",
|
"chatMessageReplyNotify": "You are about replying a message.",
|
||||||
|
@ -75,6 +75,9 @@
|
|||||||
"chatChannelDescriptionLabel": "频道简介",
|
"chatChannelDescriptionLabel": "频道简介",
|
||||||
"chatChannelLeaveConfirm": "你确定你要离开这个频道吗?你在这个频道里的消息将被存储下来,但是当你重新加入本频道后你将会失去对你之前消息的权限。",
|
"chatChannelLeaveConfirm": "你确定你要离开这个频道吗?你在这个频道里的消息将被存储下来,但是当你重新加入本频道后你将会失去对你之前消息的权限。",
|
||||||
"chatChannelDeleteConfirm": "你确定你要删除这个频道吗?这个频道里的所有消息都将消失,并且不可被反转!",
|
"chatChannelDeleteConfirm": "你确定你要删除这个频道吗?这个频道里的所有消息都将消失,并且不可被反转!",
|
||||||
|
"chatCall": "通话",
|
||||||
|
"chatCallOngoing": "一则通话正在进行中",
|
||||||
|
"chatCallJoin": "加入",
|
||||||
"chatMessagePlaceholder": "发条消息……",
|
"chatMessagePlaceholder": "发条消息……",
|
||||||
"chatMessageEditNotify": "你正在编辑信息中……",
|
"chatMessageEditNotify": "你正在编辑信息中……",
|
||||||
"chatMessageReplyNotify": "你正在回复消息中……",
|
"chatMessageReplyNotify": "你正在回复消息中……",
|
||||||
|
49
lib/models/call.dart
Normal file
49
lib/models/call.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
|
||||||
|
class Call {
|
||||||
|
int id;
|
||||||
|
DateTime createdAt;
|
||||||
|
DateTime updatedAt;
|
||||||
|
DateTime? deletedAt;
|
||||||
|
DateTime? endedAt;
|
||||||
|
String externalId;
|
||||||
|
int founderId;
|
||||||
|
int channelId;
|
||||||
|
Channel channel;
|
||||||
|
|
||||||
|
Call({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.deletedAt,
|
||||||
|
this.endedAt,
|
||||||
|
required this.externalId,
|
||||||
|
required this.founderId,
|
||||||
|
required this.channelId,
|
||||||
|
required this.channel,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Call.fromJson(Map<String, dynamic> json) => Call(
|
||||||
|
id: json["id"],
|
||||||
|
createdAt: DateTime.parse(json["created_at"]),
|
||||||
|
updatedAt: DateTime.parse(json["updated_at"]),
|
||||||
|
deletedAt: json["deleted_at"],
|
||||||
|
endedAt: json["ended_at"] != null ? DateTime.parse(json["ended_at"]) : null,
|
||||||
|
externalId: json["external_id"],
|
||||||
|
founderId: json["founder_id"],
|
||||||
|
channelId: json["channel_id"],
|
||||||
|
channel: Channel.fromJson(json["channel"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id,
|
||||||
|
"created_at": createdAt.toIso8601String(),
|
||||||
|
"updated_at": updatedAt.toIso8601String(),
|
||||||
|
"deleted_at": deletedAt,
|
||||||
|
"ended_at": endedAt?.toIso8601String(),
|
||||||
|
"external_id": externalId,
|
||||||
|
"founder_id": founderId,
|
||||||
|
"channel_id": channelId,
|
||||||
|
"channel": channel.toJson(),
|
||||||
|
};
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:solian/models/call.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
import 'package:solian/screens/account/friend.dart';
|
import 'package:solian/screens/account/friend.dart';
|
||||||
|
import 'package:solian/screens/chat/call.dart';
|
||||||
import 'package:solian/screens/chat/chat.dart';
|
import 'package:solian/screens/chat/chat.dart';
|
||||||
import 'package:solian/screens/chat/index.dart';
|
import 'package:solian/screens/chat/index.dart';
|
||||||
import 'package:solian/screens/chat/manage.dart';
|
import 'package:solian/screens/chat/manage.dart';
|
||||||
@ -42,6 +44,11 @@ final router = GoRouter(
|
|||||||
name: 'chat.channel',
|
name: 'chat.channel',
|
||||||
builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String),
|
builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/c/:channel/call',
|
||||||
|
name: 'chat.channel.call',
|
||||||
|
builder: (context, state) => ChatCall(call: state.extra as Call),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat/c/:channel/manage',
|
path: '/chat/c/:channel/manage',
|
||||||
name: 'chat.channel.manage',
|
name: 'chat.channel.manage',
|
||||||
|
66
lib/screens/chat/call.dart
Normal file
66
lib/screens/chat/call.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/call.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class ChatCall extends StatefulWidget {
|
||||||
|
final Call call;
|
||||||
|
|
||||||
|
const ChatCall({super.key, required this.call});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatCall> createState() => _ChatCallState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatCallState extends State<ChatCall> {
|
||||||
|
String? _token;
|
||||||
|
|
||||||
|
Future<String> exchangeToken() async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) {
|
||||||
|
router.pop();
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = getRequestUri('messaging', '/api/channels/${widget.call.channel.alias}/calls/ongoing/token');
|
||||||
|
|
||||||
|
var res = await auth.client!.post(uri);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||||
|
_token = result['token'];
|
||||||
|
return _token!;
|
||||||
|
} else {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
throw Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IndentWrapper(
|
||||||
|
title: AppLocalizations.of(context)!.chatCall,
|
||||||
|
noSafeArea: true,
|
||||||
|
hideDrawer: true,
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: exchangeToken(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
print(snapshot.data!);
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/call.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/message.dart';
|
import 'package:solian/models/message.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/models/pagination.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
import 'package:solian/widgets/chat/channel_action.dart';
|
import 'package:solian/widgets/chat/channel_action.dart';
|
||||||
import 'package:solian/widgets/chat/maintainer.dart';
|
import 'package:solian/widgets/chat/maintainer.dart';
|
||||||
@ -14,6 +17,7 @@ import 'package:solian/widgets/chat/message.dart';
|
|||||||
import 'package:solian/widgets/chat/message_action.dart';
|
import 'package:solian/widgets/chat/message_action.dart';
|
||||||
import 'package:solian/widgets/chat/message_editor.dart';
|
import 'package:solian/widgets/chat/message_editor.dart';
|
||||||
import 'package:solian/widgets/indent_wrapper.dart';
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
@ -26,6 +30,7 @@ class ChatScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChatScreenState extends State<ChatScreen> {
|
class _ChatScreenState extends State<ChatScreen> {
|
||||||
|
Call? _ongoingCall;
|
||||||
Channel? _channelMeta;
|
Channel? _channelMeta;
|
||||||
|
|
||||||
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
|
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
|
||||||
@ -48,6 +53,24 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Call?> fetchCall() async {
|
||||||
|
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}/calls/ongoing');
|
||||||
|
var res = await _client.get(uri);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||||
|
setState(() => _ongoingCall = Call.fromJson(result));
|
||||||
|
return _ongoingCall;
|
||||||
|
} else if (res.statusCode != 404) {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
throw Exception(message);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchMessages(int pageKey, BuildContext context) async {
|
Future<void> fetchMessages(int pageKey, BuildContext context) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
if (!await auth.isAuthorized()) return;
|
if (!await auth.isAuthorized()) return;
|
||||||
@ -124,6 +147,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
fetchMetadata();
|
fetchMetadata();
|
||||||
|
fetchCall();
|
||||||
});
|
});
|
||||||
|
|
||||||
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
||||||
@ -133,12 +157,61 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Widget chatHistoryBuilder(context, item, index) {
|
||||||
|
bool isMerged = false, hasMerged = false;
|
||||||
|
if (index > 0) {
|
||||||
|
hasMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||||
|
}
|
||||||
|
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||||
|
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||||
|
}
|
||||||
|
return InkWell(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: !isMerged ? 8 : 0,
|
||||||
|
bottom: !hasMerged ? 8 : 0,
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
),
|
||||||
|
child: ChatMessage(
|
||||||
|
key: Key('m${item.id}'),
|
||||||
|
item: item,
|
||||||
|
underMerged: isMerged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onLongPress: () => viewActions(item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final callBanner = MaterialBanner(
|
||||||
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
||||||
|
leading: const Icon(Icons.call_received),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||||
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
|
content: Text(AppLocalizations.of(context)!.chatCallOngoing),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.chatCallJoin),
|
||||||
|
onPressed: () {
|
||||||
|
router.pushNamed(
|
||||||
|
'chat.channel.call',
|
||||||
|
extra: _ongoingCall,
|
||||||
|
pathParameters: {'channel': widget.alias},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
hideDrawer: true,
|
hideDrawer: true,
|
||||||
title: _channelMeta?.name ?? "Loading...",
|
title: _channelMeta?.name ?? "Loading...",
|
||||||
appBarActions: [
|
appBarActions: _channelMeta != null
|
||||||
_channelMeta != null ? ChannelAction(channel: _channelMeta!, onUpdate: () => fetchMetadata()) : Container(),
|
? [
|
||||||
],
|
ChannelCallAction(call: _ongoingCall, channel: _channelMeta!, onUpdate: () => fetchMetadata()),
|
||||||
|
ChannelManageAction(channel: _channelMeta!, onUpdate: () => fetchMetadata()),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: fetchMetadata(),
|
future: fetchMetadata(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -148,56 +221,39 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
return ChatMaintainer(
|
return ChatMaintainer(
|
||||||
channel: snapshot.data!,
|
channel: snapshot.data!,
|
||||||
child: Column(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Column(
|
||||||
child: PagedListView<int, Message>(
|
children: [
|
||||||
reverse: true,
|
Expanded(
|
||||||
pagingController: _pagingController,
|
child: PagedListView<int, Message>(
|
||||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
reverse: true,
|
||||||
noItemsFoundIndicatorBuilder: (_) => Container(),
|
pagingController: _pagingController,
|
||||||
itemBuilder: (context, item, index) {
|
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||||
bool isMerged = false, hasMerged = false;
|
noItemsFoundIndicatorBuilder: (_) => Container(),
|
||||||
if (index > 0) {
|
itemBuilder: chatHistoryBuilder,
|
||||||
hasMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
),
|
||||||
}
|
),
|
||||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
|
||||||
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
|
||||||
}
|
|
||||||
return InkWell(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: !isMerged ? 8 : 0,
|
|
||||||
bottom: !hasMerged ? 8 : 0,
|
|
||||||
left: 12,
|
|
||||||
right: 12,
|
|
||||||
),
|
|
||||||
child: ChatMessage(
|
|
||||||
key: Key('m${item.id}'),
|
|
||||||
item: item,
|
|
||||||
underMerged: isMerged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onLongPress: () => viewActions(item),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
ChatMessageEditor(
|
||||||
),
|
channel: widget.alias,
|
||||||
ChatMessageEditor(
|
editing: _editingItem,
|
||||||
channel: widget.alias,
|
replying: _replyingItem,
|
||||||
editing: _editingItem,
|
onReset: () => setState(() {
|
||||||
replying: _replyingItem,
|
_editingItem = null;
|
||||||
onReset: () => setState(() {
|
_replyingItem = null;
|
||||||
_editingItem = null;
|
}),
|
||||||
_replyingItem = null;
|
),
|
||||||
}),
|
],
|
||||||
),
|
),
|
||||||
|
_ongoingCall != null ? callBanner.animate().slideY() : Container(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onInsertMessage: (message) => addMessage(message),
|
onInsertMessage: (message) => addMessage(message),
|
||||||
onUpdateMessage: (message) => updateMessage(message),
|
onUpdateMessage: (message) => updateMessage(message),
|
||||||
onDeleteMessage: (message) => deleteMessage(message),
|
onDeleteMessage: (message) => deleteMessage(message),
|
||||||
|
onCallStarted: (call) => setState(() => _ongoingCall = call),
|
||||||
|
onCallEnded: () => setState(() => _ongoingCall = null),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,14 +1,93 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'dart:convert';
|
||||||
import 'package:solian/models/channel.dart';
|
|
||||||
import 'package:solian/router.dart';
|
|
||||||
|
|
||||||
class ChannelAction extends StatelessWidget {
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/call.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
|
||||||
|
class ChannelCallAction extends StatefulWidget {
|
||||||
|
final Call? call;
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
|
|
||||||
ChannelAction({super.key, required this.channel, required this.onUpdate});
|
const ChannelCallAction({super.key, this.call, required this.channel, required this.onUpdate});
|
||||||
|
|
||||||
final FocusNode _focusNode = FocusNode();
|
@override
|
||||||
|
State<ChannelCallAction> createState() => _ChannelCallActionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelCallActionState extends State<ChannelCallAction> {
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
|
Future<void> makeCall() async {
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) {
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls');
|
||||||
|
|
||||||
|
var res = await auth.client!.post(uri);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> endsCall() async {
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) {
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls/ongoing');
|
||||||
|
|
||||||
|
var res = await auth.client!.delete(uri);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: _isSubmitting
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
if (widget.call == null) {
|
||||||
|
makeCall();
|
||||||
|
} else {
|
||||||
|
endsCall();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: widget.call == null ? const Icon(Icons.call) : const Icon(Icons.call_end),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChannelManageAction extends StatelessWidget {
|
||||||
|
final Channel channel;
|
||||||
|
final Function onUpdate;
|
||||||
|
|
||||||
|
const ChannelManageAction({super.key, required this.channel, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -19,16 +98,14 @@ class ChannelAction extends StatelessWidget {
|
|||||||
extra: channel,
|
extra: channel,
|
||||||
pathParameters: {'channel': channel.alias},
|
pathParameters: {'channel': channel.alias},
|
||||||
);
|
);
|
||||||
switch(result) {
|
switch (result) {
|
||||||
case 'disposed':
|
case 'disposed':
|
||||||
if(router.canPop()) router.pop('refresh');
|
if (router.canPop()) router.pop('refresh');
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
onUpdate();
|
onUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusNode: _focusNode,
|
icon: const Icon(Icons.settings),
|
||||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
|
||||||
icon: const Icon(Icons.more_horiz),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/call.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/message.dart';
|
import 'package:solian/models/message.dart';
|
||||||
import 'package:solian/models/packet.dart';
|
import 'package:solian/models/packet.dart';
|
||||||
@ -15,6 +16,8 @@ class ChatMaintainer extends StatefulWidget {
|
|||||||
final Function(Message val) onInsertMessage;
|
final Function(Message val) onInsertMessage;
|
||||||
final Function(Message val) onUpdateMessage;
|
final Function(Message val) onUpdateMessage;
|
||||||
final Function(Message val) onDeleteMessage;
|
final Function(Message val) onDeleteMessage;
|
||||||
|
final Function(Call val) onCallStarted;
|
||||||
|
final Function() onCallEnded;
|
||||||
|
|
||||||
const ChatMaintainer({
|
const ChatMaintainer({
|
||||||
super.key,
|
super.key,
|
||||||
@ -23,6 +26,8 @@ class ChatMaintainer extends StatefulWidget {
|
|||||||
required this.onInsertMessage,
|
required this.onInsertMessage,
|
||||||
required this.onUpdateMessage,
|
required this.onUpdateMessage,
|
||||||
required this.onDeleteMessage,
|
required this.onDeleteMessage,
|
||||||
|
required this.onCallStarted,
|
||||||
|
required this.onCallEnded,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,6 +65,14 @@ class _ChatMaintainerState extends State<ChatMaintainer> {
|
|||||||
final payload = Message.fromJson(result.payload!);
|
final payload = Message.fromJson(result.payload!);
|
||||||
if (payload.channelId == widget.channel.id) widget.onDeleteMessage(payload);
|
if (payload.channelId == widget.channel.id) widget.onDeleteMessage(payload);
|
||||||
break;
|
break;
|
||||||
|
case 'calls.new':
|
||||||
|
final payload = Call.fromJson(result.payload!);
|
||||||
|
if (payload.channelId == widget.channel.id) widget.onCallStarted(payload);
|
||||||
|
break;
|
||||||
|
case 'calls.end':
|
||||||
|
final payload = Call.fromJson(result.payload!);
|
||||||
|
if (payload.channelId == widget.channel.id) widget.onCallEnded();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (_, __) => connect(),
|
onError: (_, __) => connect(),
|
||||||
|
Loading…
Reference in New Issue
Block a user