Skip to content

Commit f404194

Browse files
compose_box: Add video call url button
Added Video call url button for UI, corresponding example data in example_data.dart and tests.
1 parent 01d3d7e commit f404194

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-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/example_data.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,7 @@ InitialSnapshot initialSnapshot({
13351335
RealmDeleteOwnMessagePolicy? realmDeleteOwnMessagePolicy,
13361336
RealmWildcardMentionPolicy? realmWildcardMentionPolicy,
13371337
bool? realmMandatoryTopics,
1338+
int? realmVideoChatProvider,
13381339
String? realmName,
13391340
int? realmWaitingPeriodThreshold,
13401341
int? realmMessageContentDeleteLimitSeconds,
@@ -1344,6 +1345,7 @@ InitialSnapshot initialSnapshot({
13441345
Uri? realmIconUrl,
13451346
bool? realmPresenceDisabled,
13461347
Map<String, RealmDefaultExternalAccount>? realmDefaultExternalAccounts,
1348+
String? jitsiServerUrl,
13471349
int? maxFileUploadSizeMib,
13481350
Uri? serverEmojiDataUrl,
13491351
String? realmEmptyTopicDisplayName,
@@ -1398,6 +1400,7 @@ InitialSnapshot initialSnapshot({
13981400
realmDeleteOwnMessagePolicy: realmDeleteOwnMessagePolicy,
13991401
realmWildcardMentionPolicy: realmWildcardMentionPolicy ?? RealmWildcardMentionPolicy.everyone,
14001402
realmMandatoryTopics: realmMandatoryTopics ?? true,
1403+
realmVideoChatProvider: realmVideoChatProvider ?? 1,
14011404
realmName: realmName ?? 'Example Zulip organization',
14021405
realmWaitingPeriodThreshold: realmWaitingPeriodThreshold ?? 0,
14031406
realmMessageContentDeleteLimitSeconds: realmMessageContentDeleteLimitSeconds,
@@ -1407,6 +1410,7 @@ InitialSnapshot initialSnapshot({
14071410
realmIconUrl: realmIconUrl ?? _realmIcon,
14081411
realmPresenceDisabled: realmPresenceDisabled ?? false,
14091412
realmDefaultExternalAccounts: realmDefaultExternalAccounts ?? {},
1413+
jitsiServerUrl: jitsiServerUrl ?? 'https://meet.jit.si',
14101414
maxFileUploadSizeMib: maxFileUploadSizeMib ?? 25,
14111415
serverEmojiDataUrl: serverEmojiDataUrl
14121416
?? realmUrl.replace(path: '/static/emoji.json'),

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)