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-icons/android_icon_core_aurora.png b/ios/app-icons/android_icon_core_aurora.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_aurora.png differ
diff --git a/ios/app-icons/android_icon_core_bonfire.png b/ios/app-icons/android_icon_core_bonfire.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_bonfire.png differ
diff --git a/ios/app-icons/android_icon_core_classic.png b/ios/app-icons/android_icon_core_classic.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_classic.png differ
diff --git a/ios/app-icons/android_icon_core_flat_black.png b/ios/app-icons/android_icon_core_flat_black.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_black.png differ
diff --git a/ios/app-icons/android_icon_core_flat_blue.png b/ios/app-icons/android_icon_core_flat_blue.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_blue.png differ
diff --git a/ios/app-icons/android_icon_core_flat_white.png b/ios/app-icons/android_icon_core_flat_white.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_white.png differ
diff --git a/ios/app-icons/android_icon_core_midnight.png b/ios/app-icons/android_icon_core_midnight.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_midnight.png differ
diff --git a/ios/app-icons/android_icon_core_sunrise.png b/ios/app-icons/android_icon_core_sunrise.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_sunrise.png differ
diff --git a/ios/app-icons/android_icon_core_sunset.png b/ios/app-icons/android_icon_core_sunset.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_core_sunset.png differ
diff --git a/ios/app-icons/android_icon_default_next.png b/ios/app-icons/android_icon_default_next.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_default_next.png differ
diff --git a/ios/app-icons/android_icon_legacy_dark.png b/ios/app-icons/android_icon_legacy_dark.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_legacy_dark.png differ
diff --git a/ios/app-icons/android_icon_legacy_light.png b/ios/app-icons/android_icon_legacy_light.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/android_icon_legacy_light.png differ
diff --git a/ios/app-icons/ios_icon_core_aurora.png b/ios/app-icons/ios_icon_core_aurora.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_aurora.png differ
diff --git a/ios/app-icons/ios_icon_core_bonfire.png b/ios/app-icons/ios_icon_core_bonfire.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_bonfire.png differ
diff --git a/ios/app-icons/ios_icon_core_classic.png b/ios/app-icons/ios_icon_core_classic.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_classic.png differ
diff --git a/ios/app-icons/ios_icon_core_flat_black.png b/ios/app-icons/ios_icon_core_flat_black.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_black.png differ
diff --git a/ios/app-icons/ios_icon_core_flat_blue.png b/ios/app-icons/ios_icon_core_flat_blue.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_blue.png differ
diff --git a/ios/app-icons/ios_icon_core_flat_white.png b/ios/app-icons/ios_icon_core_flat_white.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_white.png differ
diff --git a/ios/app-icons/ios_icon_core_midnight.png b/ios/app-icons/ios_icon_core_midnight.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_midnight.png differ
diff --git a/ios/app-icons/ios_icon_core_sunrise.png b/ios/app-icons/ios_icon_core_sunrise.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_sunrise.png differ
diff --git a/ios/app-icons/ios_icon_core_sunset.png b/ios/app-icons/ios_icon_core_sunset.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_core_sunset.png differ
diff --git a/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png b/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png differ
diff --git a/ios/app-icons/ios_icon_default.icon/icon.json b/ios/app-icons/ios_icon_default.icon/icon.json
new file mode 100644
index 0000000..8a681cb
--- /dev/null
+++ b/ios/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/app-icons/ios_icon_default.icon/icon.png b/ios/app-icons/ios_icon_default.icon/icon.png
new file mode 100644
index 0000000..4c67271
Binary files /dev/null and b/ios/app-icons/ios_icon_default.icon/icon.png differ
diff --git a/ios/app-icons/ios_icon_default_next.png b/ios/app-icons/ios_icon_default_next.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_default_next.png differ
diff --git a/ios/app-icons/ios_icon_legacy_dark.png b/ios/app-icons/ios_icon_legacy_dark.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_legacy_dark.png differ
diff --git a/ios/app-icons/ios_icon_legacy_light.png b/ios/app-icons/ios_icon_legacy_light.png
new file mode 100644
index 0000000..f8f45fb
Binary files /dev/null and b/ios/app-icons/ios_icon_legacy_light.png differ
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/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..6526b61
--- /dev/null
+++ b/ios/patching/001-social-app-ios-config.patch
@@ -0,0 +1,150 @@
+diff --git a/Dockerfile b/Dockerfile
+index 371e8402c..2e139503e 100644
+--- a/Dockerfile
++++ b/Dockerfile
+@@ -66,7 +66,8 @@ RUN \. "$NVM_DIR/nvm.sh" && \
+ echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \
+ echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \
+ npm install --global yarn && \
+- yarn && \
++ yarn config set registry https://registry.npmjs.org/ && \
++ yarn install --frozen-lockfile --network-timeout 100000 && \
+ yarn intl:build 2>&1 | tee i18n.log && \
+ if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \
+ SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web
+diff --git a/app.config.js b/app.config.js
+index 246d8abd3..a6582864b 100644
+--- a/app.config.js
++++ b/app.config.js
+@@ -33,8 +33,8 @@ module.exports = function (_config) {
+ return {
+ expo: {
+ version: VERSION,
+- name: 'Bluesky',
+- slug: 'bluesky',
++ name: 'Aiat',
++ slug: 'aiat',
+ scheme: 'bluesky',
+ owner: 'blueskysocial',
+ runtimeVersion: {
+@@ -45,15 +45,20 @@ module.exports = function (_config) {
+ primaryColor: '#1083fe',
+ newArchEnabled: false,
+ ios: {
++ infoPlist: {
++ NSAppTransportSecurity: {
++ NSAllowsArbitraryLoads: true,
++ },
++ },
+ 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',
++ : './assets/app-icons/ios_icon_default_next.png',
+ infoPlist: {
+ UIBackgroundModes: ['remote-notification'],
+ NSCameraUsageDescription:
+@@ -113,7 +118,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: [
+@@ -182,7 +187,7 @@ module.exports = function (_config) {
+ backgroundColor: '#006AFF',
+ },
+ googleServicesFile: './google-services.json',
+- package: 'xyz.blueskyweb.app',
++ package: 'ai.syui.at',
+ intentFilters: [
+ {
+ action: 'VIEW',
+@@ -220,7 +225,7 @@ module.exports = function (_config) {
+ checkAutomatically: 'NEVER',
+ },
+ plugins: [
+- 'expo-video',
++ 'expo-video', './plugins/withCodeSignEntitlements.js',
+ 'expo-localization',
+ 'expo-web-browser',
+ [
+@@ -239,6 +244,11 @@ module.exports = function (_config) {
+ 'expo-build-properties',
+ {
+ ios: {
++ infoPlist: {
++ NSAppTransportSecurity: {
++ NSAllowsArbitraryLoads: true,
++ },
++ },
+ deploymentTarget: '15.1',
+ buildReactNativeFromSource: true,
+ },
+@@ -264,7 +274,6 @@ module.exports = function (_config) {
+ networkInstrumentation: true,
+ },
+ ],
+- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
+ './plugins/withGradleJVMHeapSizeIncrease.js',
+ './plugins/withAndroidManifestLargeHeapPlugin.js',
+ './plugins/withAndroidManifestFCMIconPlugin.js',
+@@ -296,6 +305,11 @@ module.exports = function (_config) {
+ 'expo-splash-screen',
+ {
+ ios: {
++ infoPlist: {
++ NSAppTransportSecurity: {
++ NSAllowsArbitraryLoads: true,
++ },
++ },
+ enableFullScreenImage_legacy: true,
+ backgroundColor: '#ffffff',
+ image: './assets/splash.png',
+@@ -394,29 +408,30 @@ module.exports = function (_config) {
+ build: {
+ experimental: {
+ ios: {
++ infoPlist: {
++ NSAppTransportSecurity: {
++ NSAllowsArbitraryLoads: true,
++ },
++ },
+ appExtensions: [
+ {
+ targetName: 'Share-with-Bluesky',
+- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky',
++ bundleIdentifier: 'ai.syui.at.Share-with-Bluesky',
+ entitlements: {
+ 'com.apple.security.application-groups': [
+- 'group.app.bsky',
++ 'group.ai.syui.at',
+ ],
+ },
+ },
+ {
+ targetName: 'BlueskyNSE',
+- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE',
++ bundleIdentifier: 'ai.syui.at.BlueskyNSE',
+ entitlements: {
+ 'com.apple.security.application-groups': [
+- 'group.app.bsky',
++ 'group.ai.syui.at',
+ ],
+ },
+ },
+- {
+- targetName: 'BlueskyClip',
+- bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
+- },
+ ],
+ },
+ },
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/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 (
+