@mediacubeco/expo-template-fsd 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/git-commit.md +164 -0
- package/.claude/rules/architecture.md +38 -0
- package/.claude/rules/{prettier-eslint-typescript.mdc → prettier-eslint-typescript.md} +5 -10
- package/.claude/settings.json +2 -2
- package/.claude/skills/api/SKILL.md +362 -0
- package/.claude/skills/api/references/client/overview.md +106 -0
- package/.claude/skills/api/references/hooks/index.ts +3 -0
- package/.claude/skills/api/references/hooks/use-api-health.ts +25 -0
- package/.claude/skills/api/references/hooks/use-api-post.ts +46 -0
- package/.claude/skills/api/references/hooks/use-api-posts.ts +24 -0
- package/.claude/skills/api/references/mock/post.mock.ts +29 -0
- package/.claude/skills/{generate-api-model/references → api/references/model}/entities/common.ts +5 -2
- package/.claude/skills/api/references/model/resources/common.ts +9 -0
- package/.claude/skills/api/references/queries/health.ts +14 -0
- package/.claude/skills/api/references/queries/index.ts +2 -0
- package/.claude/skills/api/references/queries/post.ts +23 -0
- package/.claude/skills/api/references/repository/health/health.ts +11 -0
- package/.claude/skills/api/references/repository/health/health.types.ts +25 -0
- package/.claude/skills/api/references/repository/health/index.ts +2 -0
- package/.claude/skills/api/references/repository/post/index.ts +9 -0
- package/.claude/skills/api/references/repository/post/post.ts +34 -0
- package/.claude/skills/api/references/repository/post/post.types.ts +61 -0
- package/.claude/skills/code-style/SKILL.md +1025 -1
- package/.claude/skills/code-style/references/components/payout-speed-option/index.ts +2 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.styles.ts +76 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.tsx +58 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.types.ts +22 -0
- package/.claude/skills/code-style/references/components/post/index.ts +2 -0
- package/.claude/skills/code-style/references/components/post/post.styles.ts +19 -0
- package/.claude/skills/code-style/references/components/post/post.tsx +21 -0
- package/.claude/skills/code-style/references/components/post/post.types.ts +9 -0
- package/.claude/skills/code-style/references/components/wrap/index.ts +2 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.styles.ts +15 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.tsx +31 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.types.ts +10 -0
- package/.claude/skills/code-style/references/hooks/use-posts.ts +37 -0
- package/.claude/skills/code-style/references/pages/post/index.ts +4 -0
- package/.claude/skills/code-style/references/pages/post/post-details/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-details/post-details.tsx +13 -0
- package/.claude/skills/code-style/references/pages/post/post-guide/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-guide/post-guide.tsx +17 -0
- package/.claude/skills/code-style/references/pages/post/post-list/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-list/post-list.styles.ts +17 -0
- package/.claude/skills/code-style/references/pages/post/post-list/post-list.tsx +25 -0
- package/.claude/skills/code-style/references/pages/post/post-share/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-share/post-share.tsx +13 -0
- package/.claude/skills/code-style/references/routes/(stack)/_layout.tsx +18 -0
- package/.claude/skills/code-style/references/routes/(stack)/details.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/guide.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/share.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/(post-details)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/(post-list)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/_layout.tsx +50 -0
- package/.claude/skills/code-style/references/routes/_app.tsx +20 -0
- package/.claude/skills/code-style/references/routes/guide.tsx +8 -0
- package/.claude/skills/code-style/references/routes/share.tsx +8 -0
- package/.claude/skills/code-style/references/store/use-post-draft-store.ts +62 -0
- package/.claude/skills/design-style/SKILL.md +320 -0
- package/.env +3 -1
- package/.env.example +3 -1
- package/CLAUDE.md +5 -11
- package/app.json +6 -3
- package/axios.d.ts +5 -0
- package/env.d.ts +3 -1
- package/package.json +33 -40
- package/patches/react-native-screens+4.25.2.patch +118 -0
- package/react-query.d.ts +13 -0
- package/src/app/(root)/(home)/_layout.tsx +2 -2
- package/src/app/(root)/(home)/index.tsx +2 -2
- package/src/app/(root)/(settings)/_layout.tsx +2 -2
- package/src/app/(root)/(settings)/index.tsx +2 -2
- package/src/app/(root)/_layout.tsx +35 -32
- package/src/pages/home/home-main.tsx +3 -7
- package/src/pages/settings/settings-main.tsx +3 -7
- package/src/shared/assets/fonts/index.ts +3 -3
- package/src/shared/assets/fonts/sf-pro-text-bold.ttf +0 -0
- package/src/shared/assets/fonts/sf-pro-text-regular.ttf +0 -0
- package/src/shared/assets/fonts/sf-pro-text-semibold.ttf +0 -0
- package/src/shared/assets/icons/back.tsx +21 -0
- package/src/shared/assets/icons/close.tsx +21 -0
- package/src/shared/assets/icons/common.ts +8 -0
- package/src/shared/assets/icons/default.tsx +44 -0
- package/src/shared/assets/icons/index.ts +13 -1
- package/src/shared/assets/icons/refresh.tsx +23 -0
- package/src/shared/assets/icons/share.tsx +38 -0
- package/src/shared/assets/icons/type.ts +7 -0
- package/src/shared/assets/translations/index.ts +4 -4
- package/src/shared/constants/config.ts +13 -0
- package/src/shared/constants/environment.ts +16 -4
- package/src/shared/constants/index.ts +1 -0
- package/src/shared/hooks/index.ts +1 -4
- package/src/shared/hooks/use-compose.ts +23 -0
- package/src/shared/lib/api/api.client.ts +157 -0
- package/src/shared/lib/api/api.provider.tsx +14 -36
- package/src/shared/lib/api/api.ts +1 -5
- package/src/shared/lib/api/api.types.ts +6 -3
- package/src/shared/lib/api/hooks/index.ts +1 -0
- package/src/shared/lib/api/index.ts +2 -3
- package/src/shared/lib/api/models/api-instance/api-instance.client.ts +50 -0
- package/src/shared/lib/api/models/api-instance/api-instance.ts +30 -82
- package/src/shared/lib/api/models/api-instance/api-instance.types.ts +16 -18
- package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +13 -20
- package/src/shared/lib/api/models/api-instance/index.ts +3 -0
- package/src/shared/lib/api/queries/index.ts +1 -0
- package/src/shared/lib/api/repositories/index.ts +1 -1
- package/src/shared/lib/localization/constants/index.ts +16 -5
- package/src/shared/lib/localization/index.ts +5 -1
- package/src/shared/lib/navigation/hooks/index.ts +8 -3
- package/src/shared/{hooks/use-breakpoints.ts → lib/navigation/hooks/use-navigation-breakpoints.ts} +2 -5
- package/src/shared/lib/navigation/hooks/use-navigation-dimensions.ts +12 -0
- package/src/shared/lib/navigation/hooks/use-navigation-options.tsx +175 -0
- package/src/shared/lib/navigation/hooks/{use-navigation.ts → use-navigation-route.ts} +2 -2
- package/src/shared/lib/navigation/hooks/use-navigation-safe-area.ts +61 -0
- package/src/shared/lib/navigation/hooks/use-navigation-tabs-native.ts +10 -0
- package/src/shared/lib/navigation/hooks/use-navigation-tabs.tsx +10 -0
- package/src/shared/lib/navigation/hooks/use-navigation-transition.ts +27 -0
- package/src/shared/lib/navigation/index.ts +11 -4
- package/src/shared/lib/navigation/navigation-tabs-native.context.tsx +5 -0
- package/src/shared/lib/navigation/navigation-tabs-native.provider.tsx +17 -0
- package/src/shared/lib/navigation/navigation-tabs.context.tsx +10 -0
- package/src/shared/lib/navigation/navigation-tabs.provider.tsx +22 -0
- package/src/shared/lib/navigation/navigation.provider.tsx +6 -1
- package/src/shared/lib/storage/index.ts +1 -1
- package/src/shared/lib/storage/storage.provider.tsx +4 -0
- package/src/shared/lib/storage/storage.ts +29 -25
- package/src/shared/lib/theme/constants/animations.ts +26 -0
- package/src/shared/lib/theme/constants/colors.ts +336 -43
- package/src/shared/lib/theme/constants/delays.ts +1 -1
- package/src/shared/lib/theme/constants/durations.ts +1 -1
- package/src/shared/lib/theme/constants/fonts.ts +2 -1
- package/src/shared/lib/theme/constants/index.ts +42 -20
- package/src/shared/lib/theme/constants/shadows.ts +7 -25
- package/src/shared/lib/theme/constants/sizes.ts +11 -2
- package/src/shared/lib/theme/constants/transparencies.ts +1 -1
- package/src/shared/lib/theme/hooks/use-theme-setup.ts +3 -1
- package/src/shared/lib/theme/index.ts +10 -1
- package/src/shared/lib/theme/theme.provider.tsx +45 -19
- package/src/shared/lib/theme/theme.scope.tsx +43 -0
- package/src/shared/lib/theme/theme.types.ts +15 -3
- package/src/shared/lib/theme/utils/color.ts +81 -0
- package/src/shared/lib/theme/utils/index.ts +2 -0
- package/src/shared/lib/theme/utils/snapshot.ts +17 -0
- package/src/shared/lib/theme/utils/storage.ts +5 -2
- package/src/shared/services/browser/browser.ts +66 -0
- package/src/shared/services/browser/index.ts +1 -0
- package/src/shared/services/cookies/cookies.ts +73 -0
- package/src/shared/services/cookies/index.ts +1 -0
- package/src/shared/services/index.ts +5 -0
- package/src/shared/services/linking/index.ts +1 -0
- package/src/shared/services/linking/linking.ts +22 -0
- package/src/shared/services/share/index.ts +1 -0
- package/src/shared/services/share/share.ts +28 -0
- package/src/shared/services/universal-links/index.ts +104 -0
- package/src/shared/services/universal-links/universal-links.ts +25 -0
- package/src/shared/services/universal-links/universal-links.types.ts +5 -0
- package/src/shared/ui/atoms/blur/blur.tsx +9 -4
- package/src/shared/ui/atoms/blur/blur.types.ts +1 -1
- package/src/shared/ui/atoms/blur-progress/blur-progress.styles.ts +16 -0
- package/src/shared/ui/atoms/blur-progress/blur-progress.tsx +30 -0
- package/src/shared/ui/atoms/blur-progress/blur-progress.types.tsx +5 -0
- package/src/shared/ui/atoms/blur-progress/index.ts +1 -0
- package/src/shared/ui/atoms/glass/glass.tsx +13 -5
- package/src/shared/ui/atoms/glass/glass.types.ts +1 -1
- package/src/shared/ui/atoms/icon/icon.styles.ts +82 -0
- package/src/shared/ui/atoms/icon/icon.tsx +21 -0
- package/src/shared/ui/atoms/icon/icon.types.ts +23 -0
- package/src/shared/ui/atoms/icon/index.ts +2 -0
- package/src/shared/ui/atoms/index.ts +7 -0
- package/src/shared/ui/atoms/skeleton/index.ts +2 -0
- package/src/shared/ui/atoms/skeleton/skeleton.styles.ts +69 -0
- package/src/shared/ui/atoms/skeleton/skeleton.tsx +26 -0
- package/src/shared/ui/atoms/skeleton/skeleton.types.ts +9 -0
- package/src/shared/ui/atoms/spinner/index.ts +2 -0
- package/src/shared/ui/atoms/spinner/spinner.styles.ts +102 -0
- package/src/shared/ui/atoms/spinner/spinner.tsx +48 -0
- package/src/shared/ui/atoms/spinner/spinner.types.ts +25 -0
- package/src/shared/ui/atoms/text/index.ts +2 -0
- package/src/shared/ui/atoms/text/text.styles.ts +186 -0
- package/src/shared/ui/atoms/text/text.tsx +37 -0
- package/src/shared/ui/atoms/text/text.types.ts +35 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.styles.ts +21 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.tsx +28 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.types.ts +17 -0
- package/src/shared/ui/atoms/touch/components/haptics/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/index.ts +3 -0
- package/src/shared/ui/atoms/touch/components/ripple/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.styles.ts +78 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.tsx +29 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.types.ts +17 -0
- package/src/shared/ui/atoms/touch/components/spring/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.styles.ts +31 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.tsx +28 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.types.ts +17 -0
- package/src/shared/ui/atoms/touch/index.ts +2 -0
- package/src/shared/ui/atoms/touch/touch.styles.ts +14 -0
- package/src/shared/ui/atoms/touch/touch.tsx +137 -0
- package/src/shared/ui/atoms/touch/touch.types.ts +27 -0
- package/src/shared/ui/atoms/visibility/index.ts +2 -0
- package/src/shared/ui/atoms/visibility/visibility.styles.ts +85 -0
- package/src/shared/ui/atoms/visibility/visibility.tsx +61 -0
- package/src/shared/ui/atoms/visibility/visibility.types.ts +34 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +11 -9
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +3 -6
- package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +3 -5
- package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.styles.ts +41 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.tsx +12 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.types.ts +13 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/index.ts +2 -0
- package/src/shared/ui/molecules/slider/components/indicator/index.ts +2 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.styles.ts +20 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.tsx +22 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.types.ts +10 -0
- package/src/shared/ui/molecules/slider/index.ts +3 -0
- package/src/shared/ui/molecules/slider/slider.styles.ts +68 -0
- package/src/shared/ui/molecules/slider/slider.tsx +64 -0
- package/src/shared/ui/molecules/slider/slider.types.ts +28 -0
- package/src/shared/ui/organisms/bar/bar.styles.ts +100 -0
- package/src/shared/ui/organisms/bar/bar.tsx +89 -0
- package/src/shared/ui/organisms/bar/bar.types.ts +49 -0
- package/src/shared/ui/organisms/bar/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab/bar-tab.tsx +9 -0
- package/src/shared/ui/organisms/bar-tab/bar-tab.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab/components/index.ts +1 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.styles.ts +21 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.tsx +18 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.styles.ts +22 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.tsx +66 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/index.ts +1 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.styles.ts +56 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.tsx +30 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.types.ts +7 -0
- package/src/shared/ui/organisms/bar-tab-navigation/index.ts +2 -0
- package/src/shared/ui/organisms/index.ts +4 -1
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.styles.ts +41 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.tsx +12 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.types.ts +13 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/index.ts +2 -0
- package/src/shared/ui/organisms/slider/components/indicator/index.ts +2 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.styles.ts +20 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.tsx +22 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.types.ts +10 -0
- package/src/shared/ui/organisms/slider/index.ts +3 -0
- package/src/shared/ui/organisms/slider/slider.styles.ts +68 -0
- package/src/shared/ui/organisms/slider/slider.tsx +64 -0
- package/src/shared/ui/organisms/slider/slider.types.ts +28 -0
- package/src/shared/ui/templates/index.ts +3 -0
- package/src/shared/ui/templates/modal-container/index.ts +13 -0
- package/src/shared/ui/templates/modal-container/modal-container-list.tsx +45 -0
- package/src/shared/ui/templates/modal-container/modal-container-scroll.tsx +66 -0
- package/src/shared/ui/templates/modal-container/modal-container-section-list.tsx +46 -0
- package/src/shared/ui/templates/modal-container/modal-container.hooks.tsx +172 -0
- package/src/shared/ui/templates/modal-container/modal-container.styles.ts +121 -0
- package/src/shared/ui/templates/modal-container/modal-container.types.ts +69 -0
- package/src/shared/ui/templates/screen-container/index.ts +1 -1
- package/src/shared/ui/templates/screen-container/screen-container-list.tsx +28 -10
- package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +28 -10
- package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +29 -11
- package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +79 -8
- package/src/shared/ui/templates/screen-container/screen-container.styles.ts +105 -15
- package/src/shared/ui/templates/screen-container/screen-container.types.tsx +34 -7
- package/src/shared/ui/templates/screen-header/components/index.ts +6 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.tsx +15 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.types.ts +11 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.styles.ts +44 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.tsx +65 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.types.ts +14 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.tsx +17 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.types.ts +6 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.tsx +27 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.types.ts +10 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.tsx +15 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.types.ts +10 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.tsx +19 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.types.ts +13 -0
- package/src/shared/ui/templates/screen-header/index.ts +3 -0
- package/src/shared/ui/templates/screen-header/screen-header.styles.ts +49 -0
- package/src/shared/ui/templates/screen-header/screen-header.tsx +26 -0
- package/src/shared/ui/templates/screen-header/screen-header.types.ts +15 -0
- package/src/shared/ui/templates/web-container/index.ts +2 -0
- package/src/shared/ui/templates/web-container/web-container.styles.ts +34 -0
- package/src/shared/ui/templates/web-container/web-container.tsx +172 -0
- package/src/shared/ui/templates/web-container/web-container.types.ts +21 -0
- package/.claude/rules/app-routing.mdc +0 -47
- package/.claude/rules/exports-at-end.mdc +0 -55
- package/.claude/rules/kebab-case-naming.mdc +0 -29
- package/.claude/rules/mediacubeco-packages.mdc +0 -56
- package/.claude/skills/generate-api-model/SKILL.md +0 -230
- package/.claude/skills/generate-api-model/references/resources/common.ts +0 -8
- package/.claude/skills/generate-api-model-mock/SKILL.md +0 -56
- package/.claude/skills/git-commit/SKILL.md +0 -139
- package/.gitignore +0 -34
- package/src/shared/assets/icons/like.tsx +0 -17
- package/src/shared/hooks/use-animated.ts +0 -200
- package/src/shared/hooks/use-mixins.ts +0 -36
- package/src/shared/hooks/use-safe-area.ts +0 -75
- package/src/shared/lib/api/repositories/auth/auth.ts +0 -23
- package/src/shared/lib/api/repositories/auth/auth.types.ts +0 -22
- package/src/shared/lib/api/repositories/auth/index.ts +0 -1
- package/src/shared/lib/navigation/hooks/use-navigation-native-tabs.ts +0 -10
- package/src/shared/lib/navigation/hooks/use-navigation-options.ts +0 -123
- package/src/shared/lib/navigation/navigation-native-tabs.context.tsx +0 -5
- package/src/shared/lib/navigation/navigation-native-tabs.provider.tsx +0 -12
- package/src/shared/lib/theme/constants/borders.ts +0 -8
- package/src/shared/lib/theme/constants/weights.ts +0 -13
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post-comment.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-settings.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-verification.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post-comment.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-settings.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-verification.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user.ts +0 -0
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { Platform } from '@mediacubeco/base-react-native'
|
|
2
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
2
|
import { Appearance, StatusBar, useColorScheme } from 'react-native'
|
|
4
|
-
import { themeStorage } from './utils'
|
|
5
|
-
import {
|
|
3
|
+
import { themeSnapshot, themeStorage } from './utils'
|
|
4
|
+
import {
|
|
5
|
+
ThemeAccent,
|
|
6
|
+
ThemeMode,
|
|
7
|
+
ThemeModeStatusBar,
|
|
8
|
+
ThemeModeSystem,
|
|
9
|
+
themesByAccent,
|
|
10
|
+
} from './constants'
|
|
6
11
|
import { ThemeContext } from './theme.context'
|
|
7
12
|
import { type ThemeProviderProps } from './theme.types'
|
|
8
13
|
|
|
14
|
+
/** Провайдер темы: разрешает режим отображения, применяет акцент и синхронизирует выбор с хранилищем. */
|
|
9
15
|
const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
|
10
16
|
const didDefine = useRef(false)
|
|
11
|
-
const [themeMode, setThemeMode] = useState(
|
|
17
|
+
const [themeMode, setThemeMode] = useState(ThemeMode.System)
|
|
18
|
+
const [themeAccent, setThemeAccent] = useState<ThemeAccent>(ThemeAccent.Mint)
|
|
12
19
|
const colorScheme = useColorScheme()
|
|
13
20
|
|
|
14
21
|
const themeModeSystem =
|
|
@@ -21,28 +28,29 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
|
|
21
28
|
}[themeMode]
|
|
22
29
|
|
|
23
30
|
const theme = useMemo(() => {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
const theme = themesByAccent[themeAccent][currentThemeMode]
|
|
32
|
+
|
|
33
|
+
return theme
|
|
34
|
+
}, [currentThemeMode, themeAccent])
|
|
26
35
|
|
|
27
36
|
const value = useMemo(() => {
|
|
28
|
-
return { theme, themeMode, themeModeSystem, setThemeMode }
|
|
29
|
-
}, [theme, themeMode, themeModeSystem, setThemeMode])
|
|
37
|
+
return { theme, themeMode, themeModeSystem, themeAccent, setThemeMode, setThemeAccent }
|
|
38
|
+
}, [theme, themeMode, themeModeSystem, themeAccent, setThemeMode, setThemeAccent])
|
|
30
39
|
|
|
31
40
|
const updateThemeModeSystem = useCallback((themeMode: ThemeMode) => {
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Appearance.setColorScheme('unspecified')
|
|
37
|
-
} else {
|
|
38
|
-
Appearance.setColorScheme(themeMode)
|
|
39
|
-
}
|
|
41
|
+
if (themeMode === ThemeMode.System) {
|
|
42
|
+
Appearance.setColorScheme('unspecified')
|
|
43
|
+
} else {
|
|
44
|
+
Appearance.setColorScheme(themeMode)
|
|
40
45
|
}
|
|
41
46
|
}, [])
|
|
42
47
|
|
|
43
48
|
useEffect(() => {
|
|
44
|
-
const
|
|
45
|
-
const storageThemeMode = await
|
|
49
|
+
const defineTheme = async () => {
|
|
50
|
+
const [storageThemeMode, storageThemeAccent] = await Promise.all([
|
|
51
|
+
themeStorage.mode.get(),
|
|
52
|
+
themeStorage.accent.get(),
|
|
53
|
+
])
|
|
46
54
|
|
|
47
55
|
if (storageThemeMode) {
|
|
48
56
|
setThemeMode(storageThemeMode)
|
|
@@ -50,10 +58,14 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
|
|
50
58
|
updateThemeModeSystem(storageThemeMode)
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
if (storageThemeAccent && themesByAccent[storageThemeAccent]) {
|
|
62
|
+
setThemeAccent(storageThemeAccent)
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
didDefine.current = true
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
defineTheme()
|
|
57
69
|
}, [updateThemeModeSystem])
|
|
58
70
|
|
|
59
71
|
useEffect(() => {
|
|
@@ -68,6 +80,20 @@ const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
|
|
68
80
|
updateThemeMode()
|
|
69
81
|
}, [themeMode, updateThemeModeSystem])
|
|
70
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const updateThemeAccent = async () => {
|
|
85
|
+
if (didDefine.current) {
|
|
86
|
+
await themeStorage.accent.set(themeAccent)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
updateThemeAccent()
|
|
91
|
+
}, [themeAccent])
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
themeSnapshot.set(theme)
|
|
95
|
+
}, [theme])
|
|
96
|
+
|
|
71
97
|
useEffect(() => {
|
|
72
98
|
StatusBar.setBarStyle(
|
|
73
99
|
currentThemeMode === ThemeModeSystem.Dark ? ThemeModeStatusBar.Light : ThemeModeStatusBar.Dark
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { ThemeMode, ThemeModeSystem, themesByAccent } from './constants'
|
|
3
|
+
import { ThemeContext } from './theme.context'
|
|
4
|
+
import { useTheme } from './hooks/use-theme'
|
|
5
|
+
import { type ThemeScopeProps, type ThemeSomeScopeProps } from './theme.types'
|
|
6
|
+
|
|
7
|
+
/** Режим темы приложения, соответствующий режиму области. */
|
|
8
|
+
const THEME_MODE_BY_SCOPE_MODE = {
|
|
9
|
+
[ThemeModeSystem.Light]: ThemeMode.Light,
|
|
10
|
+
[ThemeModeSystem.Dark]: ThemeMode.Dark,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Локальная область темы: рендерит поддерево в заданном режиме независимо от темы
|
|
15
|
+
* приложения. Для контента, свёрстанного под конкретную схему — например, графики
|
|
16
|
+
* онбординг-слайдов, задизайненной в светлой. Акцент и сеттеры наследуются от
|
|
17
|
+
* родительского провайдера.
|
|
18
|
+
*/
|
|
19
|
+
const ThemeScope = ({ mode, children }: ThemeScopeProps) => {
|
|
20
|
+
const context = useTheme()
|
|
21
|
+
|
|
22
|
+
const value = useMemo(() => {
|
|
23
|
+
return {
|
|
24
|
+
...context,
|
|
25
|
+
theme: themesByAccent[context.themeAccent][mode],
|
|
26
|
+
themeMode: THEME_MODE_BY_SCOPE_MODE[mode],
|
|
27
|
+
themeModeSystem: mode,
|
|
28
|
+
}
|
|
29
|
+
}, [context, mode])
|
|
30
|
+
|
|
31
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ThemeScopeLight = ({ children }: ThemeSomeScopeProps) => {
|
|
35
|
+
return <ThemeScope mode={ThemeModeSystem.Light}>{children}</ThemeScope>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ThemeScopeDark = ({ children }: ThemeSomeScopeProps) => {
|
|
39
|
+
return <ThemeScope mode={ThemeModeSystem.Dark}>{children}</ThemeScope>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { ThemeScopeLight, ThemeScopeDark }
|
|
43
|
+
export default ThemeScope
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { type Dispatch, type PropsWithChildren, type SetStateAction } from 'react'
|
|
2
|
-
import { type ThemeMode, type ThemeModeSystem, type themes } from './constants'
|
|
2
|
+
import { type ThemeAccent, type ThemeMode, type ThemeModeSystem, type themes } from './constants'
|
|
3
3
|
|
|
4
|
+
/** Набор токенов активной темы. */
|
|
4
5
|
type Theme = (typeof themes)[ThemeModeSystem]
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
/** Значение контекста темы. */
|
|
8
|
+
type ThemeContextValue = {
|
|
7
9
|
theme: Theme
|
|
8
10
|
themeMode: ThemeMode
|
|
9
11
|
themeModeSystem: ThemeModeSystem
|
|
12
|
+
themeAccent: ThemeAccent
|
|
10
13
|
setThemeMode: Dispatch<SetStateAction<ThemeMode>>
|
|
14
|
+
setThemeAccent: Dispatch<SetStateAction<ThemeAccent>>
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
/** Пропсы провайдера темы. */
|
|
13
18
|
type ThemeProviderProps = PropsWithChildren
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
/** Пропсы локальной области темы. */
|
|
21
|
+
type ThemeScopeProps = PropsWithChildren<{
|
|
22
|
+
mode: ThemeModeSystem
|
|
23
|
+
}>
|
|
24
|
+
|
|
25
|
+
type ThemeSomeScopeProps = PropsWithChildren
|
|
26
|
+
|
|
27
|
+
export type { Theme, ThemeContextValue, ThemeProviderProps, ThemeScopeProps, ThemeSomeScopeProps }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { clamp } from '@mediacubeco/base/utils'
|
|
2
|
+
|
|
3
|
+
const transparencyCache = new Map<string, string>()
|
|
4
|
+
|
|
5
|
+
const parseColor = (color: string) => {
|
|
6
|
+
const sanitized = color.trim().toLowerCase()
|
|
7
|
+
|
|
8
|
+
if (sanitized === 'transparent') {
|
|
9
|
+
return { red: 0, green: 0, blue: 0 }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isRgb = sanitized.startsWith('rgb(')
|
|
13
|
+
const isRgba = sanitized.startsWith('rgba(')
|
|
14
|
+
const isClosed = sanitized.endsWith(')')
|
|
15
|
+
|
|
16
|
+
if ((!isRgb && !isRgba) || !isClosed) {
|
|
17
|
+
throw new Error(`Unsupported color format: ${color}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const valuesBody = sanitized.slice(sanitized.indexOf('(') + 1, sanitized.length - 1)
|
|
21
|
+
const valueList = valuesBody.split(',').map((value) => value.trim())
|
|
22
|
+
|
|
23
|
+
if (valueList.length !== 3 && valueList.length !== 4) {
|
|
24
|
+
throw new Error(`Unsupported color format: ${color}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const redValue = Number(valueList[0])
|
|
28
|
+
const greenValue = Number(valueList[1])
|
|
29
|
+
const blueValue = Number(valueList[2])
|
|
30
|
+
|
|
31
|
+
if (Number.isNaN(redValue) || Number.isNaN(greenValue) || Number.isNaN(blueValue)) {
|
|
32
|
+
throw new Error(`Unsupported color format: ${color}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const red = clamp(Math.round(redValue), 0, 255)
|
|
36
|
+
const green = clamp(Math.round(greenValue), 0, 255)
|
|
37
|
+
const blue = clamp(Math.round(blueValue), 0, 255)
|
|
38
|
+
|
|
39
|
+
return { red, green, blue }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const brightnessCache = new Map<string, string>()
|
|
43
|
+
|
|
44
|
+
/** Затемняет (factor < 1) или осветляет (factor > 1) цвет умножением RGB-каналов. */
|
|
45
|
+
const withBrightness = (color: string, factor: number) => {
|
|
46
|
+
const cacheKey = `${color}|${factor}`
|
|
47
|
+
const cachedColor = brightnessCache.get(cacheKey)
|
|
48
|
+
|
|
49
|
+
if (cachedColor) {
|
|
50
|
+
return cachedColor
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { red, green, blue } = parseColor(color)
|
|
54
|
+
const scaledRed = clamp(Math.round(red * factor), 0, 255)
|
|
55
|
+
const scaledGreen = clamp(Math.round(green * factor), 0, 255)
|
|
56
|
+
const scaledBlue = clamp(Math.round(blue * factor), 0, 255)
|
|
57
|
+
const scaledColor = `rgba(${scaledRed}, ${scaledGreen}, ${scaledBlue}, 1)`
|
|
58
|
+
|
|
59
|
+
brightnessCache.set(cacheKey, scaledColor)
|
|
60
|
+
|
|
61
|
+
return scaledColor
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const withTransparency = (color: string, alpha: number) => {
|
|
65
|
+
const cacheKey = `${color}|${alpha}`
|
|
66
|
+
const cachedColor = transparencyCache.get(cacheKey)
|
|
67
|
+
|
|
68
|
+
if (cachedColor) {
|
|
69
|
+
return cachedColor
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { red, green, blue } = parseColor(color)
|
|
73
|
+
const clampedAlpha = clamp(alpha, 0, 1)
|
|
74
|
+
const transparentColor = `rgba(${red}, ${green}, ${blue}, ${clampedAlpha})`
|
|
75
|
+
|
|
76
|
+
transparencyCache.set(cacheKey, transparentColor)
|
|
77
|
+
|
|
78
|
+
return transparentColor
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { withBrightness, withTransparency }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ThemeModeSystem, themes } from '../constants'
|
|
2
|
+
import { type Theme } from '../theme.types'
|
|
3
|
+
|
|
4
|
+
/** Актуальная тема приложения; до инициализации провайдера — светлая. */
|
|
5
|
+
let currentTheme: Theme = themes[ThemeModeSystem.Light]
|
|
6
|
+
|
|
7
|
+
/** Снапшот текущей темы для кода вне React-дерева (сервисы, хелперы); обновляется из ThemeProvider. */
|
|
8
|
+
const themeSnapshot = {
|
|
9
|
+
get() {
|
|
10
|
+
return currentTheme
|
|
11
|
+
},
|
|
12
|
+
set: (theme: Theme) => {
|
|
13
|
+
currentTheme = theme
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { themeSnapshot }
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Storage } from '@mediacubeco/base/services'
|
|
2
2
|
import { storageModel } from '../../storage'
|
|
3
|
-
import { type ThemeMode } from '../constants'
|
|
3
|
+
import { type ThemeAccent, type ThemeMode } from '../constants'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/** Хранилище настроек темы: режим и акцентный цвет. */
|
|
6
|
+
const themeStorage = new Storage(storageModel)
|
|
7
|
+
.create<'mode', ThemeMode>('mode')
|
|
8
|
+
.create<'accent', ThemeAccent>('accent')
|
|
6
9
|
|
|
7
10
|
export { themeStorage }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import * as WebBrowser from 'expo-web-browser'
|
|
3
|
+
import { themeSnapshot } from '../../lib'
|
|
4
|
+
|
|
5
|
+
/** Работа с системным in-app браузером. */
|
|
6
|
+
class Browser {
|
|
7
|
+
/** Завершает подвисшую auth-сессию браузера после возврата в приложение. */
|
|
8
|
+
static async init() {
|
|
9
|
+
try {
|
|
10
|
+
WebBrowser.maybeCompleteAuthSession()
|
|
11
|
+
|
|
12
|
+
Log.formatted.success('browser', 'init success')
|
|
13
|
+
} catch (error) {
|
|
14
|
+
ErrorHandler.capture(error, {
|
|
15
|
+
signal: 'browser',
|
|
16
|
+
message: 'init error',
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Открывает URL в in-app браузере (iOS — page sheet, Android — Custom Tabs) в цветах текущей темы. */
|
|
22
|
+
static async open(url: string) {
|
|
23
|
+
try {
|
|
24
|
+
const { COLORS } = themeSnapshot.get()
|
|
25
|
+
|
|
26
|
+
await WebBrowser.openBrowserAsync(url, {
|
|
27
|
+
showTitle: true,
|
|
28
|
+
createTask: true,
|
|
29
|
+
readerMode: false,
|
|
30
|
+
showInRecents: true,
|
|
31
|
+
useProxyActivity: true,
|
|
32
|
+
enableBarCollapsing: true,
|
|
33
|
+
enableDefaultShareMenuItem: true,
|
|
34
|
+
dismissButtonStyle: 'close',
|
|
35
|
+
toolbarColor: COLORS.BACKGROUND.BASE,
|
|
36
|
+
secondaryToolbarColor: COLORS.BACKGROUND.SECONDARY,
|
|
37
|
+
controlsColor: COLORS.TYPOGRAPHY.PRIMARY,
|
|
38
|
+
presentationStyle: WebBrowser.WebBrowserPresentationStyle.PAGE_SHEET,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
Log.formatted.success('browser', 'open success', { url })
|
|
42
|
+
} catch (error) {
|
|
43
|
+
ErrorHandler.capture(error, {
|
|
44
|
+
signal: 'browser',
|
|
45
|
+
message: 'open error',
|
|
46
|
+
payload: { url },
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Закрывает открытый in-app браузер. */
|
|
52
|
+
static async close() {
|
|
53
|
+
try {
|
|
54
|
+
await WebBrowser.dismissBrowser()
|
|
55
|
+
|
|
56
|
+
Log.formatted.success('browser', 'close success')
|
|
57
|
+
} catch (error) {
|
|
58
|
+
ErrorHandler.capture(error, {
|
|
59
|
+
signal: 'browser',
|
|
60
|
+
message: 'close error',
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default Browser
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Browser } from './browser'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import NitroCookies, { type Cookie } from 'react-native-nitro-cookies'
|
|
3
|
+
import { APP_URL } from '../../constants'
|
|
4
|
+
|
|
5
|
+
/** Управление cookie в нативном хранилище, общем с WebView. */
|
|
6
|
+
class Cookies {
|
|
7
|
+
/** Базовый URL веб-приложения, для которого управляются cookie. */
|
|
8
|
+
private static url = ''
|
|
9
|
+
|
|
10
|
+
/** Запоминает базовый URL веб-приложения из окружения. */
|
|
11
|
+
static async init() {
|
|
12
|
+
Cookies.url = APP_URL
|
|
13
|
+
|
|
14
|
+
Log.formatted.success('cookies', 'init success', { url: Cookies.url })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Записывает cookie; `useWebKit` пишет в хранилище WKWebView (iOS). */
|
|
18
|
+
static async set(cookie: Cookie, useWebKit?: boolean) {
|
|
19
|
+
try {
|
|
20
|
+
if (!Cookies.url) {
|
|
21
|
+
ErrorHandler.throw('Cookies not initialized')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await NitroCookies.set(Cookies.url, cookie, useWebKit)
|
|
25
|
+
|
|
26
|
+
Log.formatted.success('cookies', 'set success', { name: cookie.name, useWebKit })
|
|
27
|
+
} catch (error) {
|
|
28
|
+
ErrorHandler.forward(error, {
|
|
29
|
+
signal: 'cookies',
|
|
30
|
+
message: 'set error',
|
|
31
|
+
payload: { name: cookie.name, useWebKit },
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Возвращает cookie, доступные для базового URL. */
|
|
37
|
+
static async get(useWebKit?: boolean) {
|
|
38
|
+
try {
|
|
39
|
+
if (!Cookies.url) {
|
|
40
|
+
ErrorHandler.throw('Cookies not initialized')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cookies = await NitroCookies.get(Cookies.url, useWebKit)
|
|
44
|
+
|
|
45
|
+
Log.formatted.success('cookies', 'get success', { useWebKit })
|
|
46
|
+
|
|
47
|
+
return cookies
|
|
48
|
+
} catch (error) {
|
|
49
|
+
ErrorHandler.forward(error, {
|
|
50
|
+
signal: 'cookies',
|
|
51
|
+
message: 'get error',
|
|
52
|
+
payload: { useWebKit },
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Полностью очищает cookie-хранилище (например, при логауте). */
|
|
58
|
+
static async clear(useWebKit?: boolean) {
|
|
59
|
+
try {
|
|
60
|
+
await NitroCookies.clearAll(useWebKit)
|
|
61
|
+
|
|
62
|
+
Log.formatted.success('cookies', 'clear success', { useWebKit })
|
|
63
|
+
} catch (error) {
|
|
64
|
+
ErrorHandler.forward(error, {
|
|
65
|
+
signal: 'cookies',
|
|
66
|
+
message: 'clear error',
|
|
67
|
+
payload: { useWebKit },
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default Cookies
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Cookies } from './cookies'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Linking } from './linking'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ErrorHandler, Log } from '@mediacubeco/base/helpers'
|
|
2
|
+
import { openURL } from 'expo-linking'
|
|
3
|
+
|
|
4
|
+
/** Открытие внешних ссылок в соответствующем системном приложении. */
|
|
5
|
+
class Linking {
|
|
6
|
+
/** Открывает URL вне приложения (системный браузер, App Store, Google Play и т. п.). */
|
|
7
|
+
static async open(url: string) {
|
|
8
|
+
try {
|
|
9
|
+
await openURL(url)
|
|
10
|
+
|
|
11
|
+
Log.formatted.success('linking', 'open success', { url })
|
|
12
|
+
} catch (error) {
|
|
13
|
+
ErrorHandler.capture(error, {
|
|
14
|
+
signal: 'linking',
|
|
15
|
+
message: 'open error',
|
|
16
|
+
payload: { url },
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default Linking
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Share } from './share'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Platform } from '@mediacubeco/base-react-native/helpers'
|
|
2
|
+
import { Share as ShareNative } from 'react-native'
|
|
3
|
+
import * as Sharing from 'expo-sharing'
|
|
4
|
+
import { File, Paths } from 'expo-file-system'
|
|
5
|
+
|
|
6
|
+
/** Шаринг файлов и текста через системный share sheet. */
|
|
7
|
+
class Share {
|
|
8
|
+
/** Отдаёт base64-содержимое в share sheet, предварительно записав его во временный файл кэша. */
|
|
9
|
+
static async openBase64(base64: string, fileName: string) {
|
|
10
|
+
const file = new File(Paths.cache, fileName)
|
|
11
|
+
|
|
12
|
+
file.write(base64, { encoding: 'base64' })
|
|
13
|
+
|
|
14
|
+
await Sharing.shareAsync(file.uri)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Отдаёт текстовое сообщение в share sheet. */
|
|
18
|
+
static async openMessage(message: string) {
|
|
19
|
+
await ShareNative.share({ message })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Отдаёт ссылку в share sheet: на iOS нативным полем `url`, на остальных платформах текстом. */
|
|
23
|
+
static async openUrl(url: string) {
|
|
24
|
+
await ShareNative.share(Platform.isIos ? { url } : { message: url })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default Share
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export { default as UniversalLinks } from './universal-links'
|
|
2
|
+
export type { UniversalLinksHandler } from './universal-links.types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Backend hosting (required to verify domain ownership):
|
|
6
|
+
*
|
|
7
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* iOS — Universal Links
|
|
9
|
+
* Host at: https://garna.io/.well-known/apple-app-site-association
|
|
10
|
+
* - no file extension (NOT .json)
|
|
11
|
+
* - Content-Type: application/json
|
|
12
|
+
* - HTTPS, no redirects
|
|
13
|
+
* - replace <APPLE_TEAM_ID> with the value from EXPO_PUBLIC_APPLE_TEAM_ID
|
|
14
|
+
*
|
|
15
|
+
* {
|
|
16
|
+
* "applinks": {
|
|
17
|
+
* "apps": [],
|
|
18
|
+
* "details": [
|
|
19
|
+
* {
|
|
20
|
+
* "appID": "<APPLE_TEAM_ID>.com.mediacube.application",
|
|
21
|
+
* "paths": ["/auth/*", "/invite/*", "/app/*"]
|
|
22
|
+
* }
|
|
23
|
+
* ]
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
28
|
+
* Android — App Links
|
|
29
|
+
* Host at: https://garna.io/.well-known/assetlinks.json
|
|
30
|
+
* - Content-Type: application/json
|
|
31
|
+
* - HTTPS, no redirects
|
|
32
|
+
* - replace <SHA256_CERT_FINGERPRINT> with the signing fingerprint(s) from
|
|
33
|
+
* `eas credentials` and Play Console → App integrity (App signing key)
|
|
34
|
+
*
|
|
35
|
+
* [
|
|
36
|
+
* {
|
|
37
|
+
* "relation": ["delegate_permission/common.handle_all_urls"],
|
|
38
|
+
* "target": {
|
|
39
|
+
* "namespace": "android_app",
|
|
40
|
+
* "package_name": "com.mediacube.application",
|
|
41
|
+
* "sha256_cert_fingerprints": ["<SHA256_CERT_FINGERPRINT>"]
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ]
|
|
45
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
46
|
+
*
|
|
47
|
+
* `getAppleAssociation()` / `getAndroidAssociation()` below build these exact
|
|
48
|
+
* payloads at runtime once the env value and fingerprints are filled in.
|
|
49
|
+
*
|
|
50
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
* Ignoring domains and paths
|
|
52
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
*
|
|
54
|
+
* The app only claims what is explicitly declared — anything else opens in the
|
|
55
|
+
* browser. Rules differ per platform and per scope (domain vs path).
|
|
56
|
+
*
|
|
57
|
+
* ── Ignore a whole domain / subdomain ─────────────────────────────────────
|
|
58
|
+
* - iOS: omit it from app.json `ios.associatedDomains`. Avoid the wildcard
|
|
59
|
+
* `applinks:*.garna.io` if some subdomains must stay in the browser; list
|
|
60
|
+
* explicit hosts instead, e.g. ["applinks:app.garna.io", "applinks:garna.io"].
|
|
61
|
+
* - Android: omit its `host` from app.json `android.intentFilters[].data`.
|
|
62
|
+
*
|
|
63
|
+
* ── Ignore paths on iOS (edit AASA, app.json untouched) ───────────────────
|
|
64
|
+
* First match wins, so put excludes first.
|
|
65
|
+
*
|
|
66
|
+
* Modern format (iOS 13+, recommended) — `exclude: true`:
|
|
67
|
+
* {
|
|
68
|
+
* "applinks": {
|
|
69
|
+
* "details": [
|
|
70
|
+
* {
|
|
71
|
+
* "appIDs": ["<APPLE_TEAM_ID>.com.mediacube.application"],
|
|
72
|
+
* "components": [
|
|
73
|
+
* { "/": "/help/*", "exclude": true, "comment": "open in Safari" },
|
|
74
|
+
* { "/": "/blog/*", "exclude": true },
|
|
75
|
+
* { "/": "/auth/*" },
|
|
76
|
+
* { "/": "/invite/*" },
|
|
77
|
+
* { "/": "/app/*" }
|
|
78
|
+
* ]
|
|
79
|
+
* }
|
|
80
|
+
* ]
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
84
|
+
* Legacy format — `NOT ` prefix:
|
|
85
|
+
* "paths": ["NOT /help/*", "NOT /blog/*", "/auth/*", "/invite/*", "/app/*"]
|
|
86
|
+
*
|
|
87
|
+
* ── Ignore paths on Android (edit app.json, assetlinks untouched) ─────────
|
|
88
|
+
* No "exclude" exists — whitelist allowed paths via pathPrefix / pathPattern.
|
|
89
|
+
* Everything outside the listed prefixes opens in the browser:
|
|
90
|
+
*
|
|
91
|
+
* "intentFilters": [
|
|
92
|
+
* {
|
|
93
|
+
* "action": "VIEW",
|
|
94
|
+
* "autoVerify": true,
|
|
95
|
+
* "category": ["BROWSABLE", "DEFAULT"],
|
|
96
|
+
* "data": [
|
|
97
|
+
* { "scheme": "https", "host": "garna.io", "pathPrefix": "/auth" },
|
|
98
|
+
* { "scheme": "https", "host": "garna.io", "pathPrefix": "/invite" },
|
|
99
|
+
* { "scheme": "https", "host": "garna.io", "pathPrefix": "/app" }
|
|
100
|
+
* ]
|
|
101
|
+
* }
|
|
102
|
+
* ]
|
|
103
|
+
* ──────────────────────────────────────────────────────────────────────────
|
|
104
|
+
*/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type EmitterSubscription } from 'react-native'
|
|
2
|
+
import * as Linking from 'expo-linking'
|
|
3
|
+
import { type UniversalLinksHandler } from './universal-links.types'
|
|
4
|
+
|
|
5
|
+
class UniversalLinks {
|
|
6
|
+
static subscription: EmitterSubscription
|
|
7
|
+
|
|
8
|
+
static get() {
|
|
9
|
+
return Linking.getInitialURL()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static parse(url: string) {
|
|
13
|
+
return Linking.parse(url)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static subscribe(handler: UniversalLinksHandler) {
|
|
17
|
+
UniversalLinks.subscription = Linking.addEventListener('url', handler)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static unsubscribe() {
|
|
21
|
+
UniversalLinks.subscription.remove()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default UniversalLinks
|