diff --git a/install.zsh b/install.zsh
index 58cdca6..48ed4d6 100755
--- a/install.zsh
+++ b/install.zsh
@@ -1,5 +1,23 @@
#!/bin/zsh
+# Sed compatibility wrapper
+function sediment() {
+ if [[ "$OSTYPE" == "darwin"* ]]; then
+ sed -i '' "$@"
+ else
+ sed -i "$@"
+ fi
+}
+
+
+# Patch compatibility wrapper
+function patchment() {
+ # -f : Force. Do not ask questions. (Standard in GNU and BSD patch)
+ # -N : Ignore patches that seem to be reversed or already applied (Forward)
+ # But we control these flags in the caller.
+ patch "$@"
+}
+
function at-repos-env() {
APP_PASSWORD=xxx
host=syu.is
@@ -102,26 +120,23 @@ function at-repos-social-app-avatar-write() {
did_admin=did:plc:6qyecktefllvenje24fcxnie
dt=$d/repos/social-app/src
cd $dt
- grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g"
- grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g"
+ grep -R syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/syu.is/${host}/g"
+ grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/web.syu.is/web.${host}/g"
f=$dt/lib/constants.ts
- sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
- sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
- sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
- sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
- sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
-
- f=$dt/view/icons/Logotype.tsx
- o=$d/icons/Logotype.tsx
- cp -rf $o $f
+ sediment "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
+ sediment "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
+ sediment "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
+ sediment "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
+ sediment "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
+
f=$dt/view/com/util/UserAvatar.tsx
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/view/com/util/UserAvatar.tsx -o $f
- sed -i "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
- sed -i "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
- sed -i "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
+ sediment "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
+ sediment "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
+ sediment "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/lib/strings/url-helpers.ts -o $dt/lib/strings/url-helpers.ts
- sed -i "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
- grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sed -i "s/${did_admin}/${did}/g"
+ sediment "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
+ grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sediment "s/${did_admin}/${did}/g"
}
@@ -139,7 +154,8 @@ function apply-patch() {
pushd ${target_dir} > /dev/null
# Check if patch is already applied (reverse dry-run succeeds)
- if patch --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
+ # Use -f to force dry-run to fail instead of asking questions if unapplied
+ if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
echo "✅ Already applied - skipping"
popd > /dev/null
echo ""
@@ -147,9 +163,9 @@ function apply-patch() {
fi
# Check if patch can be applied (forward dry-run succeeds)
- if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
+ if patch -f --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
echo "🔧 Applying patch..."
- if patch -p1 < ${patch_file}; then
+ if patch -f -p1 < ${patch_file}; then
echo "✅ Applied successfully"
popd > /dev/null
echo ""
@@ -284,29 +300,29 @@ function at-repos-ozone-patch() {
fi
# Replace process.env with env()
- sed -i 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
- sed -i 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
+ sediment 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
+ sediment 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
# Add missing SOCIAL_APP_DOMAIN constant after SOCIAL_APP_URL
- sed -i '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
+ sediment '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
export const SOCIAL_APP_DOMAIN =\
env('\''NEXT_PUBLIC_SOCIAL_APP_DOMAIN'\'') || '\''bsky.app'\''\
}' lib/constants.ts 2>/dev/null || true
# Fix multiline process.env patterns
- sed -i '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
+ sediment '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
s/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
- sed -i '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
+ sediment '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
s/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
- sed -i '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
+ sediment '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
s/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/export const HIGH_PROFILE_FOLLOWER_THRESHOLD = env('\''NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'\'')/
/^ \.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD$/d
}' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || ''
- sed -i "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
+ sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
popd > /dev/null
}
diff --git a/ios/.env b/ios/.env
new file mode 100644
index 0000000..43469ef
--- /dev/null
+++ b/ios/.env
@@ -0,0 +1,13 @@
+APP_NAME="Aiat"
+REPO_DIR="../repos/social-app"
+APP_SLUG="aiat"
+APP_SCHEME="syui"
+BUNDLE_ID="ai.syui.at"
+APP_GROUP="group.ai.syui.at"
+SERVICE_URL="https://syu.is"
+HELP_URL="https://syu.is/about/support/help"
+PRIVACY_URL="https://syu.is/about/support/privacy-policy"
+TERMS_URL="https://syu.is/about/support/tos"
+REPO_DIR="../repos/social-app"
+CONFIG_FILE="$REPO_DIR/app.config.js"
+CONSTANTS_FILE="$REPO_DIR/src/lib/constants.ts"
diff --git a/ios/AppInfo.tsx b/ios/AppInfo.tsx
deleted file mode 100644
index 6242b12..0000000
--- a/ios/AppInfo.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import React from 'react'
-import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
-
-interface AppInfoProps {
- onLinkPress?: (url: string) => void
-}
-
-export default function AppInfo({onLinkPress}: AppInfoProps) {
- const handleLinkPress = (url: string) => {
- if (onLinkPress) {
- onLinkPress(url)
- } else {
- Linking.openURL(url)
- }
- }
-
- return (
-
-
- About This App
-
- This is a customized AT Protocol social networking client. It allows you to
- connect to any Personal Data Server (PDS) and participate in the decentralized
- social network.
-
-
-
-
- Key Features
-
- • Connect to any AT Protocol PDS
- • Post text, images, and videos
- • Follow users and view timelines
- • Customize feeds and moderation settings
- • Direct messaging support
-
-
-
-
- Open Source
-
- This application is based on the Bluesky social-app, licensed under the MIT
- License. The original source code is available at:
-
-
- handleLinkPress('https://github.com/bluesky-social/social-app')
- }>
- github.com/bluesky-social/social-app
-
-
-
-
- AT Protocol
-
- This app uses the AT Protocol (Authenticated Transfer Protocol), an open and
- decentralized standard for social applications.
-
- handleLinkPress('https://atproto.com')}>
- atproto.com
-
-
-
-
- License
-
- Copyright 2023–2025 Bluesky Social PBC
-
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software.
-
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
-
-
-
-
- Contact
- handleLinkPress('https://syu.is')}>
- https://syu.is
-
-
-
-
- Version 1.0.0
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- section: {
- marginBottom: 24,
- },
- sectionTitle: {
- fontSize: 20,
- fontWeight: '600',
- color: '#1d1d1f',
- marginBottom: 12,
- },
- paragraph: {
- fontSize: 15,
- lineHeight: 22,
- color: '#3a3a3c',
- marginBottom: 8,
- },
- list: {
- marginLeft: 8,
- marginTop: 8,
- },
- listItem: {
- fontSize: 15,
- lineHeight: 24,
- color: '#3a3a3c',
- },
- link: {
- fontSize: 15,
- color: '#007aff',
- textDecorationLine: 'underline',
- marginTop: 8,
- },
- versionText: {
- fontSize: 13,
- color: '#8e8e93',
- fontStyle: 'italic',
- },
-})
diff --git a/ios/LicenseNotice.tsx b/ios/LicenseNotice.tsx
deleted file mode 100644
index 496d367..0000000
--- a/ios/LicenseNotice.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react'
-import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
-
-export default function LicenseNotice() {
- return (
-
- Open Source Licenses
-
-
- Bluesky Social App
- MIT License
- Copyright 2023–2025 Bluesky Social PBC
-
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
-
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
-
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
-
-
-
- Linking.openURL('https://github.com/bluesky-social/social-app')
- }>
- View Source Code
-
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- padding: 16,
- },
- title: {
- fontSize: 24,
- fontWeight: 'bold',
- marginBottom: 20,
- color: '#1d1d1f',
- },
- section: {
- marginBottom: 24,
- padding: 16,
- backgroundColor: '#f5f5f7',
- borderRadius: 8,
- },
- projectName: {
- fontSize: 18,
- fontWeight: '600',
- marginBottom: 8,
- color: '#1d1d1f',
- },
- license: {
- fontSize: 14,
- fontWeight: '500',
- color: '#007aff',
- marginBottom: 4,
- },
- copyright: {
- fontSize: 13,
- color: '#3a3a3c',
- marginBottom: 12,
- },
- licenseText: {
- fontSize: 12,
- lineHeight: 18,
- color: '#3a3a3c',
- marginBottom: 12,
- },
- link: {
- fontSize: 14,
- color: '#007aff',
- textDecorationLine: 'underline',
- marginTop: 8,
- },
-})
diff --git a/ios/PrivacyContent.tsx b/ios/PrivacyContent.tsx
deleted file mode 100644
index d780104..0000000
--- a/ios/PrivacyContent.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import React from 'react'
-import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
-
-interface PrivacyContentProps {
- onLinkPress?: (url: string) => void
-}
-
-export default function PrivacyContent({onLinkPress}: PrivacyContentProps) {
- const handleLinkPress = (url: string) => {
- if (onLinkPress) {
- onLinkPress(url)
- } else {
- Linking.openURL(url)
- }
- }
-
- return (
-
-
- Introduction
-
- This Privacy Policy explains how this AT Protocol client application
- (hereinafter referred to as "the App") handles personal information.
- Please read this policy carefully before using the App.
-
-
-
-
- Information We Collect
-
- The App may collect and use the following information:
-
-
- 1. Information Collected Automatically
-
- • Device information (model, OS version)
- • App usage data (sessions, features used)
- • Crash logs and performance data
-
-
- 2. Information Provided by Users
-
-
- • DID (Decentralized Identifier) and handle for authentication
-
- • Posts, media, and social interactions
- • Profile information (avatar, display name, bio)
-
-
-
-
- Important: Your data is stored on your chosen PDS (Personal Data Server).
- This app does not store your content on our servers.
-
-
-
-
-
- How We Use Your Information
-
-
- • To provide AT Protocol social networking features
-
- • To improve app performance and user experience
- • To diagnose and fix technical issues
-
-
-
-
- Data Sharing
-
- The App interacts with your chosen PDS and AppView services. Your posts and
- profile information are shared according to the AT Protocol specification and
- your privacy settings.
-
-
-
-
- Your Rights
-
- You have the right to access, modify, or delete your data through your PDS.
- You can also switch to a different PDS at any time while maintaining your
- identity.
-
-
-
-
- Contact
-
- For questions about this Privacy Policy, please contact:
-
- handleLinkPress('https://syu.is')}>
- https://syu.is
-
-
-
-
- Last Updated: December 3, 2025
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- section: {
- marginBottom: 24,
- },
- sectionTitle: {
- fontSize: 20,
- fontWeight: '600',
- color: '#1d1d1f',
- marginBottom: 12,
- },
- subTitle: {
- fontSize: 16,
- fontWeight: '500',
- color: '#1d1d1f',
- marginTop: 12,
- marginBottom: 8,
- },
- paragraph: {
- fontSize: 15,
- lineHeight: 22,
- color: '#3a3a3c',
- marginBottom: 8,
- },
- list: {
- marginLeft: 8,
- marginTop: 8,
- },
- listItem: {
- fontSize: 15,
- lineHeight: 24,
- color: '#3a3a3c',
- },
- highlight: {
- backgroundColor: '#fff3cd',
- borderLeftWidth: 4,
- borderLeftColor: '#ffc107',
- padding: 12,
- marginTop: 12,
- borderRadius: 4,
- },
- highlightText: {
- fontSize: 14,
- lineHeight: 20,
- color: '#856404',
- },
- link: {
- fontSize: 15,
- color: '#007aff',
- textDecorationLine: 'underline',
- marginTop: 8,
- },
- lastUpdated: {
- fontSize: 13,
- color: '#8e8e93',
- fontStyle: 'italic',
- },
-})
diff --git a/ios/PrivacyPolicy.screen.tsx b/ios/PrivacyPolicy.screen.tsx
deleted file mode 100644
index df5d5c8..0000000
--- a/ios/PrivacyPolicy.screen.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {
- type CommonNavigatorParams,
- type NativeStackScreenProps,
-} from '#/lib/routes/types'
-import {s} from '#/lib/styles'
-import {useSetMinimalShellMode} from '#/state/shell'
-import {ScrollView} from '#/view/com/util/Views'
-import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader'
-import PrivacyContent from '#/components/custom/PrivacyContent'
-
-type Props = NativeStackScreenProps
-export const PrivacyPolicyScreen = (_props: Props) => {
- const pal = usePalette('default')
- const {_} = useLingui()
- const setMinimalShellMode = useSetMinimalShellMode()
-
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
-
- return (
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/ios/README.md b/ios/README.md
new file mode 100644
index 0000000..de45071
--- /dev/null
+++ b/ios/README.md
@@ -0,0 +1,72 @@
+今回の./ios (social-app)開発の要点をまとめます。
+
+1. MITのライセンスを遵守すること、iosアプリとして出品しても問題ないようにすること
+https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
+
+2. "Bluesky"という名称を使用しないこと。アイコンの変更。リンクの変更
+
+3. selfhostでも動くこと。本来のsocial-appは動きませんので、これは不便なのでiosアプリに出品することにしました。なお、これはすでにpatchで実現しています。
+
+
+```sh
+$ ./install.zsh pull
+$ ./install.zsh patch
+$ ./ios/setup.zsh
+$ ./ios/preview.zsh
+```
+
+## 実装済み
+
+1. 最初の画面で、webではちゃんと私のサイトのロゴが表示されていますが、ios モバイル版では、未だにBluesky (icon)です。アカウント作成、サインイン、が表示されています。
+
+2. 上のメニューバーにもBlueskyのロゴが表示されています。
+
+3. サインイン後のホスティングプロバイダーで中身はsyu.isですが、表示は"Bluesky Social"になっています。これをsyu.isに変更してください。ios/webでコードは異なります。
+
+4. チャット機能
+チャット機能は今回無効化するので、下メニューバーやプロフィール、設定画面に表示しないでください。
+
+5. 設定ボタン(左カラム)を押すと、フィードバック、ヘルプが表示されますが、非表示にしてください。
+
+6. 設定ボタン(左カラム)を押すと、フィード、リスト、保存済みの項目がありますが、これを削除してください。
+
+7. 設定ボタン(左カラム)を押すと、下に利用規約、プライバシーポリシーが表示されますが、リンクがbsky.socialです。
+- /about/support/privacy-policy
+- /about/support/tos
+このページを独自に作って表示してください。
+
+8. LOG 09:52:20 (logger) Poll latest failed {
+ "feed": "following",
+ "message": "Error: Could not find repo: did:plc:z72i7hdynmk6r22z27h6tvur"
+}
+
+9. LOG 10:24:03 (metric) router:navigate
+ LOG 10:24:04 (dms-agent) init failed {
+ "safeMessage": "could not resolve iss did"
+}
+
+9. 設定ボタン(左カラム)の一番下、利用規約やプライバシーポリシーが表示されいてるライセンスという項目を追加。ページを追加して、ライセンスの表示。
+https://github.com/bluesky-social/social-app
+https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
+
+10. アカウント作成時(create account)のページに"Having trouble?"で`Contact support`のリンクがありますが、これを削除してください。
+
+11. スタートページ、つまり、`Create account`, `Sign in`があるページの一番下にライセンスページへのリンクを追加してください。また、footerに`© syui`を表示してください。このページのタイトル下にある文字`What's up?`の項目は削除。
+
+12. スタートページのラインセンスリンクが機能しない。おそらくページ変遷に問題があるため。また、ライセンスページは上下が隠れて見えてしまうため、大きく上下に空間を開けること。
+
+13. 利用規約、プライバシーポリシーのページの言語が日本語で書かれています。ラインセンスと同様に、英語を基本とし、日本語訳をその下に表示してください。
+
+14. Settings/ 項目の非表示を追加。
+- Helpの非表示
+- Aboutのリンクを変更
+
+## 壊れた実装
+
+1. ログイン後のメイン画面、"Following"の項目(フィード)に表示されるものをシンプルにします。表示するのはFollowingのみで、以下のものを削除してください。
+- おすすめの削除
+- Discoverの削除
+- アカウントを探すの削除
+
+2. 年齢保証、年齢確認ページがでてくるのを削除。誕生日を入力する処理を削除。アプリ配布国は限定します。
+
diff --git a/ios/Support.screen.tsx b/ios/Support.screen.tsx
deleted file mode 100644
index 3fddf92..0000000
--- a/ios/Support.screen.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
-import {
- type CommonNavigatorParams,
- type NativeStackScreenProps,
-} from '#/lib/routes/types'
-import {s} from '#/lib/styles'
-import {useSetMinimalShellMode} from '#/state/shell'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {ScrollView} from '#/view/com/util/Views'
-import * as Layout from '#/components/Layout'
-import AppInfo from '#/components/custom/AppInfo'
-
-type Props = NativeStackScreenProps
-export const SupportScreen = (_props: Props) => {
- const pal = usePalette('default')
- const setMinimalShellMode = useSetMinimalShellMode()
- const {_} = useLingui()
-
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
-
- return (
-
-
-
-
-
-
- )
-}
diff --git a/ios/app.config.patch.js b/ios/app.config.patch.js
deleted file mode 100644
index 136a4d9..0000000
--- a/ios/app.config.patch.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// Aiat app configuration overrides
-module.exports = {
- name: 'Aiat',
- slug: 'aiat',
- scheme: 'aiat',
- owner: 'syui', // Your Expo account
- bundleIdentifier: 'ai.syui.at',
- // Icon will be set separately
-}
diff --git a/ios/assets/app-icons/android_icon_core_aurora.png b/ios/assets/app-icons/android_icon_core_aurora.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_aurora.png differ
diff --git a/ios/assets/app-icons/android_icon_core_bonfire.png b/ios/assets/app-icons/android_icon_core_bonfire.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_bonfire.png differ
diff --git a/ios/assets/app-icons/android_icon_core_classic.png b/ios/assets/app-icons/android_icon_core_classic.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_classic.png differ
diff --git a/ios/assets/app-icons/android_icon_core_flat_black.png b/ios/assets/app-icons/android_icon_core_flat_black.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_black.png differ
diff --git a/ios/assets/app-icons/android_icon_core_flat_blue.png b/ios/assets/app-icons/android_icon_core_flat_blue.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_blue.png differ
diff --git a/ios/assets/app-icons/android_icon_core_flat_white.png b/ios/assets/app-icons/android_icon_core_flat_white.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_white.png differ
diff --git a/ios/assets/app-icons/android_icon_core_midnight.png b/ios/assets/app-icons/android_icon_core_midnight.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_midnight.png differ
diff --git a/ios/assets/app-icons/android_icon_core_sunrise.png b/ios/assets/app-icons/android_icon_core_sunrise.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_sunrise.png differ
diff --git a/ios/assets/app-icons/android_icon_core_sunset.png b/ios/assets/app-icons/android_icon_core_sunset.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_sunset.png differ
diff --git a/ios/assets/app-icons/android_icon_default_next.png b/ios/assets/app-icons/android_icon_default_next.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_default_next.png differ
diff --git a/ios/assets/app-icons/android_icon_legacy_dark.png b/ios/assets/app-icons/android_icon_legacy_dark.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_legacy_dark.png differ
diff --git a/ios/assets/app-icons/android_icon_legacy_light.png b/ios/assets/app-icons/android_icon_legacy_light.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/android_icon_legacy_light.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_aurora.png b/ios/assets/app-icons/ios_icon_core_aurora.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_aurora.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_bonfire.png b/ios/assets/app-icons/ios_icon_core_bonfire.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_bonfire.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_classic.png b/ios/assets/app-icons/ios_icon_core_classic.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_classic.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_flat_black.png b/ios/assets/app-icons/ios_icon_core_flat_black.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_black.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_flat_blue.png b/ios/assets/app-icons/ios_icon_core_flat_blue.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_blue.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_flat_white.png b/ios/assets/app-icons/ios_icon_core_flat_white.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_white.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_midnight.png b/ios/assets/app-icons/ios_icon_core_midnight.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_midnight.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_sunrise.png b/ios/assets/app-icons/ios_icon_core_sunrise.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_sunrise.png differ
diff --git a/ios/assets/app-icons/ios_icon_core_sunset.png b/ios/assets/app-icons/ios_icon_core_sunset.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_sunset.png differ
diff --git a/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png b/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png differ
diff --git a/ios/assets/app-icons/ios_icon_default.icon/icon.json b/ios/assets/app-icons/ios_icon_default.icon/icon.json
new file mode 100644
index 0000000..8a681cb
--- /dev/null
+++ b/ios/assets/app-icons/ios_icon_default.icon/icon.json
@@ -0,0 +1,31 @@
+{
+ "fill" : {
+ "automatic-gradient" : "srgb:0.00000,0.41569,1.00000,1.00000"
+ },
+ "groups" : [
+ {
+ "layers" : [
+ {
+ "fill" : "none",
+ "glass" : false,
+ "image-name" : "iOS transparent.png",
+ "name" : "iOS transparent"
+ }
+ ],
+ "shadow" : {
+ "kind" : "neutral",
+ "opacity" : 0.5
+ },
+ "translucency" : {
+ "enabled" : true,
+ "value" : 0.5
+ }
+ }
+ ],
+ "supported-platforms" : {
+ "circles" : [
+ "watchOS"
+ ],
+ "squares" : "shared"
+ }
+}
\ No newline at end of file
diff --git a/ios/assets/app-icons/ios_icon_default.icon/icon.png b/ios/assets/app-icons/ios_icon_default.icon/icon.png
new file mode 100644
index 0000000..4c67271
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default.icon/icon.png differ
diff --git a/ios/assets/app-icons/ios_icon_default_next.png b/ios/assets/app-icons/ios_icon_default_next.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default_next.png differ
diff --git a/ios/assets/app-icons/ios_icon_legacy_dark.png b/ios/assets/app-icons/ios_icon_legacy_dark.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_legacy_dark.png differ
diff --git a/ios/assets/app-icons/ios_icon_legacy_light.png b/ios/assets/app-icons/ios_icon_legacy_light.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/app-icons/ios_icon_legacy_light.png differ
diff --git a/ios/assets/logo.png b/ios/assets/logo.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/assets/logo.png differ
diff --git a/ios/bin/build.zsh b/ios/bin/build.zsh
deleted file mode 100755
index d1fe8ea..0000000
--- a/ios/bin/build.zsh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/zsh
-set -e
-
-d=~/ai/at/repos/social-app
-APP_NAME=Aiat
-PKG=aiat
-TEAM_NAME=
-TEAM_ID=
-CERT="Apple Distribution: ${TEAM_NAME} (${TEAM_ID})"
-MAIL=user@example.com
-KEY_CHAIN=EXAMPLE
-
-cd $d
-# npx expo prebuild --clean
-# cd ios && pod install && cd ..
-
-## アーカイブ
-xcodebuild -workspace ios/${PKG}.xcworkspace \
- -scheme ${PKG} \
- -configuration Release \
- -archivePath build/${APP_NAME}.xcarchive \
- -allowProvisioningUpdates \
- archive
-
-cd build
-
-# IPA作成
-rm -rf Payload ${APP_NAME}.ipa
-mkdir -p Payload
-cp -R ${APP_NAME}.xcarchive/Products/Applications/${PKG}.app Payload/
-cp ../store.mobileprovision Payload/${PKG}.app/embedded.mobileprovision
-
-# entitlements抽出
-security cms -D -i Payload/${PKG}.app/embedded.mobileprovision > /tmp/profile.plist
-/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist
-
-codesign -f -s "$CERT" Payload/${PKG}.app/Frameworks/*.framework 2>/dev/null || true
-codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${PKG}.app
-
-zip -r ${APP_NAME}.ipa Payload
-
-xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${MAIL}" -p "@keychain:${KEY_CHAIN}"
-
-echo "Upload complete"
diff --git a/ios/bin/install.zsh b/ios/bin/install.zsh
deleted file mode 100644
index 2e372c7..0000000
--- a/ios/bin/install.zsh
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/zsh
-
-if [ "$1" = "social-app-custom" ];then
- at-social-app-custom-pages
- at-social-app-custom-screens
- at-social-app-aiat-config
- at-social-app-aiat-logo
- at-origin-social-app
- exit
-fi
-
-function at-social-app-custom-pages() {
- d_=$d/repos/social-app
- custom=$d/social-app-custom
-
- echo "copying custom components to social-app"
-
- # Create components directory if not exists
- mkdir -p ${d_}/src/components/custom
-
- # Copy custom components
- cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/
- cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/
-
- echo "custom components copied successfully"
-}
-
-function at-social-app-aiat-config() {
- d_=$d/repos/social-app
- custom=$d/social-app-custom
-
- echo "applying Aiat configuration"
-
- # Update app.config.js
- cd ${d_}
-
- # Backup original
- cp app.config.js app.config.js.orig
-
- # Apply changes using sed
- sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js
- sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js
- sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js
- sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js
- sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js
-
- # Update package.json name
- sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json
-
- echo "Aiat configuration applied"
-}
-
-function at-social-app-aiat-logo() {
- d_=$d/repos/social-app
- custom=$d/social-app-custom
-
- echo "applying Aiat logo"
-
- # Create logo directory if not exists
- mkdir -p ${custom}/assets
-
- # Copy logo if exists in custom folder
- if [ -f ${custom}/assets/icon.png ]; then
- cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png
- echo "Aiat logo applied"
- else
- echo "Warning: Logo file not found at ${custom}/assets/icon.png"
- echo "Please add your logo file there"
- fi
-}
-
-function at-social-app-custom-screens() {
- d_=$d/repos/social-app
- custom=$d/social-app-custom
-
- echo "applying custom screens"
-
- # Copy custom screen files
- cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx
- cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx
- cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/
-
- echo "custom screens applied"
-}
-
-
diff --git a/ios/icon.png b/ios/icon.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/icon.png differ
diff --git a/ios/patching/001-social-app-ios-config.patch b/ios/patching/001-social-app-ios-config.patch
new file mode 100644
index 0000000..24f2818
--- /dev/null
+++ b/ios/patching/001-social-app-ios-config.patch
@@ -0,0 +1,156 @@
+diff --git a/app.config.js b/app.config.js
+index 246d8abd3..f56905b0a 100644
+--- a/app.config.js
++++ b/app.config.js
+@@ -18,10 +18,7 @@ module.exports = function (_config) {
+ const IS_DEV = !IS_TESTFLIGHT || !IS_PRODUCTION
+
+ const ASSOCIATED_DOMAINS = [
+- 'applinks:bsky.app',
+- 'applinks:staging.bsky.app',
+- 'appclips:bsky.app',
+- 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes
++ 'applinks:syu.is',
+ // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
+ ...(IS_DEV || IS_TESTFLIGHT ? [] : []),
+ ]
+@@ -33,27 +30,24 @@ module.exports = function (_config) {
+ return {
+ expo: {
+ version: VERSION,
+- name: 'Bluesky',
+- slug: 'bluesky',
+- scheme: 'bluesky',
++ name: 'Aiat',
++ slug: 'aiat',
++ scheme: 'syui',
+ owner: 'blueskysocial',
+ runtimeVersion: {
+ policy: 'appVersion',
+ },
+- icon: './assets/app-icons/ios_icon_default_next.png',
++ icon: './assets/logo.png',
+ userInterfaceStyle: 'automatic',
+ primaryColor: '#1083fe',
+ newArchEnabled: false,
+ ios: {
+ supportsTablet: false,
+- bundleIdentifier: 'xyz.blueskyweb.app',
++ bundleIdentifier: 'ai.syui.at',
+ config: {
+ usesNonExemptEncryption: false,
+ },
+- icon:
+- PLATFORM === 'web' // web build doesn't like .icon files
+- ? './assets/app-icons/ios_icon_default_next.png'
+- : './assets/app-icons/ios_icon_default.icon',
++ icon: './assets/logo.png',
+ infoPlist: {
+ UIBackgroundModes: ['remote-notification'],
+ NSCameraUsageDescription:
+@@ -113,7 +107,7 @@ module.exports = function (_config) {
+ entitlements: {
+ 'com.apple.developer.kernel.increased-memory-limit': true,
+ 'com.apple.developer.kernel.extended-virtual-addressing': true,
+- 'com.apple.security.application-groups': 'group.app.bsky',
++ 'com.apple.security.application-groups': 'group.ai.syui.at',
+ },
+ privacyManifests: {
+ NSPrivacyCollectedDataTypes: [
+@@ -175,14 +169,14 @@ module.exports = function (_config) {
+ barStyle: 'light-content',
+ },
+ android: {
+- icon: './assets/app-icons/android_icon_default_next.png',
++ icon: './assets/logo.png',
+ adaptiveIcon: {
+ foregroundImage: './assets/icon-android-foreground.png',
+ monochromeImage: './assets/icon-android-monochrome.png',
+ backgroundColor: '#006AFF',
+ },
+ googleServicesFile: './google-services.json',
+- package: 'xyz.blueskyweb.app',
++ package: 'ai.syui.at',
+ intentFilters: [
+ {
+ action: 'VIEW',
+@@ -190,7 +184,7 @@ module.exports = function (_config) {
+ data: [
+ {
+ scheme: 'https',
+- host: 'bsky.app',
++ host: 'syu.is',
+ },
+ IS_DEV && {
+ scheme: 'http',
+@@ -213,9 +207,9 @@ 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',
+ },
+@@ -225,7 +219,7 @@ module.exports = function (_config) {
+ 'expo-web-browser',
+ [
+ 'react-native-edge-to-edge',
+- {android: {enforceNavigationBarContrast: false}},
++ { android: { enforceNavigationBarContrast: false } },
+ ],
+ USE_SENTRY && [
+ '@sentry/react-native/expo',
+@@ -264,7 +258,6 @@ module.exports = function (_config) {
+ networkInstrumentation: true,
+ },
+ ],
+- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
+ './plugins/withGradleJVMHeapSizeIncrease.js',
+ './plugins/withAndroidManifestLargeHeapPlugin.js',
+ './plugins/withAndroidManifestFCMIconPlugin.js',
+@@ -386,7 +379,7 @@ module.exports = function (_config) {
+ },
+ },
+ ],
+- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
++ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }],
+ ['expo-location'],
+ ].filter(Boolean),
+ extra: {
+@@ -394,30 +387,7 @@ module.exports = function (_config) {
+ build: {
+ experimental: {
+ ios: {
+- appExtensions: [
+- {
+- targetName: 'Share-with-Bluesky',
+- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky',
+- entitlements: {
+- 'com.apple.security.application-groups': [
+- 'group.app.bsky',
+- ],
+- },
+- },
+- {
+- targetName: 'BlueskyNSE',
+- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE',
+- entitlements: {
+- 'com.apple.security.application-groups': [
+- 'group.app.bsky',
+- ],
+- },
+- },
+- {
+- targetName: 'BlueskyClip',
+- bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
+- },
+- ],
++ appExtensions: [],
+ },
+ },
+ },
diff --git a/ios/patching/002-social-app-ios-lib.patch b/ios/patching/002-social-app-ios-lib.patch
new file mode 100644
index 0000000..0ef4ec5
--- /dev/null
+++ b/ios/patching/002-social-app-ios-lib.patch
@@ -0,0 +1,79 @@
+diff --git a/src/lib/constants.ts b/src/lib/constants.ts
+index 231447b4f..33c51cc0a 100644
+--- a/src/lib/constants.ts
++++ b/src/lib/constants.ts
+@@ -7,12 +7,12 @@ import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env'
+ export const LOCAL_DEV_SERVICE =
+ Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
+ export const STAGING_SERVICE = 'https://staging.bsky.dev'
+-export const BSKY_SERVICE = 'https://bsky.social'
+-export const BSKY_SERVICE_DID = 'did:web:bsky.social'
+-export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
++export const BSKY_SERVICE = 'https://syu.is'
++export const BSKY_SERVICE_DID = 'did:web:syu.is'
++export const PUBLIC_BSKY_SERVICE = 'https://bsky.syu.is'
+ export const DEFAULT_SERVICE = BSKY_SERVICE
+-const HELP_DESK_LANG = 'en-us'
+-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
++const HELP_DESK_LANG = 'ja-jp'
++export const HELP_DESK_URL = 'https://syu.is/about/support/help'
+ export const EMBED_SERVICE = 'https://embed.bsky.app'
+ export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
+ export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
+@@ -209,8 +209,8 @@ export const urls = {
+ },
+ }
+
+-export const PUBLIC_APPVIEW = 'https://api.bsky.app'
+-export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'
++export const PUBLIC_APPVIEW = 'https://bsky.syu.is'
++export const PUBLIC_APPVIEW_DID = 'did:web:bsky.syu.is'
+ export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev'
+
+ export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same
+@@ -236,8 +236,8 @@ export const BLUESKY_MOD_SERVICE_HEADERS = {
+ }
+
+ export const webLinks = {
+- tos: `https://bsky.social/about/support/tos`,
+- privacy: `https://bsky.social/about/support/privacy-policy`,
++ tos: `https://syu.is/about/support/tos`,
++ privacy: `https://syu.is/about/support/privacy-policy`,
+ community: `https://bsky.social/about/support/community-guidelines`,
+ communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`,
+ }
+diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
+index 860e841eb..a595b0868 100644
+--- a/src/lib/statsig/statsig.tsx
++++ b/src/lib/statsig/statsig.tsx
+@@ -265,6 +265,7 @@ export async function tryFetchGates(
+ }
+
+ export function initialize() {
++ if (!SDK_KEY) return Promise.resolve()
+ return Statsig.initialize(SDK_KEY, null, createStatsigOptions([]))
+ }
+
+@@ -309,6 +310,9 @@ export function Provider({children}: {children: React.ReactNode}) {
+ return () => clearInterval(id)
+ }, [handleIntervalTick])
+
++ if (!SDK_KEY) {
++ return {children}
++ }
+ return (
+
+ 0), }}
+ accessibilityRole="image"
+ />
+ ) : (
+@@ -619,7 +619,7 @@ export {PreviewableUserAvatar}
+ // -prf
+ function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string {
+ return isEnabled
+- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/')
++ ? uri.replace('https://cdn.web.syu.is/img/avatar/plain/', 'https://bsky.syu.is/img/avatar/plain/')
+ : uri
+ }
+
+diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx
+index d7208df13..b711f71c7 100644
+--- a/src/view/icons/Logo.tsx
++++ b/src/view/icons/Logo.tsx
+@@ -1,75 +1,17 @@
+ import React from 'react'
+-import {type TextProps} from 'react-native'
+-import Svg, {
+- Defs,
+- LinearGradient,
+- Path,
+- type PathProps,
+- Stop,
+- type SvgProps,
+-} from 'react-native-svg'
+ import {Image} from 'expo-image'
++import {flatten} from '#/alf'
+
+-import {useKawaiiMode} from '#/state/preferences/kawaii'
+-import {flatten, useTheme} from '#/alf'
+-
+-const ratio = 57 / 64
+-
+-type Props = {
+- fill?: PathProps['fill']
+- style?: TextProps['style']
+-} & Omit
+-
+-export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
+- const t = useTheme()
+- const {fill, ...rest} = props
+- const gradient = fill === 'sky'
+- const styles = flatten(props.style)
+- const _fill = gradient
+- ? 'url(#sky)'
+- : fill || styles?.color || t.palette.primary_500
+- // @ts-ignore it's fiiiiine
+- const size = parseInt(rest.width || 32, 10)
+-
+- const isKawaii = useKawaiiMode()
+-
+- if (isKawaii) {
+- return (
+- 100
+- ? require('../../../assets/kawaii.png')
+- : require('../../../assets/kawaii_smol.png')
+- }
+- accessibilityLabel="Bluesky"
+- accessibilityHint=""
+- accessibilityIgnoresInvertColors
+- style={[{height: size, aspectRatio: 1.4}]}
+- />
+- )
+- }
+-
++export const Logo = React.forwardRef(function LogoImpl(props: any, ref) {
++ const {width, style} = props
++ // @ts-ignore
++ const size = parseInt(width || 32, 10)
+ return (
+-
++
+ )
+ })
+diff --git a/src/view/icons/Logotype.tsx b/src/view/icons/Logotype.tsx
+index 270c913fc..a60ffe07c 100644
+--- a/src/view/icons/Logotype.tsx
++++ b/src/view/icons/Logotype.tsx
+@@ -1,28 +1,22 @@
+-import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg'
+-
+-import {usePalette} from '#/lib/hooks/usePalette'
+-
+-const ratio = 17 / 64
+-
+-export function Logotype({
+- fill,
+- ...rest
+-}: {fill?: PathProps['fill']} & SvgProps) {
+- const pal = usePalette('default')
+- // @ts-ignore it's fiiiiine
+- const size = parseInt(rest.width || 32)
++import React from 'react'
++import {Text} from 'react-native'
++import {useTheme, atoms as a} from '#/alf'
+
++export function Logotype({width, fill, style}: any) {
++ const t = useTheme()
++ const fontSize = width ? parseInt(width) / 3.5 : 22
++
+ return (
+-
++
++ Aiat
++
+ )
+ }
+diff --git a/src/Splash.tsx b/src/Splash.tsx
+index 47e70b375..616f351ed 100644
+--- a/src/Splash.tsx
++++ b/src/Splash.tsx
+@@ -15,8 +15,8 @@ import Animated, {
+ withTiming,
+ } from 'react-native-reanimated'
+ import {useSafeAreaInsets} from 'react-native-safe-area-context'
+-import Svg, {Path, type SvgProps} from 'react-native-svg'
+ import {Image} from 'expo-image'
++import {type SvgProps} from 'react-native-svg'
+ import * as SplashScreen from 'expo-splash-screen'
+
+ import {Logotype} from '#/view/icons/Logotype'
+@@ -29,21 +29,18 @@ const darkSplashImageUri = RNImage.resolveAssetSource(
+ darkSplashImagePointer,
+ ).uri
+
+-export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) {
+- const width = 1000
+- const height = width * (67 / 64)
++export const Logo = React.forwardRef(function LogoImpl(props: SvgProps & {fill?: string}, ref) {
++ const size = 1000
++ // @ts-ignore
+ return (
+-
++ source={require('../assets/logo.png')}
++ style={[{width: size, height: size}, props.style]}
++ contentFit="contain"
++ accessibilityLabel="Logo"
++ />
+ )
+ })
+
diff --git a/ios/patching/004-social-app-ios-core.patch b/ios/patching/004-social-app-ios-core.patch
new file mode 100644
index 0000000..1f61904
--- /dev/null
+++ b/ios/patching/004-social-app-ios-core.patch
@@ -0,0 +1,72 @@
+diff --git a/src/App.native.tsx b/src/App.native.tsx
+index fb3008627..539ebc055 100644
+--- a/src/App.native.tsx
++++ b/src/App.native.tsx
+@@ -92,7 +92,7 @@ if (isAndroid) {
+ * Begin geolocation ASAP
+ */
+ Geo.resolve()
+-prefetchAgeAssuranceConfig()
++// // // prefetchAgeAssuranceConfig()
+
+ function InnerApp() {
+ const [isReady, setIsReady] = React.useState(false)
+diff --git a/src/routes.ts b/src/routes.ts
+index 1ed913bb2..c80340edb 100644
+--- a/src/routes.ts
++++ b/src/routes.ts
+@@ -71,8 +71,8 @@ export const router = new Router({
+ MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous',
+ // support
+ Support: '/support',
+- PrivacyPolicy: '/support/privacy',
+- TermsOfService: '/support/tos',
++ PrivacyPolicy: 'https://syu.is/about/support/privacy-policy',
++ TermsOfService: 'https://syu.is/about/support/tos',
+ CommunityGuidelines: '/support/community-guidelines',
+ CopyrightPolicy: '/support/copyright',
+ // hashtags
+diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts
+index 5c8ce3b97..ee85beb08 100644
+--- a/src/state/session/agent.ts
++++ b/src/state/session/agent.ts
+@@ -47,7 +47,8 @@ export function createPublicAgent() {
+ configureModerationForGuest() // Side effect but only relevant for tests
+
+ const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE})
+- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
++ // Disable proxy for self-hosted environments
++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ return agent
+ }
+
+@@ -88,7 +89,8 @@ export async function createAgentAndResume(
+ // after session is attached
+ const aa = prefetchAgeAssuranceData({agent})
+
+- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
++ // Disable proxy for self-hosted environments
++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+
+ return agent.prepare({
+ resolvers: [gates, moderation, aa],
+@@ -127,7 +129,8 @@ export async function createAgentAndLogin(
+ const moderation = configureModerationForAccount(agent, account)
+ const aa = prefetchAgeAssuranceData({agent})
+
+- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
++ // Disable proxy for self-hosted environments
++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+
+ return agent.prepare({
+ resolvers: [gates, moderation, aa],
+@@ -299,7 +302,8 @@ export async function createAgentAndCreateAccount(
+ logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`})
+ }
+
+- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
++ // Disable proxy for self-hosted environments
++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+
+ return agent.prepare({
+ resolvers: [gates, moderation, aa],
diff --git a/ios/patching/005-social-app-ios-screens.patch b/ios/patching/005-social-app-ios-screens.patch
new file mode 100644
index 0000000..1853137
--- /dev/null
+++ b/ios/patching/005-social-app-ios-screens.patch
@@ -0,0 +1,570 @@
+diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
+index 6b8257b91..35202224b 100644
+--- a/src/screens/Settings/AboutSettings.tsx
++++ b/src/screens/Settings/AboutSettings.tsx
+@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) {
+
+
+
+
+
+@@ -88,7 +88,7 @@ export function AboutSettingsScreen({}: Props) {
+
+
+
+
+
+diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx
+index dd319a4c6..0e80f956a 100644
+--- a/src/screens/Takendown.tsx
++++ b/src/screens/Takendown.tsx
+@@ -223,11 +223,11 @@ export function Takendown() {
+
+ Your account was found to be in violation of the{' '}
+
+- Bluesky Social Terms of Service
++ syu.is Terms of Service
+
+ . You have been sent an email outlining the specific violation
+ and suspension period, if applicable. You can appeal this
+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 } })
+diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
+index a89eaadc4..228af4966 100644
+--- a/src/view/screens/PrivacyPolicy.tsx
++++ b/src/view/screens/PrivacyPolicy.tsx
+@@ -1,52 +1,13 @@
+ import React from 'react'
+-import {View} from 'react-native'
+-import {msg, Trans} from '@lingui/macro'
+-import {useLingui} from '@lingui/react'
+-import {useFocusEffect} from '@react-navigation/native'
+-
+-import {usePalette} from '#/lib/hooks/usePalette'
+-import {
+- type CommonNavigatorParams,
+- type NativeStackScreenProps,
+-} from '#/lib/routes/types'
+-import {s} from '#/lib/styles'
+-import {useSetMinimalShellMode} from '#/state/shell'
+-import {TextLink} from '#/view/com/util/Link'
+-import {Text} from '#/view/com/util/text/Text'
+-import {ScrollView} from '#/view/com/util/Views'
++import { WebView } from 'react-native-webview'
+ import * as Layout from '#/components/Layout'
+-import {ViewHeader} from '../com/util/ViewHeader'
+-
+-type Props = NativeStackScreenProps
+-export const PrivacyPolicyScreen = (_props: Props) => {
+- const pal = usePalette('default')
+- const {_} = useLingui()
+- const setMinimalShellMode = useSetMinimalShellMode()
+-
+- useFocusEffect(
+- React.useCallback(() => {
+- setMinimalShellMode(false)
+- }, [setMinimalShellMode]),
+- )
++import {useSetTitle} from '#/lib/hooks/useSetTitle'
+
++export function PrivacyPolicyScreen() {
++ useSetTitle('Privacy Policy')
+ return (
+
+-
+-
+-
+-
+-
+- The Privacy Policy has been moved to{' '}
+-
+-
+-
+-
+-
+-
++
+
+ )
+ }
+diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
+index d843c713c..c0b34c886 100644
+--- a/src/view/screens/TermsOfService.tsx
++++ b/src/view/screens/TermsOfService.tsx
+@@ -1,50 +1,13 @@
+ import React from 'react'
+-import {View} from 'react-native'
+-import {msg, Trans} from '@lingui/macro'
+-import {useLingui} from '@lingui/react'
+-import {useFocusEffect} from '@react-navigation/native'
+-
+-import {usePalette} from '#/lib/hooks/usePalette'
+-import {
+- type CommonNavigatorParams,
+- type NativeStackScreenProps,
+-} from '#/lib/routes/types'
+-import {s} from '#/lib/styles'
+-import {useSetMinimalShellMode} from '#/state/shell'
+-import {TextLink} from '#/view/com/util/Link'
+-import {Text} from '#/view/com/util/text/Text'
+-import {ScrollView} from '#/view/com/util/Views'
++import { WebView } from 'react-native-webview'
+ import * as Layout from '#/components/Layout'
+-import {ViewHeader} from '../com/util/ViewHeader'
+-
+-type Props = NativeStackScreenProps
+-export const TermsOfServiceScreen = (_props: Props) => {
+- const pal = usePalette('default')
+- const setMinimalShellMode = useSetMinimalShellMode()
+- const {_} = useLingui()
+-
+- useFocusEffect(
+- React.useCallback(() => {
+- setMinimalShellMode(false)
+- }, [setMinimalShellMode]),
+- )
++import {useSetTitle} from '#/lib/hooks/useSetTitle'
+
++export function TermsOfServiceScreen() {
++ useSetTitle('Terms of Service')
+ return (
+
+-
+-
+-
+-
+- The Terms of Service have been moved to{' '}
+-
+-
+-
+-
+-
++
+
+ )
+ }
diff --git a/ios/patching/006-social-app-ios-shell.patch b/ios/patching/006-social-app-ios-shell.patch
new file mode 100644
index 0000000..b2ea406
--- /dev/null
+++ b/ios/patching/006-social-app-ios-shell.patch
@@ -0,0 +1,713 @@
+diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx
+index 9915d0a2d..c200a7c67 100644
+--- a/src/components/dialogs/BirthDateSettings.tsx
++++ b/src/components/dialogs/BirthDateSettings.tsx
+@@ -163,7 +163,7 @@ function BirthdayInner({
+
+ You must be at least 13 years old to use Bluesky. Read our{' '}
+
+ Terms of Service
+ {' '}
+diff --git a/src/components/dialogs/ServerInput.tsx b/src/components/dialogs/ServerInput.tsx
+index d7c02bb9f..fda1dfe4a 100644
+--- a/src/components/dialogs/ServerInput.tsx
++++ b/src/components/dialogs/ServerInput.tsx
+@@ -165,7 +165,7 @@ function DialogInner({
+
+ Bluesky is an open network where you can choose your own
+ provider. If you're new here, we recommend sticking with the
+- default Bluesky Social option.
++ default syu.is option.
+
+
+
+diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
+index ed2a6cfb7..2f387b4a8 100644
+--- a/src/view/shell/Drawer.tsx
++++ b/src/view/shell/Drawer.tsx
+@@ -1,60 +1,50 @@
+-import React, {type ComponentProps, type JSX} from 'react'
+-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native'
+-import {useSafeAreaInsets} from 'react-native-safe-area-context'
+-import {msg, Plural, plural, Trans} from '@lingui/macro'
+-import {useLingui} from '@lingui/react'
+-import {StackActions, useNavigation} from '@react-navigation/native'
+-
+-import {useActorStatus} from '#/lib/actor-status'
+-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
+-import {type PressableScale} from '#/lib/custom-animations/PressableScale'
+-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
+-import {getTabState, TabState} from '#/lib/routes/helpers'
+-import {type NavigationProp} from '#/lib/routes/types'
+-import {sanitizeHandle} from '#/lib/strings/handles'
+-import {colors} from '#/lib/styles'
+-import {isWeb} from '#/platform/detection'
+-import {emitSoftReset} from '#/state/events'
+-import {useKawaiiMode} from '#/state/preferences/kawaii'
+-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
+-import {useProfileQuery} from '#/state/queries/profile'
+-import {type SessionAccount, useSession} from '#/state/session'
+-import {useSetDrawerOpen} from '#/state/shell'
+-import {formatCount} from '#/view/com/util/numeric/format'
+-import {UserAvatar} from '#/view/com/util/UserAvatar'
+-import {NavSignupCard} from '#/view/shell/NavSignupCard'
+-import {atoms as a, tokens, useTheme, web} from '#/alf'
+-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+-import {Divider} from '#/components/Divider'
++import React, { type ComponentProps, type JSX } from 'react'
++import { Linking, ScrollView, TouchableOpacity, View } from 'react-native'
++import { useSafeAreaInsets } from 'react-native-safe-area-context'
++import { msg, Plural, plural, Trans } from '@lingui/macro'
++import { useLingui } from '@lingui/react'
++import { StackActions, useNavigation } from '@react-navigation/native'
++
++import { useActorStatus } from '#/lib/actor-status'
++import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants'
++import { type PressableScale } from '#/lib/custom-animations/PressableScale'
++import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState'
++import { getTabState, TabState } from '#/lib/routes/helpers'
++import { type NavigationProp } from '#/lib/routes/types'
++import { sanitizeHandle } from '#/lib/strings/handles'
++import { colors } from '#/lib/styles'
++import { isWeb } from '#/platform/detection'
++import { emitSoftReset } from '#/state/events'
++import { useKawaiiMode } from '#/state/preferences/kawaii'
++import { useUnreadNotifications } from '#/state/queries/notifications/unread'
++import { useProfileQuery } from '#/state/queries/profile'
++import { type SessionAccount, useSession } from '#/state/session'
++import { useSetDrawerOpen } from '#/state/shell'
++import { formatCount } from '#/view/com/util/numeric/format'
++import { UserAvatar } from '#/view/com/util/UserAvatar'
++import { NavSignupCard } from '#/view/shell/NavSignupCard'
++import { atoms as a, tokens, useTheme, web } from '#/alf'
++import { Button } from '#/components/Button'
++import { Divider } from '#/components/Divider'
+ import {
+ Bell_Filled_Corner0_Rounded as BellFilled,
+ Bell_Stroke2_Corner0_Rounded as Bell,
+ } from '#/components/icons/Bell'
+-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
+-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
+-import {
+- Hashtag_Filled_Corner0_Rounded as HashtagFilled,
+- Hashtag_Stroke2_Corner0_Rounded as Hashtag,
+-} from '#/components/icons/Hashtag'
+ import {
+ HomeOpen_Filled_Corner0_Rounded as HomeFilled,
+ HomeOpen_Stoke2_Corner0_Rounded as Home,
+ } from '#/components/icons/HomeOpen'
+-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
+-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
+-import {
+- Message_Stroke2_Corner0_Rounded as Message,
+- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
+-} from '#/components/icons/Message'
+-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
++import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass'
++import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2'
++import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2'
+ import {
+ UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
+ UserCircle_Stroke2_Corner0_Rounded as UserCircle,
+ } from '#/components/icons/UserCircle'
+-import {InlineLinkText} from '#/components/Link'
+-import {Text} from '#/components/Typography'
+-import {useSimpleVerificationState} from '#/components/verification'
+-import {VerificationCheck} from '#/components/verification/VerificationCheck'
++import { InlineLinkText } from '#/components/Link'
++import { Text } from '#/components/Typography'
++import { useSimpleVerificationState } from '#/components/verification'
++import { VerificationCheck } from '#/components/verification/VerificationCheck'
+
+ const iconWidth = 26
+
+@@ -65,11 +55,11 @@ let DrawerProfileCard = ({
+ account: SessionAccount
+ onPressProfile: () => void
+ }): React.ReactNode => {
+- const {_, i18n} = useLingui()
++ const { _, i18n } = useLingui()
+ const t = useTheme()
+- const {data: profile} = useProfileQuery({did: account.did})
+- const verification = useSimpleVerificationState({profile})
+- const {isActive: live} = useActorStatus(profile)
++ const { data: profile } = useProfileQuery({ did: account.did })
++ const verification = useSimpleVerificationState({ profile })
++ const { isActive: live } = useActorStatus(profile)
+
+ return (
+ ): React.ReactNode => {
++let DrawerContent = ({ }: React.PropsWithoutRef<{}>): React.ReactNode => {
+ const t = useTheme()
+ const insets = useSafeAreaInsets()
+ const setDrawerOpen = useSetDrawerOpen()
+@@ -150,27 +139,20 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+ const {
+ isAtHome,
+ isAtSearch,
+- isAtFeeds,
+- isAtBookmarks,
+ isAtNotifications,
+ isAtMyProfile,
+- isAtMessages,
+ } = useNavigationTabState()
+- const {hasSession, currentAccount} = useSession()
+-
+- // events
+- // =
++ const { hasSession, currentAccount } = useSession()
+
+ const onPressTab = React.useCallback(
+ (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
+ const state = navigation.getState()
+ setDrawerOpen(false)
+ if (isWeb) {
+- // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
+ if (tab === 'MyProfile') {
+- navigation.navigate('Profile', {name: currentAccount!.handle})
++ navigation.navigate('Profile', { name: currentAccount!.handle })
+ } else {
+- // @ts-expect-error struggles with string unions, apparently
++ // @ts-expect-error struggles with string unions
+ navigation.navigate(tab)
+ }
+ } else {
+@@ -178,21 +160,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+ if (tabState === TabState.InsideAtRoot) {
+ emitSoftReset()
+ } else if (tabState === TabState.Inside) {
+- // find the correct navigator in which to pop-to-top
+- const target = state.routes.find(route => route.name === `${tab}Tab`)
+- ?.state?.key
++ const target = state.routes.find(route => route.name === `${tab}Tab`)?.state?.key
+ if (target) {
+- // if we found it, trigger pop-to-top
+- navigation.dispatch({
+- ...StackActions.popToTop(),
+- target,
+- })
++ navigation.dispatch({ ...StackActions.popToTop(), target })
+ } else {
+- // fallback: reset navigation
+- navigation.reset({
+- index: 0,
+- routes: [{name: `${tab}Tab`}],
+- })
++ navigation.reset({ index: 0, routes: [{ name: `${tab}Tab` }] })
+ }
+ } else {
+ navigation.navigate(`${tab}Tab`)
+@@ -203,76 +175,21 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+ )
+
+ const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
+-
+- const onPressSearch = React.useCallback(
+- () => onPressTab('Search'),
+- [onPressTab],
+- )
+-
+- const onPressMessages = React.useCallback(
+- () => onPressTab('Messages'),
+- [onPressTab],
+- )
+-
+- const onPressNotifications = React.useCallback(
+- () => onPressTab('Notifications'),
+- [onPressTab],
+- )
+-
+- const onPressProfile = React.useCallback(() => {
+- onPressTab('MyProfile')
+- }, [onPressTab])
+-
+- const onPressMyFeeds = React.useCallback(() => {
+- navigation.navigate('Feeds')
+- setDrawerOpen(false)
+- }, [navigation, setDrawerOpen])
+-
+- const onPressLists = React.useCallback(() => {
+- navigation.navigate('Lists')
+- setDrawerOpen(false)
+- }, [navigation, setDrawerOpen])
+-
+- const onPressBookmarks = React.useCallback(() => {
+- navigation.navigate('Bookmarks')
+- setDrawerOpen(false)
+- }, [navigation, setDrawerOpen])
+-
++ const onPressSearch = React.useCallback(() => onPressTab('Search'), [onPressTab])
++ const onPressNotifications = React.useCallback(() => onPressTab('Notifications'), [onPressTab])
++ const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab])
+ const onPressSettings = React.useCallback(() => {
+ navigation.navigate('Settings')
+ setDrawerOpen(false)
+ }, [navigation, setDrawerOpen])
+
+- const onPressFeedback = React.useCallback(() => {
+- Linking.openURL(
+- FEEDBACK_FORM_URL({
+- email: currentAccount?.email,
+- handle: currentAccount?.handle,
+- }),
+- )
+- }, [currentAccount])
+-
+- const onPressHelp = React.useCallback(() => {
+- Linking.openURL(HELP_DESK_URL)
+- }, [])
+-
+- // rendering
+- // =
+-
+ return (
+
+
++ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
+
+ {hasSession && currentAccount ? (
+ ): React.ReactNode => {
+
+
+ )}
+-
+
+
+
+@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+ <>
+
+
+-
+
+-
+-
+-
+ ): React.ReactNode => {
+ ) : (
+ <>
+
+-
+
+ >
+ )}
+@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+
+
+
+-
+-
+
+ )
+ }
+ DrawerContent = React.memo(DrawerContent)
+-export {DrawerContent}
+-
+-let DrawerFooter = ({
+- onPressFeedback,
+- onPressHelp,
+-}: {
+- onPressFeedback: () => void
+- onPressHelp: () => void
+-}): React.ReactNode => {
+- const {_} = useLingui()
+- const insets = useSafeAreaInsets()
+- return (
+-
+-
+-
+-
+- )
+-}
+-DrawerFooter = React.memo(DrawerFooter)
++export { DrawerContent }
+
+ interface MenuItemProps extends ComponentProps {
+ icon: JSX.Element
+@@ -400,7 +250,7 @@ let SearchMenuItem = ({
+ isActive: boolean
+ onPress: () => void
+ }): React.ReactNode => {
+- const {_} = useLingui()
++ const { _ } = useLingui()
+ const t = useTheme()
+ return (
+
+
+ )
+ }
+diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
+index d843c713c..8d1d0d452 100644
+--- a/src/view/screens/TermsOfService.tsx
++++ b/src/view/screens/TermsOfService.tsx
+@@ -1,50 +1,61 @@
+ import React from 'react'
+-import {View} from 'react-native'
+-import {msg, Trans} from '@lingui/macro'
+-import {useLingui} from '@lingui/react'
+-import {useFocusEffect} from '@react-navigation/native'
+-
+-import {usePalette} from '#/lib/hooks/usePalette'
+-import {
+- type CommonNavigatorParams,
+- type NativeStackScreenProps,
+-} from '#/lib/routes/types'
+-import {s} from '#/lib/styles'
+-import {useSetMinimalShellMode} from '#/state/shell'
+-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 * as Layout from '#/components/Layout'
+-import {ViewHeader} from '../com/util/ViewHeader'
+-
+-type Props = NativeStackScreenProps
+-export const TermsOfServiceScreen = (_props: Props) => {
+- const pal = usePalette('default')
+- const setMinimalShellMode = useSetMinimalShellMode()
+- const {_} = useLingui()
+-
+- useFocusEffect(
+- React.useCallback(() => {
+- setMinimalShellMode(false)
+- }, [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 (
+
+-
+-
+-
+-
+- The Terms of Service have been moved to{' '}
+-
++
++
++
++ Terms of Service
++
++
++
++
++
++ 利用規約
++
++
++ 本規約は、当サービスの利用条件を定めるものです。
++
++
++ サービスの利用
++
++ ユーザーは、本規約に同意の上、当サービスを利用するものとします。
++ 違法行為、他者への迷惑行為は禁止されています。
++
++
++ アカウント
++
++ ユーザーは、アカウント情報を適切に管理する責任を負います。
++ 第三者による不正利用があった場合、速やかにご連絡ください。
++
++
++ 免責事項
++
++ 当サービスは現状のまま提供され、可用性、正確性について保証するものではありません。
++
++
++ 規約の変更
++
++ 本規約は、必要に応じて変更されることがあります。変更後の利用をもって、変更に同意したものとみなします。
++
++
++ お問い合わせ
++
++ 利用規約に関するお問い合わせは、https://syu.is までご連絡ください。
+
+-
+-
+-
++
++
+
+ )
+ }
diff --git a/ios/patching/009-social-app-ios-license.patch b/ios/patching/009-social-app-ios-license.patch
new file mode 100644
index 0000000..0d94461
--- /dev/null
+++ b/ios/patching/009-social-app-ios-license.patch
@@ -0,0 +1,53 @@
+diff --git a/src/Navigation.tsx b/src/Navigation.tsx
+index fa33a9d56..a9b724c4e 100644
+--- a/src/Navigation.tsx
++++ b/src/Navigation.tsx
+@@ -67,6 +67,7 @@ import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy'
+ import {Storybook} from '#/view/screens/Storybook'
+ import {SupportScreen} from '#/view/screens/Support'
+ import {TermsOfServiceScreen} from '#/view/screens/TermsOfService'
++import {LicenseScreen} from '#/view/screens/License'
+ import {BottomBar} from '#/view/shell/bottom-bar/BottomBar'
+ import {createNativeStackNavigatorWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth'
+ import {BookmarksScreen} from '#/screens/Bookmarks'
+@@ -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/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
+index ed2a6cfb7..0b429e6f3 100644
+--- a/src/view/shell/Drawer.tsx
++++ b/src/view/shell/Drawer.tsx
+@@ -460,6 +460,11 @@ function ExtraLinks() {
+
+ Privacy Policy
+
+
++ navigation.navigate('License')}>
++
++ License
++
++
+
+ )
+ }
diff --git a/ios/patching/010-social-app-ios-remove-contact-support.patch b/ios/patching/010-social-app-ios-remove-contact-support.patch
new file mode 100644
index 0000000..6392d9f
--- /dev/null
+++ b/ios/patching/010-social-app-ios-remove-contact-support.patch
@@ -0,0 +1,25 @@
+diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx
+index aa6cd4156..37c7a38b0 100644
+--- a/src/screens/Signup/index.tsx
++++ b/src/screens/Signup/index.tsx
+@@ -211,20 +211,6 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
+ a.align_center,
+ ]}>
+
+-
+- Having trouble?{' '}
+-
+- Contact support
+-
+-
+
+
+
diff --git a/ios/patching/011-social-app-ios-splash-license-footer.patch b/ios/patching/011-social-app-ios-splash-license-footer.patch
new file mode 100644
index 0000000..9a46e83
--- /dev/null
+++ b/ios/patching/011-social-app-ios-splash-license-footer.patch
@@ -0,0 +1,22 @@
+diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
+index 3442d1bdf..dd2d1fdfb 100644
+--- a/src/view/com/auth/SplashScreen.tsx
++++ b/src/view/com/auth/SplashScreen.tsx
+@@ -102,6 +102,17 @@ export const SplashScreen = ({
+
+
+
++
++
++ © 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
new file mode 100644
index 0000000..f69383e
--- /dev/null
+++ b/ios/patching/012-social-app-ios-settings-about-help.patch
@@ -0,0 +1,51 @@
+diff --git a/src/routes.ts b/src/routes.ts
+index 1ed913bb2..e3ddf7c67 100644
+--- a/src/routes.ts
++++ b/src/routes.ts
+@@ -71,8 +71,9 @@ export const router = new Router({
+ MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous',
+ // support
+ Support: '/support',
+- PrivacyPolicy: '/support/privacy',
++ PrivacyPolicy: '/support/privacy-policy',
+ TermsOfService: '/support/tos',
++ License: '/support/license',
+ CommunityGuidelines: '/support/community-guidelines',
+ CopyrightPolicy: '/support/copyright',
+ // hashtags
+diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
+index 6b8257b91..c204dcc31 100644
+--- a/src/screens/Settings/AboutSettings.tsx
++++ b/src/screens/Settings/AboutSettings.tsx
+@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) {
+
+
+
+
+
+@@ -88,13 +88,21 @@ export function AboutSettingsScreen({}: Props) {
+
+
+
+
+
+ Privacy Policy
+
+
++
++
++
++ License
++
++
+
diff --git a/ios/patching/013-social-app-ios-settings-remove-help.patch b/ios/patching/013-social-app-ios-settings-remove-help.patch
new file mode 100644
index 0000000..b81aaa0
--- /dev/null
+++ b/ios/patching/013-social-app-ios-settings-remove-help.patch
@@ -0,0 +1,21 @@
+diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
+index 1703036b5..42baa462c 100644
+--- a/src/screens/Settings/Settings.tsx
++++ b/src/screens/Settings/Settings.tsx
+@@ -231,16 +231,6 @@ export function SettingsScreen({}: Props) {
+ Languages
+
+
+- Linking.openURL(HELP_DESK_URL)}
+- label={_(msg`Help`)}
+- accessibilityHint={_(msg`Opens helpdesk in browser`)}>
+-
+-
+- Help
+-
+-
+-
+
+
+
diff --git a/ios/patching/019-social-app-ios-entitlements-plugin.patch b/ios/patching/019-social-app-ios-entitlements-plugin.patch
new file mode 100644
index 0000000..e5da947
--- /dev/null
+++ b/ios/patching/019-social-app-ios-entitlements-plugin.patch
@@ -0,0 +1,44 @@
+diff --git a/plugins/notificationsExtension/withNotificationsExtension.js b/plugins/notificationsExtension/withNotificationsExtension.js
+index 6a00cfd23..f91decc08 100644
+--- a/plugins/notificationsExtension/withNotificationsExtension.js
++++ b/plugins/notificationsExtension/withNotificationsExtension.js
+@@ -10,7 +10,7 @@ const EXTENSION_NAME = 'BlueskyNSE'
+ const EXTENSION_CONTROLLER_NAME = 'NotificationService'
+
+ const withNotificationsExtension = config => {
+- const soundFiles = ['dm.aiff']
++ const soundFiles = []
+
+ return withPlugins(config, [
+ // IOS
+diff --git a/plugins/withCodeSignEntitlements.js b/plugins/withCodeSignEntitlements.js
+new file mode 100644
+index 000000000..b03b6bd68
+--- /dev/null
++++ b/plugins/withCodeSignEntitlements.js
+@@ -0,0 +1,25 @@
++/* eslint-disable @typescript-eslint/no-var-requires */
++const { withXcodeProject } = require('@expo/config-plugins')
++
++const withCodeSignEntitlements = (config) => {
++ return withXcodeProject(config, (config) => {
++ const xcodeProject = config.modResults
++ const configurations = xcodeProject.pbxXCBuildConfigurationSection()
++
++ for (const key in configurations) {
++ const configuration = configurations[key]
++ if (
++ configuration.buildSettings &&
++ configuration.comment &&
++ !configuration.comment.includes('TEST')
++ ) {
++ configuration.buildSettings.CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION =
++ 'YES'
++ }
++ }
++
++ return config
++ })
++}
++
++module.exports = withCodeSignEntitlements
diff --git a/ios/patching/020-social-app-ios-bypass-age-assurance.patch b/ios/patching/020-social-app-ios-bypass-age-assurance.patch
new file mode 100644
index 0000000..9eca21d
--- /dev/null
+++ b/ios/patching/020-social-app-ios-bypass-age-assurance.patch
@@ -0,0 +1,30 @@
+diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
+index 9a0a9c9d5..5a6563e52 100644
+--- a/src/ageAssurance/index.tsx
++++ b/src/ageAssurance/index.tsx
+@@ -88,19 +88,16 @@ function InnerProvider({children}: {children: React.ReactNode}) {
+ return (
+ {
+- const chatDisabled = state.access !== AgeAssuranceAccess.Full
+- const isUnderage = data?.birthdate
+- ? isUserUnderAdultAge(data.birthdate)
+- : true
+- const adultContentDisabled =
+- state.access !== AgeAssuranceAccess.Full || isUnderage
+ return {
+ Access: AgeAssuranceAccess,
+ Status: AgeAssuranceStatus,
+- state,
++ state: {
++ ...state,
++ access: AgeAssuranceAccess.Full,
++ },
+ flags: {
+- adultContentDisabled,
+- chatDisabled,
++ adultContentDisabled: false,
++ chatDisabled: false,
+ },
+ }
+ }, [state, data])}>
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..155b0a2
--- /dev/null
+++ b/ios/patching/021-social-app-ios-clean-feed.patch
@@ -0,0 +1,247 @@
+diff --git a/src/view/com/posts/DiscoverFallbackHeader.tsx b/src/view/com/posts/DiscoverFallbackHeader.tsx
+index e35a33aaf..a36f84ae0 100644
+--- a/src/view/com/posts/DiscoverFallbackHeader.tsx
++++ b/src/view/com/posts/DiscoverFallbackHeader.tsx
+@@ -7,37 +7,5 @@ import {TextLink} from '../util/Link'
+ import {Text} from '../util/text/Text'
+
+ export function DiscoverFallbackHeader() {
+- const pal = usePalette('default')
+- return (
+-
+-
+-
+-
+-
+-
+-
+- We ran out of posts from your follows. Here's the latest from{' '}
+-
+- .
+-
+-
+-
+-
+- )
++ return null
+ }
+diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
+index 352cc1dc0..f477521af 100644
+--- a/src/view/com/posts/FollowingEmptyState.tsx
++++ b/src/view/com/posts/FollowingEmptyState.tsx
+@@ -1,37 +1,14 @@
+ import React from 'react'
+ import {StyleSheet, View} from 'react-native'
+-import {
+- FontAwesomeIcon,
+- type FontAwesomeIconStyle,
+-} from '@fortawesome/react-native-fontawesome'
+ import {Trans} from '@lingui/macro'
+-import {useNavigation} from '@react-navigation/native'
+
+ import {usePalette} from '#/lib/hooks/usePalette'
+ import {MagnifyingGlassIcon} from '#/lib/icons'
+-import {type NavigationProp} from '#/lib/routes/types'
+ import {s} from '#/lib/styles'
+-import {isWeb} from '#/platform/detection'
+-import {Button} from '../util/forms/Button'
+ import {Text} from '../util/text/Text'
+
+ export function FollowingEmptyState() {
+ const pal = usePalette('default')
+- const palInverted = usePalette('inverted')
+- const navigation = useNavigation()
+-
+- const onPressFindAccounts = React.useCallback(() => {
+- if (isWeb) {
+- navigation.navigate('Search', {})
+- } else {
+- navigation.navigate('SearchTab')
+- navigation.popToTop()
+- }
+- }, [navigation])
+-
+- const onPressDiscoverFeeds = React.useCallback(() => {
+- navigation.navigate('Feeds')
+- }, [navigation])
+
+ return (
+
+@@ -45,36 +22,6 @@ export function FollowingEmptyState() {
+ happening.
+
+
+-
+-
+-
+- You can also discover new Custom Feeds to follow.
+-
+-
+
+
+ )
+@@ -98,13 +45,4 @@ const styles = StyleSheet.create({
+ marginLeft: 'auto',
+ marginRight: 'auto',
+ },
+- emptyBtn: {
+- marginVertical: 20,
+- flexDirection: 'row',
+- alignItems: 'center',
+- justifyContent: 'space-between',
+- paddingVertical: 18,
+- paddingHorizontal: 24,
+- borderRadius: 30,
+- },
+ })
+diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx
+index e3c84d782..efb55d406 100644
+--- a/src/view/com/posts/FollowingEndOfFeed.tsx
++++ b/src/view/com/posts/FollowingEndOfFeed.tsx
+@@ -1,36 +1,13 @@
+ import React from 'react'
+ import {Dimensions, StyleSheet, View} from 'react-native'
+-import {
+- FontAwesomeIcon,
+- type FontAwesomeIconStyle,
+-} from '@fortawesome/react-native-fontawesome'
+ import {Trans} from '@lingui/macro'
+-import {useNavigation} from '@react-navigation/native'
+
+ import {usePalette} from '#/lib/hooks/usePalette'
+-import {type NavigationProp} from '#/lib/routes/types'
+ import {s} from '#/lib/styles'
+-import {isWeb} from '#/platform/detection'
+-import {Button} from '../util/forms/Button'
+ import {Text} from '../util/text/Text'
+
+ export function FollowingEndOfFeed() {
+ const pal = usePalette('default')
+- const palInverted = usePalette('inverted')
+- const navigation = useNavigation()
+-
+- const onPressFindAccounts = React.useCallback(() => {
+- if (isWeb) {
+- navigation.navigate('Search', {})
+- } else {
+- navigation.navigate('SearchTab')
+- navigation.popToTop()
+- }
+- }, [navigation])
+-
+- const onPressDiscoverFeeds = React.useCallback(() => {
+- navigation.navigate('Feeds')
+- }, [navigation])
+
+ return (
+
+
+
+-
+- You've reached the end of your feed! Find some more accounts to
+- follow.
+-
+-
+-
+-
+-
+- You can also discover new Custom Feeds to follow.
++ You've reached the end of your feed!
+
+-
+
+
+ )
+@@ -93,13 +37,4 @@ const styles = StyleSheet.create({
+ width: '100%',
+ maxWidth: 460,
+ },
+- emptyBtn: {
+- marginVertical: 20,
+- flexDirection: 'row',
+- alignItems: 'center',
+- justifyContent: 'space-between',
+- paddingVertical: 18,
+- paddingHorizontal: 24,
+- borderRadius: 30,
+- },
+ })
+diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
+index 4f25468c9..a72a10b80 100644
+--- a/src/view/com/posts/PostFeed.tsx
++++ b/src/view/com/posts/PostFeed.tsx
+@@ -766,7 +766,7 @@ let PostFeed = ({
+ } else if (row.type === 'feedShutdownMsg') {
+ return
+ } else if (row.type === 'interstitialFollows') {
+- return
++ return null
+ } else if (row.type === 'interstitialProgressGuide') {
+ return
+ } else if (row.type === 'ageAssuranceBanner') {
diff --git a/ios/patching/License.tsx b/ios/patching/License.tsx
new file mode 100644
index 0000000..f98cd6a
--- /dev/null
+++ b/ios/patching/License.tsx
@@ -0,0 +1,86 @@
+import React from 'react'
+import { ScrollView } from 'react-native'
+import * as Layout from '#/components/Layout'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+
+export function LicenseScreen() {
+ useSetTitle('License')
+ const t = useTheme()
+
+ return (
+
+
+ License
+
+
+ This application is based on Bluesky Social App.
+
+
+
+ https://github.com/bluesky-social/social-app
+
+
+ MIT License
+
+
+ Copyright (c) 2022-2025 Bluesky PBC
+
+
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+
+ 日本語訳(参考)
+
+
+ 本ソフトウェアおよび関連文書ファイル(以下「ソフトウェア」)のコピーを取得する
+ すべての人に対し、ソフトウェアを無制限に扱うことを無償で許可します。これには、
+ ソフトウェアのコピーを使用、複製、変更、結合、公開、配布、サブライセンス、
+ および/または販売する権利、ならびにソフトウェアを提供する相手にそうした行為を
+ 許可する権利が含まれますが、これらに限定されません。
+
+
+
+ 上記の著作権表示および本許諾表示を、ソフトウェアのすべてのコピーまたは
+ 重要な部分に記載するものとします。
+
+
+
+ ソフトウェアは「現状のまま」で提供され、明示黙示を問わず、商品性、特定目的への
+ 適合性、および権利非侵害についての保証を含む、いかなる種類の保証もなされません。
+ いかなる場合においても、作者または著作権者は、契約行為、不法行為、またはそれ以外で
+ あろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用または
+ その他の扱いによって生じる一切の請求、損害、その他の義務について責任を負わないものとします。
+
+
+
+ Original License: https://github.com/bluesky-social/social-app/blob/main/LICENSE
+
+
+
+ )
+}
diff --git a/ios/patching/README.md b/ios/patching/README.md
new file mode 100644
index 0000000..a7c6ba3
--- /dev/null
+++ b/ios/patching/README.md
@@ -0,0 +1,62 @@
+# iOS Social App Patches
+
+このディレクトリには、iOS版social-appのカスタマイズパッチが含まれています。
+
+## パッチファイル一覧
+
+- `001-social-app-ios-config.patch` - app.config.js の設定変更(アプリ名、Bundle ID、アイコンパス、ドメイン等)
+- `002-social-app-ios-lib.patch` - lib/constants.ts, lib/statsig, lib/url-helpers の変更
+- `003-social-app-ios-view.patch` - Logo, Logotype, UserAvatar, Splash.tsx の UI 変更(Bluesky 蝶ロゴを logo.png に変更)
+- `004-social-app-ios-core.patch` - agent.ts, App.native.tsx, routes.ts のコア変更
+- `005-social-app-ios-screens.patch` - Settings, Home, Privacy, TOS 画面の変更
+- `006-social-app-ios-shell.patch` - Drawer, BottomBar, RightNav, ServerInput などシェル変更
+- `007-social-app-ios-misc.patch` - notifications, ageAssurance, PolicyUpdate などその他変更
+- `008-social-app-ios-policy-tos-error.patch` - プライバシーポリシー・利用規約をネイティブコンポーネントで表示(WebView から ScrollView + Text へ変更)
+- `009-social-app-ios-license.patch` - ライセンスページの追加(Drawer, Navigation, routes, types)
+- `010-social-app-ios-remove-contact-support.patch` - アカウント作成時の「Contact support」リンクを削除
+- `011-social-app-ios-splash-license-footer.patch` - スプラッシュ画面の「What's up?」削除、© syui フッター追加
+- `012-social-app-ios-settings-about-help.patch` - About 設定と routes.ts のリンクを内部ルートに変更(/support/tos, /support/privacy-policy, /support/license)
+- `013-social-app-ios-settings-remove-help.patch` - Settings から Help 項目を削除
+- `019-social-app-ios-entitlements-plugin.patch` - iOS entitlements プラグイン設定
+- `020-social-app-ios-bypass-age-assurance.patch` - 年齢確認を完全に無効化(access を Full に固定、chatDisabled と adultContentDisabled を false に固定)
+- `021-social-app-ios-clean-feed.patch` - Following フィードのシンプル化(DiscoverFallbackHeader の (i) アイコンと Discover リンク削除、SuggestedFollows インタースティシャル無効化、おすすめボタン削除)
+- `License.tsx` - ライセンス表示画面(新規ファイル)
+
+## 使用方法
+
+### パッチの適用
+
+```bash
+cd /Users/syui/ai/at/ios
+./setup.zsh patch
+```
+
+**注意**: setup.zsh が自動的に以下を実行します:
+- パッチファイルの適用
+- License.tsx のコピー
+- Xcode AppIcon を logo.png から 1024x1024 にリサイズして配置(GraphicsMagick または sips を使用)
+
+### リポジトリのリセット
+
+```bash
+cd /Users/syui/ai/at/ios
+./setup.zsh reset
+```
+
+### すべてのパッチを適用(デフォルト)
+
+```bash
+cd /Users/syui/ai/at/ios
+./setup.zsh
+```
+
+## パッチの更新方法
+
+repos/social-app で変更を加えた後:
+
+```bash
+cd /Users/syui/ai/at/repos/social-app
+git diff [ファイル名] > /Users/syui/ai/at/ios/patching/新しいパッチ.patch
+```
+
+その後、`setup.zsh` の `PATCH_FILES_IOS` 配列に新しいパッチファイル名を追加してください。
diff --git a/ios/preview.zsh b/ios/preview.zsh
new file mode 100755
index 0000000..2d3ea90
--- /dev/null
+++ b/ios/preview.zsh
@@ -0,0 +1,82 @@
+#!/bin/zsh
+set -e
+d=${0:a:h}
+cd $d
+
+source $d/.env
+
+xcrun simctl uninstall booted $BUNDLE_ID
+
+echo "Running iOS preview workflow..."
+cd "$REPO_DIR"
+
+# 0. Environment Setup (Fix Node Version)
+export NVM_DIR="$HOME/.nvm"
+[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+echo "Checking Node version..."
+if command -v nvm >/dev/null; then
+ nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)"
+else
+ echo "nvm not found, using system node: $(node -v)"
+fi
+
+# 1. Install dependencies
+echo "1. Installing dependencies (yarn)..."
+yarn install
+
+# 1.5. Copy assets
+echo "1.5. Copying assets..."
+ASSETS_DIR="${0:a:h}/assets"
+if [ -d "$ASSETS_DIR" ]; then
+ cp -rf "$ASSETS_DIR/"* "$REPO_DIR/assets/"
+ echo "✅ Copied all assets (including logo.png, logo-1024.png)"
+else
+ echo "⚠️ Warning: $ASSETS_DIR not found"
+fi
+
+# 2. Prebuild (Generate ios directory)
+echo "2. Running Expo Prebuild..."
+# Clean old ios folder to remove old entitlements/AppClip targets
+rm -rf ios
+npx expo prebuild --platform ios --clean
+
+# 3. CocoaPods
+echo "3. Installing CocoaPods..."
+# Ensure PATH includes Homebrew ruby gems if needed
+export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
+cd ios
+pod install
+cd ..
+
+# 4. Signing (Manual Step)
+echo "4. Opening Xcode for Signing..."
+XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
+# Fallback search if variable name logic differs
+if [ ! -d "$XCODE_PROJ" ]; then
+ XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
+fi
+
+open "$XCODE_PROJ"
+echo "========================================================"
+echo " [ACTION REQUIRED] "
+echo " Xcode opened ($XCODE_PROJ)."
+echo " 1. Go to 'Signing & Capabilities' tab."
+echo " 2. Select your Team."
+echo " 3. Verify 'App Clip' target is gone."
+echo " 4. Ensure no red errors exist."
+echo " Press ENTER here once you are done to continue building."
+echo "========================================================"
+read
+
+# 5. Run
+echo "5. Building and Running..."
+# If user wants specific device ID, uncomment below, otherwise let Expo ask/pick boot simulator
+
+case $1 in
+ d|devicei)
+ npx expo run:ios --device "$DEVICE_ID" --configuration Release
+ ;;
+ *)
+ npx expo run:ios
+ ;;
+esac
diff --git a/ios/setup.zsh b/ios/setup.zsh
new file mode 100755
index 0000000..e4b9d40
--- /dev/null
+++ b/ios/setup.zsh
@@ -0,0 +1,183 @@
+#!/bin/zsh
+
+cd ${0:a:h}
+
+# iOS Social App Patch Setup Script
+# Usage: ./ios/setup.zsh [patch|reset]
+
+# Arrays for patch management
+typeset -a FAILED_PATCHES
+
+# Patch file lists for iOS
+typeset -a PATCH_FILES_IOS
+PATCH_FILES_IOS=(
+ "001-social-app-ios-config.patch"
+ "002-social-app-ios-lib.patch"
+ "003-social-app-ios-view.patch"
+ "004-social-app-ios-core.patch"
+ "005-social-app-ios-screens.patch"
+ "006-social-app-ios-shell.patch"
+ "007-social-app-ios-misc.patch"
+ "008-social-app-ios-policy-tos-error.patch"
+ "009-social-app-ios-license.patch"
+ "010-social-app-ios-remove-contact-support.patch"
+ "011-social-app-ios-splash-license-footer.patch"
+ "012-social-app-ios-settings-about-help.patch"
+ "013-social-app-ios-settings-remove-help.patch"
+ "019-social-app-ios-entitlements-plugin.patch"
+ "020-social-app-ios-bypass-age-assurance.patch"
+ "021-social-app-ios-clean-feed.patch"
+)
+
+function ios-env() {
+ d=${0:a:h:h}
+ patching_dir=${0:a:h}/patching
+ target_dir=$d/repos/social-app
+}
+
+# Common patch function with status detection
+function apply-patch() {
+ local patch_name=$1
+ local target_dir=$2
+ local patch_file=$3
+
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo "📝 Patch: ${patch_name}"
+ echo " Target: ${target_dir}"
+ echo " File: ${patch_file}"
+
+ pushd ${target_dir} > /dev/null
+
+ # Check if patch is already applied (reverse dry-run succeeds)
+ if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
+ echo "✅ Already applied - skipping"
+ popd > /dev/null
+ echo ""
+ return 0
+ fi
+
+ # Check if patch can be applied (forward dry-run succeeds)
+ if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
+ echo "🔧 Applying patch..."
+ if patch -p1 < ${patch_file}; then
+ echo "✅ Applied successfully"
+ popd > /dev/null
+ echo ""
+ return 0
+ else
+ echo "❌ Failed to apply"
+ FAILED_PATCHES+=("${patch_name} (${patch_file})")
+ popd > /dev/null
+ echo ""
+ return 1
+ fi
+ else
+ echo "⚠️ Cannot apply - file may have been modified"
+ echo " Please check manually"
+ FAILED_PATCHES+=("${patch_name} (${patch_file}) - file modified")
+ popd > /dev/null
+ echo ""
+ return 1
+ fi
+}
+
+function show-failed-patches() {
+ if [ ${#FAILED_PATCHES[@]} -eq 0 ]; then
+ echo ""
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo "✅ All patches applied successfully!"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo ""
+ return 0
+ fi
+
+ echo ""
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo "⚠️ FAILED PATCHES SUMMARY"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo ""
+ echo "The following patches could not be applied:"
+ echo ""
+ for failed_patch in "${FAILED_PATCHES[@]}"; do
+ echo " ❌ ${failed_patch}"
+ done
+ echo ""
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo ""
+}
+
+# Helper function for applying patches
+function patch-apply() {
+ local name=$1
+ local patch_file=$2
+ apply-patch "${name}" "$target_dir" "$patching_dir/${patch_file}"
+}
+
+# Auto-apply patches from list
+function ios-patch-apply-all() {
+ for filename in "${PATCH_FILES_IOS[@]}"; do
+ local title="${filename%.*}"
+ patch-apply "$title" "$filename"
+ done
+}
+
+# Copy new files that aren't in patches
+function ios-copy-new-files() {
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo "📁 Copying new files..."
+
+ # Copy all assets from ios/assets/ to repos/social-app/assets/
+ if [ -d "$d/ios/assets" ]; then
+ cp -rf "$d/ios/assets/"* "$target_dir/assets/"
+ echo "✅ Copied all assets (including logo.png, app-icons)"
+ fi
+
+ # Copy License.tsx
+ if [ -f "$patching_dir/License.tsx" ]; then
+ mkdir -p "$target_dir/src/view/screens"
+ cp "$patching_dir/License.tsx" "$target_dir/src/view/screens/License.tsx"
+ echo "✅ Copied License.tsx"
+ fi
+
+ echo ""
+}
+
+function ios-setup-clone() {
+ if [ ! -d $target_dir ]; then
+ echo "Error: social-app repository not found at $target_dir"
+ echo "Please run install.zsh first to clone repositories"
+ return 1
+ fi
+ echo "Repository found: $target_dir"
+}
+
+function ios-setup-reset() {
+ echo "Resetting social-app repository..."
+ cd $target_dir
+ git stash
+ git checkout .
+ echo "Reset complete"
+}
+
+# Main execution
+ios-env
+
+case "$1" in
+ patch)
+ ios-setup-clone
+ ios-patch-apply-all
+ ios-copy-new-files
+ show-failed-patches
+ exit
+ ;;
+ reset)
+ ios-setup-reset
+ exit
+ ;;
+ *)
+ ios-setup-clone
+ ios-patch-apply-all
+ ios-copy-new-files
+ show-failed-patches
+ ;;
+esac
diff --git a/task.md b/task.md
new file mode 100644
index 0000000..3136801
--- /dev/null
+++ b/task.md
@@ -0,0 +1,4 @@
+## Rules & Constraints
+- Do not reformat existing code unless explicitly requested.
+- Maintain existing import styles (newlines, sorting).
+- Keep patches minimal (clean deltas).