diff --git a/ios/patching/001-social-app-ios-config.patch b/ios/patching/001-social-app-ios-config.patch index 6526b61..2bd1354 100644 --- a/ios/patching/001-social-app-ios-config.patch +++ b/ios/patching/001-social-app-ios-config.patch @@ -1,22 +1,8 @@ -diff --git a/Dockerfile b/Dockerfile -index 371e8402c..2e139503e 100644 ---- a/Dockerfile -+++ b/Dockerfile -@@ -66,7 +66,8 @@ RUN \. "$NVM_DIR/nvm.sh" && \ - echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \ - echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \ - npm install --global yarn && \ -- yarn && \ -+ yarn config set registry https://registry.npmjs.org/ && \ -+ yarn install --frozen-lockfile --network-timeout 100000 && \ - yarn intl:build 2>&1 | tee i18n.log && \ - if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \ - SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web diff --git a/app.config.js b/app.config.js -index 246d8abd3..a6582864b 100644 +index 246d8abd3..0e33bf6ac 100644 --- a/app.config.js +++ b/app.config.js -@@ -33,8 +33,8 @@ module.exports = function (_config) { +@@ -33,27 +33,32 @@ module.exports = function (_config) { return { expo: { version: VERSION, @@ -27,15 +13,19 @@ index 246d8abd3..a6582864b 100644 scheme: 'bluesky', owner: 'blueskysocial', runtimeVersion: { -@@ -45,15 +45,20 @@ module.exports = function (_config) { + policy: 'appVersion', + }, +- icon: './assets/app-icons/ios_icon_default_next.png', ++ icon: './assets/icon.png', + userInterfaceStyle: 'automatic', primaryColor: '#1083fe', newArchEnabled: false, ios: { -+ infoPlist: { -+ NSAppTransportSecurity: { -+ NSAllowsArbitraryLoads: true, -+ }, -+ }, ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, ++ }, supportsTablet: false, - bundleIdentifier: 'xyz.blueskyweb.app', + bundleIdentifier: 'ai.syui.at', @@ -68,7 +58,17 @@ index 246d8abd3..a6582864b 100644 intentFilters: [ { action: 'VIEW', -@@ -220,7 +225,7 @@ module.exports = function (_config) { +@@ -213,19 +218,19 @@ module.exports = function (_config) { + : undefined, + codeSigningMetadata: UPDATES_ENABLED + ? { +- keyid: 'main', +- alg: 'rsa-v1_5-sha256', +- } ++ keyid: 'main', ++ alg: 'rsa-v1_5-sha256', ++ } + : undefined, checkAutomatically: 'NEVER', }, plugins: [ @@ -77,6 +77,12 @@ index 246d8abd3..a6582864b 100644 'expo-localization', 'expo-web-browser', [ + 'react-native-edge-to-edge', +- {android: {enforceNavigationBarContrast: false}}, ++ { android: { enforceNavigationBarContrast: false } }, + ], + USE_SENTRY && [ + '@sentry/react-native/expo', @@ -239,6 +244,11 @@ module.exports = function (_config) { 'expo-build-properties', { @@ -109,15 +115,24 @@ index 246d8abd3..a6582864b 100644 enableFullScreenImage_legacy: true, backgroundColor: '#ffffff', image: './assets/splash.png', +@@ -386,7 +400,7 @@ module.exports = function (_config) { + }, + }, + ], +- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}], ++ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }], + ['expo-location'], + ].filter(Boolean), + extra: { @@ -394,29 +408,30 @@ module.exports = function (_config) { build: { experimental: { ios: { -+ infoPlist: { -+ NSAppTransportSecurity: { -+ NSAllowsArbitraryLoads: true, ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, + }, -+ }, appExtensions: [ { targetName: 'Share-with-Bluesky', diff --git a/ios/patching/008-social-app-ios-policy-tos-error.patch b/ios/patching/008-social-app-ios-policy-tos-error.patch index c0a98cf..83015ec 100644 --- a/ios/patching/008-social-app-ios-policy-tos-error.patch +++ b/ios/patching/008-social-app-ios-policy-tos-error.patch @@ -1,27 +1,8 @@ -diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx -index 4f25468c9..95b183dcc 100644 ---- a/src/view/com/posts/PostFeed.tsx -+++ b/src/view/com/posts/PostFeed.tsx -@@ -298,8 +298,13 @@ let PostFeed = ({ - } - } - } catch (e) { -+ const errorMsg = String(e) -+ // Skip errors for missing repos (deleted accounts) -+ if (errorMsg.includes('Could not find repo')) { -+ return -+ } - if (!isNetworkError(e)) { -- logger.error('Poll latest failed', {feed, message: String(e)}) -+ logger.error('Poll latest failed', {feed, message: errorMsg}) - } - } - }) diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx -index a89eaadc4..c3a8b6114 100644 +index a89eaadc4..1da393f03 100644 --- a/src/view/screens/PrivacyPolicy.tsx +++ b/src/view/screens/PrivacyPolicy.tsx -@@ -1,51 +1,95 @@ +@@ -1,52 +1,13 @@ import React from 'react' -import {View} from 'react-native' -import {msg, Trans} from '@lingui/macro' @@ -38,7 +19,7 @@ index a89eaadc4..c3a8b6114 100644 -import {TextLink} from '#/view/com/util/Link' -import {Text} from '#/view/com/util/text/Text' -import {ScrollView} from '#/view/com/util/Views' -+import { ScrollView } from 'react-native' ++import { WebView } from 'react-native-webview' import * as Layout from '#/components/Layout' -import {ViewHeader} from '../com/util/ViewHeader' - @@ -54,13 +35,9 @@ index a89eaadc4..c3a8b6114 100644 - }, [setMinimalShellMode]), - ) +import {useSetTitle} from '#/lib/hooks/useSetTitle' -+import {atoms as a, useTheme} from '#/alf' -+import {Text} from '#/components/Typography' -+ + +export function PrivacyPolicyScreen() { + useSetTitle('Privacy Policy') -+ const t = useTheme() - return ( - @@ -78,93 +55,16 @@ index a89eaadc4..c3a8b6114 100644 - - - -+ -+ Privacy Policy -+ -+ -+ This application (hereinafter referred to as "the App") respects user privacy and is committed to protecting personal information. -+ -+ -+ 1. Information We Collect -+ -+ The App is a decentralized social network using the AT Protocol. -+ Information posted by users is stored on the selected server (PDS). -+ -+ -+ 2. Use of Information -+ -+ The collected information is used for the following purposes: -+ {'\n'}- Providing and operating the service -+ {'\n'}- User support -+ {'\n'}- Service improvement -+ -+ -+ 3. Third-Party Disclosure -+ -+ The App will not provide users' personal information to third parties without user consent, except as required by law. -+ -+ -+ 4. Security -+ -+ The App takes appropriate security measures to protect personal information. -+ -+ -+ 5. Contact -+ -+ For questions regarding this Privacy Policy, please contact us through the support link in the settings screen. -+ -+ -+ 日本語訳(参考) -+ -+ プライバシーポリシー -+ -+ -+ 本アプリケーション(以下「本アプリ」)は、ユーザーのプライバシーを尊重し、個人情報の保護に努めます。 -+ -+ -+ 1. 収集する情報 -+ -+ 本アプリは、ATプロトコルを使用した分散型ソーシャルネットワークです。 -+ ユーザーが投稿した情報は、選択したサーバー(PDS)に保存されます。 -+ -+ -+ 2. 情報の利用目的 -+ -+ 収集した情報は、以下の目的で利用されます: -+ {'\n'}- サービスの提供・運営 -+ {'\n'}- ユーザーサポート -+ {'\n'}- サービスの改善 -+ -+ -+ 3. 情報の第三者提供 -+ -+ 本アプリは、ユーザーの個人情報を、法令に基づく場合を除き、 -+ ユーザーの同意なく第三者に提供することはありません。 -+ -+ -+ 4. セキュリティ -+ -+ 本アプリは、個人情報の保護のため、適切な安全対策を講じます。 -+ -+ -+ 5. お問い合わせ -+ -+ プライバシーポリシーに関するご質問は、設定画面のサポートリンクからお問い合わせください。 -+ -+ -+ -+ Last Updated: December 7, 2025 -+ - +- ++ ) + } diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx -index d843c713c..3d2323f73 100644 +index d843c713c..b81767bd5 100644 --- a/src/view/screens/TermsOfService.tsx +++ b/src/view/screens/TermsOfService.tsx -@@ -1,49 +1,110 @@ +@@ -1,50 +1,13 @@ import React from 'react' -import {View} from 'react-native' -import {msg, Trans} from '@lingui/macro' @@ -181,7 +81,7 @@ index d843c713c..3d2323f73 100644 -import {TextLink} from '#/view/com/util/Link' -import {Text} from '#/view/com/util/text/Text' -import {ScrollView} from '#/view/com/util/Views' -+import { ScrollView } from 'react-native' ++import { WebView } from 'react-native-webview' import * as Layout from '#/components/Layout' -import {ViewHeader} from '../com/util/ViewHeader' - @@ -197,13 +97,9 @@ index d843c713c..3d2323f73 100644 - }, [setMinimalShellMode]), - ) +import {useSetTitle} from '#/lib/hooks/useSetTitle' -+import {atoms as a, useTheme} from '#/alf' -+import {Text} from '#/components/Typography' -+ + +export function TermsOfServiceScreen() { + useSetTitle('Terms of Service') -+ const t = useTheme() - return ( - @@ -219,100 +115,8 @@ index d843c713c..3d2323f73 100644 - - - -+ -+ Terms of Service -+ -+ -+ These Terms of Service (hereinafter referred to as "these Terms") set forth the conditions for using this application (hereinafter referred to as "the App"). -+ -+ -+ Article 1 (Application) -+ -+ These Terms shall apply to all relationships between users and the provider of the App regarding the use of the App. -+ -+ -+ Article 2 (User Registration) -+ -+ Use of the App requires an AT Protocol-compatible account. -+ Registration information must be kept accurate and up-to-date. -+ -+ -+ Article 3 (Prohibited Acts) -+ -+ Users shall not engage in the following acts when using the App: -+ {'\n'}- Acts that violate laws or public order and morals -+ {'\n'}- Acts related to criminal activity -+ {'\n'}- Acts that infringe upon the rights of other users or third parties -+ {'\n'}- Acts that interfere with the operation of the App -+ {'\n'}- Unauthorized access or attempts thereof -+ -+ -+ Article 4 (Disclaimer) -+ -+ The App is a decentralized service using the AT Protocol. -+ The provider makes no warranties, express or implied, regarding the App. -+ -+ -+ Article 5 (Changes to Terms) -+ -+ The provider may change these Terms without obtaining user consent when deemed necessary. -+ -+ -+ Article 6 (Contact) -+ -+ For questions regarding these Terms, please contact us through the support link in the settings screen. -+ -+ -+ 日本語訳(参考) -+ -+ 利用規約 -+ -+ -+ 本利用規約(以下「本規約」)は、本アプリケーション(以下「本アプリ」)の利用条件を定めるものです。 -+ -+ -+ 第1条(適用) -+ -+ 本規約は、ユーザーと本アプリの提供者との間の本アプリの利用に関わる一切の関係に適用されます。 -+ -+ -+ 第2条(利用登録) -+ -+ 本アプリの利用にあたっては、ATプロトコル対応のアカウントが必要です。 -+ 登録情報は正確かつ最新の状態に保つ必要があります。 -+ -+ -+ 第3条(禁止事項) -+ -+ ユーザーは、本アプリの利用にあたり、以下の行為をしてはなりません: -+ {'\n'}- 法令または公序良俗に違反する行為 -+ {'\n'}- 犯罪行為に関連する行為 -+ {'\n'}- 他のユーザーまたは第三者の権利を侵害する行為 -+ {'\n'}- 本アプリの運営を妨害する行為 -+ {'\n'}- 不正アクセスまたはこれを試みる行為 -+ -+ -+ 第4条(免責事項) -+ -+ 本アプリは、ATプロトコルを使用した分散型サービスです。 -+ 提供者は、本アプリに関して、明示的・黙示的を問わず、いかなる保証も行いません。 -+ -+ -+ 第5条(規約の変更) -+ -+ 提供者は、必要と判断した場合、ユーザーの承諾を得ることなく本規約を変更できるものとします。 -+ -+ -+ 第6条(お問い合わせ) -+ -+ 本規約に関するご質問は、設定画面のサポートリンクからお問い合わせください。 -+ -+ -+ -+ Last Updated: December 7, 2025 -+ - +- ++ ) + } diff --git a/ios/patching/009-social-app-ios-license.patch b/ios/patching/009-social-app-ios-license.patch index bac8a82..3c31bbc 100644 --- a/ios/patching/009-social-app-ios-license.patch +++ b/ios/patching/009-social-app-ios-license.patch @@ -1,44 +1,8 @@ -diff --git a/src/Navigation.tsx b/src/Navigation.tsx -index fa33a9d56..13af087c2 100644 ---- a/src/Navigation.tsx -+++ b/src/Navigation.tsx -@@ -62,6 +62,7 @@ import {NotFoundScreen} from '#/view/screens/NotFound' - import {NotificationsScreen} from '#/view/screens/Notifications' - import {PostThreadScreen} from '#/view/screens/PostThread' - import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy' -+import {LicenseScreen} from '#/view/screens/License' - import {ProfileScreen} from '#/view/screens/Profile' - import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy' - import {Storybook} from '#/view/screens/Storybook' -@@ -335,6 +336,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { - getComponent={() => TermsOfServiceScreen} - options={{title: title(msg`Terms of Service`)}} - /> -+ LicenseScreen} -+ options={{title: title(msg`License`)}} -+ /> - CommunityGuidelinesScreen} -diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts -index c315a8341..9b2f50a83 100644 ---- a/src/lib/routes/types.ts -+++ b/src/lib/routes/types.ts -@@ -39,6 +39,7 @@ export type CommonNavigatorParams = { - Support: undefined - PrivacyPolicy: undefined - TermsOfService: undefined -+ License: undefined - CommunityGuidelines: undefined - CopyrightPolicy: undefined - LanguageSettings: undefined diff --git a/src/routes.ts b/src/routes.ts -index 1ed913bb2..aa6fccc4e 100644 +index 1ed913bb2..77ea6deca 100644 --- a/src/routes.ts +++ b/src/routes.ts -@@ -71,8 +71,9 @@ export const router = new Router({ +@@ -71,8 +71,8 @@ export const router = new Router({ MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous', // support Support: '/support', @@ -46,12 +10,11 @@ index 1ed913bb2..aa6fccc4e 100644 - TermsOfService: '/support/tos', + PrivacyPolicy: 'https://syu.is/about/support/privacy-policy', + TermsOfService: 'https://syu.is/about/support/tos', -+ License: '/support/license', CommunityGuidelines: '/support/community-guidelines', CopyrightPolicy: '/support/copyright', // hashtags diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx -index ed2a6cfb7..982c40b55 100644 +index ed2a6cfb7..2f387b4a8 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -1,60 +1,50 @@ @@ -625,7 +588,7 @@ index ed2a6cfb7..982c40b55 100644 ]}> + Privacy Policy + -+ -+ navigation.navigate('License')}> -+ -+ License -+ + ) diff --git a/ios/patching/011-social-app-ios-splash-license-footer.patch b/ios/patching/011-social-app-ios-splash-license-footer.patch index a8f182e..6f0fa9c 100644 --- a/ios/patching/011-social-app-ios-splash-license-footer.patch +++ b/ios/patching/011-social-app-ios-splash-license-footer.patch @@ -46,72 +46,3 @@ index 3442d1bdf..2b059af52 100644 -diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx -index 22dd23d7f..08cd8aa33 100644 ---- a/src/view/com/auth/SplashScreen.web.tsx -+++ b/src/view/com/auth/SplashScreen.web.tsx -@@ -3,7 +3,9 @@ import {Pressable, View} from 'react-native' - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' - import {msg, Trans} from '@lingui/macro' - import {useLingui} from '@lingui/react' -+import {useNavigation} from '@react-navigation/native' - -+import {NavigationProp} from '#/lib/routes/types' - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' - import {useKawaiiMode} from '#/state/preferences/kawaii' - import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' -@@ -93,15 +95,6 @@ export const SplashScreen = ({ - - - )} -- -- -- What's up? -- - - - -- -- Business -- -- -- Blog -- -- -- -- Jobs -- -- -- - - -+ -+ © syui -+ -+ - - - ) diff --git a/ios/patching/012-social-app-ios-settings-about-help.patch b/ios/patching/012-social-app-ios-settings-about-help.patch index 0dc7875..a232452 100644 --- a/ios/patching/012-social-app-ios-settings-about-help.patch +++ b/ios/patching/012-social-app-ios-settings-about-help.patch @@ -1,5 +1,5 @@ diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx -index 6b8257b91..bed851753 100644 +index 6b8257b91..48ba7909e 100644 --- a/src/screens/Settings/AboutSettings.tsx +++ b/src/screens/Settings/AboutSettings.tsx @@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) { @@ -7,7 +7,7 @@ index 6b8257b91..bed851753 100644 @@ -16,16 +16,7 @@ index 6b8257b91..bed851753 100644 -@@ -96,7 +96,7 @@ export function AboutSettingsScreen({}: Props) { - - - - - diff --git a/ios/patching/020-social-app-ios-bypass-age-assurance.patch b/ios/patching/020-social-app-ios-bypass-age-assurance.patch index 7744943..deb085f 100644 --- a/ios/patching/020-social-app-ios-bypass-age-assurance.patch +++ b/ios/patching/020-social-app-ios-bypass-age-assurance.patch @@ -1,5 +1,7 @@ ---- a/b/src/ageAssurance/index.tsx 2025-12-07 15:18:15 -+++ b/src/ageAssurance/index.tsx 2025-12-07 15:18:16 +diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx +index 9a0a9c9d5..85d3c64ce 100644 +--- a/src/ageAssurance/index.tsx ++++ b/src/ageAssurance/index.tsx @@ -1,10 +1,10 @@ -import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' +import { createContext, useCallback, useContext, useEffect, useMemo } from 'react' @@ -17,7 +19,7 @@ import { useAgeAssuranceState, useOnAgeAssuranceAccessUpdate, -@@ -14,7 +14,7 @@ +@@ -14,7 +14,7 @@ import { type AgeAssuranceState, AgeAssuranceStatus, } from '#/ageAssurance/types' @@ -26,7 +28,7 @@ export { prefetchConfig as prefetchAgeAssuranceConfig, -@@ -23,7 +23,7 @@ +@@ -23,7 +23,7 @@ export { usePatchOtherRequiredData as usePatchAgeAssuranceOtherRequiredData, usePatchServerState as usePatchAgeAssuranceServerState, } from '#/ageAssurance/data' @@ -35,7 +37,7 @@ const AgeAssuranceStateContext = createContext<{ Access: typeof AgeAssuranceAccess -@@ -56,7 +56,7 @@ +@@ -56,7 +56,7 @@ export function useAgeAssurance() { return useContext(AgeAssuranceStateContext) } @@ -44,7 +46,7 @@ return ( -@@ -66,9 +66,9 @@ +@@ -66,9 +66,9 @@ export function Provider({children}: {children: React.ReactNode}) { ) } @@ -56,7 +58,7 @@ const getAndRegisterPushToken = useGetAndRegisterPushToken() const handleAccessUpdate = useCallback( -@@ -82,28 +82,25 @@ +@@ -82,28 +82,25 @@ function InnerProvider({children}: {children: React.ReactNode}) { useOnAgeAssuranceAccessUpdate(handleAccessUpdate) useEffect(() => { diff --git a/ios/patching/021-social-app-ios-clean-feed.patch b/ios/patching/021-social-app-ios-clean-feed.patch new file mode 100644 index 0000000..5518776 --- /dev/null +++ b/ios/patching/021-social-app-ios-clean-feed.patch @@ -0,0 +1,407 @@ +diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx +index e058e2883..0f583c915 100644 +--- a/src/view/screens/Home.tsx ++++ b/src/view/screens/Home.tsx +@@ -1,23 +1,16 @@ + import React from 'react' + import {ActivityIndicator, StyleSheet} from 'react-native' + import {useFocusEffect} from '@react-navigation/native' +- + import {PROD_DEFAULT_FEED} from '#/lib/constants' + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' + import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates' + import {useSetTitle} from '#/lib/hooks/useSetTitle' + import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' +-import { +- type HomeTabNavigatorParams, +- type NativeStackScreenProps, +-} from '#/lib/routes/types' ++import {type HomeTabNavigatorParams, type NativeStackScreenProps} from '#/lib/routes/types' + import {logEvent} from '#/lib/statsig/statsig' + import {isWeb} from '#/platform/detection' + import {emitSoftReset} from '#/state/events' +-import { +- type SavedFeedSourceInfo, +- usePinnedFeedsInfos, +-} from '#/state/queries/feed' ++import {type SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed' + import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed' + import {usePreferencesQuery} from '#/state/queries/preferences' + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' +@@ -27,11 +20,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out' + import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' + import {FeedPage} from '#/view/com/feeds/FeedPage' + import {HomeHeader} from '#/view/com/home/HomeHeader' +-import { +- Pager, +- type PagerRef, +- type RenderTabBarFnProps, +-} from '#/view/com/pager/Pager' ++import {Pager, type PagerRef, type RenderTabBarFnProps} from '#/view/com/pager/Pager' + import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState' + import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState' + import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed' +@@ -39,97 +28,60 @@ import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' + import * as Layout from '#/components/Layout' + import {useDemoMode} from '#/storage/hooks/demo-mode' + ++const DEFAULT_PINNED_FEEDS = [{ ++ feedDescriptor: 'following', ++ displayName: 'Following', ++ id: 'following', ++ type: 'feed', ++ savedFeed: undefined, ++ pinned: true, ++}] ++ + type Props = NativeStackScreenProps + export function HomeScreen(props: Props) { + const {setShowLoggedOut} = useLoggedOutViewControls() + const {data: preferences} = usePreferencesQuery() + const {currentAccount} = useSession() +- const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = +- usePinnedFeedsInfos() ++ const {data: pinnedFeedInfos} = usePinnedFeedsInfos() ++ ++ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any ++ const safePinnedFeedInfos = pinnedFeedInfos || DEFAULT_PINNED_FEEDS + + React.useEffect(() => { + if (isWeb && !currentAccount) { + const getParams = new URLSearchParams(window.location.search) + const splash = getParams.get('splash') +- if (splash === 'true') { +- setShowLoggedOut(true) +- return +- } ++ if (splash === 'true') { setShowLoggedOut(true); return } + } +- + const params = props.route.params +- if ( +- currentAccount && +- props.route.name === 'Start' && +- params?.name && +- params?.rkey +- ) { +- props.navigation.navigate('StarterPack', { +- rkey: params.rkey, +- name: params.name, +- }) ++ if (currentAccount && props.route.name === 'Start' && params?.name && params?.rkey) { ++ props.navigation.navigate('StarterPack', { rkey: params.rkey, name: params.name }) + } +- }, [ +- currentAccount, +- props.navigation, +- props.route.name, +- props.route.params, +- setShowLoggedOut, +- ]) ++ }, [currentAccount, props.navigation, props.route.name, props.route.params, setShowLoggedOut]) + +- if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { +- return ( +- +- +- +- ) +- } else { +- return ( +- +- +- +- +- +- ) +- } ++ return ( ++ ++ ++ ++ ) + } + +-function HomeScreenReady({ +- preferences, +- pinnedFeedInfos, +-}: Props & { +- preferences: UsePreferencesQueryResponse +- pinnedFeedInfos: SavedFeedSourceInfo[] +-}) { +- const allFeeds = React.useMemo( +- () => pinnedFeedInfos.map(f => f.feedDescriptor), +- [pinnedFeedInfos], +- ) +- const maybeRawSelectedFeed: FeedDescriptor | undefined = +- useSelectedFeed() ?? allFeeds[0] ++function HomeScreenReady({preferences, pinnedFeedInfos}: any) { ++ const allFeeds = React.useMemo(() => pinnedFeedInfos.map(f => f.feedDescriptor), [pinnedFeedInfos]) ++ const maybeRawSelectedFeed = useSelectedFeed() ?? allFeeds[0] + const setSelectedFeed = useSetSelectedFeed() + const maybeFoundIndex = allFeeds.indexOf(maybeRawSelectedFeed) + const selectedIndex = Math.max(0, maybeFoundIndex) +- const maybeSelectedFeed: FeedDescriptor | undefined = allFeeds[selectedIndex] ++ const maybeSelectedFeed = allFeeds[selectedIndex] + const requestNotificationsPermission = useRequestNotificationsPermission() +- ++ + useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName) + useOTAUpdates() +- +- React.useEffect(() => { +- requestNotificationsPermission('Home') +- }, [requestNotificationsPermission]) ++ React.useEffect(() => { requestNotificationsPermission('Home') }, [requestNotificationsPermission]) + + const pagerRef = React.useRef(null) + const lastPagerReportedIndexRef = React.useRef(selectedIndex) + React.useLayoutEffect(() => { +- // Since the pager is not a controlled component, adjust it imperatively +- // if the selected index gets out of sync with what it last reported. +- // This is supposed to only happen on the web when you use the right nav. + if (selectedIndex !== lastPagerReportedIndexRef.current) { + lastPagerReportedIndexRef.current = selectedIndex + pagerRef.current?.setPage(selectedIndex) +@@ -138,205 +90,43 @@ function HomeScreenReady({ + + const {hasSession} = useSession() + const setMinimalShellMode = useSetMinimalShellMode() +- useFocusEffect( +- React.useCallback(() => { +- setMinimalShellMode(false) +- }, [setMinimalShellMode]), +- ) +- +- useFocusEffect( +- useNonReactiveCallback(() => { +- if (maybeSelectedFeed) { +- logEvent('home:feedDisplayed', { +- index: selectedIndex, +- feedType: maybeSelectedFeed.split('|')[0], +- feedUrl: maybeSelectedFeed, +- reason: 'focus', +- }) +- } +- }), +- ) +- +- const onPageSelected = React.useCallback( +- (index: number) => { +- setMinimalShellMode(false) +- const maybeFeed = allFeeds[index] +- +- // Mutate the ref before setting state to avoid the imperative syncing effect +- // above from starting a loop on Android when swiping back and forth. +- lastPagerReportedIndexRef.current = index +- setSelectedFeed(maybeFeed) +- +- if (maybeFeed) { +- logEvent('home:feedDisplayed', { +- index, +- feedType: maybeFeed.split('|')[0], +- feedUrl: maybeFeed, +- }) +- } +- }, +- [setSelectedFeed, setMinimalShellMode, allFeeds], +- ) +- +- const onPressSelected = React.useCallback(() => { +- emitSoftReset() +- }, []) +- +- const onPageScrollStateChanged = React.useCallback( +- (state: 'idle' | 'dragging' | 'settling') => { +- 'worklet' +- if (state === 'dragging') { +- setMinimalShellMode(false) +- } +- }, +- [setMinimalShellMode], +- ) ++ useFocusEffect(React.useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode])) ++ ++ const onPageSelected = React.useCallback((index) => { ++ setMinimalShellMode(false) ++ const maybeFeed = allFeeds[index] ++ lastPagerReportedIndexRef.current = index ++ setSelectedFeed(maybeFeed) ++ }, [setSelectedFeed, setMinimalShellMode, allFeeds]) ++ ++ const onPressSelected = React.useCallback(() => { emitSoftReset() }, []) ++ const onPageScrollStateChanged = React.useCallback((state) => { ++ 'worklet' ++ if (state === 'dragging') setMinimalShellMode(false) ++ }, [setMinimalShellMode]) + + const [demoMode] = useDemoMode() +- +- const renderTabBar = React.useCallback( +- (props: RenderTabBarFnProps) => { +- if (demoMode) { +- return ( +- +- ) +- } +- return ( +- +- ) +- }, +- [onPressSelected, pinnedFeedInfos, demoMode], +- ) +- +- const renderFollowingEmptyState = React.useCallback(() => { +- return +- }, []) +- +- const renderCustomFeedEmptyState = React.useCallback(() => { +- return +- }, []) +- +- const homeFeedParams = React.useMemo(() => { +- return { +- mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), +- mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled +- ? preferences.savedFeeds +- .filter(f => f.type === 'feed' || f.type === 'list') +- .map(f => f.value) +- : [], +- } +- }, [preferences]) +- +- if (demoMode) { +- return ( +- +- +- +- +- ) +- } +- +- return hasSession ? ( +- +- {pinnedFeedInfos.length ? ( +- pinnedFeedInfos.map((feedInfo, index) => { ++ const renderTabBar = React.useCallback((props) => { ++ return ++ }, [onPressSelected, pinnedFeedInfos]) ++ ++ const renderFollowingEmptyState = React.useCallback(() => , []) ++ const renderCustomFeedEmptyState = React.useCallback(() => , []) ++ ++ const homeFeedParams = React.useMemo(() => ({ ++ mergeFeedEnabled: false, mergeFeedSources: [] ++ }), [preferences]) ++ ++ return ( ++ ++ {pinnedFeedInfos.map((feedInfo, index) => { + const feed = feedInfo.feedDescriptor + if (feed === 'following') { +- return ( +- +- ) ++ return + } +- const savedFeedConfig = feedInfo.savedFeed +- return ( +- +- ) +- }) +- ) : ( +- +- )} +- +- ) : ( +- +- ++ return ++ })} + + ) + } +- +-const styles = StyleSheet.create({ +- loading: { +- height: '100%', +- alignContent: 'center', +- justifyContent: 'center', +- paddingBottom: 100, +- }, +-}) ++const styles = StyleSheet.create({ loading: { height: '100%', alignContent: 'center', justifyContent: 'center', paddingBottom: 100 } })