Skip to content

Commit aebfd49

Browse files
feat(companion): Update availability screen to use liquid glass (#26041)
* Use previous header for android * use activity instead of ternary statements * update layout to use header buttons in availability screen * fix styles and add context menu * add set as default action --------- Co-authored-by: Dhairyashil <dhairyashil10101010@gmail.com>
1 parent 69a857a commit aebfd49

File tree

6 files changed

+333
-149
lines changed

6 files changed

+333
-149
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Stack } from "expo-router";
2+
3+
export default function EventTypesLayout() {
4+
return (
5+
<Stack>
6+
<Stack.Screen name="index" options={{}} />
7+
</Stack>
8+
);
9+
}

companion/app/(tabs)/availability.tsx renamed to companion/app/(tabs)/(availability)/index.tsx

Lines changed: 123 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Ionicons } from "@expo/vector-icons";
2-
import { useRouter } from "expo-router";
3-
import React, { useState, useMemo } from "react";
2+
import { Stack, useRouter } from "expo-router";
3+
import React, { useState, useMemo, Activity } from "react";
44
import {
55
View,
66
Text,
@@ -10,27 +10,27 @@ import {
1010
ActionSheetIOS,
1111
Alert,
1212
Platform,
13-
Modal,
1413
TextInput,
15-
KeyboardAvoidingView,
1614
ScrollView,
1715
} from "react-native";
1816

19-
import { CalComAPIService, Schedule } from "../../services/calcom";
20-
import { Header } from "../../components/Header";
21-
import { FullScreenModal } from "../../components/FullScreenModal";
22-
import { LoadingSpinner } from "../../components/LoadingSpinner";
23-
import { EmptyScreen } from "../../components/EmptyScreen";
24-
import { showErrorAlert } from "../../utils/alerts";
25-
import { offlineAwareRefresh } from "../../utils/network";
26-
import { shadows } from "../../utils/shadows";
17+
import { CalComAPIService, Schedule } from "../../../services/calcom";
18+
import { Header } from "../../../components/Header";
19+
import { FullScreenModal } from "../../../components/FullScreenModal";
20+
import { LoadingSpinner } from "../../../components/LoadingSpinner";
21+
import { EmptyScreen } from "../../../components/EmptyScreen";
22+
import { showErrorAlert } from "../../../utils/alerts";
23+
import { offlineAwareRefresh } from "../../../utils/network";
24+
import { shadows } from "../../../utils/shadows";
2725
import {
2826
useSchedules,
2927
useCreateSchedule,
3028
useDeleteSchedule,
3129
useDuplicateSchedule,
3230
useSetScheduleAsDefault,
33-
} from "../../hooks";
31+
} from "../../../hooks";
32+
import { isLiquidGlassAvailable } from "expo-glass-effect";
33+
import { AvailabilityListItem } from "../../../components/availability-list-item/AvailabilityListItem";
3434

3535
export default function Availability() {
3636
const router = useRouter();
@@ -261,65 +261,6 @@ export default function Availability() {
261261
);
262262
};
263263

264-
const renderSchedule = ({ item: schedule, index }: { item: Schedule; index: number }) => {
265-
return (
266-
<TouchableOpacity
267-
className="border-b border-[#E5E5EA] bg-white active:bg-[#F8F9FA]"
268-
onPress={() => handleSchedulePress(schedule)}
269-
onLongPress={() => handleScheduleLongPress(schedule)}
270-
style={{ paddingHorizontal: 16, paddingVertical: 16 }}
271-
>
272-
<View className="flex-row items-center justify-between">
273-
<View className="mr-4 flex-1">
274-
<View className="mb-1 flex-row flex-wrap items-center">
275-
<Text className="text-base font-semibold text-[#333]">{schedule.name}</Text>
276-
{schedule.isDefault ? (
277-
<View className="ml-2 rounded bg-[#666] px-2 py-0.5">
278-
<Text className="text-xs font-semibold text-white">Default</Text>
279-
</View>
280-
) : null}
281-
</View>
282-
283-
{schedule.availability && schedule.availability.length > 0 ? (
284-
<View>
285-
{schedule.availability.map((slot, slotIndex) => (
286-
<View
287-
key={`${schedule.id}-${slot.days.join("-")}-${slotIndex}`}
288-
className={slotIndex > 0 ? "mt-2" : ""}
289-
>
290-
<Text className="text-sm text-[#666]">
291-
{slot.days.join(", ")} {slot.startTime} - {slot.endTime}
292-
</Text>
293-
</View>
294-
))}
295-
</View>
296-
) : (
297-
<Text className="text-sm text-[#666]">No availability set</Text>
298-
)}
299-
300-
<View className="mt-2 flex-row items-center">
301-
<Ionicons name="globe-outline" size={14} color="#666" />
302-
<Text className="ml-1.5 text-sm text-[#666]">{schedule.timeZone}</Text>
303-
</View>
304-
</View>
305-
306-
{/* Three dots button - vertically centered on the right */}
307-
<TouchableOpacity
308-
className="items-center justify-center rounded-lg border border-[#E5E5EA]"
309-
style={{ width: 32, height: 32 }}
310-
onPress={(e) => {
311-
e.stopPropagation();
312-
setSelectedSchedule(schedule);
313-
setShowActionsModal(true);
314-
}}
315-
>
316-
<Ionicons name="ellipsis-horizontal" size={18} color="#3C3F44" />
317-
</TouchableOpacity>
318-
</View>
319-
</TouchableOpacity>
320-
);
321-
};
322-
323264
if (loading) {
324265
return (
325266
<View className="flex-1 bg-[#f8f9fa]">
@@ -356,93 +297,126 @@ export default function Availability() {
356297
const showList = !showEmptyState && !showSearchEmptyState;
357298

358299
return (
359-
<View className="flex-1 bg-gray-100">
360-
<Header />
300+
<>
301+
<Stack.Header
302+
style={{ backgroundColor: "transparent", shadowColor: "transparent" }}
303+
blurEffect={isLiquidGlassAvailable() ? undefined : "light"} // Only looks cool on iOS 18 and below
304+
hidden={Platform.OS === "android"}
305+
>
306+
<Stack.Header.Title large>Availability</Stack.Header.Title>
307+
<Stack.Header.Right>
308+
<Stack.Header.Button onPress={handleCreateNew} tintColor="#000" variant="prominent">
309+
New
310+
</Stack.Header.Button>
311+
</Stack.Header.Right>
312+
<Stack.Header.SearchBar
313+
placeholder="Search schedules"
314+
onChangeText={(e) => handleSearch(e.nativeEvent.text)}
315+
obscureBackground={false}
316+
barTintColor="#fff"
317+
/>
318+
</Stack.Header>
319+
320+
<Activity mode={Platform.OS !== "ios" ? "visible" : "hidden"}>
321+
<Header />
322+
<View className="flex-row items-center gap-3 border-b border-gray-300 bg-gray-100 px-4 py-2">
323+
<TextInput
324+
className="flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-[17px] text-black focus:border-black focus:ring-2 focus:ring-black"
325+
placeholder="Search schedules"
326+
placeholderTextColor="#9CA3AF"
327+
value={searchQuery}
328+
onChangeText={handleSearch}
329+
autoCapitalize="none"
330+
autoCorrect={false}
331+
clearButtonMode="while-editing"
332+
/>
333+
<TouchableOpacity
334+
className="min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
335+
onPress={handleCreateNew}
336+
>
337+
<Ionicons name="add" size={18} color="#fff" />
338+
<Text className="text-base font-semibold text-white">New</Text>
339+
</TouchableOpacity>
340+
</View>
341+
</Activity>
361342

362343
{/* Empty state - no schedules */}
363-
{showEmptyState ? (
364-
<View className="flex-1 bg-gray-50" style={{ paddingBottom: 100 }}>
344+
<Activity mode={showEmptyState ? "visible" : "hidden"}>
345+
<ScrollView
346+
style={{ backgroundColor: "white" }}
347+
contentContainerStyle={{
348+
flexGrow: 1,
349+
justifyContent: "center",
350+
alignItems: "center",
351+
padding: 20,
352+
paddingBottom: 90,
353+
}}
354+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
355+
contentInsetAdjustmentBehavior="automatic"
356+
>
357+
<EmptyScreen
358+
icon="time-outline"
359+
headline="Create an availability schedule"
360+
description="Creating availability schedules allows you to manage availability across event types. They can be applied to one or more event types."
361+
buttonText="New"
362+
onButtonPress={handleCreateNew}
363+
/>
364+
</ScrollView>
365+
</Activity>
366+
367+
{/* Search bar and content for non-empty states */}
368+
<Activity mode={!showEmptyState ? "visible" : "hidden"}>
369+
{/* Search empty state */}
370+
<Activity mode={showSearchEmptyState ? "visible" : "hidden"}>
365371
<ScrollView
372+
style={{ backgroundColor: "white" }}
366373
contentContainerStyle={{
367374
flexGrow: 1,
368375
justifyContent: "center",
369376
alignItems: "center",
370377
padding: 20,
378+
paddingBottom: 90,
371379
}}
372380
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
373381
>
374382
<EmptyScreen
375-
icon="time-outline"
376-
headline="Create an availability schedule"
377-
description="Creating availability schedules allows you to manage availability across event types. They can be applied to one or more event types."
378-
buttonText="New"
379-
onButtonPress={handleCreateNew}
383+
icon="search-outline"
384+
headline={`No results found for "${searchQuery}"`}
385+
description="Try searching with different keywords"
380386
/>
381387
</ScrollView>
382-
</View>
383-
) : null}
388+
</Activity>
384389

385-
{/* Search bar and content for non-empty states */}
386-
{!showEmptyState ? (
387-
<>
388-
<View className="flex-row items-center gap-3 border-b border-gray-300 bg-gray-100 px-4 py-2">
389-
<TextInput
390-
className="flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-[17px] text-black focus:border-black focus:ring-2 focus:ring-black"
391-
placeholder="Search schedules"
392-
placeholderTextColor="#9CA3AF"
393-
value={searchQuery}
394-
onChangeText={handleSearch}
395-
autoCapitalize="none"
396-
autoCorrect={false}
397-
clearButtonMode="while-editing"
398-
/>
399-
<TouchableOpacity
400-
className="min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
401-
onPress={handleCreateNew}
402-
>
403-
<Ionicons name="add" size={18} color="#fff" />
404-
<Text className="text-base font-semibold text-white">New</Text>
405-
</TouchableOpacity>
406-
</View>
407-
408-
{/* Search empty state */}
409-
{showSearchEmptyState ? (
410-
<View className="flex-1 bg-gray-50" style={{ paddingBottom: 100 }}>
411-
<ScrollView
412-
contentContainerStyle={{
413-
flexGrow: 1,
414-
justifyContent: "center",
415-
alignItems: "center",
416-
padding: 20,
417-
}}
418-
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
419-
>
420-
<EmptyScreen
421-
icon="search-outline"
422-
headline={`No results found for "${searchQuery}"`}
423-
description="Try searching with different keywords"
424-
/>
425-
</ScrollView>
426-
</View>
427-
) : null}
428-
429-
{/* Schedules list */}
430-
{showList ? (
431-
<View className="flex-1 px-2 pt-4 md:px-4">
432-
<View className="flex-1 overflow-hidden rounded-lg border border-[#E5E5EA] bg-white">
433-
<FlatList
434-
data={filteredSchedules}
435-
keyExtractor={(item) => item.id.toString()}
436-
renderItem={renderSchedule}
437-
contentContainerStyle={{ paddingBottom: 90 }}
438-
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
439-
showsVerticalScrollIndicator={false}
440-
/>
441-
</View>
442-
</View>
443-
) : null}
444-
</>
445-
) : null}
390+
{/* Schedules list */}
391+
<Activity mode={showList ? "visible" : "hidden"}>
392+
<FlatList
393+
className="flex-1 rounded-lg border border-[#E5E5EA] bg-white"
394+
contentContainerStyle={{
395+
paddingBottom: 90,
396+
paddingHorizontal: 8,
397+
paddingVertical: 4,
398+
}}
399+
data={filteredSchedules}
400+
keyExtractor={(item) => item.id.toString()}
401+
renderItem={({ item, index }) => (
402+
<AvailabilityListItem
403+
item={item}
404+
index={index}
405+
handleSchedulePress={handleSchedulePress}
406+
handleScheduleLongPress={handleScheduleLongPress}
407+
setSelectedSchedule={setSelectedSchedule}
408+
setShowActionsModal={setShowActionsModal}
409+
onDuplicate={handleDuplicate}
410+
onDelete={handleDelete}
411+
onSetAsDefault={handleSetAsDefault}
412+
/>
413+
)}
414+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
415+
showsVerticalScrollIndicator={false}
416+
contentInsetAdjustmentBehavior="automatic"
417+
/>
418+
</Activity>
419+
</Activity>
446420

447421
{/* Create Schedule Modal */}
448422
<FullScreenModal
@@ -536,7 +510,9 @@ export default function Availability() {
536510
{/* Actions List */}
537511
<View className="p-2">
538512
{/* Set as Default - only show if not already default */}
539-
{selectedSchedule && !selectedSchedule.isDefault ? (
513+
<Activity
514+
mode={selectedSchedule && !selectedSchedule.isDefault ? "visible" : "hidden"}
515+
>
540516
<>
541517
<TouchableOpacity
542518
onPress={() => {
@@ -553,7 +529,7 @@ export default function Availability() {
553529

554530
<View className="mx-4 my-2 h-px bg-gray-200" />
555531
</>
556-
) : null}
532+
</Activity>
557533

558534
{/* Duplicate */}
559535
<TouchableOpacity
@@ -651,6 +627,6 @@ export default function Availability() {
651627
</View>
652628
</View>
653629
</FullScreenModal>
654-
</View>
630+
</>
655631
);
656632
}

companion/app/(tabs)/(event-types)/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ export default function EventTypes() {
473473
<Stack.Header
474474
style={{ backgroundColor: "transparent", shadowColor: "transparent" }}
475475
blurEffect={isLiquidGlassAvailable() ? undefined : "light"} // Only looks cool on iOS 18 and below
476+
hidden={Platform.OS === "android"}
476477
>
477478
<Stack.Header.Title large>Event Types</Stack.Header.Title>
478479
<Stack.Header.Right>
@@ -487,7 +488,7 @@ export default function EventTypes() {
487488
barTintColor="#fff"
488489
/>
489490
</Stack.Header>
490-
<Activity mode={Platform.OS === "web" ? "visible" : "hidden"}>
491+
<Activity mode={Platform.OS === "web" || Platform.OS === "android" ? "visible" : "hidden"}>
491492
<Header />
492493
<View className="flex-row items-center gap-3 border-b border-gray-300 bg-gray-100 px-4 py-2">
493494
<TextInput

companion/app/(tabs)/_layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default function TabLayout() {
2828
<NativeTabs.Trigger.Label>Bookings</NativeTabs.Trigger.Label>
2929
</NativeTabs.Trigger>
3030

31-
<NativeTabs.Trigger name="availability">
31+
<NativeTabs.Trigger name="(availability)">
3232
<NativeTabs.Trigger.Icon
3333
sf="clock"
3434
src={<VectorIcon family={MaterialCommunityIcons} name="clock" />}

0 commit comments

Comments
 (0)