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 ( +- +- {gradient && ( +- +- +- +- +- +- +- )} +- +- +- ++ + ) + }) +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 ( + void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + return ( + void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg`Chat`)} +- bold={isActive} +- onPress={onPress} +- /> +- ) +-} +-ChatMenuItem = React.memo(ChatMenuItem) +- + let NotificationsMenuItem = ({ + isActive, + onPress, +@@ -478,7 +302,7 @@ let NotificationsMenuItem = ({ + isActive: boolean + onPress: () => void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + const numUnreadNotifications = useUnreadNotifications() + return ( +@@ -495,11 +319,11 @@ let NotificationsMenuItem = ({ + numUnreadNotifications === '' + ? '' + : _( +- msg`${plural(numUnreadNotifications ?? 0, { +- one: '# unread item', +- other: '# unread items', +- })}` || '', +- ) ++ msg`${plural(numUnreadNotifications ?? 0, { ++ one: '# unread item', ++ other: '# unread items', ++ })}` || '', ++ ) + } + count={numUnreadNotifications} + bold={isActive} +@@ -509,72 +333,6 @@ let NotificationsMenuItem = ({ + } + NotificationsMenuItem = React.memo(NotificationsMenuItem) + +-let FeedsMenuItem = ({ +- isActive, +- onPress, +-}: { +- isActive: boolean +- onPress: () => void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg`Feeds`)} +- bold={isActive} +- onPress={onPress} +- /> +- ) +-} +-FeedsMenuItem = React.memo(FeedsMenuItem) +- +-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- +- return ( +- } +- label={_(msg`Lists`)} +- onPress={onPress} +- /> +- ) +-} +-ListsMenuItem = React.memo(ListsMenuItem) +- +-let BookmarksMenuItem = ({ +- isActive, +- onPress, +-}: { +- isActive: boolean +- onPress: () => void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))} +- onPress={onPress} +- /> +- ) +-} +-BookmarksMenuItem = React.memo(BookmarksMenuItem) +- + let ProfileMenuItem = ({ + isActive, + onPress, +@@ -582,7 +340,7 @@ let ProfileMenuItem = ({ + isActive: boolean + onPress: () => void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + return ( + void}): React.ReactNode => { +- const {_} = useLingui() ++let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => { ++ const { _ } = useLingui() + const t = useTheme() + return ( + void}): React.ReactNode => { + } + SettingsMenuItem = React.memo(SettingsMenuItem) + +-function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) { ++function MenuItem({ icon, label, count, bold, onPress }: MenuItemProps) { + const t = useTheme() + return ( + +- +- +- 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).