Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions apps/mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
"bundleIdentifier": "com.cal.companion",
"userInterfaceStyle": "automatic",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
"ITSAppUsesNonExemptEncryption": false,
"NSCameraUsageDescription": "Cal.com needs access to your camera to join video calls",
"NSMicrophoneUsageDescription": "Cal.com needs access to your microphone to join video calls"
},
"entitlements": {
"com.apple.developer.default-data-protection": "NSFileProtectionComplete",
"com.apple.security.application-groups": ["group.com.cal.companion"]
"com.apple.security.application-groups": ["group.com.cal.companion"],
"com.apple.developer.associated-domains": ["applinks:app.cal.com"]
},
"icon": "./assets/cal-logo.icon"
},
Expand All @@ -37,7 +40,21 @@
"package": "com.calcom.companion",
"softwareKeyboardLayoutMode": "pan",
"usesCleartextTraffic": false,
"userInterfaceStyle": "automatic"
"userInterfaceStyle": "automatic",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "app.cal.com",
"pathPrefix": "/video/"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
},
"web": {
"favicon": "./assets/favicon.png",
Expand Down Expand Up @@ -125,6 +142,7 @@
}
}
],
"@daily-co/config-plugin-rn-daily-js",
"@bacons/apple-targets",
[
"react-native-android-widget",
Expand Down
15 changes: 11 additions & 4 deletions apps/mobile/app/(tabs)/(bookings)/booking-detail.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Clipboard from "expo-clipboard";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Stack, useLocalSearchParams } from "expo-router";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useMemo, useRef } from "react";
import { useColorScheme } from "react-native";
import { BookingDetailScreen } from "@/components/screens/BookingDetailScreen";
Expand All @@ -10,6 +10,7 @@ import { showErrorAlert, showInfoAlert, showSuccessAlert } from "@/utils/alerts"
import { getMeetingUrl } from "@/utils/booking";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { openInDefaultBrowser } from "@/utils/browser";
import { isDailyRoomUrl } from "@/utils/cal-video";

// Empty actions result for when no booking is loaded
const EMPTY_ACTIONS: BookingActionsResult = {
Expand Down Expand Up @@ -44,6 +45,7 @@ const getMonthName = (dateString: string | undefined): string => {

export default function BookingDetailIOS() {
const { uid } = useLocalSearchParams<{ uid: string }>();
const router = useRouter();
const { userInfo } = useAuth();
const colorScheme = useColorScheme();

Expand All @@ -69,10 +71,15 @@ export default function BookingDetailIOS() {

// Handle join meeting
const handleJoinMeeting = useCallback(() => {
if (meetingUrl) {
openInDefaultBrowser(meetingUrl, "meeting link");
if (!meetingUrl) return;

if (isDailyRoomUrl(meetingUrl)) {
router.push({ pathname: "/video-call", params: { url: meetingUrl } });
return;
}
}, [meetingUrl]);

openInDefaultBrowser(meetingUrl, "meeting link");
}, [meetingUrl, router]);

// Handle copy meeting link
const handleCopyMeetingLink = useCallback(async () => {
Expand Down
12 changes: 9 additions & 3 deletions apps/mobile/app/(tabs)/(bookings)/booking-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { showErrorAlert, showInfoAlert, showSuccessAlert } from "@/utils/alerts"
import { getMeetingUrl } from "@/utils/booking";
import { type BookingActionsResult, getBookingActions } from "@/utils/booking-actions";
import { openInDefaultBrowser } from "@/utils/browser";
import { isDailyRoomUrl } from "@/utils/cal-video";

// Empty actions result for when no booking is loaded
const EMPTY_ACTIONS: BookingActionsResult = {
Expand Down Expand Up @@ -263,10 +264,15 @@ export default function BookingDetail() {
const meetingUrl = useMemo(() => getMeetingUrl(booking ?? null), [booking]);

const handleJoinMeeting = useCallback(() => {
if (meetingUrl) {
openInDefaultBrowser(meetingUrl, "meeting link");
if (!meetingUrl) return;

if (isDailyRoomUrl(meetingUrl)) {
router.push({ pathname: "/video-call", params: { url: meetingUrl } });
return;
}
}, [meetingUrl]);

openInDefaultBrowser(meetingUrl, "meeting link");
}, [meetingUrl, router]);

const handleCopyMeetingLink = useCallback(async () => {
if (meetingUrl) {
Expand Down
36 changes: 35 additions & 1 deletion apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import "../global.css";

import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
import { PortalHost } from "@rn-primitives/portal";
import * as Linking from "expo-linking";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Stack } from "expo-router";
import { Stack, useRouter } from "expo-router";
import { useEffect } from "react";
import { Platform, StatusBar, useColorScheme, View } from "react-native";
import { getBookingUidFromCalVideoUrl, isCalVideoWebUrl } from "@/utils/cal-video";
import { CalComLogo } from "@/components/CalComLogo";
import LoginScreenComponent from "@/components/LoginScreen";
import { NetworkStatusBanner } from "@/components/NetworkStatusBanner";
Expand All @@ -21,6 +24,29 @@ function RootLayoutContent() {
const colorScheme = useColorScheme();
const isDark = colorScheme === "dark";
const colors = getColors(isDark);
const router = useRouter();

// Handle incoming deep links for Cal Video URLs (app.cal.com/video/<bookingUid>)
useEffect(() => {
function handleDeepLink(event: { url: string }) {
const { url } = event;
if (isCalVideoWebUrl(url)) {
const bookingUid = getBookingUidFromCalVideoUrl(url);
if (bookingUid) {
router.push({ pathname: "/video-call", params: { bookingUid } });
}
}
}

// Handle URL that launched the app (cold start)
Linking.getInitialURL().then((url) => {
if (url) handleDeepLink({ url });
});

// Handle URLs while the app is already open (warm start)
const subscription = Linking.addEventListener("url", handleDeepLink);
return () => subscription.remove();
}, [router]);

// Show Cal.com logo while checking auth state to prevent login flash
if (loading) {
Expand Down Expand Up @@ -379,6 +405,14 @@ function RootLayoutContent() {
: "light",
}}
/>
<Stack.Screen
name="video-call"
options={{
headerShown: false,
presentation: "fullScreenModal",
animation: "slide_from_bottom",
}}
/>
<Stack.Screen
name="edit-availability-override"
options={{
Expand Down
Loading
Loading