Merge branch 'td/voip' into 'main'
feat: background and terminated calls [android] Closes #874 See merge request famedly/fluffychat!911
This commit is contained in:
commit
9e64cc64dc
18 changed files with 421 additions and 195 deletions
|
|
@ -25,7 +25,6 @@ import 'package:fluffychat/utils/matrix_sdk_extensions.dart/event_extension.dart
|
|||
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/ios_badge_client_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/voip/callkeep_manager.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../utils/account_bundles.dart';
|
||||
import '../../utils/localized_exception_extension.dart';
|
||||
|
|
@ -180,14 +179,6 @@ class ChatController extends State<Chat> {
|
|||
void initState() {
|
||||
scrollController.addListener(_updateScrollController);
|
||||
inputFocus.addListener(_inputFocusListener);
|
||||
final voipPlugin = Matrix.of(context).voipPlugin;
|
||||
|
||||
if (voipPlugin != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
CallKeepManager().setVoipPlugin(voipPlugin);
|
||||
CallKeepManager().initialize().catchError((_) => true);
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
|
|||
import '../../../utils/account_bundles.dart';
|
||||
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
|
||||
import '../../utils/url_launcher.dart';
|
||||
import '../../utils/voip/callkeep_manager.dart';
|
||||
import '../../widgets/fluffy_chat_app.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import '../bootstrap/bootstrap_dialog.dart';
|
||||
|
|
@ -53,6 +54,7 @@ enum ActiveFilter {
|
|||
}
|
||||
|
||||
class ChatList extends StatefulWidget {
|
||||
static BuildContext? contextForVoip;
|
||||
const ChatList({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -361,7 +363,7 @@ class ChatListController extends State<ChatList>
|
|||
scrollController.addListener(_onScroll);
|
||||
_waitForFirstSync();
|
||||
_hackyWebRTCFixForWeb();
|
||||
|
||||
CallKeepManager().initialize();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
searchServer = await Store().getItem(_serverStoreNamespace);
|
||||
});
|
||||
|
|
@ -670,7 +672,7 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
|
||||
void _hackyWebRTCFixForWeb() {
|
||||
Matrix.of(context).voipPlugin?.context = context;
|
||||
ChatList.contextForVoip = context;
|
||||
}
|
||||
|
||||
Future<void> _checkTorBrowser() async {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,12 @@ import 'dart:math';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
|
|
@ -124,47 +127,47 @@ class Calling extends StatefulWidget {
|
|||
}
|
||||
|
||||
class MyCallingPage extends State<Calling> {
|
||||
Room? get room => call?.room;
|
||||
Room? get room => call.room;
|
||||
|
||||
String get displayName => call?.displayName ?? '';
|
||||
String get displayName => call.displayName ?? '';
|
||||
|
||||
String get callId => widget.callId;
|
||||
|
||||
CallSession? get call => widget.call;
|
||||
CallSession get call => widget.call;
|
||||
|
||||
MediaStream? get localStream {
|
||||
if (call != null && call!.localUserMediaStream != null) {
|
||||
return call!.localUserMediaStream!.stream!;
|
||||
if (call.localUserMediaStream != null) {
|
||||
return call.localUserMediaStream!.stream!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
MediaStream? get remoteStream {
|
||||
if (call != null && call!.getRemoteStreams.isNotEmpty) {
|
||||
return call!.getRemoteStreams[0].stream!;
|
||||
if (call.getRemoteStreams.isNotEmpty) {
|
||||
return call.getRemoteStreams[0].stream!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get speakerOn => call?.speakerOn ?? false;
|
||||
bool get speakerOn => call.speakerOn;
|
||||
|
||||
bool get isMicrophoneMuted => call?.isMicrophoneMuted ?? false;
|
||||
bool get isMicrophoneMuted => call.isMicrophoneMuted;
|
||||
|
||||
bool get isLocalVideoMuted => call?.isLocalVideoMuted ?? false;
|
||||
bool get isLocalVideoMuted => call.isLocalVideoMuted;
|
||||
|
||||
bool get isScreensharingEnabled => call?.screensharingEnabled ?? false;
|
||||
bool get isScreensharingEnabled => call.screensharingEnabled;
|
||||
|
||||
bool get isRemoteOnHold => call?.remoteOnHold ?? false;
|
||||
bool get isRemoteOnHold => call.remoteOnHold;
|
||||
|
||||
bool get voiceonly => call == null || call?.type == CallType.kVoice;
|
||||
bool get voiceonly => call.type == CallType.kVoice;
|
||||
|
||||
bool get connecting => call?.state == CallState.kConnecting;
|
||||
bool get connecting => call.state == CallState.kConnecting;
|
||||
|
||||
bool get connected => call?.state == CallState.kConnected;
|
||||
bool get connected => call.state == CallState.kConnected;
|
||||
|
||||
bool get mirrored => call?.facingMode == 'user';
|
||||
bool get mirrored => call.facingMode == 'user';
|
||||
|
||||
List<WrappedMediaStream> get streams => call?.streams ?? [];
|
||||
List<WrappedMediaStream> get streams => call.streams;
|
||||
double? _localVideoHeight;
|
||||
double? _localVideoWidth;
|
||||
EdgeInsetsGeometry? _localVideoMargin;
|
||||
|
|
@ -190,8 +193,6 @@ class MyCallingPage extends State<Calling> {
|
|||
|
||||
void initialize() async {
|
||||
final call = this.call;
|
||||
if (call == null) return;
|
||||
|
||||
call.onCallStateChanged.stream.listen(_handleCallState);
|
||||
call.onCallEventChanged.stream.listen((event) {
|
||||
if (event == CallEvent.kFeedsChanged) {
|
||||
|
|
@ -220,7 +221,7 @@ class MyCallingPage extends State<Calling> {
|
|||
const Duration(seconds: 2),
|
||||
() => widget.onClear?.call(),
|
||||
);
|
||||
if (call?.type == CallType.kVideo) {
|
||||
if (call.type == CallType.kVideo) {
|
||||
try {
|
||||
unawaited(Wakelock.disable());
|
||||
} catch (_) {}
|
||||
|
|
@ -230,7 +231,7 @@ class MyCallingPage extends State<Calling> {
|
|||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
call?.cleanUp.call();
|
||||
call.cleanUp.call();
|
||||
}
|
||||
|
||||
void _resizeLocalVideo(Orientation orientation) {
|
||||
|
|
@ -249,6 +250,14 @@ class MyCallingPage extends State<Calling> {
|
|||
|
||||
void _handleCallState(CallState state) {
|
||||
Logs().v('CallingPage::handleCallState: ${state.toString()}');
|
||||
if ({CallState.kConnected, CallState.kEnded}.contains(state)) {
|
||||
try {
|
||||
Vibration.vibrate(duration: 200);
|
||||
} catch (e) {
|
||||
Logs().e('[Dialer] could not vibrate for call updates');
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_state = state;
|
||||
|
|
@ -259,52 +268,69 @@ class MyCallingPage extends State<Calling> {
|
|||
|
||||
void _answerCall() {
|
||||
setState(() {
|
||||
call?.answer();
|
||||
call.answer();
|
||||
});
|
||||
}
|
||||
|
||||
void _hangUp() {
|
||||
setState(() {
|
||||
if (call != null && (call?.isRinging ?? false)) {
|
||||
call?.reject();
|
||||
if (call.isRinging) {
|
||||
call.reject();
|
||||
} else {
|
||||
call?.hangup();
|
||||
call.hangup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _muteMic() {
|
||||
setState(() {
|
||||
call?.setMicrophoneMuted(!call!.isMicrophoneMuted);
|
||||
call.setMicrophoneMuted(!call.isMicrophoneMuted);
|
||||
});
|
||||
}
|
||||
|
||||
void _screenSharing() {
|
||||
void _screenSharing() async {
|
||||
if (PlatformInfos.isAndroid) {
|
||||
if (!call.screensharingEnabled) {
|
||||
await FlutterForegroundTask.init(
|
||||
androidNotificationOptions: AndroidNotificationOptions(
|
||||
channelId: 'notification_channel_id',
|
||||
channelName: 'Foreground Notification',
|
||||
channelDescription: L10n.of(context)!.foregroundServiceRunning,
|
||||
),
|
||||
);
|
||||
FlutterForegroundTask.startService(
|
||||
notificationTitle: L10n.of(context)!.screenSharingTitle,
|
||||
notificationText: L10n.of(context)!.screenSharingDetail);
|
||||
} else {
|
||||
FlutterForegroundTask.stopService();
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
call?.setScreensharingEnabled(!call!.screensharingEnabled);
|
||||
call.setScreensharingEnabled(!call.screensharingEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
void _remoteOnHold() {
|
||||
setState(() {
|
||||
call?.setRemoteOnHold(!call!.remoteOnHold);
|
||||
call.setRemoteOnHold(!call.remoteOnHold);
|
||||
});
|
||||
}
|
||||
|
||||
void _muteCamera() {
|
||||
setState(() {
|
||||
call?.setLocalVideoMuted(!call!.isLocalVideoMuted);
|
||||
call.setLocalVideoMuted(!call.isLocalVideoMuted);
|
||||
});
|
||||
}
|
||||
|
||||
void _switchCamera() async {
|
||||
if (call!.localUserMediaStream != null) {
|
||||
if (call.localUserMediaStream != null) {
|
||||
await Helper.switchCamera(
|
||||
call!.localUserMediaStream!.stream!.getVideoTracks()[0]);
|
||||
call.localUserMediaStream!.stream!.getVideoTracks()[0]);
|
||||
if (PlatformInfos.isMobile) {
|
||||
call!.facingMode == 'user'
|
||||
? call!.facingMode = 'environment'
|
||||
: call!.facingMode = 'user';
|
||||
call.facingMode == 'user'
|
||||
? call.facingMode = 'environment'
|
||||
: call.facingMode = 'user';
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
|
|
@ -319,7 +345,7 @@ class MyCallingPage extends State<Calling> {
|
|||
*/
|
||||
|
||||
List<Widget> _buildActionButtons(bool isFloating) {
|
||||
if (isFloating || call == null) {
|
||||
if (isFloating) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -391,7 +417,7 @@ class MyCallingPage extends State<Calling> {
|
|||
case CallState.kInviteSent:
|
||||
case CallState.kCreateAnswer:
|
||||
case CallState.kConnecting:
|
||||
return call!.isOutgoing
|
||||
return call.isOutgoing
|
||||
? <Widget>[hangupButton]
|
||||
: <Widget>[answerButton, hangupButton];
|
||||
case CallState.kConnected:
|
||||
|
|
@ -429,7 +455,7 @@ class MyCallingPage extends State<Calling> {
|
|||
final stackWidgets = <Widget>[];
|
||||
|
||||
final call = this.call;
|
||||
if (call == null || call.callHasEnded) {
|
||||
if (call.callHasEnded) {
|
||||
return stackWidgets;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -6,6 +7,7 @@ import 'package:vrouter/vrouter.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/voip/callkeep_manager.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/settings_switch_list_tile.dart';
|
||||
|
|
@ -68,6 +70,17 @@ class SettingsChatView extends StatelessWidget {
|
|||
defaultValue: AppConfig.experimentalVoip,
|
||||
),
|
||||
const Divider(height: 1),
|
||||
if (Matrix.of(context).webrtcIsSupported && !kIsWeb)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.callingPermissions),
|
||||
onTap: () =>
|
||||
CallKeepManager().checkoutPhoneAccountSetting(context),
|
||||
trailing: const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Icon(Icons.call),
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.emoteSettings),
|
||||
onTap: () => VRouter.of(context).to('emotes'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue