@oxyhq/services 22.11.1 → 22.12.1
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/lib/commonjs/ui/hooks/useSurfaceHeader.js +8 -6
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +11 -37
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/screens/AvatarCropScreen.js +9 -13
- package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +66 -18
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +11 -0
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +91 -117
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +22 -54
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/hooks/useSurfaceHeader.js +8 -6
- package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js +11 -37
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/screens/AvatarCropScreen.js +9 -13
- package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +66 -18
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js +11 -0
- package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +93 -119
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +23 -54
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +7 -5
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +8 -9
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +7 -5
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +8 -9
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/hooks/useSurfaceHeader.ts +21 -16
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +16 -2
- package/src/ui/navigation/surfaceRegistry.ts +14 -42
- package/src/ui/screens/AvatarCropScreen.tsx +14 -23
- package/src/ui/screens/FileManagementScreen.tsx +76 -29
- package/src/ui/screens/PaymentGatewayScreen.tsx +11 -0
- package/src/ui/screens/ProfileScreen.tsx +96 -111
- package/src/ui/screens/WelcomeNewUserScreen.tsx +23 -63
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +39 -171
|
@@ -25,13 +25,10 @@ import FileLibraryError from './FileLibraryError';
|
|
|
25
25
|
* import. Matches the pattern used by AvatarCropScreen.
|
|
26
26
|
*/
|
|
27
27
|
type HapticImpact = 'light' | 'medium' | 'heavy';
|
|
28
|
-
type HapticNotification = 'success' | 'warning' | 'error';
|
|
29
28
|
interface HapticsModule {
|
|
30
29
|
impactAsync: (style: unknown) => Promise<void>;
|
|
31
|
-
notificationAsync: (type: unknown) => Promise<void>;
|
|
32
30
|
selectionAsync: () => Promise<void>;
|
|
33
31
|
ImpactFeedbackStyle: { Light: unknown; Medium: unknown; Heavy: unknown };
|
|
34
|
-
NotificationFeedbackType: { Success: unknown; Warning: unknown; Error: unknown };
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
let hapticsModulePromise: Promise<HapticsModule | null> | null = null;
|
|
@@ -76,35 +73,23 @@ const hapticSelection = async (): Promise<void> => {
|
|
|
76
73
|
}
|
|
77
74
|
};
|
|
78
75
|
|
|
79
|
-
const hapticNotification = async (type: HapticNotification): Promise<void> => {
|
|
80
|
-
const h = await getHaptics();
|
|
81
|
-
if (!h) return;
|
|
82
|
-
const typeEnum =
|
|
83
|
-
type === 'error'
|
|
84
|
-
? h.NotificationFeedbackType.Error
|
|
85
|
-
: type === 'warning'
|
|
86
|
-
? h.NotificationFeedbackType.Warning
|
|
87
|
-
: h.NotificationFeedbackType.Success;
|
|
88
|
-
try {
|
|
89
|
-
await h.notificationAsync(typeEnum);
|
|
90
|
-
} catch {
|
|
91
|
-
// Silent.
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
76
|
/**
|
|
96
|
-
* The picker renders inside a Bloom `<Dialog>`
|
|
97
|
-
* card on `md+`). The Dialog
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* the
|
|
103
|
-
*
|
|
77
|
+
* The picker renders inside a Bloom `<Dialog>` in NAV-HEADER mode with
|
|
78
|
+
* `tone: 'onImage'` (bottom-sheet on narrow, centered card on `md+`). The Dialog
|
|
79
|
+
* owns the shared gradient nav bar (Cancel / "Choose Photo" / Upload, wired by
|
|
80
|
+
* `FileManagementScreen` through `useSurfaceHeader`). Because the tone is
|
|
81
|
+
* `'onImage'`, the Dialog inserts NO nav-bar spacer above this content
|
|
82
|
+
* (`DialogNavBarSpacer` collapses to nothing), so the black photo grid slides UP
|
|
83
|
+
* under the floating translucent bar and reads EDGE-TO-EDGE — the Apple/Google
|
|
84
|
+
* Photos immersive picker. The grid therefore takes the FULL `frameSize` height
|
|
85
|
+
* and carries no top padding; only the pull-to-refresh spinner is offset so it
|
|
86
|
+
* lands below the floating bar.
|
|
87
|
+
*
|
|
88
|
+
* `FLOATING_NAV_BAR_HEIGHT` mirrors Bloom's `DIALOG_NAV_BAR_HEIGHT` (not exported
|
|
89
|
+
* from `@oxyhq/bloom/dialog`) — the height of the floating bar the refresh spinner
|
|
90
|
+
* must clear.
|
|
104
91
|
*/
|
|
105
|
-
const
|
|
106
|
-
const HEADER_HEIGHT = APP_BAR_HEIGHT; // 56
|
|
107
|
-
const GRID_CONTENT_PADDING_TOP = HEADER_HEIGHT + 4; // 60
|
|
92
|
+
const FLOATING_NAV_BAR_HEIGHT = 52;
|
|
108
93
|
|
|
109
94
|
/**
|
|
110
95
|
* Props for the dedicated photo picker view. Used by FileManagementScreen
|
|
@@ -120,7 +105,6 @@ export interface PhotoPickerViewProps {
|
|
|
120
105
|
refreshing: boolean;
|
|
121
106
|
uploading: boolean;
|
|
122
107
|
isPickingDocument: boolean;
|
|
123
|
-
uploadProgress: { current: number; total: number } | null;
|
|
124
108
|
hasMore: boolean;
|
|
125
109
|
loadingMore: boolean;
|
|
126
110
|
/**
|
|
@@ -143,8 +127,6 @@ export interface PhotoPickerViewProps {
|
|
|
143
127
|
onUpload: () => void;
|
|
144
128
|
onRefresh: () => void;
|
|
145
129
|
onLoadMore: () => void;
|
|
146
|
-
onCancel: () => void;
|
|
147
|
-
onConfirm: () => void;
|
|
148
130
|
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
149
131
|
}
|
|
150
132
|
|
|
@@ -362,7 +344,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
362
344
|
refreshing,
|
|
363
345
|
uploading,
|
|
364
346
|
isPickingDocument,
|
|
365
|
-
uploadProgress,
|
|
366
347
|
hasMore,
|
|
367
348
|
loadingMore,
|
|
368
349
|
loading,
|
|
@@ -377,8 +358,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
377
358
|
onUpload,
|
|
378
359
|
onRefresh,
|
|
379
360
|
onLoadMore,
|
|
380
|
-
onCancel,
|
|
381
|
-
onConfirm,
|
|
382
361
|
t,
|
|
383
362
|
}) => {
|
|
384
363
|
// Grid sizing width. We PREFER the measured grid-container width (the
|
|
@@ -400,11 +379,14 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
400
379
|
|
|
401
380
|
// The picker FILLS its panel: the surface morphs the container UP to an
|
|
402
381
|
// explicit large target (`frameSize.heightRatio: 0.9` in `surfaceRegistry`),
|
|
403
|
-
// and this root takes that SAME 0.9-of-viewport height
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
382
|
+
// and this root takes that SAME 0.9-of-viewport height. Under `tone:'onImage'`
|
|
383
|
+
// the Dialog inserts NO nav-bar spacer, so the grid uses the FULL morph target
|
|
384
|
+
// (no `-52`) and slides up under the floating bar, edge-to-edge, within the
|
|
385
|
+
// panel's `maxHeightRatio: 0.9` cap. A `flex: 1` root can't fill (no definite
|
|
386
|
+
// bound propagates through the sheet's flex chain at rest, where the panel is
|
|
387
|
+
// height:auto), so the fixed height is required; the FlatList scrolls within
|
|
388
|
+
// it. Keep the 0.9 ratio in sync with the picker's `frameSize` in
|
|
389
|
+
// `surfaceRegistry.ts`.
|
|
408
390
|
const panelHeight = useMemo(() => Math.round(windowHeight * 0.9), [windowHeight]);
|
|
409
391
|
const rootStyle = useMemo(() => ({ height: panelHeight }), [panelHeight]);
|
|
410
392
|
|
|
@@ -421,7 +403,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
421
403
|
const skeletonTileCount = useMemo(() => {
|
|
422
404
|
const rowHeight = cellSize + gutter;
|
|
423
405
|
if (rowHeight <= 0) return columns * SKELETON_MIN_ROWS;
|
|
424
|
-
const available = Math.max(0, panelHeight -
|
|
406
|
+
const available = Math.max(0, panelHeight - FLOATING_NAV_BAR_HEIGHT);
|
|
425
407
|
const rows = Math.max(SKELETON_MIN_ROWS, Math.floor(available / rowHeight));
|
|
426
408
|
return rows * columns;
|
|
427
409
|
}, [cellSize, gutter, columns, panelHeight]);
|
|
@@ -440,9 +422,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
440
422
|
|
|
441
423
|
const hasAnySelection = selectedIds.size > 0;
|
|
442
424
|
|
|
443
|
-
// Compact icon-only upload pill on narrow sheets; full pill otherwise.
|
|
444
|
-
const showUploadLabel = effectiveWidth >= 360;
|
|
445
|
-
|
|
446
425
|
const isEmpty = photos.length === 0;
|
|
447
426
|
const atSelectionLimit =
|
|
448
427
|
multiSelect && maxSelection != null && selectedIds.size >= maxSelection;
|
|
@@ -467,16 +446,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
467
446
|
[onPreviewPhoto],
|
|
468
447
|
);
|
|
469
448
|
|
|
470
|
-
const handleConfirm = useCallback(() => {
|
|
471
|
-
if (multiSelect) {
|
|
472
|
-
if (selectedIds.size === 0) return;
|
|
473
|
-
if (Platform.OS !== 'web') {
|
|
474
|
-
void hapticNotification('success');
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
onConfirm();
|
|
478
|
-
}, [multiSelect, selectedIds.size, onConfirm]);
|
|
479
|
-
|
|
480
449
|
// FlatList renderItem: each cell knows its enterIndex (for stagger).
|
|
481
450
|
const renderItem = useCallback(
|
|
482
451
|
({ item, index }: { item: FileMetadata; index: number }) => {
|
|
@@ -537,16 +506,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
537
506
|
onLoadMore();
|
|
538
507
|
}, [loadingMore, hasMore, onLoadMore]);
|
|
539
508
|
|
|
540
|
-
const confirmDisabled = multiSelect && selectedIds.size === 0;
|
|
541
|
-
const confirmLabel = multiSelect
|
|
542
|
-
? t('fileManagement.doneWithCount', { count: selectedIds.size })
|
|
543
|
-
: t('fileManagement.done');
|
|
544
|
-
|
|
545
|
-
// The progress fill width. Guard against zero division.
|
|
546
|
-
const progressFraction = uploadProgress && uploadProgress.total > 0
|
|
547
|
-
? Math.min(1, Math.max(0, uploadProgress.current / uploadProgress.total))
|
|
548
|
-
: 0;
|
|
549
|
-
|
|
550
509
|
return (
|
|
551
510
|
// Measured wrapper: `style`-only (no className) so RN-Web fires onLayout.
|
|
552
511
|
// `rootStyle` is placement-aware — a `maxHeight` cap that gives the
|
|
@@ -559,12 +518,15 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
559
518
|
root (the CSS flexbox `min-height:auto` trap; a no-op on native,
|
|
560
519
|
where Yoga already defaults min-height to 0). */}
|
|
561
520
|
<View className="flex-1 min-h-0 bg-black">
|
|
562
|
-
{/* Photo grid
|
|
521
|
+
{/* Photo grid — full-bleed black canvas CONTENT under the shared
|
|
522
|
+
Dialog nav bar (the Dialog reserves the bar's height above this
|
|
523
|
+
and its gradient fades a little further down; the top padding
|
|
524
|
+
clears that fade). */}
|
|
563
525
|
{isEmpty && loadError ? (
|
|
564
526
|
/* Terminal load failure — a DISTINCT error surface (not the
|
|
565
|
-
empty state),
|
|
566
|
-
|
|
567
|
-
<View className="flex-1 items-center justify-center
|
|
527
|
+
empty state), centered in the immersive frame. Retry re-runs
|
|
528
|
+
the list query. */
|
|
529
|
+
<View className="flex-1 items-center justify-center">
|
|
568
530
|
<FileLibraryError
|
|
569
531
|
title={t('fileManagement.loadError.title')}
|
|
570
532
|
description={t('fileManagement.loadError.description')}
|
|
@@ -579,14 +541,11 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
579
541
|
) : isEmpty && loading ? (
|
|
580
542
|
/* Loading skeleton — the picker's OWN shape: shimmer tiles laid
|
|
581
543
|
out with the SAME placement-aware geometry the real grid uses
|
|
582
|
-
(`computePhotoGridLayout(effectiveWidth)`),
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
measured width by construction). The real translucent header
|
|
587
|
-
still floats on top, so Cancel stays reachable while loading. */
|
|
544
|
+
(`computePhotoGridLayout(effectiveWidth)`), edge-to-edge under
|
|
545
|
+
the floating bar. `flexWrap` breaks rows exactly at `columns`
|
|
546
|
+
because the last tile in each row carries no right margin (a
|
|
547
|
+
full row + gutters fits the measured width by construction). */
|
|
588
548
|
<View
|
|
589
|
-
className="pt-[60px]"
|
|
590
549
|
style={{ flexDirection: 'row', flexWrap: 'wrap' }}
|
|
591
550
|
pointerEvents="none"
|
|
592
551
|
accessibilityElementsHidden
|
|
@@ -594,7 +553,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
594
553
|
>
|
|
595
554
|
{Array.from({ length: skeletonTileCount }, (_, index) => (
|
|
596
555
|
<Skeleton.Box
|
|
597
|
-
key={index}
|
|
556
|
+
key={`skeleton-${index}`}
|
|
598
557
|
width={cellSize}
|
|
599
558
|
height={cellSize}
|
|
600
559
|
borderRadius={CELL_CORNER_RADIUS}
|
|
@@ -607,7 +566,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
607
566
|
))}
|
|
608
567
|
</View>
|
|
609
568
|
) : isEmpty ? (
|
|
610
|
-
<View className="flex-1 items-center justify-center px-space-32
|
|
569
|
+
<View className="flex-1 items-center justify-center px-space-32">
|
|
611
570
|
<View className="opacity-30 mb-space-16">
|
|
612
571
|
<MaterialCommunityIcons name="image-outline" size={64} color="#FFFFFF" />
|
|
613
572
|
</View>
|
|
@@ -648,7 +607,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
648
607
|
keyExtractor={keyExtractor}
|
|
649
608
|
numColumns={columns}
|
|
650
609
|
className="flex-1 min-h-0"
|
|
651
|
-
contentContainerClassName="
|
|
610
|
+
contentContainerClassName="pb-space-24"
|
|
652
611
|
showsVerticalScrollIndicator={false}
|
|
653
612
|
refreshControl={
|
|
654
613
|
<RefreshControl
|
|
@@ -656,7 +615,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
656
615
|
onRefresh={onRefresh}
|
|
657
616
|
tintColor="#FFFFFF"
|
|
658
617
|
colors={[primaryColor]}
|
|
659
|
-
progressViewOffset={
|
|
618
|
+
progressViewOffset={FLOATING_NAV_BAR_HEIGHT}
|
|
660
619
|
/>
|
|
661
620
|
}
|
|
662
621
|
onEndReached={handleEndReached}
|
|
@@ -667,97 +626,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
667
626
|
windowSize={9}
|
|
668
627
|
/>
|
|
669
628
|
)}
|
|
670
|
-
|
|
671
|
-
{/* Translucent black header floating over the grid. The Dialog owns
|
|
672
|
-
the drag handle (a floating pill above this bar in bottom
|
|
673
|
-
placement, drawn at a higher z-index; none in center), so the
|
|
674
|
-
header no longer reserves the 28dp handle strip — it is just the
|
|
675
|
-
56dp app bar (`min-h-[56px]`). */}
|
|
676
|
-
<View className="absolute top-0 left-0 right-0 flex-row items-center justify-between px-space-12 z-30 bg-[#000000EB] min-h-[56px]">
|
|
677
|
-
<View className="flex-row items-center justify-between w-full h-14">
|
|
678
|
-
<View className="basis-0 grow flex-row items-center justify-start">
|
|
679
|
-
<TouchableOpacity
|
|
680
|
-
onPress={onCancel}
|
|
681
|
-
className="px-1.5 py-space-8 min-h-9 min-w-11 justify-center"
|
|
682
|
-
accessibilityRole="button"
|
|
683
|
-
accessibilityLabel={t('fileManagement.a11y.cancelPicker')}
|
|
684
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
685
|
-
>
|
|
686
|
-
<Text className="text-white text-[17px] font-medium">
|
|
687
|
-
{t('fileManagement.cancel')}
|
|
688
|
-
</Text>
|
|
689
|
-
</TouchableOpacity>
|
|
690
|
-
</View>
|
|
691
|
-
<View pointerEvents="none">
|
|
692
|
-
<Text
|
|
693
|
-
className="text-white text-[17px] font-semibold text-center"
|
|
694
|
-
style={{ letterSpacing: -0.2 }}
|
|
695
|
-
numberOfLines={1}
|
|
696
|
-
>
|
|
697
|
-
{t('fileManagement.choosePhoto')}
|
|
698
|
-
</Text>
|
|
699
|
-
</View>
|
|
700
|
-
<View className="basis-0 grow flex-row items-center justify-end">
|
|
701
|
-
{multiSelect ? (
|
|
702
|
-
<TouchableOpacity
|
|
703
|
-
onPress={handleConfirm}
|
|
704
|
-
disabled={confirmDisabled}
|
|
705
|
-
className="flex-row items-center gap-1.5 px-3.5 py-space-8 rounded-full min-h-9"
|
|
706
|
-
style={{
|
|
707
|
-
backgroundColor: confirmDisabled
|
|
708
|
-
? 'rgba(255,255,255,0.18)'
|
|
709
|
-
: primaryColor,
|
|
710
|
-
opacity: confirmDisabled ? 0.6 : 1,
|
|
711
|
-
}}
|
|
712
|
-
accessibilityRole="button"
|
|
713
|
-
accessibilityLabel={t('fileManagement.a11y.confirmSelection')}
|
|
714
|
-
accessibilityState={{ disabled: confirmDisabled }}
|
|
715
|
-
>
|
|
716
|
-
<Text className="text-white text-[15px] font-semibold">
|
|
717
|
-
{confirmLabel}
|
|
718
|
-
</Text>
|
|
719
|
-
</TouchableOpacity>
|
|
720
|
-
) : (
|
|
721
|
-
isOwner && allowUpload && (
|
|
722
|
-
<TouchableOpacity
|
|
723
|
-
onPress={onUpload}
|
|
724
|
-
disabled={uploading || isPickingDocument}
|
|
725
|
-
className={`flex-row items-center gap-1.5 py-space-8 rounded-full min-h-9${showUploadLabel ? ' px-3.5' : ' px-space-8 w-9 justify-center'}`}
|
|
726
|
-
style={{ backgroundColor: primaryColor }}
|
|
727
|
-
accessibilityRole="button"
|
|
728
|
-
accessibilityLabel={t('fileManagement.a11y.uploadFromDevice')}
|
|
729
|
-
accessibilityState={{ busy: uploading || isPickingDocument }}
|
|
730
|
-
>
|
|
731
|
-
{(uploading || isPickingDocument) ? (
|
|
732
|
-
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
733
|
-
) : (
|
|
734
|
-
<>
|
|
735
|
-
<Ionicons name="cloud-upload" size={16} color="#FFFFFF" />
|
|
736
|
-
{showUploadLabel && (
|
|
737
|
-
<Text className="text-white text-[15px] font-semibold">
|
|
738
|
-
{t('fileManagement.upload')}
|
|
739
|
-
</Text>
|
|
740
|
-
)}
|
|
741
|
-
</>
|
|
742
|
-
)}
|
|
743
|
-
</TouchableOpacity>
|
|
744
|
-
)
|
|
745
|
-
)}
|
|
746
|
-
</View>
|
|
747
|
-
</View>
|
|
748
|
-
{/* Subtle top progress bar during upload (non-blocking). */}
|
|
749
|
-
{uploading && (
|
|
750
|
-
<View className="absolute left-0 right-0 bottom-0 h-0.5 bg-[#FFFFFF1F]">
|
|
751
|
-
<View
|
|
752
|
-
className="h-full"
|
|
753
|
-
style={{
|
|
754
|
-
width: `${Math.round(progressFraction * 100)}%`,
|
|
755
|
-
backgroundColor: primaryColor,
|
|
756
|
-
}}
|
|
757
|
-
/>
|
|
758
|
-
</View>
|
|
759
|
-
)}
|
|
760
|
-
</View>
|
|
761
629
|
</View>
|
|
762
630
|
</View>
|
|
763
631
|
);
|