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),
|
showPresences<bool>('chat.fluffy.show_presences', true),
|
||||||
displayNavigationRail<bool>('chat.fluffy.display_navigation_rail', false),
|
displayNavigationRail<bool>('chat.fluffy.display_navigation_rail', false),
|
||||||
experimentalVoip<bool>('chat.fluffy.experimental_voip', 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'),
|
shareKeysWith<String>('chat.fluffy.share_keys_with_2', 'all'),
|
||||||
noEncryptionWarningShown<bool>(
|
noEncryptionWarningShown<bool>(
|
||||||
'chat.fluffy.no_encryption_warning_shown',
|
'chat.fluffy.no_encryption_warning_shown',
|
||||||
|
|
|
||||||
|
|
@ -2807,4 +2807,4 @@
|
||||||
"skipSupportingFluffyChat": "FluffyChat unterstützen überspringen",
|
"skipSupportingFluffyChat": "FluffyChat unterstützen überspringen",
|
||||||
"iDoNotWantToSupport": "Ich möchte nicht unterstützen",
|
"iDoNotWantToSupport": "Ich möchte nicht unterstützen",
|
||||||
"iAlreadySupportFluffyChat": "I unterstütze FluffyChat bereits"
|
"iAlreadySupportFluffyChat": "I unterstütze FluffyChat bereits"
|
||||||
}
|
}
|
||||||
|
|
@ -2789,5 +2789,13 @@
|
||||||
"iDoNotWantToSupport": "I do not want to support",
|
"iDoNotWantToSupport": "I do not want to support",
|
||||||
"iAlreadySupportFluffyChat": "I already support FluffyChat",
|
"iAlreadySupportFluffyChat": "I already support FluffyChat",
|
||||||
"setLowPriority": "Set low priority",
|
"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",
|
"iDoNotWantToSupport": "Ma ei soovi toetada",
|
||||||
"setLowPriority": "Märgi vähetähtsaks",
|
"setLowPriority": "Märgi vähetähtsaks",
|
||||||
"unsetLowPriority": "Eemalda märkimine vähetähtsaks"
|
"unsetLowPriority": "Eemalda märkimine vähetähtsaks"
|
||||||
}
|
}
|
||||||
|
|
@ -2807,4 +2807,4 @@
|
||||||
"iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin",
|
"iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin",
|
||||||
"setLowPriority": "Socraigh tosaíocht íseal",
|
"setLowPriority": "Socraigh tosaíocht íseal",
|
||||||
"unsetLowPriority": "Díshuiteáil tosaíocht íseal"
|
"unsetLowPriority": "Díshuiteáil tosaíocht íseal"
|
||||||
}
|
}
|
||||||
|
|
@ -2801,4 +2801,4 @@
|
||||||
"iAlreadySupportFluffyChat": "Xa apoiei a FluffyChat",
|
"iAlreadySupportFluffyChat": "Xa apoiei a FluffyChat",
|
||||||
"setLowPriority": "Establecer prioridade baixa",
|
"setLowPriority": "Establecer prioridade baixa",
|
||||||
"unsetLowPriority": "Non establecer prioridade baixa"
|
"unsetLowPriority": "Non establecer prioridade baixa"
|
||||||
}
|
}
|
||||||
|
|
@ -2808,4 +2808,4 @@
|
||||||
"iAlreadySupportFluffyChat": "Jeg støtter allerede FluffyChat",
|
"iAlreadySupportFluffyChat": "Jeg støtter allerede FluffyChat",
|
||||||
"setLowPriority": "Sett lav prioritet",
|
"setLowPriority": "Sett lav prioritet",
|
||||||
"unsetLowPriority": "Fjern lav prioritet"
|
"unsetLowPriority": "Fjern lav prioritet"
|
||||||
}
|
}
|
||||||
|
|
@ -2800,4 +2800,4 @@
|
||||||
"support": "Steunen",
|
"support": "Steunen",
|
||||||
"setLowPriority": "Lage prioriteit instellen",
|
"setLowPriority": "Lage prioriteit instellen",
|
||||||
"unsetLowPriority": "Lage prioriteit uitschakelen"
|
"unsetLowPriority": "Lage prioriteit uitschakelen"
|
||||||
}
|
}
|
||||||
|
|
@ -2801,4 +2801,4 @@
|
||||||
"iAlreadySupportFluffyChat": "我已支持 FluffyChat",
|
"iAlreadySupportFluffyChat": "我已支持 FluffyChat",
|
||||||
"setLowPriority": "设置低优先级",
|
"setLowPriority": "设置低优先级",
|
||||||
"unsetLowPriority": "取消设置低优先级"
|
"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_app_bar_title.dart';
|
||||||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||||
import 'package:fluffychat/pages/chat/encryption_button.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/pinned_events.dart';
|
||||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||||
import 'package:fluffychat/utils/account_config.dart';
|
import 'package:fluffychat/utils/account_config.dart';
|
||||||
|
|
@ -224,14 +225,16 @@ class ChatView extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
] else if (!controller.room.isArchived) ...[
|
] else if (!controller.room.isArchived) ...[
|
||||||
if (AppSettings.experimentalVoip.value &&
|
if ((AppSettings.experimentalVoip.value &&
|
||||||
Matrix.of(context).voipPlugin != null &&
|
Matrix.of(context).voipPlugin != null &&
|
||||||
controller.room.isDirectChat)
|
controller.room.isDirectChat))
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: controller.onPhoneButtonTap,
|
onPressed: controller.onPhoneButtonTap,
|
||||||
icon: const Icon(Icons.call_outlined),
|
icon: const Icon(Icons.call_outlined),
|
||||||
tooltip: L10n.of(context).placeCall,
|
tooltip: L10n.of(context).placeCall,
|
||||||
),
|
)
|
||||||
|
else if (AppSettings.jitsiFeature.value)
|
||||||
|
JitsiPopupButton(controller.room),
|
||||||
EncryptionButton(controller.room),
|
EncryptionButton(controller.room),
|
||||||
ChatSettingsPopupMenu(controller.room, true),
|
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