@oxyhq/services 22.11.1 → 22.12.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/lib/commonjs/ui/hooks/useSurfaceHeader.js +7 -5
- 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 +55 -13
- 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 +43 -153
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/hooks/useSurfaceHeader.js +7 -5
- 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 +55 -13
- 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 +43 -153
- 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.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.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 +25 -5
- 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 +69 -25
- 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 +47 -170
|
@@ -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,27 @@ 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>` (bottom-sheet on
|
|
97
|
-
* card on `md+`). The Dialog owns
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
77
|
+
* The picker renders inside a Bloom `<Dialog>` in NAV-HEADER mode (bottom-sheet on
|
|
78
|
+
* narrow, centered card on `md+`). The Dialog owns the shared gradient nav bar
|
|
79
|
+
* (Cancel / "Choose Photo" / Upload, wired by `FileManagementScreen` through
|
|
80
|
+
* `useSurfaceHeader`) — the picker no longer paints a bar of its own. In this mode
|
|
81
|
+
* the Dialog reserves a fixed nav-bar-height SPACER above the picker's content and
|
|
82
|
+
* its gradient overlay fades a little further into the content; the grid's own
|
|
83
|
+
* top padding just clears that fade so the first row reads cleanly. Two fixed
|
|
84
|
+
* layout constants mirror Bloom internals:
|
|
85
|
+
* - `DIALOG_NAV_BAR_SPACER` mirrors Bloom's `DIALOG_NAV_BAR_HEIGHT` (not exported
|
|
86
|
+
* from `@oxyhq/bloom/dialog`) — the spacer the Dialog inserts above header-mode
|
|
87
|
+
* `scrollable:false` content. The picker's own height is reduced by it so the
|
|
88
|
+
* spacer + grid together fill the `frameSize` morph target (and never overflow
|
|
89
|
+
* the panel's `maxHeightRatio: 0.9` cap, which would clip the last row).
|
|
90
|
+
* - `HEADER_GRADIENT_FADE` is the gradient tail that bleeds past the bar into the
|
|
91
|
+
* content; the grid's `pt-[24px]` (fade + 4dp breathing room) clears it. The
|
|
92
|
+
* `GRID_CONTENT_PADDING_TOP` constant MUST stay in sync with that class.
|
|
104
93
|
*/
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const GRID_CONTENT_PADDING_TOP =
|
|
94
|
+
const DIALOG_NAV_BAR_SPACER = 52;
|
|
95
|
+
const HEADER_GRADIENT_FADE = 20;
|
|
96
|
+
const GRID_CONTENT_PADDING_TOP = HEADER_GRADIENT_FADE + 4; // 24 — matches `pt-[24px]`
|
|
108
97
|
|
|
109
98
|
/**
|
|
110
99
|
* Props for the dedicated photo picker view. Used by FileManagementScreen
|
|
@@ -120,7 +109,6 @@ export interface PhotoPickerViewProps {
|
|
|
120
109
|
refreshing: boolean;
|
|
121
110
|
uploading: boolean;
|
|
122
111
|
isPickingDocument: boolean;
|
|
123
|
-
uploadProgress: { current: number; total: number } | null;
|
|
124
112
|
hasMore: boolean;
|
|
125
113
|
loadingMore: boolean;
|
|
126
114
|
/**
|
|
@@ -143,8 +131,6 @@ export interface PhotoPickerViewProps {
|
|
|
143
131
|
onUpload: () => void;
|
|
144
132
|
onRefresh: () => void;
|
|
145
133
|
onLoadMore: () => void;
|
|
146
|
-
onCancel: () => void;
|
|
147
|
-
onConfirm: () => void;
|
|
148
134
|
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
149
135
|
}
|
|
150
136
|
|
|
@@ -362,7 +348,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
362
348
|
refreshing,
|
|
363
349
|
uploading,
|
|
364
350
|
isPickingDocument,
|
|
365
|
-
uploadProgress,
|
|
366
351
|
hasMore,
|
|
367
352
|
loadingMore,
|
|
368
353
|
loading,
|
|
@@ -377,8 +362,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
377
362
|
onUpload,
|
|
378
363
|
onRefresh,
|
|
379
364
|
onLoadMore,
|
|
380
|
-
onCancel,
|
|
381
|
-
onConfirm,
|
|
382
365
|
t,
|
|
383
366
|
}) => {
|
|
384
367
|
// Grid sizing width. We PREFER the measured grid-container width (the
|
|
@@ -400,12 +383,18 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
400
383
|
|
|
401
384
|
// The picker FILLS its panel: the surface morphs the container UP to an
|
|
402
385
|
// 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
|
-
//
|
|
408
|
-
|
|
386
|
+
// and this root takes that SAME 0.9-of-viewport height MINUS the Dialog's
|
|
387
|
+
// nav-bar spacer (which the Dialog inserts ABOVE this content in header mode).
|
|
388
|
+
// spacer + root then exactly fill the morph target and stay within the panel's
|
|
389
|
+
// `maxHeightRatio: 0.9` cap — without the subtraction the grid's last row would
|
|
390
|
+
// clip. A `flex: 1` root can't fill (no definite bound propagates through the
|
|
391
|
+
// sheet's flex chain at rest, where the panel is height:auto), so the fixed
|
|
392
|
+
// height is required; the FlatList scrolls within it. Keep the 0.9 ratio in
|
|
393
|
+
// sync with the picker's `frameSize` in `surfaceRegistry.ts`.
|
|
394
|
+
const panelHeight = useMemo(
|
|
395
|
+
() => Math.round(windowHeight * 0.9) - DIALOG_NAV_BAR_SPACER,
|
|
396
|
+
[windowHeight],
|
|
397
|
+
);
|
|
409
398
|
const rootStyle = useMemo(() => ({ height: panelHeight }), [panelHeight]);
|
|
410
399
|
|
|
411
400
|
const effectiveWidth = gridWidth > 0 ? gridWidth : windowWidth;
|
|
@@ -440,9 +429,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
440
429
|
|
|
441
430
|
const hasAnySelection = selectedIds.size > 0;
|
|
442
431
|
|
|
443
|
-
// Compact icon-only upload pill on narrow sheets; full pill otherwise.
|
|
444
|
-
const showUploadLabel = effectiveWidth >= 360;
|
|
445
|
-
|
|
446
432
|
const isEmpty = photos.length === 0;
|
|
447
433
|
const atSelectionLimit =
|
|
448
434
|
multiSelect && maxSelection != null && selectedIds.size >= maxSelection;
|
|
@@ -467,16 +453,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
467
453
|
[onPreviewPhoto],
|
|
468
454
|
);
|
|
469
455
|
|
|
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
456
|
// FlatList renderItem: each cell knows its enterIndex (for stagger).
|
|
481
457
|
const renderItem = useCallback(
|
|
482
458
|
({ item, index }: { item: FileMetadata; index: number }) => {
|
|
@@ -537,16 +513,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
537
513
|
onLoadMore();
|
|
538
514
|
}, [loadingMore, hasMore, onLoadMore]);
|
|
539
515
|
|
|
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
516
|
return (
|
|
551
517
|
// Measured wrapper: `style`-only (no className) so RN-Web fires onLayout.
|
|
552
518
|
// `rootStyle` is placement-aware — a `maxHeight` cap that gives the
|
|
@@ -559,12 +525,15 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
559
525
|
root (the CSS flexbox `min-height:auto` trap; a no-op on native,
|
|
560
526
|
where Yoga already defaults min-height to 0). */}
|
|
561
527
|
<View className="flex-1 min-h-0 bg-black">
|
|
562
|
-
{/* Photo grid
|
|
528
|
+
{/* Photo grid — full-bleed black canvas CONTENT under the shared
|
|
529
|
+
Dialog nav bar (the Dialog reserves the bar's height above this
|
|
530
|
+
and its gradient fades a little further down; the top padding
|
|
531
|
+
clears that fade). */}
|
|
563
532
|
{isEmpty && loadError ? (
|
|
564
533
|
/* Terminal load failure — a DISTINCT error surface (not the
|
|
565
|
-
empty state),
|
|
566
|
-
|
|
567
|
-
<View className="flex-1 items-center justify-center pt-[
|
|
534
|
+
empty state), cleared below the nav bar's gradient fade.
|
|
535
|
+
Retry re-runs the list query. */
|
|
536
|
+
<View className="flex-1 items-center justify-center pt-[24px]">
|
|
568
537
|
<FileLibraryError
|
|
569
538
|
title={t('fileManagement.loadError.title')}
|
|
570
539
|
description={t('fileManagement.loadError.description')}
|
|
@@ -579,14 +548,13 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
579
548
|
) : isEmpty && loading ? (
|
|
580
549
|
/* Loading skeleton — the picker's OWN shape: shimmer tiles laid
|
|
581
550
|
out with the SAME placement-aware geometry the real grid uses
|
|
582
|
-
(`computePhotoGridLayout(effectiveWidth)`),
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
still floats on top, so Cancel stays reachable while loading. */
|
|
551
|
+
(`computePhotoGridLayout(effectiveWidth)`), cleared below the
|
|
552
|
+
nav bar's gradient fade. `flexWrap` breaks rows exactly at
|
|
553
|
+
`columns` because the last tile in each row carries no right
|
|
554
|
+
margin (a full row + gutters fits the measured width by
|
|
555
|
+
construction). */
|
|
588
556
|
<View
|
|
589
|
-
className="pt-[
|
|
557
|
+
className="pt-[24px]"
|
|
590
558
|
style={{ flexDirection: 'row', flexWrap: 'wrap' }}
|
|
591
559
|
pointerEvents="none"
|
|
592
560
|
accessibilityElementsHidden
|
|
@@ -594,7 +562,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
594
562
|
>
|
|
595
563
|
{Array.from({ length: skeletonTileCount }, (_, index) => (
|
|
596
564
|
<Skeleton.Box
|
|
597
|
-
key={index}
|
|
565
|
+
key={`skeleton-${index}`}
|
|
598
566
|
width={cellSize}
|
|
599
567
|
height={cellSize}
|
|
600
568
|
borderRadius={CELL_CORNER_RADIUS}
|
|
@@ -607,7 +575,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
607
575
|
))}
|
|
608
576
|
</View>
|
|
609
577
|
) : isEmpty ? (
|
|
610
|
-
<View className="flex-1 items-center justify-center px-space-32 pt-[
|
|
578
|
+
<View className="flex-1 items-center justify-center px-space-32 pt-[24px]">
|
|
611
579
|
<View className="opacity-30 mb-space-16">
|
|
612
580
|
<MaterialCommunityIcons name="image-outline" size={64} color="#FFFFFF" />
|
|
613
581
|
</View>
|
|
@@ -648,7 +616,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
648
616
|
keyExtractor={keyExtractor}
|
|
649
617
|
numColumns={columns}
|
|
650
618
|
className="flex-1 min-h-0"
|
|
651
|
-
contentContainerClassName="pt-[
|
|
619
|
+
contentContainerClassName="pt-[24px] pb-space-24"
|
|
652
620
|
showsVerticalScrollIndicator={false}
|
|
653
621
|
refreshControl={
|
|
654
622
|
<RefreshControl
|
|
@@ -667,97 +635,6 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
|
|
|
667
635
|
windowSize={9}
|
|
668
636
|
/>
|
|
669
637
|
)}
|
|
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
638
|
</View>
|
|
762
639
|
</View>
|
|
763
640
|
);
|