Skip to content

Commit f47a787

Browse files
compose_box: Add video call button in compose box
1 parent eb76876 commit f47a787

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

lib/widgets/compose_box.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,63 @@ Future<void> _uploadFiles({
10311031
}
10321032
}
10331033

1034+
class _AttachVideoChatUrlButton extends StatelessWidget {
1035+
const _AttachVideoChatUrlButton({
1036+
required this.controller,
1037+
required this.enabled,
1038+
});
1039+
1040+
final ComposeBoxController controller;
1041+
final bool enabled;
1042+
1043+
static const int jitsi = 1;
1044+
static const int zoom = 2; //TODO: Add other supported video chat providers
1045+
1046+
String _generateJitsiUrl(String serverUrl, String visibleText) {
1047+
final id = List.generate(15, (_) => Random.secure().nextInt(10)).join();
1048+
return inlineLink(visibleText, '$serverUrl/$id#config.startWithVideoMuted=false');
1049+
}
1050+
1051+
String? _getMeetingUrl(ZulipLocalizations zulipLocalization, int? provider, String? jitsiServerUrl) {
1052+
final visibleText = zulipLocalization.composeBoxUploadedVideoCallUrl;
1053+
1054+
switch (provider) {
1055+
case 0: return null; //TODO: Implement feedback no video chat provider is setup
1056+
case jitsi: return jitsiServerUrl == null ? null :_generateJitsiUrl(jitsiServerUrl, visibleText);
1057+
case zoom: return inlineLink(visibleText,
1058+
'https://zoom.us/start/meeting');
1059+
default: return null;
1060+
}
1061+
}
1062+
1063+
void _handlePress(BuildContext context) {
1064+
final store = PerAccountStoreWidget.of(context);
1065+
final zulipLocalizations = ZulipLocalizations.of(context);
1066+
1067+
final placeholder = _getMeetingUrl(zulipLocalizations,
1068+
store.realmVideoChatProvider, store.jitsiServerUrl);
1069+
if (placeholder == null) return;
1070+
1071+
final contentController = controller.content;
1072+
final insertionRange = contentController.insertionIndex();
1073+
contentController.value = contentController.value.replaced(insertionRange, '$placeholder\n\n');
1074+
controller.contentFocusNode.requestFocus();
1075+
}
1076+
1077+
@override
1078+
Widget build(BuildContext context) {
1079+
final designVariables = DesignVariables.of(context);
1080+
final zulipLocalizations = ZulipLocalizations.of(context);
1081+
1082+
return SizedBox(
1083+
width: _composeButtonSize,
1084+
child: IconButton(
1085+
icon: Icon(ZulipIcons.video, color: designVariables.foreground.withFadedAlpha(0.5)),
1086+
tooltip: zulipLocalizations.composeBoxAttachFromVideoCallTooltip,
1087+
onPressed: enabled ? () => _handlePress(context) : null));
1088+
}
1089+
}
1090+
10341091
abstract class _AttachUploadsButton extends StatelessWidget {
10351092
const _AttachUploadsButton({required this.controller, required this.enabled});
10361093

@@ -1469,6 +1526,7 @@ abstract class _ComposeBoxBody extends StatelessWidget {
14691526
_AttachFileButton(controller: controller, enabled: composeButtonsEnabled),
14701527
_AttachMediaButton(controller: controller, enabled: composeButtonsEnabled),
14711528
_AttachFromCameraButton(controller: controller, enabled: composeButtonsEnabled),
1529+
_AttachVideoChatUrlButton(controller: controller, enabled: composeButtonsEnabled),
14721530
];
14731531

14741532
final topicInput = buildTopicInput();

test/widgets/compose_box_test.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ void main() {
6363
List<Subscription> subscriptions = const [],
6464
List<Message>? messages,
6565
bool? mandatoryTopics,
66+
int? realmVideoChatProvider,
67+
String? jitsiServerUrl,
6668
int? zulipFeatureLevel,
6769
}) async {
6870
streams ??= subscriptions;
@@ -89,6 +91,8 @@ void main() {
8991
subscriptions: subscriptions,
9092
zulipFeatureLevel: zulipFeatureLevel,
9193
realmMandatoryTopics: mandatoryTopics,
94+
realmVideoChatProvider: realmVideoChatProvider,
95+
jitsiServerUrl: jitsiServerUrl,
9296
realmAllowMessageEditing: true,
9397
realmMessageContentEditLimitSeconds: null,
9498
));
@@ -1037,6 +1041,54 @@ void main() {
10371041
});
10381042
});
10391043

1044+
group('video call button', () {
1045+
Future<void> prepare(WidgetTester tester, {
1046+
String? jitsiServerUrl,
1047+
int? realmVideoChatProvider,
1048+
}) async {
1049+
TypingNotifier.debugEnable = false;
1050+
addTearDown(TypingNotifier.debugReset);
1051+
1052+
final channel = eg.stream();
1053+
final narrow = ChannelNarrow(channel.streamId);
1054+
await prepareComposeBox(tester,
1055+
narrow: narrow,
1056+
streams: [channel],
1057+
jitsiServerUrl : jitsiServerUrl,
1058+
realmVideoChatProvider : realmVideoChatProvider,
1059+
);
1060+
1061+
await enterTopic(tester, narrow: narrow, topic: 'some topic');
1062+
await tester.pump();
1063+
}
1064+
1065+
group('attach video call link', () {
1066+
testWidgets('jitsi success', (tester) async {
1067+
await prepare(tester);
1068+
connection.prepare();
1069+
1070+
await tester.tap(find.byIcon(ZulipIcons.video));
1071+
await tester.pump();
1072+
1073+
check(controller!.content.text)
1074+
..startsWith('[Join video call.](https://meet.jit.si')
1075+
..endsWith('#config.startWithVideoMuted=false)\n\n');
1076+
});
1077+
1078+
testWidgets('zoom success', (tester) async {
1079+
await prepare(tester, jitsiServerUrl: '',
1080+
realmVideoChatProvider: 2);
1081+
connection.prepare();
1082+
1083+
await tester.tap(find.byIcon(ZulipIcons.video));
1084+
await tester.pump();
1085+
1086+
check(controller!.content.text)
1087+
.equals('[Join video call.](https://zoom.us/start/meeting)\n\n');
1088+
});
1089+
});
1090+
});
1091+
10401092
group('uploads', () {
10411093
void checkAppearsLoading(WidgetTester tester, bool expected) {
10421094
final sendButtonElement = tester.element(find.ancestor(
@@ -1314,6 +1366,7 @@ void main() {
13141366
check(attachButtonFinder(ZulipIcons.attach_file).evaluate().length).equals(areShown ? 1 : 0);
13151367
check(attachButtonFinder(ZulipIcons.image).evaluate().length).equals(areShown ? 1 : 0);
13161368
check(attachButtonFinder(ZulipIcons.camera).evaluate().length).equals(areShown ? 1 : 0);
1369+
check(attachButtonFinder(ZulipIcons.video).evaluate().length).equals(areShown ? 1 : 0);
13171370
}
13181371

13191372
void checkBannerWithLabel(String label, {required bool isShown}) {

0 commit comments

Comments
 (0)