@oxyhq/services 22.8.5 → 22.8.6

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.
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react-native';
11
11
  import { MaterialCommunityIcons } from '@expo/vector-icons';
12
12
  import { Avatar } from '@oxyhq/bloom/avatar';
13
+ import * as Skeleton from '@oxyhq/bloom/skeleton';
13
14
  import { Text } from '@oxyhq/bloom/typography';
14
15
  import { useTheme } from '@oxyhq/bloom/theme';
15
16
  import { getAccountDisplayName, getAccountFallbackHandle } from '@oxyhq/core';
@@ -140,16 +141,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
140
141
  accessibilityElementsHidden
141
142
  importantForAccessibility="no-hide-descendants"
142
143
  >
143
- <View
144
- style={[
145
- styles.skeletonCircle,
146
- {
147
- width: resolvedAvatarSize,
148
- height: resolvedAvatarSize,
149
- backgroundColor: colors.backgroundSecondary,
150
- },
151
- ]}
152
- />
144
+ <Skeleton.Circle size={resolvedAvatarSize} />
153
145
  </View>
154
146
  );
155
147
  }
@@ -352,10 +344,6 @@ const styles = StyleSheet.create({
352
344
  fullWidth: {
353
345
  width: '100%',
354
346
  },
355
- // Neutral skeleton / signed-out avatar circle (`rounded-full`).
356
- skeletonCircle: {
357
- borderRadius: 9999,
358
- },
359
347
  // Shared trigger row (`flex-row items-center gap-3 rounded-full px-2 py-2`).
360
348
  row: {
361
349
  flexDirection: 'row',
@@ -65,7 +65,7 @@ const UploadPreviewContent: React.FC<{
65
65
  const isImage = pendingFile.type.startsWith('image/');
66
66
  return (
67
67
  <View
68
- key={index}
68
+ key={`${pendingFile.name}-${pendingFile.size}-${index}`}
69
69
  className="bg-secondary border-border flex-row items-center p-[12px] rounded-[12px] border mb-[12px] gap-[12px]"
70
70
  >
71
71
  {isImage && pendingFile.preview ? (
@@ -9,15 +9,13 @@ import {
9
9
  RefreshControl,
10
10
  TextInput,
11
11
  Image,
12
- Animated,
13
- Easing,
14
12
  AccessibilityInfo,
15
- Platform,
16
13
  useWindowDimensions,
17
14
  } from 'react-native';
18
15
  import { Image as ExpoImage } from 'expo-image';
19
16
  import type { FileManagementScreenProps } from '../types/fileManagement';
20
17
  import { Dialog, toast, useDialogControl } from '@oxyhq/bloom';
18
+ import * as Skeleton from '@oxyhq/bloom/skeleton';
21
19
  import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
22
20
  import type { FileMetadata } from '@oxyhq/core';
23
21
  import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
@@ -1121,193 +1119,57 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1121
1119
  </View>
1122
1120
  );
1123
1121
 
1124
- // Professional Skeleton Loading Component with Advanced Shimmer Effect
1125
- const SkeletonLoader = React.memo(() => {
1126
- const shimmerAnim = useRef(new Animated.Value(0)).current;
1127
- const skeletonContainerWidth = safeContainerWidth;
1128
-
1129
- useEffect(() => {
1130
- const shimmer = Animated.loop(
1131
- Animated.timing(shimmerAnim, {
1132
- toValue: 1,
1133
- duration: 2000,
1134
- easing: Easing.linear,
1135
- // Web has no native animated module; forcing the native
1136
- // driver there warns and falls back to JS. The shimmer only
1137
- // drives a transform, so JS-driving it on web is fine.
1138
- useNativeDriver: Platform.OS !== 'web',
1139
- })
1140
- );
1141
- shimmer.start();
1142
- return () => shimmer.stop();
1143
- }, [shimmerAnim]);
1144
-
1145
- // Create a sweeping shimmer effect
1146
- const shimmerTranslateX = shimmerAnim.interpolate({
1147
- inputRange: [0, 1],
1148
- outputRange: [-skeletonContainerWidth * 2, skeletonContainerWidth * 2],
1149
- });
1150
-
1151
- const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?: Record<string, unknown>; delay?: number }) => {
1152
- const delayedTranslateX = shimmerAnim.interpolate({
1153
- inputRange: [0, 1],
1154
- outputRange: [-skeletonContainerWidth * 2 + delay, skeletonContainerWidth * 2 + delay],
1155
- });
1156
-
1157
- return (
1158
- <View
1159
- style={[
1160
- {
1161
- width,
1162
- height,
1163
- borderRadius,
1164
- backgroundColor: colors.backgroundSecondary,
1165
- overflow: 'hidden',
1166
- position: 'relative',
1167
- },
1168
- style,
1169
- ]}
1170
- >
1171
- {/* Base background */}
1172
- <View
1173
- style={{
1174
- position: 'absolute',
1175
- top: 0,
1176
- left: 0,
1177
- right: 0,
1178
- bottom: 0,
1179
- backgroundColor: colors.backgroundSecondary,
1180
- }}
1181
- />
1182
- {/* Shimmer gradient effect */}
1183
- <Animated.View
1184
- style={{
1185
- position: 'absolute',
1186
- top: 0,
1187
- left: 0,
1188
- width: '100%',
1189
- height: '100%',
1190
- transform: [{ translateX: delayedTranslateX }],
1191
- }}
1192
- >
1193
- <View
1194
- style={{
1195
- width: skeletonContainerWidth,
1196
- height: '100%',
1197
- backgroundColor: bloomTheme.isDark
1198
- ? 'rgba(255, 255, 255, 0.08)'
1199
- : 'rgba(255, 255, 255, 0.8)',
1200
- shadowColor: bloomTheme.isDark ? '#000' : '#FFF',
1201
- shadowOffset: { width: 0, height: 0 },
1202
- shadowOpacity: 0.3,
1203
- shadowRadius: 10,
1204
- }}
1205
- />
1206
- </Animated.View>
1207
- </View>
1208
- );
1209
- };
1210
-
1211
- // Skeleton file item matching SettingsListItem structure
1212
- const SkeletonFileItem = ({ index }: { index: number }) => (
1213
- <View
1214
- style={[
1215
- {
1216
- flexDirection: 'row',
1217
- alignItems: 'center',
1218
- paddingHorizontal: 16,
1219
- paddingVertical: 12,
1220
- backgroundColor: colors.background,
1221
- borderBottomWidth: StyleSheet.hairlineWidth,
1222
- borderBottomColor: colors.border,
1223
- },
1224
- ]}
1225
- >
1226
- {/* Icon/Image skeleton */}
1227
- <SkeletonBox width={44} height={44} borderRadius={8} delay={index * 50} />
1228
-
1229
- {/* Content skeleton */}
1230
- <View style={{ flex: 1, marginLeft: 12, justifyContent: 'center' }}>
1231
- <SkeletonBox
1232
- width={index % 3 === 0 ? '85%' : index % 3 === 1 ? '70%' : '90%'}
1233
- height={16}
1234
- style={{ marginBottom: 8 }}
1235
- delay={index * 50 + 20}
1236
- />
1237
- <SkeletonBox
1238
- width={index % 2 === 0 ? '50%' : '60%'}
1239
- height={12}
1240
- delay={index * 50 + 40}
1241
- />
1242
- </View>
1243
- </View>
1122
+ // Loading state — Bloom's shared `Skeleton` primitives own the shimmer, so
1123
+ // this is pure layout: the top chrome (header / controls / search) plus a
1124
+ // photo grid of shimmering tiles sized with the same 3-per-row geometry the
1125
+ // real photo grid uses. No hand-rolled `Animated` shimmer.
1126
+ if (loading) {
1127
+ const GRID_PADDING = 10;
1128
+ const TILE_GAP = 4;
1129
+ const GRID_COLUMNS = 3;
1130
+ const GRID_ROWS = 6;
1131
+ const tileSize = Math.floor(
1132
+ (safeContainerWidth - GRID_PADDING * 2 - TILE_GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS,
1244
1133
  );
1245
1134
 
1246
1135
  return (
1247
1136
  <View className="flex-1" style={{ backgroundColor }}>
1248
- {/* Header Skeleton */}
1137
+ {/* Header */}
1249
1138
  <View
1250
- className="flex-row items-center justify-between px-[16px] py-[12px] relative"
1139
+ className="flex-row items-center justify-between px-[16px] py-[12px]"
1251
1140
  style={{ borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}
1252
1141
  >
1253
- <SkeletonBox width={44} height={44} borderRadius={12} />
1254
- <View className="flex-1 items-center justify-center mx-[16px]">
1255
- <SkeletonBox width={140} height={20} style={{ marginBottom: 6 }} />
1256
- <SkeletonBox width={100} height={14} />
1257
- </View>
1258
- <SkeletonBox width={44} height={44} borderRadius={12} />
1142
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1143
+ <Skeleton.Col style={{ alignItems: 'center', marginHorizontal: 16, gap: 6 }}>
1144
+ <Skeleton.Box width={140} height={20} />
1145
+ <Skeleton.Box width={100} height={14} />
1146
+ </Skeleton.Col>
1147
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1259
1148
  </View>
1260
1149
 
1261
- {/* Controls Bar Skeleton */}
1262
- <View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
1263
- <SkeletonBox width={100} height={36} borderRadius={18} />
1264
- <SkeletonBox width={44} height={44} borderRadius={22} />
1265
- </View>
1150
+ {/* Controls bar */}
1151
+ <Skeleton.Row style={{ alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12 }}>
1152
+ <Skeleton.Box width={100} height={36} borderRadius={18} />
1153
+ <Skeleton.Box width={44} height={44} borderRadius={22} />
1154
+ </Skeleton.Row>
1266
1155
 
1267
- {/* Search Bar Skeleton */}
1268
- <View
1269
- className="flex-row items-center px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-full gap-[10px]"
1270
- style={{ backgroundColor: colors.card }}
1271
- >
1272
- <SkeletonBox width="100%" height={44} borderRadius={12} />
1156
+ {/* Search bar */}
1157
+ <View className="mx-[12px] mb-[12px]">
1158
+ <Skeleton.Box width="100%" height={44} borderRadius={22} />
1273
1159
  </View>
1274
1160
 
1275
- {/* Stats Container Skeleton */}
1276
- <View
1277
- className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
1278
- style={{ backgroundColor: colors.card }}
1279
- >
1280
- {[1, 2, 3].map((i) => (
1281
- <View key={i} className="flex-1 items-center py-[4px]">
1282
- <SkeletonBox width={50} height={20} style={{ marginBottom: 4 }} delay={i * 30} />
1283
- <SkeletonBox width={40} height={14} delay={i * 30 + 15} />
1284
- </View>
1161
+ {/* Photo grid */}
1162
+ <View style={{ padding: GRID_PADDING, gap: TILE_GAP }}>
1163
+ {Array.from({ length: GRID_ROWS }, (_, row) => (
1164
+ <Skeleton.Row key={row} style={{ gap: TILE_GAP }}>
1165
+ {Array.from({ length: GRID_COLUMNS }, (_, col) => (
1166
+ <Skeleton.Box key={col} width={tileSize} height={tileSize} borderRadius={6} />
1167
+ ))}
1168
+ </Skeleton.Row>
1285
1169
  ))}
1286
1170
  </View>
1287
-
1288
- {/* File List Skeleton - Matching SettingsListItem */}
1289
- <ScrollView
1290
- className="flex-1"
1291
- contentContainerClassName="px-[12px] pt-0 pb-[12px]"
1292
- showsVerticalScrollIndicator={false}
1293
- >
1294
- <View style={{
1295
- backgroundColor: colors.card,
1296
- borderRadius: 18,
1297
- overflow: 'hidden',
1298
- marginTop: 8,
1299
- }}>
1300
- {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
1301
- <SkeletonFileItem key={i} index={i} />
1302
- ))}
1303
- </View>
1304
- </ScrollView>
1305
1171
  </View>
1306
1172
  );
1307
- });
1308
-
1309
- if (loading) {
1310
- return <SkeletonLoader />;
1311
1173
  }
1312
1174
 
1313
1175
  // Dedicated flagship-style photo picker view used in the avatar-picker