diff --git a/.gitignore b/.gitignore index 0efae0e..bd389f8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ repos .claude deploy.yml claude.md +store.mobileprovision +.env diff --git a/README.md b/README.md index 6b1798c..5d8ef4a 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,23 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=a } } ``` + +## build + +- https://appstoreconnect.apple.com/ +- https://developer.apple.com/account/resources/profiles/list + +```sh +# build +./install.zsh + +# build social-app +./install.zsh pull;./install.zsh patch;./install.zsh build social-app;./install.zsh push social-app +--- +# server +./install.zsh +--- +# social-app ios +./install.zsh pull;./ios/setup.zsh +./ios/build.zsh +``` diff --git a/install.zsh b/install.zsh index 58cdca6..5e9c7d2 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 @@ -98,33 +116,33 @@ function at-repos-pull() { cd $d } +function at-repos-social-app-ios-patch() { + $d/ios/setup.zsh +} + 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" } - # Common patch function with status detection function apply-patch() { local patch_name=$1 @@ -139,7 +157,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 +166,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 +303,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 } @@ -480,7 +499,7 @@ case "$1" in exit ;; patch) - at-repos-social-app-avatar-write + at-repos-social-app-ios-patch at-repos-patch-apply-all at-repos-ozone-patch show-failed-patches @@ -520,7 +539,8 @@ case "`cat /etc/hostname`" in *) at-repos-clone at-repos-pull - at-repos-social-app-avatar-write + at-repos-social-app-ios-patch + #at-repos-social-app-avatar-write at-repos-patch-apply-all at-repos-ozone-patch show-failed-patches diff --git a/ios/.env.example b/ios/.env.example new file mode 100644 index 0000000..c0a09ce --- /dev/null +++ b/ios/.env.example @@ -0,0 +1,17 @@ +APP_NAME="Aiat" +REPO_DIR="../repos/social-app" +APP_SLUG="aiat" +APP_SCHEME="syui" +APP_GROUP="group.ai.syui.at" +APP_MAIL=user@example.com +APP_KEYCHAIN=@keychain:KEYCHAIN_NAME +BUNDLE_ID="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" +IOS_CERTIFICATE_NAME="Apple Distribution: $TEAM($TEAM_ID)" +PDS_HOST=syu.is 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..ab3e592 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,93 @@ +今回の./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 +``` + +## App Storeへのアップロード + +```sh +# 1. 事前にXcodeでサイニング設定を完了させる +# 2. store.mobileprovision を repos/social-app/ に配置 +# 3. キーチェーンに AC_PASSWORD を登録 + +$ cd /Users/syui/ai/at/ios +$ ./build.sh +``` + +**必要な準備:** +- Apple Distribution証明書: `Apple Distribution: syutaro inagaki (WN6KD5ZT49)` +- App Store用Provisioning Profile: `store.mobileprovision` +- App-Specific Password: キーチェーンに `AC_PASSWORD` として登録 + +```sh +# App-Specific Passwordの登録 +security add-generic-password -a "syui@syui.ai" -w "your-app-specific-password" -s "AC_PASSWORD" +``` + +## 実装済み + +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/build.sh b/ios/build.sh new file mode 100755 index 0000000..0d704b9 --- /dev/null +++ b/ios/build.sh @@ -0,0 +1,129 @@ +#!/bin/zsh +set -e + +SCRIPT_DIR=${0:a:h} +cd "$SCRIPT_DIR" +source .env + +# 絶対パスに変換 +REPO_DIR="$SCRIPT_DIR/../repos/social-app" +APP_NAME="Aiat" +WORKSPACE="$REPO_DIR/ios/${APP_NAME}.xcworkspace" +SCHEME="$APP_NAME" +BUILD_DIR="$REPO_DIR/build" +MOBILEPROVISION="$SCRIPT_DIR/store.mobileprovision" +ASSETS_DIR="$SCRIPT_DIR/assets" + +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..." +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" +read + +echo "Building $APP_NAME for App Store upload..." + +# ビルドディレクトリ作成 +mkdir -p "$BUILD_DIR" + +# アーカイブ(詳細ログ出力) +xcodebuild -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -configuration Release \ + -archivePath "$BUILD_DIR/${APP_NAME}.xcarchive" \ + -allowProvisioningUpdates \ + archive 2>&1 | tee "$BUILD_DIR/build.log" + +# アーカイブ成功確認 +if [ ! -d "$BUILD_DIR/${APP_NAME}.xcarchive" ]; then + echo "Error: Archive failed. Check $BUILD_DIR/build.log for details" + exit 1 +fi + +cd "$BUILD_DIR" + +# IPA作成 +rm -rf Payload ${APP_NAME}.ipa +mkdir -p Payload +cp -R ${APP_NAME}.xcarchive/Products/Applications/${APP_NAME}.app Payload/ + +# store.mobileprovisionの存在確認とコピー +# https://developer.apple.com/account/resources/profiles/list +if [ ! -f "$MOBILEPROVISION" ]; then + echo "Error: store.mobileprovision not found at $MOBILEPROVISION" + exit 1 +fi + +cp "$MOBILEPROVISION" Payload/${APP_NAME}.app/embedded.mobileprovision + +# entitlements抽出 +security cms -D -i Payload/${APP_NAME}.app/embedded.mobileprovision > /tmp/profile.plist +/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist + +# 署名 +CERT="$IOS_CERTIFICATE_NAME" + +# Frameworksディレクトリが存在する場合のみ署名 +if [ -d "Payload/${APP_NAME}.app/Frameworks" ]; then + for framework in Payload/${APP_NAME}.app/Frameworks/*.framework; do + if [ -e "$framework" ]; then + echo "Signing $framework" + codesign -f -s "$CERT" "$framework" + fi + done +fi + +# アプリ本体に署名 +codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${APP_NAME}.app + +# IPA作成 +zip -r ${APP_NAME}.ipa Payload + +# アップロード +xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${APP_MAIL}" -p "${APP_KEYCHAIN}" + +echo "Upload complete: ${APP_NAME}.ipa" 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..b132480 --- /dev/null +++ b/ios/patching/001-social-app-ios-config.patch @@ -0,0 +1,166 @@ +diff --git a/app.config.js b/app.config.js +index 246d8abd3..ed8f7b2b2 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,25 @@ 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', ++ buildNumber: '__BUILD_NUMBER__', + 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', +@@ -272,8 +265,6 @@ module.exports = function (_config) { + './plugins/withAndroidStylesAccentColorPlugin.js', + './plugins/withAndroidDayNightThemePlugin.js', + './plugins/withAndroidNoJitpackPlugin.js', +- './plugins/shareExtension/withShareExtensions.js', +- './plugins/notificationsExtension/withNotificationsExtension.js', + [ + 'expo-font', + { +@@ -386,7 +377,7 @@ module.exports = function (_config) { + }, + }, + ], +- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}], ++ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }], + ['expo-location'], + ].filter(Boolean), + extra: { +@@ -394,30 +385,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..d8dd7a9 --- /dev/null +++ b/ios/patching/002-social-app-ios-lib.patch @@ -0,0 +1,209 @@ +diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts +index 7a0d72d91..93554dc3e 100644 +--- a/src/lib/api/feed/home.ts ++++ b/src/lib/api/feed/home.ts +@@ -45,7 +45,7 @@ export class HomeFeedAPI implements FeedAPI { + this.following = new FollowingFeedAPI({agent}) + this.discover = new CustomFeedAPI({ + agent, +- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, ++ feedParams: {feed: PROD_DEFAULT_FEED('app')}, + }) + this.userInterests = userInterests + } +@@ -54,7 +54,7 @@ export class HomeFeedAPI implements FeedAPI { + this.following = new FollowingFeedAPI({agent: this.agent}) + this.discover = new CustomFeedAPI({ + agent: this.agent, +- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, ++ feedParams: {feed: PROD_DEFAULT_FEED('app')}, + userInterests: this.userInterests, + }) + this.usingDiscover = false +diff --git a/src/lib/constants.ts b/src/lib/constants.ts +index 231447b4f..a44b3da05 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' +@@ -79,19 +79,17 @@ export function IS_PROD_SERVICE(url?: string) { + } + + export const PROD_DEFAULT_FEED = (rkey: string) => +- `at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}` ++ `at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/${rkey}` + + export const STAGING_DEFAULT_FEED = (rkey: string) => + `at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/${rkey}` + + export const PROD_FEEDS = [ +- `feedgen|${PROD_DEFAULT_FEED('whats-hot')}`, +- `feedgen|${PROD_DEFAULT_FEED('thevids')}`, ++ `feedgen|${PROD_DEFAULT_FEED('app')}`, + ] + + export const STAGING_FEEDS = [ +- `feedgen|${STAGING_DEFAULT_FEED('whats-hot')}`, +- `feedgen|${STAGING_DEFAULT_FEED('thevids')}`, ++ `feedgen|${STAGING_DEFAULT_FEED('app')}`, + ] + + export const POST_IMG_MAX = { +@@ -129,7 +127,7 @@ export const LANG_DROPDOWN_HITSLOP = {top: 10, bottom: 10, left: 4, right: 4} + export const BACK_HITSLOP = HITSLOP_30 + export const MAX_POST_LINES = 25 + +-export const BSKY_APP_ACCOUNT_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur' ++export const BSKY_APP_ACCOUNT_DID = 'did:plc:6qyecktefllvenje24fcxnie' + + export const BSKY_FEED_OWNER_DIDS = [ + BSKY_APP_ACCOUNT_DID, +@@ -138,9 +136,9 @@ export const BSKY_FEED_OWNER_DIDS = [ + ] + + export const DISCOVER_FEED_URI = +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' ++ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app' + export const VIDEO_FEED_URI = +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/thevids' ++ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app' + export const STAGING_VIDEO_FEED_URI = + 'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids' + export const VIDEO_FEED_URIS = [VIDEO_FEED_URI, STAGING_VIDEO_FEED_URI] +@@ -209,8 +207,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 +234,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/demo.ts b/src/lib/demo.ts +index 5ead62c9d..7c80dfe15 100644 +--- a/src/lib/demo.ts ++++ b/src/lib/demo.ts +@@ -1,7 +1,7 @@ + import {type AppBskyFeedGetFeed} from '@atproto/api' + import {subDays, subMinutes} from 'date-fns' + +-const DID = `did:plc:z72i7hdynmk6r22z27h6tvur` ++const DID = `did:plc:6qyecktefllvenje24fcxnie` + const NOW = new Date() + const POST_1_DATE = subMinutes(NOW, 2).toISOString() + const POST_2_DATE = subMinutes(NOW, 4).toISOString() +diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts +index 6088e2806..0f6787a4d 100644 +--- a/src/lib/strings/url-helpers.ts ++++ b/src/lib/strings/url-helpers.ts +@@ -338,7 +338,7 @@ export function createProxiedUrl(url: string): string { + return url + } + +- return `https://go.bsky.app/redirect?u=${encodeURIComponent(url)}` ++ return url + } + + export function isShortLink(url: string): boolean { +diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts +index de1e92533..3d1566800 100644 +--- a/src/state/queries/feed.ts ++++ b/src/state/queries/feed.ts +@@ -201,14 +201,7 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) { + // for the ones we know need it + // -prf + export const KNOWN_AUTHED_ONLY_FEEDS = [ +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app +- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed +- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed +- 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow +- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky +- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz +- 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why ++ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app', // app feed, by syu.is + ] + + type GetPopularFeedsOptions = {limit?: number; enabled?: boolean} +diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts +index 0cf6ab546..399e592bc 100644 +--- a/src/state/queries/preferences/index.ts ++++ b/src/state/queries/preferences/index.ts +@@ -270,7 +270,7 @@ export function useReplaceForYouWithDiscoverFeedMutation() { + await agent.addSavedFeeds([ + { + type: 'feed', +- value: PROD_DEFAULT_FEED('whats-hot'), ++ value: PROD_DEFAULT_FEED('app'), + pinned: true, + }, + ]) +diff --git a/src/view/com/posts/FeedShutdownMsg.tsx b/src/view/com/posts/FeedShutdownMsg.tsx +index 620382175..928480da2 100644 +--- a/src/view/com/posts/FeedShutdownMsg.tsx ++++ b/src/view/com/posts/FeedShutdownMsg.tsx +@@ -32,7 +32,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + f => f.value === feedUri && f.pinned, + ) + const discoverFeedConfig = preferences?.savedFeeds?.find( +- f => f.value === PROD_DEFAULT_FEED('whats-hot'), ++ f => f.value === PROD_DEFAULT_FEED('app'), + ) + const hasFeedPinned = Boolean(feedConfig) + const hasDiscoverPinned = Boolean(discoverFeedConfig?.pinned) +@@ -44,7 +44,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + Toast.show(_(msg`Removed from your feeds`)) + } + if (hasDiscoverPinned) { +- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`) ++ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`) + } + } catch (err: any) { + Toast.show( +@@ -63,7 +63,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + forYouFeedConfig: feedConfig, + discoverFeedConfig, + }) +- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`) ++ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`) + Toast.show(_(msg`The feed has been replaced with Discover.`)) + } catch (err: any) { + Toast.show( +@@ -100,7 +100,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + This feed is no longer online. We are showing{' '} + + Discover + {' '} diff --git a/ios/patching/003-social-app-ios-view.patch b/ios/patching/003-social-app-ios-view.patch new file mode 100644 index 0000000..aa0cc29 --- /dev/null +++ b/ios/patching/003-social-app-ios-view.patch @@ -0,0 +1,213 @@ +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/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx +index 8a9e51a33..65d643b89 100644 +--- a/src/view/com/util/UserAvatar.tsx ++++ b/src/view/com/util/UserAvatar.tsx +@@ -444,7 +444,7 @@ let EditableUserAvatar = ({ + 0), }} + accessibilityRole="image" + /> + ) : ( +@@ -618,9 +618,8 @@ export {PreviewableUserAvatar} + // manually string-replace to use the smaller ones + // -prf + function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string { +- return isEnabled +- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/') +- : uri ++ // syu.is: avatars are served directly from bsky.syu.is, no CDN transformation needed ++ return uri + } + + const styles = StyleSheet.create({ +diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx +index d7208df13..2763800ac 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/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..d2154d2 --- /dev/null +++ b/ios/patching/006-social-app-ios-shell.patch @@ -0,0 +1,717 @@ +diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx +index 9915d0a2d..4ae51215d 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..1dc7f9227 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/022-social-app-ios-bskyweb-support-pages.patch b/ios/patching/022-social-app-ios-bskyweb-support-pages.patch new file mode 100644 index 0000000..a07a3ae --- /dev/null +++ b/ios/patching/022-social-app-ios-bskyweb-support-pages.patch @@ -0,0 +1,329 @@ +diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go +index ec5261dee..c670cf75a 100644 +--- a/bskyweb/cmd/bskyweb/server.go ++++ b/bskyweb/cmd/bskyweb/server.go +@@ -300,6 +300,10 @@ func serve(cctx *cli.Context) error { + e.GET("/support/tos", server.WebGeneric) + e.GET("/support/community-guidelines", server.WebGeneric) + e.GET("/support/copyright", server.WebGeneric) ++ // about/support pages (syu.is specific) ++ e.GET("/about/support/tos", server.WebAboutTOS) ++ e.GET("/about/support/privacy-policy", server.WebAboutPrivacy) ++ e.GET("/about/support/help", server.WebAboutHelp) + e.GET("/intent/compose", server.WebGeneric) + e.GET("/intent/verify-email", server.WebGeneric) + e.GET("/intent/age-assurance", server.WebGeneric) +@@ -753,3 +757,21 @@ func (srv *Server) WebIpCC(c echo.Context) error { + } + return c.JSON(200, outResponse) + } ++ ++// Handler for About TOS page (syu.is specific) ++func (srv *Server) WebAboutTOS(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-tos.html", data) ++} ++ ++// Handler for About Privacy Policy page (syu.is specific) ++func (srv *Server) WebAboutPrivacy(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-privacy.html", data) ++} ++ ++// Handler for About Help page (syu.is specific) ++func (srv *Server) WebAboutHelp(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-help.html", data) ++} +diff --git a/bskyweb/templates/about-help.html b/bskyweb/templates/about-help.html +new file mode 100644 +index 000000000..d37db25c5 +--- /dev/null ++++ b/bskyweb/templates/about-help.html +@@ -0,0 +1,98 @@ ++ ++ ++ ++ ++ ++ Help - syu.is ++ ++ ++ ++ ++
++ ← Back to syu.is ++

Help Center

++
++ ++

About syu.is

++

syu.is is a social networking service built on the AT Protocol (Authenticated Transfer Protocol). It allows users to share content, connect with others, and participate in a decentralized social network.

++ ++

Frequently Asked Questions

++ ++
++

What is the AT Protocol?

++

The AT Protocol is a decentralized social networking protocol that allows users to own their data and identity. It enables federation between different services while maintaining user control.

++
++ ++
++

How do I create an account?

++

You can create an account by downloading the app or visiting the website. You'll need to provide an email address and choose a username.

++
++ ++
++

How do I reset my password?

++

You can reset your password through the login screen by selecting "Forgot Password" and following the instructions sent to your email.

++
++ ++
++

How do I delete my account?

++

You can delete your account through Settings > Account. Please note that account deletion is permanent and cannot be undone.

++
++ ++
++

How do I report abuse or inappropriate content?

++

You can report content by using the report function available on each post. Our moderation team will review reports and take appropriate action.

++
++ ++

Contact

++
++

For additional support or questions:

++ ++
++ ++

Related Links

++ ++ ++ ++ ++ +diff --git a/bskyweb/templates/about-privacy.html b/bskyweb/templates/about-privacy.html +new file mode 100644 +index 000000000..14a1168ad +--- /dev/null ++++ b/bskyweb/templates/about-privacy.html +@@ -0,0 +1,92 @@ ++ ++ ++ ++ ++ ++ Privacy Policy - syu.is ++ ++ ++ ++ ++
++ ← Back to syu.is ++

Privacy Policy

++
++ ++

1. Introduction

++

This Privacy Policy explains how syu.is collects, uses, and protects your personal information when you use our service.

++ ++

2. Information We Collect

++

We collect the following types of information:

++
    ++
  • Account Information: Email address, username, and profile information you provide
  • ++
  • Content: Posts, messages, and other content you create on the platform
  • ++
  • Usage Data: Information about how you interact with our service
  • ++
  • Device Information: Browser type, operating system, and device identifiers
  • ++
++ ++

3. How We Use Your Information

++

We use your information to:

++
    ++
  • Provide and maintain our service
  • ++
  • Improve and personalize your experience
  • ++
  • Communicate with you about the service
  • ++
  • Ensure security and prevent abuse
  • ++
++ ++

4. Data Sharing

++

As part of the AT Protocol federation, your public content may be shared with other servers in the network. We do not sell your personal information to third parties.

++ ++

5. Data Security

++

We implement appropriate security measures to protect your personal information. However, no method of transmission over the Internet is 100% secure.

++ ++

6. Your Rights

++

You have the right to:

++
    ++
  • Access your personal data
  • ++
  • Request correction of your data
  • ++
  • Request deletion of your account
  • ++
  • Export your data
  • ++
++ ++

7. Cookies

++

We use cookies and similar technologies to maintain your session and improve your experience.

++ ++

8. Changes to This Policy

++

We may update this Privacy Policy from time to time. We will notify you of any significant changes.

++ ++

9. Contact

++

For privacy-related questions, please visit our Help page.

++ ++ ++ ++ +diff --git a/bskyweb/templates/about-tos.html b/bskyweb/templates/about-tos.html +new file mode 100644 +index 000000000..db5d82f5c +--- /dev/null ++++ b/bskyweb/templates/about-tos.html +@@ -0,0 +1,84 @@ ++ ++ ++ ++ ++ ++ Terms of Service - syu.is ++ ++ ++ ++ ++
++ ← Back to syu.is ++

Terms of Service

++
++ ++

1. Introduction

++

Welcome to syu.is. By using our service, you agree to these terms. Please read them carefully.

++ ++

2. Service Description

++

syu.is is a social networking service built on the AT Protocol. We provide a platform for users to share content and connect with others.

++ ++

3. User Responsibilities

++

As a user of syu.is, you agree to:

++
    ++
  • Provide accurate information when creating an account
  • ++
  • Keep your account credentials secure
  • ++
  • Not use the service for illegal activities
  • ++
  • Respect other users and their content
  • ++
  • Comply with applicable laws and regulations
  • ++
++ ++

4. Content Guidelines

++

Users are responsible for the content they post. Prohibited content includes:

++
    ++
  • Illegal content
  • ++
  • Harassment or abuse
  • ++
  • Spam or misleading information
  • ++
  • Content that violates others' rights
  • ++
++ ++

5. Privacy

++

Your privacy is important to us. Please review our Privacy Policy to understand how we handle your data.

++ ++

6. Disclaimer

++

The service is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the service.

++ ++

7. Changes to Terms

++

We may update these terms from time to time. Continued use of the service after changes constitutes acceptance of the new terms.

++ ++

8. Contact

++

For questions about these terms, please visit our Help page.

++ ++ ++ ++ diff --git a/ios/patching/023-social-app-ios-disable-dm.patch b/ios/patching/023-social-app-ios-disable-dm.patch new file mode 100644 index 0000000..2f2236e --- /dev/null +++ b/ios/patching/023-social-app-ios-disable-dm.patch @@ -0,0 +1,70 @@ +diff --git a/src/state/messages/events/index.tsx b/src/state/messages/events/index.tsx +index 2ff0784ae..dc314ecc5 100644 +--- a/src/state/messages/events/index.tsx ++++ b/src/state/messages/events/index.tsx +@@ -10,13 +10,7 @@ const MessagesEventBusContext = React.createContext( + MessagesEventBusContext.displayName = 'MessagesEventBusContext' + + export function useMessagesEventBus() { +- const ctx = React.useContext(MessagesEventBusContext) +- if (!ctx) { +- throw new Error( +- 'useMessagesEventBus must be used within a MessagesEventBusProvider', +- ) +- } +- return ctx ++ return React.useContext(MessagesEventBusContext) + } + + export function MessagesEventBusProvider({ +@@ -24,18 +18,11 @@ export function MessagesEventBusProvider({ + }: { + children: React.ReactNode + }) { +- const {currentAccount} = useSession() +- +- if (!currentAccount) { +- return ( +- +- {children} +- +- ) +- } +- ++ // DM functionality is disabled for syu.is + return ( +- {children} ++ ++ {children} ++ + ) + } + +diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx +index c5457d1cb..5bc37bdce 100644 +--- a/src/state/queries/messages/list-conversations.tsx ++++ b/src/state/queries/messages/list-conversations.tsx +@@ -74,17 +74,12 @@ export function useListConvos() { + + const empty = {accepted: [], request: []} + export function ListConvosProvider({children}: {children: React.ReactNode}) { +- const {hasSession} = useSession() +- +- if (!hasSession) { +- return ( +- +- {children} +- +- ) +- } +- +- return {children} ++ // DM functionality is disabled for syu.is - always return empty ++ return ( ++ ++ {children} ++ ++ ) + } + + export function ListConvosProviderInner({ diff --git a/ios/patching/024-social-app-ios-disable-external-services.patch b/ios/patching/024-social-app-ios-disable-external-services.patch new file mode 100644 index 0000000..66bd786 --- /dev/null +++ b/ios/patching/024-social-app-ios-disable-external-services.patch @@ -0,0 +1,15 @@ +diff --git a/src/env/common.ts b/src/env/common.ts +--- a/src/env/common.ts ++++ b/src/env/common.ts +@@ -107,9 +107,8 @@ export const GCP_PROJECT_ID: number = + /** + * URLs for the app config web worker. Can be a + * locally running server, see `env.example` for more. ++ * Disabled for self-hosted environment to avoid CORS errors + */ + export const BAPP_CONFIG_DEV_URL = process.env.BAPP_CONFIG_DEV_URL + export const BAPP_CONFIG_PROD_URL = `https://ip.bsky.app` +-export const BAPP_CONFIG_URL = IS_DEV +- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_PROD_URL) +- : BAPP_CONFIG_PROD_URL ++export const BAPP_CONFIG_URL = null diff --git a/ios/patching/025-social-app-ios-bskyweb-title.patch b/ios/patching/025-social-app-ios-bskyweb-title.patch new file mode 100644 index 0000000..9b674e6 --- /dev/null +++ b/ios/patching/025-social-app-ios-bskyweb-title.patch @@ -0,0 +1,91 @@ +diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html +--- a/bskyweb/templates/base.html ++++ b/bskyweb/templates/base.html +@@ -7,9 +7,9 @@ + +- +- +- {%- block head_title -%}Bluesky{%- endblock -%} ++ ++ ++ {%- block head_title -%}syu.is{%- endblock -%} + + + +@@ -121,7 +121,7 @@ +