From 2194c01906bd2b62cf36f86ab11c684ba68b3902 Mon Sep 17 00:00:00 2001 From: syui Date: Sun, 7 Dec 2025 19:09:59 +0900 Subject: [PATCH] test gemini --- ios/patching/001-social-app-ios-config.patch | 69 +-- ios/patching/009-social-app-ios-license.patch | 41 +- ...-social-app-ios-bypass-age-assurance.patch | 16 +- .../021-social-app-ios-clean-feed.patch | 446 ++++++++++++++++++ 4 files changed, 499 insertions(+), 73 deletions(-) create mode 100644 ios/patching/021-social-app-ios-clean-feed.patch 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/009-social-app-ios-license.patch b/ios/patching/009-social-app-ios-license.patch index bac8a82..1b64b87 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,7 +10,6 @@ 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/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..bccb1ac --- /dev/null +++ b/ios/patching/021-social-app-ios-clean-feed.patch @@ -0,0 +1,446 @@ +diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx +index e058e2883..a458ab3e1 100644 +--- a/src/view/screens/Home.tsx ++++ b/src/view/screens/Home.tsx +@@ -1,342 +1,140 @@ + 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 {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 FeedDescriptor, type FeedParams} from '#/state/queries/post-feed' +-import {usePreferencesQuery} from '#/state/queries/preferences' +-import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' +-import {useSession} from '#/state/session' +-import {useSetMinimalShellMode} from '#/state/shell' +-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 {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState' +-import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState' +-import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed' +-import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' ++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 { 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 FeedDescriptor, type FeedParams } from '#/state/queries/post-feed' ++import { usePreferencesQuery } from '#/state/queries/preferences' ++import { type UsePreferencesQueryResponse } from '#/state/queries/preferences/types' ++import { useSession } from '#/state/session' ++import { useSetMinimalShellMode } from '#/state/shell' ++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 { CustomFeedEmptyState } from '#/view/com/posts/CustomFeedEmptyState' ++import { FollowingEmptyState } from '#/view/com/posts/FollowingEmptyState' ++import { FollowingEndOfFeed } from '#/view/com/posts/FollowingEndOfFeed' ++import { NoFeedsPinned } from '#/screens/Home/NoFeedsPinned' + import * as Layout from '#/components/Layout' +-import {useDemoMode} from '#/storage/hooks/demo-mode' ++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 { setShowLoggedOut } = useLoggedOutViewControls() ++ const { data: preferences } = usePreferencesQuery() ++ const { currentAccount } = useSession() ++ const { data: pinnedFeedInfos } = usePinnedFeedsInfos() ++ ++ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any ++ // Force only Following feed to be pinned/visible ++ const safePinnedFeedInfos = [{ ++ feedDescriptor: 'following', ++ displayName: 'Following', ++ id: 'following', ++ type: 'feed', ++ savedFeed: undefined, ++ pinned: true, ++ }] + + 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) + } + }, [selectedIndex]) + +- const {hasSession} = useSession() ++ 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', +- }) +- } +- }), +- ) ++ useFocusEffect(React.useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode])) + +- 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 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: 'idle' | 'dragging' | 'settling') => { +- 'worklet' +- if (state === 'dragging') { +- setMinimalShellMode(false) +- } +- }, +- [setMinimalShellMode], +- ) ++ 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 feed = feedInfo.feedDescriptor +- if (feed === 'following') { +- return ( +- +- ) +- } +- const savedFeedConfig = feedInfo.savedFeed +- return ( +- +- ) +- }) +- ) : ( +- +- )} +- +- ) : ( +- +- ++ 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 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 } })