Merge pull request #2741 from krille-chan/krille/implement-jitsi
feat: Implement experimental jitsi group calls behind a feature flag
This commit is contained in:
commit
c94b4ebe47
11 changed files with 202 additions and 12 deletions
|
|
@ -40,6 +40,8 @@ enum AppSettings<T> {
|
|||
showPresences<bool>('chat.fluffy.show_presences', true),
|
||||
displayNavigationRail<bool>('chat.fluffy.display_navigation_rail', false),
|
||||
experimentalVoip<bool>('chat.fluffy.experimental_voip', false),
|
||||
jitsiFeature<bool>('chat.fluffy.enable_jitsi', false),
|
||||
jitsiDomain<String>('chat.fluffy.jitsi_domain', 'meet.jit.si'),
|
||||
shareKeysWith<String>('chat.fluffy.share_keys_with_2', 'all'),
|
||||
noEncryptionWarningShown<bool>(
|
||||
'chat.fluffy.no_encryption_warning_shown',
|
||||
|
|
|
|||
|
|
@ -2807,4 +2807,4 @@
|
|||
"skipSupportingFluffyChat": "FluffyChat unterstützen überspringen",
|
||||
"iDoNotWantToSupport": "Ich möchte nicht unterstützen",
|
||||
"iAlreadySupportFluffyChat": "I unterstütze FluffyChat bereits"
|
||||
}
|
||||
}
|
||||
|
|
@ -2789,5 +2789,13 @@
|
|||
"iDoNotWantToSupport": "I do not want to support",
|
||||
"iAlreadySupportFluffyChat": "I already support FluffyChat",
|
||||
"setLowPriority": "Set low priority",
|
||||
"unsetLowPriority": "Unset low priority"
|
||||
}
|
||||
"unsetLowPriority": "Unset low priority",
|
||||
"removeCallFromChat": "Remove call from chat",
|
||||
"removeCallFromChatDescription": "Do you want to remove the call from the chat for all members?",
|
||||
"removeCallForEveryone": "Remove call for everyone",
|
||||
"startVoiceCall": "Start voice call",
|
||||
"startVideoCall": "Start video call",
|
||||
"joinVoiceCall": "Join voice call",
|
||||
"joinVideoCall": "Join video call",
|
||||
"live": "Live"
|
||||
}
|
||||
|
|
@ -2801,4 +2801,4 @@
|
|||
"iDoNotWantToSupport": "Ma ei soovi toetada",
|
||||
"setLowPriority": "Märgi vähetähtsaks",
|
||||
"unsetLowPriority": "Eemalda märkimine vähetähtsaks"
|
||||
}
|
||||
}
|
||||
|
|
@ -2807,4 +2807,4 @@
|
|||
"iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin",
|
||||
"setLowPriority": "Socraigh tosaíocht íseal",
|
||||
"unsetLowPriority": "Díshuiteáil tosaíocht íseal"
|
||||
}
|
||||
}
|
||||
|
|
@ -2801,4 +2801,4 @@
|
|||
"iAlreadySupportFluffyChat": "Xa apoiei a FluffyChat",
|
||||
"setLowPriority": "Establecer prioridade baixa",
|
||||
"unsetLowPriority": "Non establecer prioridade baixa"
|
||||
}
|
||||
}
|
||||
|
|
@ -2808,4 +2808,4 @@
|
|||
"iAlreadySupportFluffyChat": "Jeg støtter allerede FluffyChat",
|
||||
"setLowPriority": "Sett lav prioritet",
|
||||
"unsetLowPriority": "Fjern lav prioritet"
|
||||
}
|
||||
}
|
||||
|
|
@ -2800,4 +2800,4 @@
|
|||
"support": "Steunen",
|
||||
"setLowPriority": "Lage prioriteit instellen",
|
||||
"unsetLowPriority": "Lage prioriteit uitschakelen"
|
||||
}
|
||||
}
|
||||
|
|
@ -2801,4 +2801,4 @@
|
|||
"iAlreadySupportFluffyChat": "我已支持 FluffyChat",
|
||||
"setLowPriority": "设置低优先级",
|
||||
"unsetLowPriority": "取消设置低优先级"
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
|||
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||
import 'package:fluffychat/pages/chat/encryption_button.dart';
|
||||
import 'package:fluffychat/pages/chat/jitsi_popup_button.dart';
|
||||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
|
|
@ -224,14 +225,16 @@ class ChatView extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
] else if (!controller.room.isArchived) ...[
|
||||
if (AppSettings.experimentalVoip.value &&
|
||||
if ((AppSettings.experimentalVoip.value &&
|
||||
Matrix.of(context).voipPlugin != null &&
|
||||
controller.room.isDirectChat)
|
||||
controller.room.isDirectChat))
|
||||
IconButton(
|
||||
onPressed: controller.onPhoneButtonTap,
|
||||
icon: const Icon(Icons.call_outlined),
|
||||
tooltip: L10n.of(context).placeCall,
|
||||
),
|
||||
)
|
||||
else if (AppSettings.jitsiFeature.value)
|
||||
JitsiPopupButton(controller.room),
|
||||
EncryptionButton(controller.room),
|
||||
ChatSettingsPopupMenu(controller.room, true),
|
||||
],
|
||||
|
|
|
|||
177
lib/pages/chat/jitsi_popup_button.dart
Normal file
177
lib/pages/chat/jitsi_popup_button.dart
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class JitsiPopupButton extends StatelessWidget {
|
||||
final Room room;
|
||||
const JitsiPopupButton(this.room, {super.key});
|
||||
|
||||
Future<void> _startCall(BuildContext context, bool isAudioOnly) async {
|
||||
final l10n = L10n.of(context);
|
||||
final urlResult = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final conferenceId = room.client.generateUniqueTransactionId();
|
||||
final domain = AppSettings.jitsiDomain.value;
|
||||
final uri = Uri(
|
||||
scheme: 'https',
|
||||
host: domain,
|
||||
path: conferenceId,
|
||||
fragment: isAudioOnly ? 'config.startWithVideoMuted=true' : null,
|
||||
);
|
||||
await room.addWidget(
|
||||
MatrixWidget(
|
||||
room: room,
|
||||
name: 'Jitsi Meet',
|
||||
type: 'jitsi',
|
||||
url: uri.toString(),
|
||||
data: {
|
||||
'domain': domain,
|
||||
'isAudioOnly': isAudioOnly,
|
||||
'conferenceId': conferenceId,
|
||||
'roomName': room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
return uri;
|
||||
},
|
||||
);
|
||||
final url = urlResult.result;
|
||||
if (url == null) return;
|
||||
await launchUrl(url);
|
||||
|
||||
if (!context.mounted) return;
|
||||
final consent = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: l10n.removeCallFromChat,
|
||||
message: l10n.removeCallFromChatDescription,
|
||||
okLabel: l10n.remove,
|
||||
);
|
||||
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
if (!context.mounted) return;
|
||||
|
||||
await _endAllCalls(context);
|
||||
}
|
||||
|
||||
Future<void> _endAllCalls(BuildContext context) => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final activeJitsiCalls = room.states['im.vector.modular.widgets']?.values
|
||||
.where((state) => state.content['type'] == 'jitsi');
|
||||
if (activeJitsiCalls == null) return;
|
||||
for (final call in activeJitsiCalls) {
|
||||
await room.deleteWidget(call.stateKey!);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context);
|
||||
final activeJitsiCalls = room.widgets
|
||||
.where((widget) => widget.type == 'jitsi')
|
||||
.map((widget) {
|
||||
final isAudioOnly = widget.data?.tryGet<bool>('isAudioOnly') ?? false;
|
||||
final domain = widget.data?.tryGet<String>('domain');
|
||||
final conferenceId = widget.data?.tryGet<String>('conferenceId');
|
||||
return (
|
||||
isAudioOnly: isAudioOnly,
|
||||
domain: domain,
|
||||
conferenceId: conferenceId,
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
final canEditCalls = room.canChangeStateEvent('im.vector.modular.widgets');
|
||||
if (activeJitsiCalls.isEmpty && !canEditCalls) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return PopupMenuButton(
|
||||
itemBuilder: (context) => [
|
||||
...activeJitsiCalls.map(
|
||||
(call) => PopupMenuItem(
|
||||
onTap: () => launchUrl(
|
||||
Uri(
|
||||
scheme: 'https',
|
||||
host: call.domain,
|
||||
path: call.conferenceId,
|
||||
fragment: call.isAudioOnly
|
||||
? 'config.startWithVideoMuted=true'
|
||||
: null,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(call.isAudioOnly ? Icons.add_call : Icons.video_call),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
call.isAudioOnly ? l10n.joinVoiceCall : l10n.joinVideoCall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (canEditCalls) ...[
|
||||
if (activeJitsiCalls.isEmpty) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () => _startCall(context, true),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
const Icon(Icons.add_call),
|
||||
const SizedBox(width: 12),
|
||||
Text(l10n.startVoiceCall),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => _startCall(context, false),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
const Icon(Icons.video_call),
|
||||
const SizedBox(width: 12),
|
||||
Text(l10n.startVideoCall),
|
||||
],
|
||||
),
|
||||
),
|
||||
] else
|
||||
PopupMenuItem(
|
||||
onTap: () => _endAllCalls(context),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.call_end_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.removeCallForEveryone,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
icon: Badge(
|
||||
label: Text(l10n.live),
|
||||
isLabelVisible: activeJitsiCalls.isNotEmpty,
|
||||
child: Icon(Icons.video_call_outlined),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue