@syra.fm/sdk 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -4
- package/lib/commonjs/live/colors.js +54 -0
- package/lib/commonjs/live/colors.js.map +1 -0
- package/lib/commonjs/live/components/LiveRoomSheet.js +9 -8
- package/lib/commonjs/live/components/LiveRoomSheet.js.map +1 -1
- package/lib/commonjs/live/components/MiniRoomBar.js +3 -2
- package/lib/commonjs/live/components/MiniRoomBar.js.map +1 -1
- package/lib/commonjs/live/components/PodcastStreamPicker.js +1 -1
- package/lib/commonjs/live/components/RecordingsPanel.js +3 -2
- package/lib/commonjs/live/components/RecordingsPanel.js.map +1 -1
- package/lib/commonjs/live/components/RoomCard.js +283 -404
- package/lib/commonjs/live/components/RoomCard.js.map +1 -1
- package/lib/commonjs/live/index.js +31 -0
- package/lib/commonjs/live/index.js.map +1 -1
- package/lib/module/live/colors.js +49 -0
- package/lib/module/live/colors.js.map +1 -0
- package/lib/module/live/components/LiveRoomSheet.js +9 -8
- package/lib/module/live/components/LiveRoomSheet.js.map +1 -1
- package/lib/module/live/components/MiniRoomBar.js +3 -2
- package/lib/module/live/components/MiniRoomBar.js.map +1 -1
- package/lib/module/live/components/PodcastStreamPicker.js +1 -1
- package/lib/module/live/components/RecordingsPanel.js +3 -2
- package/lib/module/live/components/RecordingsPanel.js.map +1 -1
- package/lib/module/live/components/RoomCard.js +284 -407
- package/lib/module/live/components/RoomCard.js.map +1 -1
- package/lib/module/live/index.js +4 -0
- package/lib/module/live/index.js.map +1 -1
- package/lib/typescript/commonjs/live/colors.d.ts +35 -0
- package/lib/typescript/commonjs/live/colors.d.ts.map +1 -0
- package/lib/typescript/commonjs/live/components/LiveRoomSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/live/components/MiniRoomBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/live/components/PodcastStreamPicker.d.ts +1 -1
- package/lib/typescript/commonjs/live/components/RecordingsPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/live/components/RoomCard.d.ts +18 -0
- package/lib/typescript/commonjs/live/components/RoomCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/live/index.d.ts +1 -0
- package/lib/typescript/commonjs/live/index.d.ts.map +1 -1
- package/lib/typescript/module/live/colors.d.ts +35 -0
- package/lib/typescript/module/live/colors.d.ts.map +1 -0
- package/lib/typescript/module/live/components/LiveRoomSheet.d.ts.map +1 -1
- package/lib/typescript/module/live/components/MiniRoomBar.d.ts.map +1 -1
- package/lib/typescript/module/live/components/PodcastStreamPicker.d.ts +1 -1
- package/lib/typescript/module/live/components/RecordingsPanel.d.ts.map +1 -1
- package/lib/typescript/module/live/components/RoomCard.d.ts +18 -0
- package/lib/typescript/module/live/components/RoomCard.d.ts.map +1 -1
- package/lib/typescript/module/live/index.d.ts +1 -0
- package/lib/typescript/module/live/index.d.ts.map +1 -1
- package/package.json +3 -13
- package/src/live/colors.ts +57 -0
- package/src/live/components/LiveRoomSheet.tsx +9 -8
- package/src/live/components/MiniRoomBar.tsx +3 -2
- package/src/live/components/PodcastStreamPicker.tsx +1 -1
- package/src/live/components/RecordingsPanel.tsx +3 -2
- package/src/live/components/RoomCard.tsx +245 -323
- package/src/live/index.ts +12 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useMemo } from 'react';
|
|
4
|
-
import { View, Text,
|
|
4
|
+
import { View, Text, TouchableOpacity } from 'react-native';
|
|
5
5
|
import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
|
|
6
6
|
import { useAuth } from '@oxyhq/services';
|
|
7
7
|
import { useLiveConfig } from "../context/LiveConfigContext.js";
|
|
8
8
|
import { AnimatedPulse } from "./AnimatedPulse.js";
|
|
9
9
|
import { useRoomUsers, getAvatarUrl } from "../hooks/useRoomUsers.js";
|
|
10
|
-
|
|
11
|
-
// --- Utility helpers ---
|
|
10
|
+
import { LIVE_COLOR, LIVE_FOREGROUND_COLOR } from "../colors.js";
|
|
12
11
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
|
+
// --- Utility helpers ---
|
|
13
|
+
|
|
13
14
|
function formatCompact(num) {
|
|
14
15
|
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
|
|
15
16
|
if (num >= 1_000) return `${(num / 1_000).toFixed(1).replace(/\.0$/, '')}k`;
|
|
@@ -23,48 +24,86 @@ function formatDuration(startedAt, endedAt) {
|
|
|
23
24
|
const m = totalMin % 60;
|
|
24
25
|
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
return `${d.getDate()} ${MONTH_SHORT[d.getMonth()]} ${d.getFullYear()}`;
|
|
27
|
+
const DAY_MS = 86_400_000;
|
|
28
|
+
function startOfDay(date) {
|
|
29
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
|
|
32
|
+
/** `Today` / `Tomorrow` / `Yesterday`, else a locale short date (year only when it differs). */
|
|
33
|
+
function formatDay(date, now) {
|
|
34
|
+
const dayDelta = Math.round((startOfDay(date) - startOfDay(now)) / DAY_MS);
|
|
35
|
+
if (dayDelta === 0) return 'Today';
|
|
36
|
+
if (dayDelta === 1) return 'Tomorrow';
|
|
37
|
+
if (dayDelta === -1) return 'Yesterday';
|
|
38
|
+
const options = {
|
|
39
|
+
day: 'numeric',
|
|
40
|
+
month: 'short'
|
|
41
|
+
};
|
|
42
|
+
if (date.getFullYear() !== now.getFullYear()) options.year = 'numeric';
|
|
43
|
+
return date.toLocaleDateString(undefined, options);
|
|
44
|
+
}
|
|
45
|
+
function formatTime(date) {
|
|
46
|
+
return date.toLocaleTimeString(undefined, {
|
|
47
|
+
hour: 'numeric',
|
|
48
|
+
minute: '2-digit'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The when-label for a room that is NOT live: the start time for a scheduled room
|
|
54
|
+
* (`Tomorrow 18:00`), the day + duration for an ended one (`Yesterday · 45 min`).
|
|
55
|
+
* A live room never uses this — its lead metric is the listener count.
|
|
56
|
+
*/
|
|
57
|
+
function getScheduleLabel(room, now) {
|
|
38
58
|
if (room.status === 'scheduled' && room.scheduledStart) {
|
|
39
|
-
|
|
59
|
+
const start = new Date(room.scheduledStart);
|
|
60
|
+
return `${formatDay(start, now)} ${formatTime(start)}`;
|
|
61
|
+
}
|
|
62
|
+
if (room.status === 'ended' && room.endedAt) {
|
|
63
|
+
const day = formatDay(new Date(room.endedAt), now);
|
|
64
|
+
return room.startedAt ? `${day} · ${formatDuration(room.startedAt, room.endedAt)}` : day;
|
|
40
65
|
}
|
|
41
66
|
if (room.createdAt) {
|
|
42
|
-
return
|
|
67
|
+
return formatDay(new Date(room.createdAt), now);
|
|
43
68
|
}
|
|
44
69
|
return '';
|
|
45
70
|
}
|
|
46
71
|
|
|
47
72
|
// --- Constants ---
|
|
48
73
|
|
|
49
|
-
const
|
|
50
|
-
stage: {
|
|
51
|
-
icon: 'account-voice',
|
|
52
|
-
label: 'Stage',
|
|
53
|
-
color: '#3B82F6'
|
|
54
|
-
},
|
|
55
|
-
broadcast: {
|
|
56
|
-
icon: 'broadcast',
|
|
57
|
-
label: 'Broadcast',
|
|
58
|
-
color: '#FF6B35'
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
const MAX_SPEAKER_AVATARS = 4;
|
|
74
|
+
const MAX_SPEAKER_AVATARS = 3;
|
|
62
75
|
const SPEAKER_AVATAR_SIZE = 44;
|
|
76
|
+
const ROW_AVATAR_SIZE = 40;
|
|
77
|
+
const BYLINE_AVATAR_SIZE = 20;
|
|
78
|
+
const ACTION_HIT_SLOP = {
|
|
79
|
+
top: 8,
|
|
80
|
+
bottom: 8,
|
|
81
|
+
left: 8,
|
|
82
|
+
right: 8
|
|
83
|
+
};
|
|
63
84
|
|
|
64
85
|
// --- Types ---
|
|
65
86
|
|
|
66
87
|
// --- Component ---
|
|
67
88
|
|
|
89
|
+
/**
|
|
90
|
+
* A room in a list. The `default` variant renders ONE of two presentations,
|
|
91
|
+
* selected by `room.status`, because a live room and a listing of one are
|
|
92
|
+
* different objects:
|
|
93
|
+
*
|
|
94
|
+
* - **live** → a featured card: the LIVE chip + the listener count (the only
|
|
95
|
+
* number that matters while a room is on air), the speaker faces, the host,
|
|
96
|
+
* and an explicit Join CTA.
|
|
97
|
+
* - **scheduled / ended** → a flush full-width row: host avatar, title + when,
|
|
98
|
+
* byline, and one subtle right-side action. No card chrome, no CTA — there is
|
|
99
|
+
* nothing to join yet (or ever again).
|
|
100
|
+
*
|
|
101
|
+
* At most ONE status chip is ever shown (a live room is not a scheduled one, and
|
|
102
|
+
* the date already says "scheduled"); the room type carries no badge at all.
|
|
103
|
+
*
|
|
104
|
+
* The `compact` variant is the small fixed-width card used by post attachments
|
|
105
|
+
* and the composer preview, and follows the same rules in miniature.
|
|
106
|
+
*/
|
|
68
107
|
export const RoomCard = ({
|
|
69
108
|
room,
|
|
70
109
|
onPress,
|
|
@@ -91,6 +130,7 @@ export const RoomCard = ({
|
|
|
91
130
|
const isLive = room.status === 'live';
|
|
92
131
|
const isScheduled = room.status === 'scheduled';
|
|
93
132
|
const isCompact = variant === 'compact';
|
|
133
|
+
const isFeatured = isLive && !isCompact;
|
|
94
134
|
|
|
95
135
|
// Resolve host display: an explicit prop wins, then the @handle, then the
|
|
96
136
|
// canonical API-owned `name.displayName` from the Oxy user DTO (never a
|
|
@@ -99,264 +139,260 @@ export const RoomCard = ({
|
|
|
99
139
|
const hostName = hostNameProp ?? (hostProfile?.username ? `@${hostProfile.username}` : (typeof hostProfileName === 'object' ? hostProfileName?.displayName : null) || room.host?.slice(0, 10) || 'Unknown');
|
|
100
140
|
const hostAvatarUri = hostAvatarUriProp ?? getAvatarUrl(hostProfile, oxyServices, getCachedFileDownloadUrlSync);
|
|
101
141
|
|
|
102
|
-
//
|
|
142
|
+
// Speaker faces only earn their space on the featured (live) card.
|
|
103
143
|
const speakerIds = useMemo(() => {
|
|
104
|
-
if (
|
|
144
|
+
if (!isFeatured) return [];
|
|
105
145
|
const ids = room.speakers?.length ? room.speakers : [room.host];
|
|
106
146
|
return ids.slice(0, MAX_SPEAKER_AVATARS);
|
|
107
|
-
}, [room.speakers, room.host
|
|
147
|
+
}, [isFeatured, room.speakers, room.host]);
|
|
148
|
+
const hiddenSpeakerCount = Math.max(0, (room.speakers?.length ?? 0) - speakerIds.length);
|
|
108
149
|
useRoomUsers(speakerIds);
|
|
109
|
-
const typeMeta = room.type && room.type !== 'talk' ? ROOM_TYPE_META[room.type] : null;
|
|
110
150
|
const listenerCount = room.participants?.length || room.stats?.totalJoined || 0;
|
|
111
|
-
const
|
|
151
|
+
const scheduleLabel = isLive ? '' : getScheduleLabel(room, new Date());
|
|
152
|
+
const byline = house ? `by ${hostName} · ${house.name}` : `by ${hostName}`;
|
|
112
153
|
|
|
113
|
-
// --- Compact variant (
|
|
154
|
+
// --- Compact variant (post attachments, composer preview) ---
|
|
155
|
+
// It lives inside HORIZONTAL carousels, where a percentage width has no parent
|
|
156
|
+
// to resolve against — hence the intrinsic width, capped at the parent so a
|
|
157
|
+
// constrained host box (the composer preview) can still shrink it. Hosts
|
|
158
|
+
// override any of this through `style`, which wins over the className.
|
|
114
159
|
if (isCompact) {
|
|
115
160
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
borderColor: theme.colors.border
|
|
119
|
-
}, style],
|
|
161
|
+
className: "w-[200px] max-w-full min-h-[140px] justify-between rounded-2xl border border-border bg-surface p-3",
|
|
162
|
+
style: style,
|
|
120
163
|
onPress: onPress,
|
|
121
164
|
activeOpacity: onPress ? 0.7 : 1,
|
|
122
165
|
disabled: !onPress,
|
|
123
166
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
124
|
-
|
|
125
|
-
children: [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}],
|
|
129
|
-
numberOfLines: 1,
|
|
130
|
-
children: house.name.toUpperCase()
|
|
167
|
+
className: "flex-1",
|
|
168
|
+
children: [isLive && /*#__PURE__*/_jsx(View, {
|
|
169
|
+
className: "mb-1.5 flex-row",
|
|
170
|
+
children: /*#__PURE__*/_jsx(LiveBadge, {})
|
|
131
171
|
}), /*#__PURE__*/_jsx(Text, {
|
|
132
|
-
|
|
133
|
-
color: theme.colors.text
|
|
134
|
-
}],
|
|
172
|
+
className: "text-sm font-semibold leading-[18px] text-foreground",
|
|
135
173
|
numberOfLines: 2,
|
|
136
174
|
children: room.title
|
|
137
|
-
}), (isLive || isScheduled || typeMeta) && /*#__PURE__*/_jsxs(View, {
|
|
138
|
-
style: styles.compactBadgeRow,
|
|
139
|
-
children: [isLive && /*#__PURE__*/_jsxs(View, {
|
|
140
|
-
style: [styles.liveBadge, {
|
|
141
|
-
backgroundColor: '#FF4458'
|
|
142
|
-
}],
|
|
143
|
-
children: [/*#__PURE__*/_jsx(AnimatedPulse, {
|
|
144
|
-
size: 6
|
|
145
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
146
|
-
style: styles.liveText,
|
|
147
|
-
children: "LIVE"
|
|
148
|
-
})]
|
|
149
|
-
}), isScheduled && /*#__PURE__*/_jsxs(View, {
|
|
150
|
-
style: [styles.scheduledBadge, {
|
|
151
|
-
backgroundColor: theme.colors.backgroundSecondary
|
|
152
|
-
}],
|
|
153
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
154
|
-
name: "calendar",
|
|
155
|
-
size: 10,
|
|
156
|
-
color: theme.colors.textSecondary
|
|
157
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
158
|
-
style: [styles.scheduledText, {
|
|
159
|
-
color: theme.colors.textSecondary
|
|
160
|
-
}],
|
|
161
|
-
children: "SCHEDULED"
|
|
162
|
-
})]
|
|
163
|
-
}), typeMeta && /*#__PURE__*/_jsxs(View, {
|
|
164
|
-
style: [styles.typeBadge, {
|
|
165
|
-
backgroundColor: typeMeta.color + '20'
|
|
166
|
-
}],
|
|
167
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
168
|
-
name: typeMeta.icon,
|
|
169
|
-
size: 10,
|
|
170
|
-
color: typeMeta.color
|
|
171
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
172
|
-
style: [styles.typeText, {
|
|
173
|
-
color: typeMeta.color
|
|
174
|
-
}],
|
|
175
|
-
children: typeMeta.label
|
|
176
|
-
})]
|
|
177
|
-
})]
|
|
178
175
|
})]
|
|
179
176
|
}), /*#__PURE__*/_jsxs(View, {
|
|
180
|
-
|
|
181
|
-
children: [/*#__PURE__*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
177
|
+
className: "mt-2 gap-1",
|
|
178
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
179
|
+
className: "flex-row items-center gap-1",
|
|
180
|
+
children: [/*#__PURE__*/_jsx(AvatarComponent, {
|
|
181
|
+
size: 14,
|
|
182
|
+
source: hostAvatarUri,
|
|
183
|
+
shape: "squircle"
|
|
184
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
185
|
+
className: "flex-1 text-[11px] text-muted-foreground",
|
|
186
|
+
numberOfLines: 1,
|
|
187
|
+
children: byline
|
|
188
|
+
})]
|
|
190
189
|
}), /*#__PURE__*/_jsx(Text, {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
className: "text-[11px] text-muted-foreground",
|
|
191
|
+
numberOfLines: 1,
|
|
192
|
+
children: isLive ? `${formatCompact(listenerCount)} listening` : scheduleLabel
|
|
193
|
+
})]
|
|
194
|
+
})]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- Featured card: a LIVE room, with presence and an explicit CTA ---
|
|
199
|
+
if (isFeatured) {
|
|
200
|
+
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
201
|
+
className: "mb-3 w-full gap-2.5 rounded-2xl border border-border bg-surface p-3",
|
|
202
|
+
style: style,
|
|
203
|
+
onPress: onPress,
|
|
204
|
+
activeOpacity: onPress ? 0.7 : 1,
|
|
205
|
+
disabled: !onPress,
|
|
206
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
207
|
+
className: "flex-row items-center gap-1.5",
|
|
208
|
+
children: [/*#__PURE__*/_jsx(LiveBadge, {}), listenerCount > 0 && /*#__PURE__*/_jsxs(_Fragment, {
|
|
209
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
210
|
+
className: "text-[13px] text-muted-foreground",
|
|
211
|
+
children: "\xB7"
|
|
212
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
213
|
+
className: "text-[13px] font-semibold text-muted-foreground",
|
|
214
|
+
numberOfLines: 1,
|
|
215
|
+
children: [formatCompact(listenerCount), " listening"]
|
|
216
|
+
})]
|
|
217
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
218
|
+
className: "flex-1"
|
|
219
|
+
}), onSave && /*#__PURE__*/_jsx(SaveAction, {
|
|
220
|
+
onSave: onSave,
|
|
221
|
+
isSaved: isSaved,
|
|
222
|
+
isScheduled: false,
|
|
223
|
+
theme: theme
|
|
224
|
+
}), onMenuPress && /*#__PURE__*/_jsx(MenuAction, {
|
|
225
|
+
onMenuPress: onMenuPress,
|
|
226
|
+
theme: theme
|
|
227
|
+
})]
|
|
228
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
229
|
+
className: "text-[17px] font-bold leading-[22px] text-foreground",
|
|
230
|
+
numberOfLines: 2,
|
|
231
|
+
children: room.title
|
|
232
|
+
}), /*#__PURE__*/_jsx(SpeakerStack, {
|
|
233
|
+
speakerIds: speakerIds,
|
|
234
|
+
hiddenCount: hiddenSpeakerCount
|
|
235
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
236
|
+
className: "flex-row items-center gap-2",
|
|
237
|
+
children: [/*#__PURE__*/_jsx(AvatarComponent, {
|
|
238
|
+
size: BYLINE_AVATAR_SIZE,
|
|
198
239
|
source: hostAvatarUri,
|
|
199
|
-
shape: "squircle"
|
|
200
|
-
style: {
|
|
201
|
-
marginRight: 2
|
|
202
|
-
}
|
|
240
|
+
shape: "squircle"
|
|
203
241
|
}), /*#__PURE__*/_jsx(Text, {
|
|
204
|
-
|
|
205
|
-
color: theme.colors.textSecondary
|
|
206
|
-
}],
|
|
242
|
+
className: "flex-1 text-[13px] text-muted-foreground",
|
|
207
243
|
numberOfLines: 1,
|
|
208
|
-
children:
|
|
244
|
+
children: byline
|
|
245
|
+
}), onPress && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
246
|
+
className: "rounded-full px-4 py-1.5",
|
|
247
|
+
style: {
|
|
248
|
+
backgroundColor: LIVE_COLOR
|
|
249
|
+
},
|
|
250
|
+
onPress: onPress,
|
|
251
|
+
accessibilityRole: "button",
|
|
252
|
+
accessibilityLabel: "Join room",
|
|
253
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
254
|
+
className: "text-[13px] font-bold",
|
|
255
|
+
style: {
|
|
256
|
+
color: LIVE_FOREGROUND_COLOR
|
|
257
|
+
},
|
|
258
|
+
children: "Join"
|
|
259
|
+
})
|
|
209
260
|
})]
|
|
210
261
|
})]
|
|
211
262
|
});
|
|
212
263
|
}
|
|
213
264
|
|
|
214
|
-
// ---
|
|
265
|
+
// --- Compact row: a SCHEDULED or ENDED room, flush in the feed ---
|
|
215
266
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
borderColor: theme.colors.border
|
|
219
|
-
}, style],
|
|
267
|
+
className: "w-full flex-row items-center gap-3 border-b border-border px-3 py-3",
|
|
268
|
+
style: style,
|
|
220
269
|
onPress: onPress,
|
|
221
270
|
activeOpacity: onPress ? 0.7 : 1,
|
|
222
271
|
disabled: !onPress,
|
|
223
|
-
children: [
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
name: "home",
|
|
228
|
-
size: 16,
|
|
229
|
-
color: theme.colors.primary
|
|
230
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
231
|
-
style: [styles.houseName, {
|
|
232
|
-
color: theme.colors.textSecondary
|
|
233
|
-
}],
|
|
234
|
-
numberOfLines: 1,
|
|
235
|
-
children: house.name.toUpperCase()
|
|
236
|
-
})]
|
|
237
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
238
|
-
style: {
|
|
239
|
-
flex: 1
|
|
240
|
-
}
|
|
241
|
-
}), onMenuPress && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
242
|
-
onPress: onMenuPress,
|
|
243
|
-
hitSlop: {
|
|
244
|
-
top: 8,
|
|
245
|
-
bottom: 8,
|
|
246
|
-
left: 8,
|
|
247
|
-
right: 8
|
|
248
|
-
},
|
|
249
|
-
children: /*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
250
|
-
name: "dots-horizontal",
|
|
251
|
-
size: 20,
|
|
252
|
-
color: theme.colors.textSecondary
|
|
253
|
-
})
|
|
254
|
-
})]
|
|
272
|
+
children: [/*#__PURE__*/_jsx(AvatarComponent, {
|
|
273
|
+
size: ROW_AVATAR_SIZE,
|
|
274
|
+
source: hostAvatarUri,
|
|
275
|
+
shape: "squircle"
|
|
255
276
|
}), /*#__PURE__*/_jsxs(View, {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}],
|
|
268
|
-
children: [/*#__PURE__*/_jsx(AnimatedPulse, {
|
|
269
|
-
size: 6
|
|
270
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
271
|
-
style: styles.liveText,
|
|
272
|
-
children: "LIVE"
|
|
273
|
-
})]
|
|
274
|
-
}), isScheduled && /*#__PURE__*/_jsxs(View, {
|
|
275
|
-
style: [styles.scheduledBadge, {
|
|
276
|
-
backgroundColor: theme.colors.backgroundSecondary
|
|
277
|
-
}],
|
|
278
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
279
|
-
name: "calendar",
|
|
280
|
-
size: 12,
|
|
281
|
-
color: theme.colors.textSecondary
|
|
282
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
283
|
-
style: [styles.scheduledText, {
|
|
284
|
-
color: theme.colors.textSecondary
|
|
285
|
-
}],
|
|
286
|
-
children: "SCHEDULED"
|
|
287
|
-
})]
|
|
288
|
-
}), typeMeta && /*#__PURE__*/_jsxs(View, {
|
|
289
|
-
style: [styles.typeBadge, {
|
|
290
|
-
backgroundColor: typeMeta.color + '20'
|
|
291
|
-
}],
|
|
292
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
293
|
-
name: typeMeta.icon,
|
|
294
|
-
size: 10,
|
|
295
|
-
color: typeMeta.color
|
|
296
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
297
|
-
style: [styles.typeText, {
|
|
298
|
-
color: typeMeta.color
|
|
299
|
-
}],
|
|
300
|
-
children: typeMeta.label
|
|
301
|
-
})]
|
|
277
|
+
className: "flex-1 gap-0.5",
|
|
278
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
279
|
+
className: "flex-row items-baseline gap-1.5",
|
|
280
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
281
|
+
className: "shrink text-[15px] font-semibold text-foreground",
|
|
282
|
+
numberOfLines: 1,
|
|
283
|
+
children: room.title
|
|
284
|
+
}), scheduleLabel !== '' && /*#__PURE__*/_jsxs(Text, {
|
|
285
|
+
className: "shrink-0 text-[13px] text-muted-foreground",
|
|
286
|
+
numberOfLines: 1,
|
|
287
|
+
children: ["\xB7 ", scheduleLabel]
|
|
302
288
|
})]
|
|
289
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
290
|
+
className: "text-[13px] text-muted-foreground",
|
|
291
|
+
numberOfLines: 1,
|
|
292
|
+
children: byline
|
|
303
293
|
})]
|
|
304
|
-
}), /*#__PURE__*/_jsx(
|
|
305
|
-
|
|
306
|
-
|
|
294
|
+
}), onSave && /*#__PURE__*/_jsx(SaveAction, {
|
|
295
|
+
onSave: onSave,
|
|
296
|
+
isSaved: isSaved,
|
|
297
|
+
isScheduled: isScheduled,
|
|
298
|
+
theme: theme
|
|
299
|
+
}), onMenuPress && /*#__PURE__*/_jsx(MenuAction, {
|
|
300
|
+
onMenuPress: onMenuPress,
|
|
307
301
|
theme: theme
|
|
308
|
-
}), (timeLabel || onSave) && /*#__PURE__*/_jsxs(View, {
|
|
309
|
-
style: styles.metaRow,
|
|
310
|
-
children: [timeLabel ? /*#__PURE__*/_jsx(Text, {
|
|
311
|
-
style: [styles.metaText, {
|
|
312
|
-
color: theme.colors.textSecondary
|
|
313
|
-
}],
|
|
314
|
-
children: timeLabel
|
|
315
|
-
}) : /*#__PURE__*/_jsx(View, {}), onSave && /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
316
|
-
onPress: onSave,
|
|
317
|
-
style: styles.saveButton,
|
|
318
|
-
hitSlop: {
|
|
319
|
-
top: 8,
|
|
320
|
-
bottom: 8,
|
|
321
|
-
left: 8,
|
|
322
|
-
right: 8
|
|
323
|
-
},
|
|
324
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
325
|
-
name: isSaved ? 'bookmark' : 'bookmark-outline',
|
|
326
|
-
size: 18,
|
|
327
|
-
color: isSaved ? theme.colors.primary : theme.colors.textSecondary
|
|
328
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
329
|
-
style: [styles.saveText, {
|
|
330
|
-
color: isSaved ? theme.colors.primary : theme.colors.textSecondary
|
|
331
|
-
}],
|
|
332
|
-
children: "Save"
|
|
333
|
-
})]
|
|
334
|
-
})]
|
|
335
302
|
})]
|
|
336
303
|
});
|
|
337
304
|
};
|
|
338
305
|
|
|
339
|
-
// ---
|
|
306
|
+
// --- Badges ---
|
|
340
307
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
308
|
+
// The LIVE badge is the one surface that must NOT theme with the host: it is the
|
|
309
|
+
// brand's "on air" signal, so its red comes from the shared live palette and its
|
|
310
|
+
// content sits on white regardless of light/dark mode.
|
|
311
|
+
function LiveBadge() {
|
|
312
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
313
|
+
className: "flex-row items-center gap-1 rounded-[4px] px-2 py-1",
|
|
314
|
+
style: {
|
|
315
|
+
backgroundColor: LIVE_COLOR
|
|
316
|
+
},
|
|
317
|
+
children: [/*#__PURE__*/_jsx(AnimatedPulse, {
|
|
318
|
+
size: 6,
|
|
319
|
+
color: LIVE_FOREGROUND_COLOR
|
|
320
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
321
|
+
className: "text-[10px] font-bold text-white",
|
|
322
|
+
children: "LIVE"
|
|
323
|
+
})]
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- Actions (icon-only, like the surrounding feed) ---
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The saved-state toggle. On a SCHEDULED room, saving it is how you ask to be
|
|
331
|
+
* pulled back when it starts, so the icon reads as a reminder bell; anywhere
|
|
332
|
+
* else it is a bookmark.
|
|
333
|
+
*/
|
|
334
|
+
function SaveAction({
|
|
335
|
+
onSave,
|
|
336
|
+
isSaved,
|
|
337
|
+
isScheduled,
|
|
344
338
|
theme
|
|
345
339
|
}) {
|
|
340
|
+
const icon = isScheduled ? isSaved ? 'bell' : 'bell-outline' : isSaved ? 'bookmark' : 'bookmark-outline';
|
|
341
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
342
|
+
onPress: onSave,
|
|
343
|
+
hitSlop: ACTION_HIT_SLOP,
|
|
344
|
+
accessibilityRole: "button",
|
|
345
|
+
accessibilityLabel: isScheduled ? 'Remind me about this room' : 'Save room',
|
|
346
|
+
accessibilityState: {
|
|
347
|
+
selected: Boolean(isSaved)
|
|
348
|
+
},
|
|
349
|
+
children: /*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
350
|
+
name: icon,
|
|
351
|
+
size: 20,
|
|
352
|
+
color: isSaved ? theme.colors.primary : theme.colors.textSecondary
|
|
353
|
+
})
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function MenuAction({
|
|
357
|
+
onMenuPress,
|
|
358
|
+
theme
|
|
359
|
+
}) {
|
|
360
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
361
|
+
onPress: onMenuPress,
|
|
362
|
+
hitSlop: ACTION_HIT_SLOP,
|
|
363
|
+
accessibilityRole: "button",
|
|
364
|
+
accessibilityLabel: "Room options",
|
|
365
|
+
children: /*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
366
|
+
name: "dots-horizontal",
|
|
367
|
+
size: 20,
|
|
368
|
+
color: theme.colors.textSecondary
|
|
369
|
+
})
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// --- Speaker stack (featured card only) ---
|
|
374
|
+
|
|
375
|
+
function SpeakerStack({
|
|
376
|
+
speakerIds,
|
|
377
|
+
hiddenCount
|
|
378
|
+
}) {
|
|
379
|
+
if (speakerIds.length === 0) return null;
|
|
346
380
|
return /*#__PURE__*/_jsxs(View, {
|
|
347
|
-
|
|
348
|
-
children: [speakerIds.map(id => /*#__PURE__*/_jsx(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
381
|
+
className: "flex-row items-center",
|
|
382
|
+
children: [speakerIds.map((id, index) => /*#__PURE__*/_jsx(View, {
|
|
383
|
+
className: index === 0 ? '' : '-ml-3',
|
|
384
|
+
children: /*#__PURE__*/_jsx(SpeakerAvatar, {
|
|
385
|
+
userId: id
|
|
386
|
+
})
|
|
387
|
+
}, id)), hiddenCount > 0 && /*#__PURE__*/_jsx(View, {
|
|
388
|
+
className: "-ml-3 items-center justify-center rounded-full border-2 border-surface bg-muted",
|
|
389
|
+
style: {
|
|
390
|
+
width: SPEAKER_AVATAR_SIZE,
|
|
391
|
+
height: SPEAKER_AVATAR_SIZE
|
|
392
|
+
},
|
|
355
393
|
children: /*#__PURE__*/_jsxs(Text, {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}],
|
|
359
|
-
children: ["+", formatCompact(listenerCount)]
|
|
394
|
+
className: "text-[13px] font-bold text-muted-foreground",
|
|
395
|
+
children: ["+", formatCompact(hiddenCount)]
|
|
360
396
|
})
|
|
361
397
|
})]
|
|
362
398
|
});
|
|
@@ -367,19 +403,17 @@ function SpeakerAvatar({
|
|
|
367
403
|
const {
|
|
368
404
|
useUserById,
|
|
369
405
|
AvatarComponent,
|
|
370
|
-
getCachedFileDownloadUrlSync
|
|
371
|
-
useTheme
|
|
406
|
+
getCachedFileDownloadUrlSync
|
|
372
407
|
} = useLiveConfig();
|
|
373
|
-
const theme = useTheme();
|
|
374
408
|
const {
|
|
375
409
|
oxyServices
|
|
376
410
|
} = useAuth();
|
|
377
411
|
const profile = useUserById(userId);
|
|
378
412
|
const avatarUri = getAvatarUrl(profile, oxyServices, getCachedFileDownloadUrlSync);
|
|
413
|
+
|
|
414
|
+
// The ring is the card's own surface color, so overlapping faces stay separable.
|
|
379
415
|
return /*#__PURE__*/_jsx(View, {
|
|
380
|
-
|
|
381
|
-
borderColor: theme.colors.border
|
|
382
|
-
}],
|
|
416
|
+
className: "rounded-full border-2 border-surface",
|
|
383
417
|
children: /*#__PURE__*/_jsx(AvatarComponent, {
|
|
384
418
|
size: SPEAKER_AVATAR_SIZE,
|
|
385
419
|
source: avatarUri,
|
|
@@ -387,162 +421,5 @@ function SpeakerAvatar({
|
|
|
387
421
|
})
|
|
388
422
|
});
|
|
389
423
|
}
|
|
390
|
-
|
|
391
|
-
// --- Styles ---
|
|
392
|
-
|
|
393
|
-
const styles = StyleSheet.create({
|
|
394
|
-
card: {
|
|
395
|
-
borderRadius: 12,
|
|
396
|
-
borderWidth: 1,
|
|
397
|
-
padding: 16,
|
|
398
|
-
marginBottom: 12,
|
|
399
|
-
gap: 12
|
|
400
|
-
},
|
|
401
|
-
compactCard: {
|
|
402
|
-
width: 200,
|
|
403
|
-
minHeight: 140,
|
|
404
|
-
borderRadius: 14,
|
|
405
|
-
borderWidth: 1,
|
|
406
|
-
padding: 12,
|
|
407
|
-
justifyContent: 'space-between'
|
|
408
|
-
},
|
|
409
|
-
// House header
|
|
410
|
-
houseRow: {
|
|
411
|
-
flexDirection: 'row',
|
|
412
|
-
alignItems: 'center',
|
|
413
|
-
gap: 6
|
|
414
|
-
},
|
|
415
|
-
houseName: {
|
|
416
|
-
fontSize: 11,
|
|
417
|
-
fontWeight: '700',
|
|
418
|
-
letterSpacing: 0.5
|
|
419
|
-
},
|
|
420
|
-
// Title
|
|
421
|
-
title: {
|
|
422
|
-
fontSize: 32,
|
|
423
|
-
fontWeight: '700',
|
|
424
|
-
lineHeight: 38
|
|
425
|
-
},
|
|
426
|
-
// Badges
|
|
427
|
-
badgeRow: {
|
|
428
|
-
flexDirection: 'row',
|
|
429
|
-
alignItems: 'center',
|
|
430
|
-
gap: 6,
|
|
431
|
-
marginTop: 6
|
|
432
|
-
},
|
|
433
|
-
liveBadge: {
|
|
434
|
-
flexDirection: 'row',
|
|
435
|
-
alignItems: 'center',
|
|
436
|
-
paddingHorizontal: 8,
|
|
437
|
-
paddingVertical: 4,
|
|
438
|
-
borderRadius: 4,
|
|
439
|
-
gap: 4
|
|
440
|
-
},
|
|
441
|
-
liveText: {
|
|
442
|
-
fontSize: 10,
|
|
443
|
-
fontWeight: '700',
|
|
444
|
-
color: '#FFFFFF'
|
|
445
|
-
},
|
|
446
|
-
scheduledBadge: {
|
|
447
|
-
flexDirection: 'row',
|
|
448
|
-
alignItems: 'center',
|
|
449
|
-
paddingHorizontal: 8,
|
|
450
|
-
paddingVertical: 4,
|
|
451
|
-
borderRadius: 4,
|
|
452
|
-
gap: 4
|
|
453
|
-
},
|
|
454
|
-
scheduledText: {
|
|
455
|
-
fontSize: 10,
|
|
456
|
-
fontWeight: '700'
|
|
457
|
-
},
|
|
458
|
-
typeBadge: {
|
|
459
|
-
flexDirection: 'row',
|
|
460
|
-
alignItems: 'center',
|
|
461
|
-
paddingHorizontal: 6,
|
|
462
|
-
paddingVertical: 3,
|
|
463
|
-
borderRadius: 4,
|
|
464
|
-
gap: 3
|
|
465
|
-
},
|
|
466
|
-
typeText: {
|
|
467
|
-
fontSize: 9,
|
|
468
|
-
fontWeight: '700'
|
|
469
|
-
},
|
|
470
|
-
// Speaker avatars
|
|
471
|
-
avatarRow: {
|
|
472
|
-
flexDirection: 'row',
|
|
473
|
-
alignItems: 'center',
|
|
474
|
-
gap: 8
|
|
475
|
-
},
|
|
476
|
-
avatarRing: {
|
|
477
|
-
borderRadius: 14,
|
|
478
|
-
padding: 2,
|
|
479
|
-
borderWidth: 2
|
|
480
|
-
},
|
|
481
|
-
countTile: {
|
|
482
|
-
width: SPEAKER_AVATAR_SIZE + 4,
|
|
483
|
-
height: SPEAKER_AVATAR_SIZE + 4,
|
|
484
|
-
borderRadius: 14,
|
|
485
|
-
borderWidth: 2,
|
|
486
|
-
alignItems: 'center',
|
|
487
|
-
justifyContent: 'center',
|
|
488
|
-
gap: 2
|
|
489
|
-
},
|
|
490
|
-
countTileText: {
|
|
491
|
-
fontSize: 14,
|
|
492
|
-
fontWeight: '700'
|
|
493
|
-
},
|
|
494
|
-
// Metadata
|
|
495
|
-
metaRow: {
|
|
496
|
-
flexDirection: 'row',
|
|
497
|
-
alignItems: 'center',
|
|
498
|
-
justifyContent: 'space-between'
|
|
499
|
-
},
|
|
500
|
-
metaText: {
|
|
501
|
-
fontSize: 13
|
|
502
|
-
},
|
|
503
|
-
saveButton: {
|
|
504
|
-
flexDirection: 'row',
|
|
505
|
-
alignItems: 'center',
|
|
506
|
-
gap: 4
|
|
507
|
-
},
|
|
508
|
-
saveText: {
|
|
509
|
-
fontSize: 13,
|
|
510
|
-
fontWeight: '500'
|
|
511
|
-
},
|
|
512
|
-
// Compact variant
|
|
513
|
-
compactHeader: {
|
|
514
|
-
flex: 1
|
|
515
|
-
},
|
|
516
|
-
compactHouseName: {
|
|
517
|
-
fontSize: 9,
|
|
518
|
-
fontWeight: '700',
|
|
519
|
-
letterSpacing: 0.5,
|
|
520
|
-
marginBottom: 4
|
|
521
|
-
},
|
|
522
|
-
compactTitle: {
|
|
523
|
-
fontSize: 14,
|
|
524
|
-
fontWeight: '600',
|
|
525
|
-
lineHeight: 18
|
|
526
|
-
},
|
|
527
|
-
compactBadgeRow: {
|
|
528
|
-
flexDirection: 'row',
|
|
529
|
-
marginTop: 4,
|
|
530
|
-
gap: 4,
|
|
531
|
-
flexWrap: 'wrap'
|
|
532
|
-
},
|
|
533
|
-
compactFooter: {
|
|
534
|
-
flexDirection: 'row',
|
|
535
|
-
alignItems: 'center',
|
|
536
|
-
gap: 4,
|
|
537
|
-
marginTop: 8
|
|
538
|
-
},
|
|
539
|
-
compactCount: {
|
|
540
|
-
fontSize: 11
|
|
541
|
-
},
|
|
542
|
-
compactHost: {
|
|
543
|
-
fontSize: 11,
|
|
544
|
-
flex: 1
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
424
|
export default RoomCard;
|
|
548
425
|
//# sourceMappingURL=RoomCard.js.map
|