@oxyhq/services 5.10.15 → 5.11.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/README.md +604 -127
- package/lib/commonjs/assets/assets/icons/OxyServices.tsx +2 -2
- package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +1 -1
- package/lib/commonjs/assets/icons/OxyServices.js +0 -2
- package/lib/commonjs/assets/icons/OxyServices.js.map +1 -1
- package/lib/commonjs/assets/illustrations/HighFive.js +0 -2
- package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -1
- package/lib/commonjs/core/OxyServices.js +244 -26
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/index.js +7 -0
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +93 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/sonner-safe.js +2 -0
- package/lib/commonjs/lib/sonner-safe.js.map +1 -1
- package/lib/commonjs/lib/sonner.js +2 -0
- package/lib/commonjs/lib/sonner.js.map +1 -1
- package/lib/commonjs/node/index.js +7 -0
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/ui/components/Avatar.js +0 -2
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +4 -4
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/FontLoader.js +3 -2
- package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +7 -4
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +0 -1
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyLogo.js +0 -2
- package/lib/commonjs/ui/components/OxyLogo.js.map +1 -1
- package/lib/commonjs/ui/components/OxyPayButton.js +4 -5
- package/lib/commonjs/ui/components/OxyPayButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +4 -3
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +0 -1
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +0 -1
- package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
- package/lib/commonjs/ui/components/QuickActions.js +0 -2
- package/lib/commonjs/ui/components/QuickActions.js.map +1 -1
- package/lib/commonjs/ui/components/Section.js +0 -1
- package/lib/commonjs/ui/components/Section.js.map +1 -1
- package/lib/commonjs/ui/components/SectionTitle.js +0 -2
- package/lib/commonjs/ui/components/SectionTitle.js.map +1 -1
- package/lib/commonjs/ui/components/icon/FAIRWalletIcon.js +0 -2
- package/lib/commonjs/ui/components/icon/FAIRWalletIcon.js.map +1 -1
- package/lib/commonjs/ui/components/icon/OxyIcon.js +0 -2
- package/lib/commonjs/ui/components/icon/OxyIcon.js.map +1 -1
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +0 -2
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/commonjs/ui/components/internal/PinInput.js +4 -3
- package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +3 -3
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAssets.js +263 -0
- package/lib/commonjs/ui/hooks/useAssets.js.map +1 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js +1 -2
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +0 -2
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountManagementDemo.js +0 -1
- package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +2 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +5 -6
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +1 -2
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +631 -430
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +2 -2
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +1 -2
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -2
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/RecoverAccountScreen.js +1 -2
- package/lib/commonjs/ui/screens/RecoverAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +1 -2
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +1 -2
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/UserLinksScreen.js +0 -2
- package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +1 -2
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +1 -2
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js +1 -2
- package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js +1 -2
- package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +0 -2
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -2
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +1 -2
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +0 -2
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -2
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/assetStore.js +225 -0
- package/lib/commonjs/ui/stores/assetStore.js.map +1 -0
- package/lib/commonjs/ui/stores/authStore.js +1 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/stores/fileStore.js +153 -0
- package/lib/commonjs/ui/stores/fileStore.js.map +1 -0
- package/lib/commonjs/ui/stores/followStore.js +2 -2
- package/lib/commonjs/ui/stores/followStore.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +1 -1
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/errorUtils.js +19 -11
- package/lib/commonjs/utils/errorUtils.js.map +1 -1
- package/lib/commonjs/utils/hookUtils.js +1 -1
- package/lib/commonjs/utils/hookUtils.js.map +1 -1
- package/lib/commonjs/utils/loggerUtils.js.map +1 -1
- package/lib/commonjs/utils/validationUtils.js +2 -2
- package/lib/commonjs/utils/validationUtils.js.map +1 -1
- package/lib/module/assets/assets/icons/OxyServices.tsx +2 -2
- package/lib/module/assets/assets/illustrations/HighFive.tsx +1 -1
- package/lib/module/assets/icons/OxyServices.js +0 -1
- package/lib/module/assets/icons/OxyServices.js.map +1 -1
- package/lib/module/assets/illustrations/HighFive.js +0 -1
- package/lib/module/assets/illustrations/HighFive.js.map +1 -1
- package/lib/module/core/OxyServices.js +243 -25
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/index.js +1 -1
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib/sonner-safe.js +2 -0
- package/lib/module/lib/sonner-safe.js.map +1 -1
- package/lib/module/lib/sonner.js +3 -0
- package/lib/module/lib/sonner.js.map +1 -1
- package/lib/module/node/index.js +2 -2
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/ui/components/Avatar.js +0 -1
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +4 -3
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/FontLoader.js +3 -2
- package/lib/module/ui/components/FontLoader.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +7 -3
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +1 -1
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/Header.js +0 -1
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyLogo.js +0 -1
- package/lib/module/ui/components/OxyLogo.js.map +1 -1
- package/lib/module/ui/components/OxyPayButton.js +4 -4
- package/lib/module/ui/components/OxyPayButton.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +4 -2
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +0 -1
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +0 -1
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/QuickActions.js +0 -1
- package/lib/module/ui/components/QuickActions.js.map +1 -1
- package/lib/module/ui/components/Section.js +0 -1
- package/lib/module/ui/components/Section.js.map +1 -1
- package/lib/module/ui/components/SectionTitle.js +0 -1
- package/lib/module/ui/components/SectionTitle.js.map +1 -1
- package/lib/module/ui/components/icon/FAIRWalletIcon.js +0 -1
- package/lib/module/ui/components/icon/FAIRWalletIcon.js.map +1 -1
- package/lib/module/ui/components/icon/OxyIcon.js +0 -1
- package/lib/module/ui/components/icon/OxyIcon.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +0 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/module/ui/components/internal/PinInput.js +4 -2
- package/lib/module/ui/components/internal/PinInput.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +3 -3
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useAssets.js +257 -0
- package/lib/module/ui/hooks/useAssets.js.map +1 -0
- package/lib/module/ui/navigation/OxyRouter.js +1 -1
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +0 -1
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountManagementDemo.js +0 -1
- package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +2 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +5 -5
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +633 -432
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +1 -1
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/RecoverAccountScreen.js +1 -1
- package/lib/module/ui/screens/RecoverAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +1 -1
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/UserLinksScreen.js +0 -1
- package/lib/module/ui/screens/UserLinksScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +1 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignUpIdentityStep.js +1 -1
- package/lib/module/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignUpSecurityStep.js +1 -1
- package/lib/module/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignUpSummaryStep.js +0 -1
- package/lib/module/ui/screens/internal/SignUpSummaryStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignUpWelcomeStep.js +0 -1
- package/lib/module/ui/screens/internal/SignUpWelcomeStep.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js +0 -1
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +0 -1
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/stores/assetStore.js +212 -0
- package/lib/module/ui/stores/assetStore.js.map +1 -0
- package/lib/module/ui/stores/authStore.js +1 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/stores/fileStore.js +145 -0
- package/lib/module/ui/stores/fileStore.js.map +1 -0
- package/lib/module/ui/stores/followStore.js +2 -2
- package/lib/module/ui/stores/followStore.js.map +1 -1
- package/lib/module/ui/styles/fonts.js.map +1 -1
- package/lib/module/ui/styles/theme.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +1 -1
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/errorUtils.js +19 -11
- package/lib/module/utils/errorUtils.js.map +1 -1
- package/lib/module/utils/hookUtils.js +1 -1
- package/lib/module/utils/hookUtils.js.map +1 -1
- package/lib/module/utils/loggerUtils.js.map +1 -1
- package/lib/module/utils/validationUtils.js +2 -2
- package/lib/module/utils/validationUtils.js.map +1 -1
- package/lib/typescript/assets/icons/OxyServices.d.ts +2 -2
- package/lib/typescript/assets/icons/OxyServices.d.ts.map +1 -1
- package/lib/typescript/assets/illustrations/HighFive.d.ts +1 -1
- package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -1
- package/lib/typescript/core/OxyServices.d.ts +69 -12
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/index.d.ts +1 -1
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lib/sonner-safe.d.ts +2 -1
- package/lib/typescript/lib/sonner-safe.d.ts.map +1 -1
- package/lib/typescript/lib/sonner.d.ts +11 -3
- package/lib/typescript/lib/sonner.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +105 -6
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +2 -2
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/types/expo-vector-icons.d.ts +8 -1
- package/lib/typescript/types/express.d.ts +22 -3
- package/lib/typescript/ui/components/Avatar.d.ts +2 -2
- package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts +2 -2
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/FontLoader.d.ts +1 -1
- package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/ui/components/GroupedItem.d.ts +2 -1
- package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/ui/components/GroupedSection.d.ts +2 -1
- package/lib/typescript/ui/components/GroupedSection.d.ts.map +1 -1
- package/lib/typescript/ui/components/Header.d.ts +1 -1
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyLogo.d.ts +2 -2
- package/lib/typescript/ui/components/OxyLogo.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyPayButton.d.ts +2 -2
- package/lib/typescript/ui/components/OxyPayButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxySignInButton.d.ts +2 -2
- package/lib/typescript/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
- package/lib/typescript/ui/components/QuickActions.d.ts +1 -1
- package/lib/typescript/ui/components/QuickActions.d.ts.map +1 -1
- package/lib/typescript/ui/components/Section.d.ts +1 -1
- package/lib/typescript/ui/components/Section.d.ts.map +1 -1
- package/lib/typescript/ui/components/SectionTitle.d.ts +1 -1
- package/lib/typescript/ui/components/SectionTitle.d.ts.map +1 -1
- package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts +1 -1
- package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts.map +1 -1
- package/lib/typescript/ui/components/icon/OxyIcon.d.ts +1 -1
- package/lib/typescript/ui/components/icon/OxyIcon.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/PinInput.d.ts +1 -1
- package/lib/typescript/ui/components/internal/PinInput.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +3 -3
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useAssets.d.ts +35 -0
- package/lib/typescript/ui/hooks/useAssets.d.ts.map +1 -0
- package/lib/typescript/ui/navigation/OxyRouter.d.ts +2 -2
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/types.d.ts +7 -7
- package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountManagementDemo.d.ts +1 -1
- package/lib/typescript/ui/screens/AccountManagementDemo.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AppInfoScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/ProfileScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts +1 -1
- package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts +1 -1
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts +2 -2
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/assetStore.d.ts +54 -0
- package/lib/typescript/ui/stores/assetStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/fileStore.d.ts +31 -0
- package/lib/typescript/ui/stores/fileStore.d.ts.map +1 -0
- package/lib/typescript/ui/stores/followStore.d.ts +1 -1
- package/lib/typescript/ui/stores/followStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/fonts.d.ts +1 -1
- package/lib/typescript/ui/styles/fonts.d.ts.map +1 -1
- package/lib/typescript/ui/styles/theme.d.ts +1 -1
- package/lib/typescript/ui/styles/theme.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/errorUtils.d.ts +5 -5
- package/lib/typescript/utils/errorUtils.d.ts.map +1 -1
- package/lib/typescript/utils/hookUtils.d.ts +4 -4
- package/lib/typescript/utils/hookUtils.d.ts.map +1 -1
- package/lib/typescript/utils/loggerUtils.d.ts +18 -18
- package/lib/typescript/utils/loggerUtils.d.ts.map +1 -1
- package/lib/typescript/utils/validationUtils.d.ts +6 -6
- package/lib/typescript/utils/validationUtils.d.ts.map +1 -1
- package/package.json +149 -143
- package/src/assets/icons/OxyServices.tsx +2 -2
- package/src/assets/illustrations/HighFive.tsx +1 -1
- package/src/core/OxyServices.ts +268 -41
- package/src/core/__tests__/OxyServices.test.ts +180 -0
- package/src/core/index.ts +1 -1
- package/src/index.ts +16 -2
- package/src/lib/sonner-safe.ts +4 -1
- package/src/lib/sonner.ts +19 -2
- package/src/models/interfaces.ts +117 -6
- package/src/node/index.ts +2 -2
- package/src/types/expo-vector-icons.d.ts +8 -1
- package/src/types/express.d.ts +22 -3
- package/src/ui/components/Avatar.tsx +2 -2
- package/src/ui/components/FollowButton.tsx +10 -8
- package/src/ui/components/FontLoader.tsx +5 -3
- package/src/ui/components/GroupedItem.tsx +12 -2
- package/src/ui/components/GroupedSection.tsx +3 -1
- package/src/ui/components/Header.tsx +1 -1
- package/src/ui/components/OxyLogo.tsx +2 -2
- package/src/ui/components/OxyPayButton.tsx +6 -5
- package/src/ui/components/OxyProvider.tsx +7 -4
- package/src/ui/components/OxySignInButton.tsx +2 -2
- package/src/ui/components/ProfileCard.tsx +1 -1
- package/src/ui/components/QuickActions.tsx +1 -1
- package/src/ui/components/Section.tsx +1 -1
- package/src/ui/components/SectionTitle.tsx +1 -1
- package/src/ui/components/icon/FAIRWalletIcon.tsx +1 -1
- package/src/ui/components/icon/OxyIcon.tsx +1 -1
- package/src/ui/components/internal/GroupedPillButtons.tsx +1 -1
- package/src/ui/components/internal/PinInput.tsx +3 -2
- package/src/ui/components/internal/TextField.tsx +9 -11
- package/src/ui/context/OxyContext.tsx +3 -3
- package/src/ui/hooks/useAssets.ts +306 -0
- package/src/ui/navigation/OxyRouter.tsx +3 -2
- package/src/ui/navigation/types.ts +8 -8
- package/src/ui/screens/AccountCenterScreen.tsx +2 -2
- package/src/ui/screens/AccountManagementDemo.tsx +1 -1
- package/src/ui/screens/AccountOverviewScreen.tsx +1 -1
- package/src/ui/screens/AccountSettingsScreen.tsx +3 -3
- package/src/ui/screens/AccountSwitcherScreen.tsx +9 -8
- package/src/ui/screens/AppInfoScreen.tsx +3 -2
- package/src/ui/screens/FeedbackScreen.tsx +1 -1
- package/src/ui/screens/FileManagementScreen.tsx +619 -494
- package/src/ui/screens/PaymentGatewayScreen.tsx +3 -2
- package/src/ui/screens/PremiumSubscriptionScreen.tsx +3 -2
- package/src/ui/screens/ProfileScreen.tsx +3 -2
- package/src/ui/screens/RecoverAccountScreen.tsx +3 -2
- package/src/ui/screens/SessionManagementScreen.tsx +4 -3
- package/src/ui/screens/SignInScreen.tsx +3 -2
- package/src/ui/screens/SignUpScreen.tsx +1 -1
- package/src/ui/screens/UserLinksScreen.tsx +2 -2
- package/src/ui/screens/internal/SignInPasswordStep.tsx +3 -2
- package/src/ui/screens/internal/SignInUsernameStep.tsx +3 -2
- package/src/ui/screens/internal/SignUpIdentityStep.tsx +3 -2
- package/src/ui/screens/internal/SignUpSecurityStep.tsx +3 -2
- package/src/ui/screens/internal/SignUpSummaryStep.tsx +1 -1
- package/src/ui/screens/internal/SignUpWelcomeStep.tsx +1 -1
- package/src/ui/screens/karma/KarmaAboutScreen.tsx +2 -2
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +3 -2
- package/src/ui/screens/karma/KarmaFAQScreen.tsx +1 -1
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +3 -2
- package/src/ui/screens/karma/KarmaRewardsScreen.tsx +2 -2
- package/src/ui/screens/karma/KarmaRulesScreen.tsx +3 -2
- package/src/ui/stores/assetStore.ts +281 -0
- package/src/ui/stores/authStore.ts +1 -1
- package/src/ui/stores/fileStore.ts +118 -0
- package/src/ui/stores/followStore.ts +4 -4
- package/src/ui/styles/fonts.ts +1 -1
- package/src/ui/styles/theme.ts +1 -1
- package/src/utils/__tests__/validationUtils.test.ts +236 -0
- package/src/utils/asyncUtils.ts +4 -4
- package/src/utils/errorUtils.ts +35 -23
- package/src/utils/hookUtils.ts +7 -7
- package/src/utils/loggerUtils.ts +18 -18
- package/src/utils/validationUtils.ts +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -11,39 +11,26 @@ import {
|
|
|
11
11
|
Dimensions,
|
|
12
12
|
Modal,
|
|
13
13
|
TextInput,
|
|
14
|
-
Image,
|
|
14
|
+
Image, // kept for Image.getSize only
|
|
15
15
|
} from 'react-native';
|
|
16
|
-
import {
|
|
16
|
+
import { Image as ExpoImage } from 'expo-image';
|
|
17
|
+
import type { BaseScreenProps } from '../navigation/types';
|
|
17
18
|
import { useOxy } from '../context/OxyContext';
|
|
18
19
|
import { fontFamilies } from '../styles/fonts';
|
|
19
20
|
import { toast } from '../../lib/sonner';
|
|
20
21
|
import { Ionicons } from '@expo/vector-icons';
|
|
21
|
-
import { FileMetadata } from '../../models/interfaces';
|
|
22
|
+
import type { FileMetadata } from '../../models/interfaces';
|
|
23
|
+
import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
|
|
24
|
+
import Header from '../components/Header';
|
|
25
|
+
import { GroupedSection } from '../components';
|
|
22
26
|
|
|
23
27
|
interface FileManagementScreenProps extends BaseScreenProps {
|
|
24
28
|
userId?: string;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
// Add this helper function near the top (after imports):
|
|
28
|
-
async function uploadFileRaw(file: File | Blob, userId: string) {
|
|
29
|
-
|
|
30
|
-
const mimeType = (file as any).type || 'application/octet-stream';
|
|
31
|
-
|
|
32
|
-
const res = await fetch('/api/files/upload-raw', {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
headers: {
|
|
35
|
-
'Content-Type': mimeType,
|
|
36
|
-
'X-File-Name': encodeURIComponent(fileName),
|
|
37
|
-
'X-User-Id': userId,
|
|
38
|
-
},
|
|
39
|
-
body: file,
|
|
40
|
-
credentials: 'include', // if you use cookies/session
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
throw new Error(await res.text());
|
|
45
|
-
}
|
|
46
|
-
return await res.json();
|
|
32
|
+
async function uploadFileRaw(file: File | Blob, userId: string, oxyServices: any) {
|
|
33
|
+
return await oxyServices.uploadRawFile(file);
|
|
47
34
|
}
|
|
48
35
|
|
|
49
36
|
const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
@@ -67,12 +54,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
67
54
|
console.log('[FileManagementScreen] Available content width:', availableContentWidth);
|
|
68
55
|
console.log('[FileManagementScreen] Spacing fix applied: 4px uniform gap both horizontal and vertical');
|
|
69
56
|
}, [containerWidth]);
|
|
70
|
-
const
|
|
57
|
+
const files = useFiles();
|
|
58
|
+
const uploading = useUploadingStore();
|
|
59
|
+
const uploadProgress = useUploadAggregateProgress();
|
|
60
|
+
const deleting = useDeletingStore();
|
|
71
61
|
const [loading, setLoading] = useState(true);
|
|
72
62
|
const [refreshing, setRefreshing] = useState(false);
|
|
73
|
-
const [uploading, setUploading] = useState(false);
|
|
74
|
-
const [uploadProgress, setUploadProgress] = useState<{ current: number, total: number } | null>(null);
|
|
75
|
-
const [deleting, setDeleting] = useState<string | null>(null);
|
|
76
63
|
const [selectedFile, setSelectedFile] = useState<FileMetadata | null>(null);
|
|
77
64
|
const [showFileDetails, setShowFileDetails] = useState(false);
|
|
78
65
|
const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
|
|
@@ -81,11 +68,74 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
81
68
|
const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
|
|
82
69
|
const [viewMode, setViewMode] = useState<'all' | 'photos'>('all');
|
|
83
70
|
const [searchQuery, setSearchQuery] = useState('');
|
|
84
|
-
|
|
71
|
+
// Derived filtered files (avoid setState loops)
|
|
72
|
+
const filteredFiles = useMemo(() => {
|
|
73
|
+
let filteredByMode = files;
|
|
74
|
+
if (viewMode === 'photos') {
|
|
75
|
+
filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
|
|
76
|
+
}
|
|
77
|
+
if (!searchQuery.trim()) {
|
|
78
|
+
return filteredByMode;
|
|
79
|
+
}
|
|
80
|
+
const query = searchQuery.toLowerCase();
|
|
81
|
+
return filteredByMode.filter(file =>
|
|
82
|
+
file.filename.toLowerCase().includes(query) ||
|
|
83
|
+
file.contentType.toLowerCase().includes(query) ||
|
|
84
|
+
(file.metadata?.description && file.metadata.description.toLowerCase().includes(query))
|
|
85
|
+
);
|
|
86
|
+
}, [files, searchQuery, viewMode]);
|
|
85
87
|
const [isDragging, setIsDragging] = useState(false);
|
|
86
88
|
const [photoDimensions, setPhotoDimensions] = useState<{ [key: string]: { width: number, height: number } }>({});
|
|
87
89
|
const [loadingDimensions, setLoadingDimensions] = useState(false);
|
|
88
90
|
const [hoveredPreview, setHoveredPreview] = useState<string | null>(null);
|
|
91
|
+
const uploadStartRef = useRef<number | null>(null);
|
|
92
|
+
const MIN_BANNER_MS = 600;
|
|
93
|
+
const endUpload = useCallback(() => {
|
|
94
|
+
const started = uploadStartRef.current;
|
|
95
|
+
const elapsed = started ? Date.now() - started : MIN_BANNER_MS;
|
|
96
|
+
const remaining = elapsed < MIN_BANNER_MS ? MIN_BANNER_MS - elapsed : 0;
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
useFileStore.getState().setUploading(false);
|
|
99
|
+
uploadStartRef.current = null;
|
|
100
|
+
}, remaining);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
// Helper to safely request a thumbnail variant only for image mime types.
|
|
104
|
+
// Prevents backend warnings: "Variant thumb not supported for mime application/pdf".
|
|
105
|
+
const getSafeDownloadUrl = useCallback(
|
|
106
|
+
(file: FileMetadata, variant: string = 'thumb') => {
|
|
107
|
+
const isImage = file.contentType.startsWith('image/');
|
|
108
|
+
const isVideo = file.contentType.startsWith('video/');
|
|
109
|
+
|
|
110
|
+
// Prefer explicit variant key if variants metadata present
|
|
111
|
+
if (file.variants && file.variants.length > 0) {
|
|
112
|
+
// For videos, try 'poster' regardless of requested variant
|
|
113
|
+
if (isVideo) {
|
|
114
|
+
const poster = file.variants.find(v => v.type === 'poster');
|
|
115
|
+
if (poster) return oxyServices.getFileDownloadUrl(file.id, 'poster');
|
|
116
|
+
}
|
|
117
|
+
if (isImage) {
|
|
118
|
+
const desired = file.variants.find(v => v.type === variant);
|
|
119
|
+
if (desired) return oxyServices.getFileDownloadUrl(file.id, variant);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (isImage) {
|
|
124
|
+
return oxyServices.getFileDownloadUrl(file.id, variant);
|
|
125
|
+
}
|
|
126
|
+
if (isVideo) {
|
|
127
|
+
// Fallback to poster if backend supports implicit generation
|
|
128
|
+
try {
|
|
129
|
+
return oxyServices.getFileDownloadUrl(file.id, 'poster');
|
|
130
|
+
} catch {
|
|
131
|
+
return oxyServices.getFileDownloadUrl(file.id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Other mime types: no variant
|
|
135
|
+
return oxyServices.getFileDownloadUrl(file.id);
|
|
136
|
+
},
|
|
137
|
+
[oxyServices]
|
|
138
|
+
);
|
|
89
139
|
|
|
90
140
|
// Memoize theme-related calculations to prevent unnecessary recalculations
|
|
91
141
|
const themeStyles = useMemo(() => {
|
|
@@ -108,18 +158,33 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
108
158
|
|
|
109
159
|
const targetUserId = userId || user?.id;
|
|
110
160
|
|
|
111
|
-
const
|
|
161
|
+
const storeSetUploading = useFileStore(s => s.setUploading);
|
|
162
|
+
const storeSetUploadProgress = useFileStore(s => s.setUploadProgress);
|
|
163
|
+
const storeSetDeleting = useFileStore(s => s.setDeleting);
|
|
164
|
+
|
|
165
|
+
const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' = 'initial') => {
|
|
112
166
|
if (!targetUserId) return;
|
|
113
167
|
|
|
114
168
|
try {
|
|
115
|
-
if (
|
|
169
|
+
if (mode === 'refresh') {
|
|
116
170
|
setRefreshing(true);
|
|
117
|
-
} else {
|
|
171
|
+
} else if (mode === 'initial') {
|
|
118
172
|
setLoading(true);
|
|
119
173
|
}
|
|
120
174
|
|
|
121
|
-
const response = await oxyServices.listUserFiles(
|
|
122
|
-
|
|
175
|
+
const response = await oxyServices.listUserFiles();
|
|
176
|
+
const assets: FileMetadata[] = (response.files || []).map((f: any) => ({
|
|
177
|
+
id: f.id,
|
|
178
|
+
filename: f.originalName || f.sha256,
|
|
179
|
+
contentType: f.mime,
|
|
180
|
+
length: f.size,
|
|
181
|
+
chunkSize: 0,
|
|
182
|
+
uploadDate: f.createdAt,
|
|
183
|
+
metadata: f.metadata || {},
|
|
184
|
+
variants: f.variants || [],
|
|
185
|
+
}));
|
|
186
|
+
// Merge to preserve existing order & allow incremental updates
|
|
187
|
+
useFileStore.getState().setFiles(assets, { merge: true });
|
|
123
188
|
} catch (error: any) {
|
|
124
189
|
console.error('Failed to load files:', error);
|
|
125
190
|
toast.error(error.message || 'Failed to load files');
|
|
@@ -129,28 +194,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
129
194
|
}
|
|
130
195
|
}, [targetUserId, oxyServices]);
|
|
131
196
|
|
|
132
|
-
//
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
let filteredByMode = files;
|
|
135
|
-
|
|
136
|
-
// Filter by view mode first
|
|
137
|
-
if (viewMode === 'photos') {
|
|
138
|
-
filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Then filter by search query
|
|
142
|
-
if (!searchQuery.trim()) {
|
|
143
|
-
setFilteredFiles(filteredByMode);
|
|
144
|
-
} else {
|
|
145
|
-
const query = searchQuery.toLowerCase();
|
|
146
|
-
const filtered = filteredByMode.filter(file =>
|
|
147
|
-
file.filename.toLowerCase().includes(query) ||
|
|
148
|
-
file.contentType.toLowerCase().includes(query) ||
|
|
149
|
-
(file.metadata?.description && file.metadata.description.toLowerCase().includes(query))
|
|
150
|
-
);
|
|
151
|
-
setFilteredFiles(filtered);
|
|
152
|
-
}
|
|
153
|
-
}, [files, searchQuery, viewMode]);
|
|
197
|
+
// (removed effect; filteredFiles is memoized)
|
|
154
198
|
|
|
155
199
|
// Load photo dimensions for justified grid
|
|
156
200
|
const loadPhotoDimensions = useCallback(async (photos: FileMetadata[]) => {
|
|
@@ -172,7 +216,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
172
216
|
await Promise.all(
|
|
173
217
|
photosToLoad.map(async (photo) => {
|
|
174
218
|
try {
|
|
175
|
-
const downloadUrl =
|
|
219
|
+
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
176
220
|
|
|
177
221
|
if (Platform.OS === 'web') {
|
|
178
222
|
const img = new (window as any).Image();
|
|
@@ -249,7 +293,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
249
293
|
if (selectedFiles.length === 0) return;
|
|
250
294
|
if (!targetUserId) return; // Guard clause to ensure userId is defined
|
|
251
295
|
try {
|
|
252
|
-
|
|
296
|
+
storeSetUploadProgress({ current: 0, total: selectedFiles.length });
|
|
253
297
|
const maxSize = 50 * 1024 * 1024; // 50MB
|
|
254
298
|
const oversizedFiles = selectedFiles.filter(file => file.size > maxSize);
|
|
255
299
|
if (oversizedFiles.length > 0) {
|
|
@@ -261,9 +305,42 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
261
305
|
let failureCount = 0;
|
|
262
306
|
const errors: string[] = [];
|
|
263
307
|
for (let i = 0; i < selectedFiles.length; i++) {
|
|
264
|
-
|
|
308
|
+
storeSetUploadProgress({ current: i + 1, total: selectedFiles.length });
|
|
265
309
|
try {
|
|
266
|
-
|
|
310
|
+
const raw = selectedFiles[i];
|
|
311
|
+
const optimisticId = `temp-${Date.now()}-${i}`;
|
|
312
|
+
const optimisticFile: FileMetadata = {
|
|
313
|
+
id: optimisticId,
|
|
314
|
+
filename: raw.name,
|
|
315
|
+
contentType: raw.type || 'application/octet-stream',
|
|
316
|
+
length: raw.size,
|
|
317
|
+
chunkSize: 0,
|
|
318
|
+
uploadDate: new Date().toISOString(),
|
|
319
|
+
metadata: { uploading: true },
|
|
320
|
+
variants: [],
|
|
321
|
+
};
|
|
322
|
+
useFileStore.getState().addFile(optimisticFile, { prepend: true });
|
|
323
|
+
const result = await uploadFileRaw(raw, targetUserId, oxyServices);
|
|
324
|
+
// Attempt to refresh file list incrementally – fetch single file metadata if API allows
|
|
325
|
+
if (result?.file || result?.files?.[0]) {
|
|
326
|
+
const f = result.file || result.files[0];
|
|
327
|
+
const merged: FileMetadata = {
|
|
328
|
+
id: f.id,
|
|
329
|
+
filename: f.originalName || f.sha256 || raw.name,
|
|
330
|
+
contentType: f.mime || raw.type || 'application/octet-stream',
|
|
331
|
+
length: f.size || raw.size,
|
|
332
|
+
chunkSize: 0,
|
|
333
|
+
uploadDate: f.createdAt || new Date().toISOString(),
|
|
334
|
+
metadata: f.metadata || {},
|
|
335
|
+
variants: f.variants || [],
|
|
336
|
+
};
|
|
337
|
+
// Remove optimistic then add real
|
|
338
|
+
useFileStore.getState().removeFile(optimisticId);
|
|
339
|
+
useFileStore.getState().addFile(merged, { prepend: true });
|
|
340
|
+
} else {
|
|
341
|
+
// Fallback: will reconcile on later list refresh
|
|
342
|
+
useFileStore.getState().updateFile(optimisticId, { metadata: { uploading: false } as any });
|
|
343
|
+
}
|
|
267
344
|
successCount++;
|
|
268
345
|
} catch (error: any) {
|
|
269
346
|
failureCount++;
|
|
@@ -277,21 +354,21 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
277
354
|
const errorMessage = `${failureCount} file(s) failed to upload${errors.length > 0 ? ':\n' + errors.slice(0, 3).join('\n') + (errors.length > 3 ? '\n...' : '') : ''}`;
|
|
278
355
|
toast.error(errorMessage);
|
|
279
356
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}, 500);
|
|
357
|
+
// Silent background refresh to ensure metadata/variants updated
|
|
358
|
+
setTimeout(() => { loadFiles('silent'); }, 1200);
|
|
283
359
|
} catch (error: any) {
|
|
284
360
|
console.error('Upload error:', error);
|
|
285
361
|
toast.error(error.message || 'Failed to upload files');
|
|
286
362
|
} finally {
|
|
287
|
-
|
|
363
|
+
storeSetUploadProgress(null);
|
|
288
364
|
}
|
|
289
365
|
};
|
|
290
366
|
|
|
291
367
|
const handleFileUpload = async () => {
|
|
292
368
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
369
|
+
uploadStartRef.current = Date.now();
|
|
370
|
+
storeSetUploading(true);
|
|
371
|
+
storeSetUploadProgress(null);
|
|
295
372
|
|
|
296
373
|
if (Platform.OS === 'web') {
|
|
297
374
|
// Web file picker implementation
|
|
@@ -299,10 +376,26 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
299
376
|
input.type = 'file';
|
|
300
377
|
input.multiple = true;
|
|
301
378
|
input.accept = '*/*';
|
|
379
|
+
// Fallback: if the user cancels the dialog (no onchange fires or 0 files), hide banner
|
|
380
|
+
const cancellationTimer = setTimeout(() => {
|
|
381
|
+
const state = useFileStore.getState();
|
|
382
|
+
if (state.uploading && uploadStartRef.current && !state.uploadProgress) {
|
|
383
|
+
// No selection happened; treat as cancel
|
|
384
|
+
endUpload();
|
|
385
|
+
}
|
|
386
|
+
}, 1500); // allow enough time for user to pick
|
|
302
387
|
|
|
303
388
|
input.onchange = async (e: any) => {
|
|
304
|
-
|
|
389
|
+
clearTimeout(cancellationTimer);
|
|
390
|
+
const selectedFiles = Array.from(e.target.files || []) as File[];
|
|
391
|
+
if (selectedFiles.length === 0) {
|
|
392
|
+
// User explicitly canceled (some browsers still fire onchange with empty list)
|
|
393
|
+
endUpload();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
storeSetUploadProgress({ current: 0, total: selectedFiles.length });
|
|
305
397
|
await processFileUploads(selectedFiles);
|
|
398
|
+
endUpload();
|
|
306
399
|
};
|
|
307
400
|
|
|
308
401
|
input.click();
|
|
@@ -320,8 +413,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
320
413
|
} catch (error: any) {
|
|
321
414
|
toast.error(error.message || 'Failed to upload file');
|
|
322
415
|
} finally {
|
|
323
|
-
|
|
324
|
-
|
|
416
|
+
// IMPORTANT: Do NOT call endUpload here.
|
|
417
|
+
// We only want to hide the banner after the actual upload(s) complete.
|
|
418
|
+
// The input.onchange handler invokes processFileUploads then calls endUpload().
|
|
419
|
+
// Calling endUpload here caused the banner to disappear while files were still uploading.
|
|
420
|
+
storeSetUploadProgress(null); // keep clearing any stale progress
|
|
325
421
|
}
|
|
326
422
|
};
|
|
327
423
|
|
|
@@ -338,7 +434,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
338
434
|
console.log('Deleting file:', { fileId, filename });
|
|
339
435
|
console.log('Target user ID:', targetUserId);
|
|
340
436
|
console.log('Current user ID:', user?.id);
|
|
341
|
-
|
|
437
|
+
storeSetDeleting(fileId);
|
|
342
438
|
|
|
343
439
|
const result = await oxyServices.deleteFile(fileId);
|
|
344
440
|
console.log('Delete result:', result);
|
|
@@ -346,9 +442,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
346
442
|
toast.success('File deleted successfully');
|
|
347
443
|
|
|
348
444
|
// Reload files after successful deletion
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
445
|
+
// Optimistic remove
|
|
446
|
+
useFileStore.getState().removeFile(fileId);
|
|
447
|
+
// Silent background reconcile
|
|
448
|
+
setTimeout(() => loadFiles('silent'), 800);
|
|
352
449
|
} catch (error: any) {
|
|
353
450
|
console.error('Delete error:', error);
|
|
354
451
|
console.error('Error details:', error.response?.data || error.message);
|
|
@@ -357,16 +454,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
357
454
|
if (error.message?.includes('File not found') || error.message?.includes('404')) {
|
|
358
455
|
toast.error('File not found. It may have already been deleted.');
|
|
359
456
|
// Still reload files to refresh the list
|
|
360
|
-
setTimeout(
|
|
361
|
-
await loadFiles();
|
|
362
|
-
}, 500);
|
|
457
|
+
setTimeout(() => loadFiles('silent'), 800);
|
|
363
458
|
} else if (error.message?.includes('permission') || error.message?.includes('403')) {
|
|
364
459
|
toast.error('You do not have permission to delete this file.');
|
|
365
460
|
} else {
|
|
366
461
|
toast.error(error.message || 'Failed to delete file');
|
|
367
462
|
}
|
|
368
463
|
} finally {
|
|
369
|
-
|
|
464
|
+
storeSetDeleting(null);
|
|
370
465
|
}
|
|
371
466
|
};
|
|
372
467
|
|
|
@@ -378,6 +473,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
378
473
|
}
|
|
379
474
|
};
|
|
380
475
|
|
|
476
|
+
const handleDragEnter = (e: any) => {
|
|
477
|
+
if (Platform.OS === 'web' && user?.id === targetUserId) {
|
|
478
|
+
e.preventDefault();
|
|
479
|
+
setIsDragging(true);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
381
483
|
const handleDragLeave = (e: any) => {
|
|
382
484
|
if (Platform.OS === 'web') {
|
|
383
485
|
e.preventDefault();
|
|
@@ -385,19 +487,54 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
385
487
|
}
|
|
386
488
|
};
|
|
387
489
|
|
|
490
|
+
// Global drag listeners (web) to catch drags outside component bounds
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (Platform.OS !== 'web' || user?.id !== targetUserId) return;
|
|
493
|
+
const onDocDragEnter = (e: any) => {
|
|
494
|
+
if (e?.dataTransfer?.types?.includes('Files')) setIsDragging(true);
|
|
495
|
+
};
|
|
496
|
+
const onDocDragOver = (e: any) => {
|
|
497
|
+
if (e?.dataTransfer?.types?.includes('Files')) {
|
|
498
|
+
e.preventDefault();
|
|
499
|
+
setIsDragging(true);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
const onDocDrop = (e: any) => {
|
|
503
|
+
if (e?.dataTransfer?.files?.length) {
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
setIsDragging(false);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
const onDocDragLeave = (e: any) => {
|
|
509
|
+
if (!e.relatedTarget && e.screenX === 0 && e.screenY === 0) setIsDragging(false);
|
|
510
|
+
};
|
|
511
|
+
document.addEventListener('dragenter', onDocDragEnter);
|
|
512
|
+
document.addEventListener('dragover', onDocDragOver);
|
|
513
|
+
document.addEventListener('drop', onDocDrop);
|
|
514
|
+
document.addEventListener('dragleave', onDocDragLeave);
|
|
515
|
+
return () => {
|
|
516
|
+
document.removeEventListener('dragenter', onDocDragEnter);
|
|
517
|
+
document.removeEventListener('dragover', onDocDragOver);
|
|
518
|
+
document.removeEventListener('drop', onDocDrop);
|
|
519
|
+
document.removeEventListener('dragleave', onDocDragLeave);
|
|
520
|
+
};
|
|
521
|
+
}, [user?.id, targetUserId]);
|
|
522
|
+
|
|
388
523
|
const handleDrop = async (e: any) => {
|
|
389
524
|
if (Platform.OS === 'web' && user?.id === targetUserId) {
|
|
390
525
|
e.preventDefault();
|
|
391
526
|
setIsDragging(false);
|
|
392
|
-
|
|
527
|
+
uploadStartRef.current = Date.now();
|
|
528
|
+
storeSetUploading(true);
|
|
393
529
|
|
|
394
530
|
try {
|
|
395
531
|
const files = Array.from(e.dataTransfer.files) as File[];
|
|
532
|
+
if (files.length > 0) storeSetUploadProgress({ current: 0, total: files.length });
|
|
396
533
|
await processFileUploads(files);
|
|
397
534
|
} catch (error: any) {
|
|
398
535
|
toast.error(error.message || 'Failed to upload files');
|
|
399
536
|
} finally {
|
|
400
|
-
|
|
537
|
+
endUpload();
|
|
401
538
|
}
|
|
402
539
|
}
|
|
403
540
|
};
|
|
@@ -455,7 +592,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
455
592
|
const k = 1024;
|
|
456
593
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
457
594
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
458
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
595
|
+
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
459
596
|
};
|
|
460
597
|
|
|
461
598
|
const getFileIcon = (contentType: string): string => {
|
|
@@ -532,7 +669,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
532
669
|
};
|
|
533
670
|
|
|
534
671
|
const renderSimplePhotoItem = useCallback((photo: FileMetadata, index: number) => {
|
|
535
|
-
const downloadUrl =
|
|
672
|
+
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
536
673
|
|
|
537
674
|
// Calculate photo item width based on actual container size from bottom sheet
|
|
538
675
|
let itemsPerRow = 3; // Default for mobile
|
|
@@ -560,45 +697,24 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
560
697
|
activeOpacity={0.8}
|
|
561
698
|
>
|
|
562
699
|
<View style={styles.simplePhotoContainer}>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
loading="lazy"
|
|
575
|
-
onError={(e) => {
|
|
576
|
-
console.error('Photo failed to load:', e);
|
|
577
|
-
}}
|
|
578
|
-
onMouseEnter={(e) => {
|
|
579
|
-
e.currentTarget.style.transform = 'scale(1.05)';
|
|
580
|
-
}}
|
|
581
|
-
onMouseLeave={(e) => {
|
|
582
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
583
|
-
}}
|
|
584
|
-
/>
|
|
585
|
-
) : (
|
|
586
|
-
<Image
|
|
587
|
-
source={{ uri: downloadUrl }}
|
|
588
|
-
style={styles.simplePhotoImage}
|
|
589
|
-
resizeMode="cover"
|
|
590
|
-
onError={(e) => {
|
|
591
|
-
console.error('Photo failed to load:', e);
|
|
592
|
-
}}
|
|
593
|
-
/>
|
|
594
|
-
)}
|
|
700
|
+
<ExpoImage
|
|
701
|
+
source={{ uri: downloadUrl }}
|
|
702
|
+
style={styles.simplePhotoImage}
|
|
703
|
+
contentFit="cover"
|
|
704
|
+
transition={120}
|
|
705
|
+
cachePolicy="memory-disk"
|
|
706
|
+
onError={(e: any) => {
|
|
707
|
+
console.error('Photo failed to load:', (e as any)?.nativeEvent ?? e);
|
|
708
|
+
}}
|
|
709
|
+
accessibilityLabel={photo.filename}
|
|
710
|
+
/>
|
|
595
711
|
</View>
|
|
596
712
|
</TouchableOpacity>
|
|
597
713
|
);
|
|
598
714
|
}, [oxyServices, containerWidth]);
|
|
599
715
|
|
|
600
716
|
const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
|
|
601
|
-
const downloadUrl =
|
|
717
|
+
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
602
718
|
|
|
603
719
|
return (
|
|
604
720
|
<TouchableOpacity
|
|
@@ -614,50 +730,32 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
614
730
|
activeOpacity={0.8}
|
|
615
731
|
>
|
|
616
732
|
<View style={styles.justifiedPhotoContainer}>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
loading="lazy"
|
|
629
|
-
onError={(e) => {
|
|
630
|
-
console.error('Photo failed to load:', e);
|
|
631
|
-
}}
|
|
632
|
-
onMouseEnter={(e) => {
|
|
633
|
-
e.currentTarget.style.transform = 'scale(1.02)';
|
|
634
|
-
e.currentTarget.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
|
|
635
|
-
e.currentTarget.style.zIndex = '10';
|
|
636
|
-
}}
|
|
637
|
-
onMouseLeave={(e) => {
|
|
638
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
639
|
-
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
|
|
640
|
-
e.currentTarget.style.zIndex = '1';
|
|
641
|
-
}}
|
|
642
|
-
/>
|
|
643
|
-
) : (
|
|
644
|
-
<Image
|
|
645
|
-
source={{ uri: downloadUrl }}
|
|
646
|
-
style={styles.justifiedPhotoImage}
|
|
647
|
-
resizeMode="cover"
|
|
648
|
-
onError={(e) => {
|
|
649
|
-
console.error('Photo failed to load:', e);
|
|
650
|
-
}}
|
|
651
|
-
/>
|
|
652
|
-
)}
|
|
733
|
+
<ExpoImage
|
|
734
|
+
source={{ uri: downloadUrl }}
|
|
735
|
+
style={styles.justifiedPhotoImage}
|
|
736
|
+
contentFit="cover"
|
|
737
|
+
transition={120}
|
|
738
|
+
cachePolicy="memory-disk"
|
|
739
|
+
onError={(e: any) => {
|
|
740
|
+
console.error('Photo failed to load:', (e as any)?.nativeEvent ?? e);
|
|
741
|
+
}}
|
|
742
|
+
accessibilityLabel={photo.filename}
|
|
743
|
+
/>
|
|
653
744
|
</View>
|
|
654
745
|
</TouchableOpacity>
|
|
655
746
|
);
|
|
656
747
|
}, [oxyServices]);
|
|
657
748
|
|
|
749
|
+
// Run initial load once per targetUserId change to avoid accidental loops
|
|
750
|
+
const lastLoadedFor = useRef<string | undefined>(undefined);
|
|
658
751
|
useEffect(() => {
|
|
659
|
-
|
|
660
|
-
|
|
752
|
+
const key = targetUserId || 'anonymous';
|
|
753
|
+
if (lastLoadedFor.current !== key) {
|
|
754
|
+
lastLoadedFor.current = key;
|
|
755
|
+
loadFiles('initial');
|
|
756
|
+
}
|
|
757
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
758
|
+
}, [targetUserId]);
|
|
661
759
|
|
|
662
760
|
const renderFileItem = (file: FileMetadata) => {
|
|
663
761
|
const isImage = file.contentType.startsWith('image/');
|
|
@@ -687,37 +785,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
687
785
|
})}
|
|
688
786
|
>
|
|
689
787
|
{isImage && (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
onError={(e) => {
|
|
702
|
-
// Show fallback icon if image fails to load
|
|
703
|
-
e.currentTarget.style.display = 'none';
|
|
704
|
-
const fallbackElement = e.currentTarget.parentElement?.querySelector('[data-fallback="true"]');
|
|
705
|
-
if (fallbackElement) {
|
|
706
|
-
(fallbackElement as HTMLElement).style.display = 'flex';
|
|
707
|
-
}
|
|
708
|
-
}}
|
|
709
|
-
/>
|
|
710
|
-
) : (
|
|
711
|
-
<Image
|
|
712
|
-
source={{ uri: oxyServices.getFileDownloadUrl(file.id) }}
|
|
713
|
-
style={styles.previewImage}
|
|
714
|
-
resizeMode="cover"
|
|
715
|
-
onError={() => {
|
|
716
|
-
// For React Native, you might want to set an error state
|
|
717
|
-
console.warn('Failed to load image preview for file:', file.id);
|
|
718
|
-
}}
|
|
719
|
-
/>
|
|
720
|
-
)
|
|
788
|
+
<ExpoImage
|
|
789
|
+
source={{ uri: getSafeDownloadUrl(file, 'thumb') }}
|
|
790
|
+
style={styles.previewImage}
|
|
791
|
+
contentFit="cover"
|
|
792
|
+
transition={120}
|
|
793
|
+
cachePolicy="memory-disk"
|
|
794
|
+
onError={(_: any) => {
|
|
795
|
+
console.warn('Failed to load image preview.');
|
|
796
|
+
}}
|
|
797
|
+
accessibilityLabel={file.filename}
|
|
798
|
+
/>
|
|
721
799
|
)}
|
|
722
800
|
{isPDF && (
|
|
723
801
|
<View style={styles.pdfPreview}>
|
|
@@ -726,9 +804,21 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
726
804
|
</View>
|
|
727
805
|
)}
|
|
728
806
|
{isVideo && (
|
|
729
|
-
<View style={styles.
|
|
730
|
-
<
|
|
731
|
-
|
|
807
|
+
<View style={styles.videoPreviewWrapper}>
|
|
808
|
+
<ExpoImage
|
|
809
|
+
source={{ uri: getSafeDownloadUrl(file, 'thumb') }}
|
|
810
|
+
style={styles.videoPosterImage}
|
|
811
|
+
contentFit="cover"
|
|
812
|
+
transition={120}
|
|
813
|
+
cachePolicy="memory-disk"
|
|
814
|
+
onError={(_: any) => {
|
|
815
|
+
// If thumbnail not available, we still show icon overlay
|
|
816
|
+
}}
|
|
817
|
+
accessibilityLabel={file.filename + ' video thumbnail'}
|
|
818
|
+
/>
|
|
819
|
+
<View style={styles.videoOverlay}>
|
|
820
|
+
<Ionicons name="play" size={24} color="#FFFFFF" />
|
|
821
|
+
</View>
|
|
732
822
|
</View>
|
|
733
823
|
)}
|
|
734
824
|
{/* Fallback icon (hidden by default for images) */}
|
|
@@ -816,8 +906,69 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
816
906
|
);
|
|
817
907
|
};
|
|
818
908
|
|
|
909
|
+
// GroupedSection-based file items (for 'all' view) replacing legacy flat list look
|
|
910
|
+
const groupedFileItems = useMemo(() => {
|
|
911
|
+
return filteredFiles
|
|
912
|
+
.filter(f => true) // placeholder for future filtering
|
|
913
|
+
.sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime())
|
|
914
|
+
.map((file) => {
|
|
915
|
+
const isImage = file.contentType.startsWith('image/');
|
|
916
|
+
const isVideo = file.contentType.startsWith('video/');
|
|
917
|
+
const hasPreview = isImage || isVideo;
|
|
918
|
+
const previewUrl = hasPreview ? (isVideo ? getSafeDownloadUrl(file, 'poster') : getSafeDownloadUrl(file, 'thumb')) : undefined;
|
|
919
|
+
return {
|
|
920
|
+
id: file.id,
|
|
921
|
+
image: previewUrl,
|
|
922
|
+
imageSize: 44,
|
|
923
|
+
icon: !previewUrl ? getFileIcon(file.contentType) : undefined,
|
|
924
|
+
iconColor: themeStyles.primaryColor,
|
|
925
|
+
title: file.filename,
|
|
926
|
+
subtitle: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}`,
|
|
927
|
+
theme: theme as 'light' | 'dark',
|
|
928
|
+
onPress: () => handleFileOpen(file),
|
|
929
|
+
showChevron: false,
|
|
930
|
+
dense: true,
|
|
931
|
+
multiRow: !!file.metadata?.description,
|
|
932
|
+
customContent: (
|
|
933
|
+
<View style={styles.groupedActions}>
|
|
934
|
+
{(isImage || isVideo || file.contentType.includes('pdf')) && (
|
|
935
|
+
<TouchableOpacity
|
|
936
|
+
style={[styles.groupedActionBtn, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
|
|
937
|
+
onPress={() => handleFileOpen(file)}
|
|
938
|
+
>
|
|
939
|
+
<Ionicons name="eye" size={18} color={themeStyles.primaryColor} />
|
|
940
|
+
</TouchableOpacity>
|
|
941
|
+
)}
|
|
942
|
+
<TouchableOpacity
|
|
943
|
+
style={[styles.groupedActionBtn, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
|
|
944
|
+
onPress={() => handleFileDownload(file.id, file.filename)}
|
|
945
|
+
>
|
|
946
|
+
<Ionicons name="download" size={18} color={themeStyles.primaryColor} />
|
|
947
|
+
</TouchableOpacity>
|
|
948
|
+
<TouchableOpacity
|
|
949
|
+
style={[styles.groupedActionBtn, { backgroundColor: themeStyles.isDarkTheme ? '#400000' : '#FFEBEE' }]}
|
|
950
|
+
onPress={() => handleFileDelete(file.id, file.filename)}
|
|
951
|
+
disabled={deleting === file.id}
|
|
952
|
+
>
|
|
953
|
+
{deleting === file.id ? (
|
|
954
|
+
<ActivityIndicator size="small" color={themeStyles.dangerColor} />
|
|
955
|
+
) : (
|
|
956
|
+
<Ionicons name="trash" size={18} color={themeStyles.dangerColor} />
|
|
957
|
+
)}
|
|
958
|
+
</TouchableOpacity>
|
|
959
|
+
</View>
|
|
960
|
+
),
|
|
961
|
+
customContentBelow: file.metadata?.description ? (
|
|
962
|
+
<Text style={[styles.groupedDescription, { color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666' }]} numberOfLines={2}>
|
|
963
|
+
{file.metadata.description}
|
|
964
|
+
</Text>
|
|
965
|
+
) : undefined,
|
|
966
|
+
} as any; // GroupedSectionItem shape
|
|
967
|
+
});
|
|
968
|
+
}, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl]);
|
|
969
|
+
|
|
819
970
|
const renderPhotoItem = (photo: FileMetadata, index: number) => {
|
|
820
|
-
const downloadUrl =
|
|
971
|
+
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
821
972
|
|
|
822
973
|
// Calculate photo item width based on actual container size from bottom sheet
|
|
823
974
|
let itemsPerRow = 3; // Default for mobile
|
|
@@ -844,39 +995,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
844
995
|
activeOpacity={0.8}
|
|
845
996
|
>
|
|
846
997
|
<View style={styles.photoContainer}>
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
loading="lazy"
|
|
859
|
-
onError={(e) => {
|
|
860
|
-
console.error('Photo failed to load:', e);
|
|
861
|
-
// Could replace with placeholder image
|
|
862
|
-
}}
|
|
863
|
-
onMouseEnter={(e) => {
|
|
864
|
-
e.currentTarget.style.transform = 'scale(1.02)';
|
|
865
|
-
}}
|
|
866
|
-
onMouseLeave={(e) => {
|
|
867
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
868
|
-
}}
|
|
869
|
-
/>
|
|
870
|
-
) : (
|
|
871
|
-
<Image
|
|
872
|
-
source={{ uri: downloadUrl }}
|
|
873
|
-
style={styles.photoImage}
|
|
874
|
-
resizeMode="cover"
|
|
875
|
-
onError={(e) => {
|
|
876
|
-
console.error('Photo failed to load:', e);
|
|
877
|
-
}}
|
|
878
|
-
/>
|
|
879
|
-
)}
|
|
998
|
+
<ExpoImage
|
|
999
|
+
source={{ uri: downloadUrl }}
|
|
1000
|
+
style={styles.photoImage}
|
|
1001
|
+
contentFit="cover"
|
|
1002
|
+
transition={120}
|
|
1003
|
+
cachePolicy="memory-disk"
|
|
1004
|
+
onError={(_: any) => {
|
|
1005
|
+
console.warn('Failed to load image preview for photo:', photo.id);
|
|
1006
|
+
}}
|
|
1007
|
+
accessibilityLabel={photo.filename}
|
|
1008
|
+
/>
|
|
880
1009
|
</View>
|
|
881
1010
|
</TouchableOpacity>
|
|
882
1011
|
);
|
|
@@ -890,12 +1019,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
890
1019
|
<View style={styles.emptyState}>
|
|
891
1020
|
<Ionicons name="images-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
|
|
892
1021
|
<Text style={[styles.emptyStateTitle, { color: themeStyles.textColor }]}>No Photos Yet</Text>
|
|
893
|
-
<Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
|
|
894
|
-
|
|
1022
|
+
<Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}> {
|
|
1023
|
+
user?.id === targetUserId
|
|
895
1024
|
? `Upload photos to get started. You can select multiple photos at once${Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}`
|
|
896
1025
|
: "This user hasn't uploaded any photos yet"
|
|
897
|
-
|
|
898
|
-
</Text>
|
|
1026
|
+
} </Text>
|
|
899
1027
|
{user?.id === targetUserId && (
|
|
900
1028
|
<TouchableOpacity
|
|
901
1029
|
style={[styles.emptyStateButton, { backgroundColor: themeStyles.primaryColor }]}
|
|
@@ -923,7 +1051,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
923
1051
|
refreshControl={
|
|
924
1052
|
<RefreshControl
|
|
925
1053
|
refreshing={refreshing}
|
|
926
|
-
onRefresh={() => loadFiles(
|
|
1054
|
+
onRefresh={() => loadFiles('refresh')}
|
|
927
1055
|
tintColor={themeStyles.primaryColor}
|
|
928
1056
|
/>
|
|
929
1057
|
}
|
|
@@ -932,9 +1060,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
932
1060
|
{loadingDimensions && (
|
|
933
1061
|
<View style={styles.dimensionsLoadingIndicator}>
|
|
934
1062
|
<ActivityIndicator size="small" color={themeStyles.primaryColor} />
|
|
935
|
-
<Text style={[styles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
|
|
936
|
-
Loading photo layout...
|
|
937
|
-
</Text>
|
|
1063
|
+
<Text style={[styles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>Loading photo layout...</Text>
|
|
938
1064
|
</View>
|
|
939
1065
|
)}
|
|
940
1066
|
|
|
@@ -983,15 +1109,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
983
1109
|
photoDimensions: { [key: string]: { width: number, height: number } };
|
|
984
1110
|
loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
|
|
985
1111
|
createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
|
|
986
|
-
renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) =>
|
|
987
|
-
renderSimplePhotoItem: (photo: FileMetadata, index: number) =>
|
|
1112
|
+
renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => React.ReactElement;
|
|
1113
|
+
renderSimplePhotoItem: (photo: FileMetadata, index: number) => React.ReactElement;
|
|
988
1114
|
textColor: string;
|
|
989
1115
|
containerWidth: number;
|
|
990
1116
|
}) => {
|
|
991
1117
|
// Load dimensions for new photos
|
|
992
1118
|
React.useEffect(() => {
|
|
993
1119
|
loadPhotoDimensions(photos);
|
|
994
|
-
|
|
1120
|
+
// Depend only on photo IDs to avoid re-running from dimension state changes
|
|
1121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1122
|
+
}, [photos.map(p => p.id).join(',')]);
|
|
995
1123
|
|
|
996
1124
|
// Group photos by date
|
|
997
1125
|
const photosByDate = React.useMemo(() => {
|
|
@@ -1377,34 +1505,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1377
1505
|
</View>
|
|
1378
1506
|
) : isImage && fileContent ? (
|
|
1379
1507
|
<View style={styles.imageContainer}>
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
console.error('Image failed to load:', e);
|
|
1392
|
-
}}
|
|
1393
|
-
/>
|
|
1394
|
-
) : (
|
|
1395
|
-
<Image
|
|
1396
|
-
source={{ uri: fileContent }}
|
|
1397
|
-
style={{
|
|
1398
|
-
width: '100%',
|
|
1399
|
-
height: 400,
|
|
1400
|
-
resizeMode: 'contain',
|
|
1401
|
-
borderRadius: 8,
|
|
1402
|
-
}}
|
|
1403
|
-
onError={(e) => {
|
|
1404
|
-
console.error('Image failed to load:', e);
|
|
1405
|
-
}}
|
|
1406
|
-
/>
|
|
1407
|
-
)}
|
|
1508
|
+
<ExpoImage
|
|
1509
|
+
source={{ uri: fileContent }}
|
|
1510
|
+
style={{ width: '100%', height: 400, borderRadius: 8 }}
|
|
1511
|
+
contentFit="contain"
|
|
1512
|
+
transition={120}
|
|
1513
|
+
cachePolicy="memory-disk"
|
|
1514
|
+
onError={(e: any) => {
|
|
1515
|
+
console.error('Image failed to load:', (e as any)?.nativeEvent ?? e);
|
|
1516
|
+
}}
|
|
1517
|
+
accessibilityLabel={openedFile.filename}
|
|
1518
|
+
/>
|
|
1408
1519
|
</View>
|
|
1409
1520
|
) : isText && fileContent ? (
|
|
1410
1521
|
<View style={[styles.textContainer, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }]}>
|
|
@@ -1543,160 +1654,82 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1543
1654
|
<View
|
|
1544
1655
|
style={[
|
|
1545
1656
|
styles.container,
|
|
1546
|
-
{ backgroundColor },
|
|
1547
1657
|
isDragging && Platform.OS === 'web' && styles.dragOverlay
|
|
1548
1658
|
]}
|
|
1549
1659
|
{...(Platform.OS === 'web' && user?.id === targetUserId ? {
|
|
1550
1660
|
onDragOver: handleDragOver,
|
|
1661
|
+
onDragEnter: handleDragEnter,
|
|
1551
1662
|
onDragLeave: handleDragLeave,
|
|
1552
1663
|
onDrop: handleDrop,
|
|
1553
1664
|
} : {})}
|
|
1554
1665
|
>
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
{
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
{
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
<View style={[
|
|
1602
|
-
styles.viewModeToggle,
|
|
1603
|
-
{
|
|
1604
|
-
backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
|
|
1605
|
-
borderWidth: 1,
|
|
1606
|
-
borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
|
|
1607
|
-
shadowColor: '#000000',
|
|
1608
|
-
shadowOffset: {
|
|
1609
|
-
width: 0,
|
|
1610
|
-
height: 1,
|
|
1611
|
-
},
|
|
1612
|
-
shadowOpacity: themeStyles.isDarkTheme ? 0.3 : 0.05,
|
|
1613
|
-
shadowRadius: 4,
|
|
1614
|
-
elevation: 2,
|
|
1615
|
-
}
|
|
1616
|
-
]}>
|
|
1617
|
-
<TouchableOpacity
|
|
1618
|
-
style={[
|
|
1619
|
-
styles.viewModeButton,
|
|
1620
|
-
viewMode === 'all' && {
|
|
1621
|
-
backgroundColor: themeStyles.primaryColor,
|
|
1622
|
-
shadowColor: themeStyles.primaryColor,
|
|
1623
|
-
shadowOffset: {
|
|
1624
|
-
width: 0,
|
|
1625
|
-
height: 2,
|
|
1626
|
-
},
|
|
1627
|
-
shadowOpacity: 0.3,
|
|
1628
|
-
shadowRadius: 4,
|
|
1629
|
-
elevation: 3,
|
|
1630
|
-
}
|
|
1631
|
-
]}
|
|
1632
|
-
onPress={() => setViewMode('all')}
|
|
1633
|
-
>
|
|
1634
|
-
<Ionicons
|
|
1635
|
-
name="folder"
|
|
1636
|
-
size={18}
|
|
1637
|
-
color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
|
|
1638
|
-
/>
|
|
1639
|
-
</TouchableOpacity>
|
|
1640
|
-
<TouchableOpacity
|
|
1641
|
-
style={[
|
|
1642
|
-
styles.viewModeButton,
|
|
1643
|
-
viewMode === 'photos' && {
|
|
1644
|
-
backgroundColor: themeStyles.primaryColor,
|
|
1645
|
-
shadowColor: themeStyles.primaryColor,
|
|
1646
|
-
shadowOffset: {
|
|
1647
|
-
width: 0,
|
|
1648
|
-
height: 2,
|
|
1649
|
-
},
|
|
1650
|
-
shadowOpacity: 0.3,
|
|
1651
|
-
shadowRadius: 4,
|
|
1652
|
-
elevation: 3,
|
|
1653
|
-
}
|
|
1654
|
-
]}
|
|
1655
|
-
onPress={() => setViewMode('photos')}
|
|
1656
|
-
>
|
|
1657
|
-
<Ionicons
|
|
1658
|
-
name="images"
|
|
1659
|
-
size={18}
|
|
1660
|
-
color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
|
|
1661
|
-
/>
|
|
1662
|
-
</TouchableOpacity>
|
|
1663
|
-
</View>
|
|
1664
|
-
|
|
1665
|
-
{user?.id === targetUserId && (
|
|
1666
|
-
<TouchableOpacity
|
|
1667
|
-
style={[
|
|
1668
|
-
styles.uploadButton,
|
|
1669
|
-
{
|
|
1670
|
-
backgroundColor: themeStyles.primaryColor,
|
|
1671
|
-
shadowColor: themeStyles.primaryColor,
|
|
1672
|
-
shadowOffset: {
|
|
1673
|
-
width: 0,
|
|
1674
|
-
height: 3,
|
|
1675
|
-
},
|
|
1676
|
-
shadowOpacity: 0.4,
|
|
1677
|
-
shadowRadius: 6,
|
|
1678
|
-
elevation: 5,
|
|
1679
|
-
transform: uploading ? [{ scale: 0.95 }] : [{ scale: 1 }],
|
|
1680
|
-
}
|
|
1681
|
-
]}
|
|
1682
|
-
onPress={handleFileUpload}
|
|
1683
|
-
disabled={uploading}
|
|
1684
|
-
>
|
|
1685
|
-
{uploading ? (
|
|
1686
|
-
<View style={styles.uploadProgress}>
|
|
1687
|
-
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1688
|
-
{uploadProgress && (
|
|
1689
|
-
<Text style={styles.uploadProgressText}>
|
|
1690
|
-
{uploadProgress.current}/{uploadProgress.total}
|
|
1691
|
-
</Text>
|
|
1692
|
-
)}
|
|
1693
|
-
</View>
|
|
1694
|
-
) : (
|
|
1695
|
-
<Ionicons name="add" size={26} color="#FFFFFF" />
|
|
1696
|
-
)}
|
|
1697
|
-
</TouchableOpacity>
|
|
1698
|
-
)}
|
|
1666
|
+
<Header
|
|
1667
|
+
title={viewMode === 'photos' ? 'Photos' : 'File Management'}
|
|
1668
|
+
subtitle={`${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
|
|
1669
|
+
onBack={onClose || goBack}
|
|
1670
|
+
theme={theme}
|
|
1671
|
+
showBackButton
|
|
1672
|
+
variant="minimal"
|
|
1673
|
+
elevation="none"
|
|
1674
|
+
titleAlignment="left"
|
|
1675
|
+
/>
|
|
1676
|
+
|
|
1677
|
+
<View style={styles.controlsBar}>
|
|
1678
|
+
<View style={[
|
|
1679
|
+
styles.viewModeToggle,
|
|
1680
|
+
{
|
|
1681
|
+
backgroundColor: themeStyles.isDarkTheme ? '#181818' : '#FFFFFF',
|
|
1682
|
+
borderWidth: 1,
|
|
1683
|
+
borderColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#E8E9EA',
|
|
1684
|
+
}
|
|
1685
|
+
]}>
|
|
1686
|
+
<TouchableOpacity
|
|
1687
|
+
style={[
|
|
1688
|
+
styles.viewModeButton,
|
|
1689
|
+
viewMode === 'all' && { backgroundColor: themeStyles.primaryColor }
|
|
1690
|
+
]}
|
|
1691
|
+
onPress={() => setViewMode('all')}
|
|
1692
|
+
>
|
|
1693
|
+
<Ionicons
|
|
1694
|
+
name="folder"
|
|
1695
|
+
size={18}
|
|
1696
|
+
color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
|
|
1697
|
+
/>
|
|
1698
|
+
</TouchableOpacity>
|
|
1699
|
+
<TouchableOpacity
|
|
1700
|
+
style={[
|
|
1701
|
+
styles.viewModeButton,
|
|
1702
|
+
viewMode === 'photos' && { backgroundColor: themeStyles.primaryColor }
|
|
1703
|
+
]}
|
|
1704
|
+
onPress={() => setViewMode('photos')}
|
|
1705
|
+
>
|
|
1706
|
+
<Ionicons
|
|
1707
|
+
name="images"
|
|
1708
|
+
size={18}
|
|
1709
|
+
color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
|
|
1710
|
+
/>
|
|
1711
|
+
</TouchableOpacity>
|
|
1699
1712
|
</View>
|
|
1713
|
+
{user?.id === targetUserId && (
|
|
1714
|
+
<TouchableOpacity
|
|
1715
|
+
style={[styles.uploadButton, { backgroundColor: themeStyles.primaryColor }]}
|
|
1716
|
+
onPress={handleFileUpload}
|
|
1717
|
+
disabled={uploading}
|
|
1718
|
+
>
|
|
1719
|
+
{uploading ? (
|
|
1720
|
+
<View style={styles.uploadProgress}>
|
|
1721
|
+
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1722
|
+
{uploadProgress && (
|
|
1723
|
+
<Text style={styles.uploadProgressText}>
|
|
1724
|
+
{uploadProgress.current}/{uploadProgress.total}
|
|
1725
|
+
</Text>
|
|
1726
|
+
)}
|
|
1727
|
+
</View>
|
|
1728
|
+
) : (
|
|
1729
|
+
<Ionicons name="add" size={22} color="#FFFFFF" />
|
|
1730
|
+
)}
|
|
1731
|
+
</TouchableOpacity>
|
|
1732
|
+
)}
|
|
1700
1733
|
</View>
|
|
1701
1734
|
|
|
1702
1735
|
{/* Search Bar */}
|
|
@@ -1706,14 +1739,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1706
1739
|
{
|
|
1707
1740
|
backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
|
|
1708
1741
|
borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
|
|
1709
|
-
shadowColor: '#000000',
|
|
1710
|
-
shadowOffset: {
|
|
1711
|
-
width: 0,
|
|
1712
|
-
height: 1,
|
|
1713
|
-
},
|
|
1714
|
-
shadowOpacity: themeStyles.isDarkTheme ? 0.2 : 0.05,
|
|
1715
|
-
shadowRadius: 4,
|
|
1716
|
-
elevation: 2,
|
|
1717
1742
|
}
|
|
1718
1743
|
]}>
|
|
1719
1744
|
<Ionicons name="search" size={22} color={themeStyles.isDarkTheme ? '#888888' : '#666666'} />
|
|
@@ -1742,14 +1767,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1742
1767
|
{
|
|
1743
1768
|
backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
|
|
1744
1769
|
borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
|
|
1745
|
-
shadowColor: '#000000',
|
|
1746
|
-
shadowOffset: {
|
|
1747
|
-
width: 0,
|
|
1748
|
-
height: 1,
|
|
1749
|
-
},
|
|
1750
|
-
shadowOpacity: themeStyles.isDarkTheme ? 0.2 : 0.05,
|
|
1751
|
-
shadowRadius: 4,
|
|
1752
|
-
elevation: 2,
|
|
1753
1770
|
}
|
|
1754
1771
|
]}>
|
|
1755
1772
|
<View style={styles.statItem}>
|
|
@@ -1787,7 +1804,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1787
1804
|
refreshControl={
|
|
1788
1805
|
<RefreshControl
|
|
1789
1806
|
refreshing={refreshing}
|
|
1790
|
-
onRefresh={() => loadFiles(
|
|
1807
|
+
onRefresh={() => loadFiles('refresh')}
|
|
1791
1808
|
tintColor={themeStyles.primaryColor}
|
|
1792
1809
|
/>
|
|
1793
1810
|
}
|
|
@@ -1808,15 +1825,28 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1808
1825
|
</TouchableOpacity>
|
|
1809
1826
|
</View>
|
|
1810
1827
|
) : filteredFiles.length === 0 ? renderEmptyState() : (
|
|
1811
|
-
|
|
1812
|
-
{filteredFiles.map(renderFileItem)}
|
|
1813
|
-
</>
|
|
1828
|
+
<GroupedSection items={groupedFileItems} theme={theme as 'light' | 'dark'} />
|
|
1814
1829
|
)}
|
|
1815
1830
|
</ScrollView>
|
|
1816
1831
|
)}
|
|
1817
1832
|
|
|
1818
1833
|
{renderFileDetailsModal()}
|
|
1819
1834
|
|
|
1835
|
+
{/* Uploading banner overlay */}
|
|
1836
|
+
{uploading && (
|
|
1837
|
+
<View pointerEvents="none" style={styles.uploadBannerContainer}>
|
|
1838
|
+
<View style={[styles.uploadBanner, { backgroundColor: themeStyles.isDarkTheme ? '#222831EE' : '#FFFFFFEE', borderColor: themeStyles.borderColor }]}>
|
|
1839
|
+
<Ionicons name="cloud-upload" size={18} color={themeStyles.primaryColor} />
|
|
1840
|
+
<Text style={[styles.uploadBannerText, { color: themeStyles.textColor }]}>Uploading{uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...'}</Text>
|
|
1841
|
+
<View style={styles.uploadBannerDots}>
|
|
1842
|
+
{[0, 1, 2].map(i => (
|
|
1843
|
+
<View key={i} style={[styles.dot, { opacity: ((Date.now() / 400 + i) % 3) < 1 ? 1 : 0.25 }]} />
|
|
1844
|
+
))}
|
|
1845
|
+
</View>
|
|
1846
|
+
</View>
|
|
1847
|
+
</View>
|
|
1848
|
+
)}
|
|
1849
|
+
|
|
1820
1850
|
{/* Drag and Drop Overlay */}
|
|
1821
1851
|
{isDragging && Platform.OS === 'web' && (
|
|
1822
1852
|
<View style={styles.dragDropOverlay}>
|
|
@@ -1840,9 +1870,9 @@ const styles = StyleSheet.create({
|
|
|
1840
1870
|
flex: 1,
|
|
1841
1871
|
},
|
|
1842
1872
|
dragOverlay: {
|
|
1843
|
-
backgroundColor: 'rgba(0, 122, 255, 0.
|
|
1844
|
-
borderWidth:
|
|
1845
|
-
borderColor: '#
|
|
1873
|
+
backgroundColor: 'rgba(0, 122, 255, 0.06)',
|
|
1874
|
+
borderWidth: 1,
|
|
1875
|
+
borderColor: '#66AFFF',
|
|
1846
1876
|
borderStyle: 'dashed',
|
|
1847
1877
|
},
|
|
1848
1878
|
centerContent: {
|
|
@@ -1853,8 +1883,8 @@ const styles = StyleSheet.create({
|
|
|
1853
1883
|
flexDirection: 'row',
|
|
1854
1884
|
alignItems: 'center',
|
|
1855
1885
|
justifyContent: 'space-between',
|
|
1856
|
-
paddingHorizontal:
|
|
1857
|
-
paddingVertical:
|
|
1886
|
+
paddingHorizontal: 16,
|
|
1887
|
+
paddingVertical: 12,
|
|
1858
1888
|
borderBottomWidth: 1,
|
|
1859
1889
|
position: 'relative',
|
|
1860
1890
|
},
|
|
@@ -1903,16 +1933,53 @@ const styles = StyleSheet.create({
|
|
|
1903
1933
|
fontWeight: '600',
|
|
1904
1934
|
marginTop: 2,
|
|
1905
1935
|
},
|
|
1936
|
+
uploadBannerContainer: {
|
|
1937
|
+
position: 'absolute',
|
|
1938
|
+
top: 72, // below header
|
|
1939
|
+
left: 0,
|
|
1940
|
+
right: 0,
|
|
1941
|
+
alignItems: 'center',
|
|
1942
|
+
zIndex: 50,
|
|
1943
|
+
},
|
|
1944
|
+
uploadBanner: {
|
|
1945
|
+
flexDirection: 'row',
|
|
1946
|
+
alignItems: 'center',
|
|
1947
|
+
paddingHorizontal: 14,
|
|
1948
|
+
paddingVertical: 8,
|
|
1949
|
+
borderRadius: 24,
|
|
1950
|
+
gap: 8,
|
|
1951
|
+
borderWidth: 1,
|
|
1952
|
+
shadowColor: '#000',
|
|
1953
|
+
shadowOpacity: 0.1,
|
|
1954
|
+
shadowRadius: 6,
|
|
1955
|
+
shadowOffset: { width: 0, height: 2 },
|
|
1956
|
+
elevation: 2,
|
|
1957
|
+
},
|
|
1958
|
+
uploadBannerText: {
|
|
1959
|
+
fontSize: 13,
|
|
1960
|
+
fontWeight: '500',
|
|
1961
|
+
},
|
|
1962
|
+
uploadBannerDots: {
|
|
1963
|
+
flexDirection: 'row',
|
|
1964
|
+
gap: 4,
|
|
1965
|
+
marginLeft: 2,
|
|
1966
|
+
},
|
|
1967
|
+
dot: {
|
|
1968
|
+
width: 6,
|
|
1969
|
+
height: 6,
|
|
1970
|
+
borderRadius: 3,
|
|
1971
|
+
backgroundColor: '#007AFF',
|
|
1972
|
+
},
|
|
1906
1973
|
searchContainer: {
|
|
1907
1974
|
flexDirection: 'row',
|
|
1908
1975
|
alignItems: 'center',
|
|
1909
|
-
paddingHorizontal:
|
|
1910
|
-
paddingVertical:
|
|
1911
|
-
marginHorizontal:
|
|
1912
|
-
marginTop:
|
|
1913
|
-
borderRadius:
|
|
1976
|
+
paddingHorizontal: 14,
|
|
1977
|
+
paddingVertical: 10,
|
|
1978
|
+
marginHorizontal: 16,
|
|
1979
|
+
marginTop: 12,
|
|
1980
|
+
borderRadius: 10,
|
|
1914
1981
|
borderWidth: 1,
|
|
1915
|
-
gap:
|
|
1982
|
+
gap: 10,
|
|
1916
1983
|
},
|
|
1917
1984
|
searchInput: {
|
|
1918
1985
|
flex: 1,
|
|
@@ -1931,11 +1998,11 @@ const styles = StyleSheet.create({
|
|
|
1931
1998
|
},
|
|
1932
1999
|
statsContainer: {
|
|
1933
2000
|
flexDirection: 'row',
|
|
1934
|
-
paddingHorizontal:
|
|
1935
|
-
paddingVertical:
|
|
1936
|
-
marginHorizontal:
|
|
1937
|
-
marginTop:
|
|
1938
|
-
borderRadius:
|
|
2001
|
+
paddingHorizontal: 14,
|
|
2002
|
+
paddingVertical: 10,
|
|
2003
|
+
marginHorizontal: 16,
|
|
2004
|
+
marginTop: 12,
|
|
2005
|
+
borderRadius: 10,
|
|
1939
2006
|
borderWidth: 1,
|
|
1940
2007
|
},
|
|
1941
2008
|
statItem: {
|
|
@@ -1944,31 +2011,31 @@ const styles = StyleSheet.create({
|
|
|
1944
2011
|
paddingVertical: 4,
|
|
1945
2012
|
},
|
|
1946
2013
|
statValue: {
|
|
1947
|
-
fontSize:
|
|
2014
|
+
fontSize: 20,
|
|
1948
2015
|
fontWeight: '800',
|
|
1949
2016
|
fontFamily: fontFamilies.phuduBold,
|
|
1950
2017
|
letterSpacing: -0.5,
|
|
1951
|
-
lineHeight:
|
|
2018
|
+
lineHeight: 24,
|
|
1952
2019
|
},
|
|
1953
2020
|
statLabel: {
|
|
1954
|
-
fontSize:
|
|
2021
|
+
fontSize: 12,
|
|
1955
2022
|
fontWeight: '500',
|
|
1956
2023
|
fontFamily: fontFamilies.phuduMedium,
|
|
1957
|
-
marginTop:
|
|
1958
|
-
letterSpacing: 0.
|
|
2024
|
+
marginTop: 2,
|
|
2025
|
+
letterSpacing: 0.2,
|
|
1959
2026
|
},
|
|
1960
2027
|
scrollView: {
|
|
1961
2028
|
flex: 1,
|
|
1962
2029
|
},
|
|
1963
2030
|
scrollContainer: {
|
|
1964
|
-
padding:
|
|
2031
|
+
padding: 12,
|
|
1965
2032
|
},
|
|
1966
2033
|
fileItem: {
|
|
1967
2034
|
flexDirection: 'row',
|
|
1968
2035
|
alignItems: 'center',
|
|
1969
|
-
padding:
|
|
1970
|
-
marginBottom:
|
|
1971
|
-
borderRadius:
|
|
2036
|
+
padding: 10,
|
|
2037
|
+
marginBottom: 8,
|
|
2038
|
+
borderRadius: 10,
|
|
1972
2039
|
borderWidth: 1,
|
|
1973
2040
|
},
|
|
1974
2041
|
fileContent: {
|
|
@@ -1984,15 +2051,15 @@ const styles = StyleSheet.create({
|
|
|
1984
2051
|
marginRight: 12,
|
|
1985
2052
|
},
|
|
1986
2053
|
filePreviewContainer: {
|
|
1987
|
-
width:
|
|
1988
|
-
height:
|
|
1989
|
-
marginRight:
|
|
2054
|
+
width: 52,
|
|
2055
|
+
height: 52,
|
|
2056
|
+
marginRight: 10,
|
|
1990
2057
|
},
|
|
1991
2058
|
filePreview: {
|
|
1992
2059
|
width: '100%',
|
|
1993
2060
|
height: '100%',
|
|
1994
2061
|
borderRadius: 8,
|
|
1995
|
-
backgroundColor: '
|
|
2062
|
+
backgroundColor: 'transparent',
|
|
1996
2063
|
alignItems: 'center',
|
|
1997
2064
|
justifyContent: 'center',
|
|
1998
2065
|
overflow: 'hidden',
|
|
@@ -2035,7 +2102,7 @@ const styles = StyleSheet.create({
|
|
|
2035
2102
|
bottom: 0,
|
|
2036
2103
|
alignItems: 'center',
|
|
2037
2104
|
justifyContent: 'center',
|
|
2038
|
-
backgroundColor: '
|
|
2105
|
+
backgroundColor: 'transparent',
|
|
2039
2106
|
borderRadius: 8,
|
|
2040
2107
|
},
|
|
2041
2108
|
previewOverlay: {
|
|
@@ -2044,10 +2111,56 @@ const styles = StyleSheet.create({
|
|
|
2044
2111
|
left: 0,
|
|
2045
2112
|
right: 0,
|
|
2046
2113
|
bottom: 0,
|
|
2047
|
-
backgroundColor: 'rgba(0, 0, 0, 0.
|
|
2114
|
+
backgroundColor: 'rgba(0, 0, 0, 0.25)',
|
|
2115
|
+
alignItems: 'center',
|
|
2116
|
+
justifyContent: 'center',
|
|
2117
|
+
borderRadius: 8,
|
|
2118
|
+
},
|
|
2119
|
+
groupedActions: {
|
|
2120
|
+
flexDirection: 'row',
|
|
2121
|
+
alignItems: 'center',
|
|
2122
|
+
gap: 6,
|
|
2123
|
+
marginLeft: 12,
|
|
2124
|
+
},
|
|
2125
|
+
groupedActionBtn: {
|
|
2126
|
+
width: 34,
|
|
2127
|
+
height: 34,
|
|
2128
|
+
borderRadius: 8,
|
|
2048
2129
|
alignItems: 'center',
|
|
2049
2130
|
justifyContent: 'center',
|
|
2131
|
+
},
|
|
2132
|
+
groupedDescription: {
|
|
2133
|
+
fontSize: 12,
|
|
2134
|
+
lineHeight: 16,
|
|
2135
|
+
marginTop: 6,
|
|
2136
|
+
},
|
|
2137
|
+
videoPreviewWrapper: {
|
|
2138
|
+
width: '100%',
|
|
2139
|
+
height: '100%',
|
|
2050
2140
|
borderRadius: 8,
|
|
2141
|
+
overflow: 'hidden',
|
|
2142
|
+
backgroundColor: '#000000',
|
|
2143
|
+
alignItems: 'center',
|
|
2144
|
+
justifyContent: 'center',
|
|
2145
|
+
},
|
|
2146
|
+
videoPosterImage: {
|
|
2147
|
+
position: 'absolute',
|
|
2148
|
+
top: 0,
|
|
2149
|
+
left: 0,
|
|
2150
|
+
right: 0,
|
|
2151
|
+
bottom: 0,
|
|
2152
|
+
width: '100%',
|
|
2153
|
+
height: '100%',
|
|
2154
|
+
},
|
|
2155
|
+
videoOverlay: {
|
|
2156
|
+
position: 'absolute',
|
|
2157
|
+
top: 0,
|
|
2158
|
+
left: 0,
|
|
2159
|
+
right: 0,
|
|
2160
|
+
bottom: 0,
|
|
2161
|
+
alignItems: 'center',
|
|
2162
|
+
justifyContent: 'center',
|
|
2163
|
+
backgroundColor: 'rgba(0,0,0,0.25)',
|
|
2051
2164
|
},
|
|
2052
2165
|
fileInfo: {
|
|
2053
2166
|
flex: 1,
|
|
@@ -2070,16 +2183,19 @@ const styles = StyleSheet.create({
|
|
|
2070
2183
|
gap: 8,
|
|
2071
2184
|
},
|
|
2072
2185
|
actionButton: {
|
|
2073
|
-
width:
|
|
2074
|
-
height:
|
|
2075
|
-
borderRadius:
|
|
2186
|
+
width: 36,
|
|
2187
|
+
height: 36,
|
|
2188
|
+
borderRadius: 18,
|
|
2076
2189
|
alignItems: 'center',
|
|
2077
2190
|
justifyContent: 'center',
|
|
2191
|
+
borderWidth: 1,
|
|
2192
|
+
borderColor: '#E0E0E0',
|
|
2193
|
+
backgroundColor: 'transparent',
|
|
2078
2194
|
},
|
|
2079
2195
|
emptyState: {
|
|
2080
2196
|
alignItems: 'center',
|
|
2081
|
-
paddingVertical:
|
|
2082
|
-
paddingHorizontal:
|
|
2197
|
+
paddingVertical: 40,
|
|
2198
|
+
paddingHorizontal: 24,
|
|
2083
2199
|
},
|
|
2084
2200
|
emptyStateTitle: {
|
|
2085
2201
|
fontSize: 24,
|
|
@@ -2120,8 +2236,8 @@ const styles = StyleSheet.create({
|
|
|
2120
2236
|
flexDirection: 'row',
|
|
2121
2237
|
alignItems: 'center',
|
|
2122
2238
|
justifyContent: 'space-between',
|
|
2123
|
-
paddingHorizontal:
|
|
2124
|
-
paddingVertical:
|
|
2239
|
+
paddingHorizontal: 16,
|
|
2240
|
+
paddingVertical: 12,
|
|
2125
2241
|
borderBottomWidth: 1,
|
|
2126
2242
|
},
|
|
2127
2243
|
modalCloseButton: {
|
|
@@ -2137,11 +2253,11 @@ const styles = StyleSheet.create({
|
|
|
2137
2253
|
},
|
|
2138
2254
|
modalContent: {
|
|
2139
2255
|
flex: 1,
|
|
2140
|
-
padding:
|
|
2256
|
+
padding: 16,
|
|
2141
2257
|
},
|
|
2142
2258
|
fileDetailCard: {
|
|
2143
|
-
padding:
|
|
2144
|
-
borderRadius:
|
|
2259
|
+
padding: 18,
|
|
2260
|
+
borderRadius: 14,
|
|
2145
2261
|
borderWidth: 1,
|
|
2146
2262
|
alignItems: 'center',
|
|
2147
2263
|
},
|
|
@@ -2204,25 +2320,25 @@ const styles = StyleSheet.create({
|
|
|
2204
2320
|
left: 0,
|
|
2205
2321
|
right: 0,
|
|
2206
2322
|
bottom: 0,
|
|
2207
|
-
backgroundColor: 'rgba(0, 0, 0, 0.
|
|
2323
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2208
2324
|
justifyContent: 'center',
|
|
2209
2325
|
alignItems: 'center',
|
|
2210
2326
|
zIndex: 1000,
|
|
2211
2327
|
},
|
|
2212
2328
|
dragDropContent: {
|
|
2213
2329
|
alignItems: 'center',
|
|
2214
|
-
backgroundColor: 'rgba(255, 255, 255, 0.
|
|
2215
|
-
padding:
|
|
2216
|
-
borderRadius:
|
|
2217
|
-
borderWidth:
|
|
2218
|
-
borderColor: '#
|
|
2330
|
+
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
2331
|
+
padding: 20,
|
|
2332
|
+
borderRadius: 14,
|
|
2333
|
+
borderWidth: 1,
|
|
2334
|
+
borderColor: '#66AFFF',
|
|
2219
2335
|
borderStyle: 'dashed',
|
|
2220
2336
|
},
|
|
2221
2337
|
dragDropTitle: {
|
|
2222
|
-
fontSize:
|
|
2338
|
+
fontSize: 20,
|
|
2223
2339
|
fontWeight: 'bold',
|
|
2224
|
-
marginTop:
|
|
2225
|
-
marginBottom:
|
|
2340
|
+
marginTop: 12,
|
|
2341
|
+
marginBottom: 6,
|
|
2226
2342
|
},
|
|
2227
2343
|
dragDropSubtitle: {
|
|
2228
2344
|
fontSize: 16,
|
|
@@ -2236,8 +2352,8 @@ const styles = StyleSheet.create({
|
|
|
2236
2352
|
fileViewerHeader: {
|
|
2237
2353
|
flexDirection: 'row',
|
|
2238
2354
|
alignItems: 'center',
|
|
2239
|
-
paddingHorizontal:
|
|
2240
|
-
paddingVertical:
|
|
2355
|
+
paddingHorizontal: 16,
|
|
2356
|
+
paddingVertical: 12,
|
|
2241
2357
|
borderBottomWidth: 1,
|
|
2242
2358
|
},
|
|
2243
2359
|
fileViewerTitleContainer: {
|
|
@@ -2265,7 +2381,7 @@ const styles = StyleSheet.create({
|
|
|
2265
2381
|
},
|
|
2266
2382
|
fileViewerContentContainer: {
|
|
2267
2383
|
flexGrow: 1,
|
|
2268
|
-
padding:
|
|
2384
|
+
padding: 14,
|
|
2269
2385
|
},
|
|
2270
2386
|
fileViewerLoading: {
|
|
2271
2387
|
flex: 1,
|
|
@@ -2283,10 +2399,10 @@ const styles = StyleSheet.create({
|
|
|
2283
2399
|
},
|
|
2284
2400
|
textContainer: {
|
|
2285
2401
|
flex: 1,
|
|
2286
|
-
borderRadius:
|
|
2402
|
+
borderRadius: 10,
|
|
2287
2403
|
borderWidth: 1,
|
|
2288
|
-
padding:
|
|
2289
|
-
minHeight:
|
|
2404
|
+
padding: 12,
|
|
2405
|
+
minHeight: 180,
|
|
2290
2406
|
maxHeight: '80%',
|
|
2291
2407
|
},
|
|
2292
2408
|
textContent: {
|
|
@@ -2298,8 +2414,8 @@ const styles = StyleSheet.create({
|
|
|
2298
2414
|
flex: 1,
|
|
2299
2415
|
justifyContent: 'center',
|
|
2300
2416
|
alignItems: 'center',
|
|
2301
|
-
paddingVertical:
|
|
2302
|
-
paddingHorizontal:
|
|
2417
|
+
paddingVertical: 40,
|
|
2418
|
+
paddingHorizontal: 24,
|
|
2303
2419
|
},
|
|
2304
2420
|
unsupportedFileTitle: {
|
|
2305
2421
|
fontSize: 24,
|
|
@@ -2318,9 +2434,9 @@ const styles = StyleSheet.create({
|
|
|
2318
2434
|
downloadButtonLarge: {
|
|
2319
2435
|
flexDirection: 'row',
|
|
2320
2436
|
alignItems: 'center',
|
|
2321
|
-
paddingHorizontal:
|
|
2322
|
-
paddingVertical:
|
|
2323
|
-
borderRadius:
|
|
2437
|
+
paddingHorizontal: 18,
|
|
2438
|
+
paddingVertical: 12,
|
|
2439
|
+
borderRadius: 20,
|
|
2324
2440
|
gap: 8,
|
|
2325
2441
|
},
|
|
2326
2442
|
downloadButtonText: {
|
|
@@ -2347,10 +2463,10 @@ const styles = StyleSheet.create({
|
|
|
2347
2463
|
|
|
2348
2464
|
// File Details in Viewer styles
|
|
2349
2465
|
fileDetailsSection: {
|
|
2350
|
-
margin:
|
|
2466
|
+
margin: 12,
|
|
2351
2467
|
marginTop: 0,
|
|
2352
|
-
padding:
|
|
2353
|
-
borderRadius:
|
|
2468
|
+
padding: 14,
|
|
2469
|
+
borderRadius: 10,
|
|
2354
2470
|
borderWidth: 1,
|
|
2355
2471
|
},
|
|
2356
2472
|
fileDetailsSectionTitle: {
|
|
@@ -2394,6 +2510,15 @@ const styles = StyleSheet.create({
|
|
|
2394
2510
|
alignItems: 'center',
|
|
2395
2511
|
gap: 16,
|
|
2396
2512
|
},
|
|
2513
|
+
controlsBar: {
|
|
2514
|
+
flexDirection: 'row',
|
|
2515
|
+
alignItems: 'center',
|
|
2516
|
+
justifyContent: 'space-between',
|
|
2517
|
+
paddingHorizontal: 16,
|
|
2518
|
+
paddingTop: 8,
|
|
2519
|
+
paddingBottom: 4,
|
|
2520
|
+
gap: 12,
|
|
2521
|
+
},
|
|
2397
2522
|
viewModeToggle: {
|
|
2398
2523
|
flexDirection: 'row',
|
|
2399
2524
|
borderRadius: 24,
|
|
@@ -2412,17 +2537,17 @@ const styles = StyleSheet.create({
|
|
|
2412
2537
|
|
|
2413
2538
|
// Photo Grid styles
|
|
2414
2539
|
photoScrollContainer: {
|
|
2415
|
-
padding:
|
|
2540
|
+
padding: 10,
|
|
2416
2541
|
},
|
|
2417
2542
|
photoDateSection: {
|
|
2418
|
-
marginBottom:
|
|
2543
|
+
marginBottom: 16,
|
|
2419
2544
|
},
|
|
2420
2545
|
photoDateHeader: {
|
|
2421
|
-
fontSize:
|
|
2546
|
+
fontSize: 16,
|
|
2422
2547
|
fontWeight: '600',
|
|
2423
2548
|
fontFamily: fontFamilies.phuduSemiBold,
|
|
2424
|
-
marginBottom:
|
|
2425
|
-
paddingHorizontal:
|
|
2549
|
+
marginBottom: 8,
|
|
2550
|
+
paddingHorizontal: 2,
|
|
2426
2551
|
},
|
|
2427
2552
|
photoGrid: {
|
|
2428
2553
|
flexDirection: 'row',
|
|
@@ -2475,7 +2600,7 @@ const styles = StyleSheet.create({
|
|
|
2475
2600
|
position: 'relative',
|
|
2476
2601
|
borderRadius: 6,
|
|
2477
2602
|
overflow: 'hidden',
|
|
2478
|
-
backgroundColor: '
|
|
2603
|
+
backgroundColor: 'transparent',
|
|
2479
2604
|
},
|
|
2480
2605
|
justifiedPhotoImage: {
|
|
2481
2606
|
width: '100%',
|
|
@@ -2487,7 +2612,7 @@ const styles = StyleSheet.create({
|
|
|
2487
2612
|
simplePhotoItem: {
|
|
2488
2613
|
borderRadius: 8,
|
|
2489
2614
|
overflow: 'hidden',
|
|
2490
|
-
backgroundColor: '
|
|
2615
|
+
backgroundColor: 'transparent',
|
|
2491
2616
|
},
|
|
2492
2617
|
simplePhotoContainer: {
|
|
2493
2618
|
width: '100%',
|