diff --git a/install.zsh b/install.zsh index 58cdca6..48ed4d6 100755 --- a/install.zsh +++ b/install.zsh @@ -1,5 +1,23 @@ #!/bin/zsh +# Sed compatibility wrapper +function sediment() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + + +# Patch compatibility wrapper +function patchment() { + # -f : Force. Do not ask questions. (Standard in GNU and BSD patch) + # -N : Ignore patches that seem to be reversed or already applied (Forward) + # But we control these flags in the caller. + patch "$@" +} + function at-repos-env() { APP_PASSWORD=xxx host=syu.is @@ -102,26 +120,23 @@ function at-repos-social-app-avatar-write() { did_admin=did:plc:6qyecktefllvenje24fcxnie dt=$d/repos/social-app/src cd $dt - grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g" - grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g" + grep -R syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/syu.is/${host}/g" + grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/web.syu.is/web.${host}/g" f=$dt/lib/constants.ts - sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f - sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f - sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f - sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f - sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f - - f=$dt/view/icons/Logotype.tsx - o=$d/icons/Logotype.tsx - cp -rf $o $f + sediment "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f + sediment "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f + sediment "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f + sediment "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f + sediment "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f + f=$dt/view/com/util/UserAvatar.tsx curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/view/com/util/UserAvatar.tsx -o $f - sed -i "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f - sed -i "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f - sed -i "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f + sediment "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f + sediment "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f + sediment "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/lib/strings/url-helpers.ts -o $dt/lib/strings/url-helpers.ts - sed -i "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts - grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sed -i "s/${did_admin}/${did}/g" + sediment "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts + grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sediment "s/${did_admin}/${did}/g" } @@ -139,7 +154,8 @@ function apply-patch() { pushd ${target_dir} > /dev/null # Check if patch is already applied (reverse dry-run succeeds) - if patch --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then + # Use -f to force dry-run to fail instead of asking questions if unapplied + if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then echo "✅ Already applied - skipping" popd > /dev/null echo "" @@ -147,9 +163,9 @@ function apply-patch() { fi # Check if patch can be applied (forward dry-run succeeds) - if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then + if patch -f --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then echo "🔧 Applying patch..." - if patch -p1 < ${patch_file}; then + if patch -f -p1 < ${patch_file}; then echo "✅ Applied successfully" popd > /dev/null echo "" @@ -284,29 +300,29 @@ function at-repos-ozone-patch() { fi # Replace process.env with env() - sed -i 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true - sed -i 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true + sediment 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true + sediment 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true # Add missing SOCIAL_APP_DOMAIN constant after SOCIAL_APP_URL - sed -i '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\ + sediment '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\ export const SOCIAL_APP_DOMAIN =\ env('\''NEXT_PUBLIC_SOCIAL_APP_DOMAIN'\'') || '\''bsky.app'\''\ }' lib/constants.ts 2>/dev/null || true # Fix multiline process.env patterns - sed -i '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ { + sediment '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ { s/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/ /^ \.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d }' lib/constants.ts 2>/dev/null || true - sed -i '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ { + sediment '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ { s/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/ /^ \.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d }' lib/constants.ts 2>/dev/null || true - sed -i '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ { + sediment '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ { s/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/export const HIGH_PROFILE_FOLLOWER_THRESHOLD = env('\''NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'\'')/ /^ \.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD$/d }' lib/constants.ts 2>/dev/null || true # Fix parseInt() to handle undefined by adding || '' - sed -i "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true + sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true popd > /dev/null } diff --git a/ios/.env b/ios/.env new file mode 100644 index 0000000..43469ef --- /dev/null +++ b/ios/.env @@ -0,0 +1,13 @@ +APP_NAME="Aiat" +REPO_DIR="../repos/social-app" +APP_SLUG="aiat" +APP_SCHEME="syui" +BUNDLE_ID="ai.syui.at" +APP_GROUP="group.ai.syui.at" +SERVICE_URL="https://syu.is" +HELP_URL="https://syu.is/about/support/help" +PRIVACY_URL="https://syu.is/about/support/privacy-policy" +TERMS_URL="https://syu.is/about/support/tos" +REPO_DIR="../repos/social-app" +CONFIG_FILE="$REPO_DIR/app.config.js" +CONSTANTS_FILE="$REPO_DIR/src/lib/constants.ts" diff --git a/ios/AppInfo.tsx b/ios/AppInfo.tsx deleted file mode 100644 index 6242b12..0000000 --- a/ios/AppInfo.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -interface AppInfoProps { - onLinkPress?: (url: string) => void -} - -export default function AppInfo({onLinkPress}: AppInfoProps) { - const handleLinkPress = (url: string) => { - if (onLinkPress) { - onLinkPress(url) - } else { - Linking.openURL(url) - } - } - - return ( - - - About This App - - This is a customized AT Protocol social networking client. It allows you to - connect to any Personal Data Server (PDS) and participate in the decentralized - social network. - - - - - Key Features - - • Connect to any AT Protocol PDS - • Post text, images, and videos - • Follow users and view timelines - • Customize feeds and moderation settings - • Direct messaging support - - - - - Open Source - - This application is based on the Bluesky social-app, licensed under the MIT - License. The original source code is available at: - - - handleLinkPress('https://github.com/bluesky-social/social-app') - }> - github.com/bluesky-social/social-app - - - - - AT Protocol - - This app uses the AT Protocol (Authenticated Transfer Protocol), an open and - decentralized standard for social applications. - - handleLinkPress('https://atproto.com')}> - atproto.com - - - - - License - - Copyright 2023–2025 Bluesky Social PBC - - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software. - - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. - - - - - Contact - handleLinkPress('https://syu.is')}> - https://syu.is - - - - - Version 1.0.0 - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - color: '#1d1d1f', - marginBottom: 12, - }, - paragraph: { - fontSize: 15, - lineHeight: 22, - color: '#3a3a3c', - marginBottom: 8, - }, - list: { - marginLeft: 8, - marginTop: 8, - }, - listItem: { - fontSize: 15, - lineHeight: 24, - color: '#3a3a3c', - }, - link: { - fontSize: 15, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, - versionText: { - fontSize: 13, - color: '#8e8e93', - fontStyle: 'italic', - }, -}) diff --git a/ios/LicenseNotice.tsx b/ios/LicenseNotice.tsx deleted file mode 100644 index 496d367..0000000 --- a/ios/LicenseNotice.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -export default function LicenseNotice() { - return ( - - Open Source Licenses - - - Bluesky Social App - MIT License - Copyright 2023–2025 Bluesky Social PBC - - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - - - Linking.openURL('https://github.com/bluesky-social/social-app') - }> - View Source Code - - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 16, - }, - title: { - fontSize: 24, - fontWeight: 'bold', - marginBottom: 20, - color: '#1d1d1f', - }, - section: { - marginBottom: 24, - padding: 16, - backgroundColor: '#f5f5f7', - borderRadius: 8, - }, - projectName: { - fontSize: 18, - fontWeight: '600', - marginBottom: 8, - color: '#1d1d1f', - }, - license: { - fontSize: 14, - fontWeight: '500', - color: '#007aff', - marginBottom: 4, - }, - copyright: { - fontSize: 13, - color: '#3a3a3c', - marginBottom: 12, - }, - licenseText: { - fontSize: 12, - lineHeight: 18, - color: '#3a3a3c', - marginBottom: 12, - }, - link: { - fontSize: 14, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, -}) diff --git a/ios/PrivacyContent.tsx b/ios/PrivacyContent.tsx deleted file mode 100644 index d780104..0000000 --- a/ios/PrivacyContent.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -interface PrivacyContentProps { - onLinkPress?: (url: string) => void -} - -export default function PrivacyContent({onLinkPress}: PrivacyContentProps) { - const handleLinkPress = (url: string) => { - if (onLinkPress) { - onLinkPress(url) - } else { - Linking.openURL(url) - } - } - - return ( - - - Introduction - - This Privacy Policy explains how this AT Protocol client application - (hereinafter referred to as "the App") handles personal information. - Please read this policy carefully before using the App. - - - - - Information We Collect - - The App may collect and use the following information: - - - 1. Information Collected Automatically - - • Device information (model, OS version) - • App usage data (sessions, features used) - • Crash logs and performance data - - - 2. Information Provided by Users - - - • DID (Decentralized Identifier) and handle for authentication - - • Posts, media, and social interactions - • Profile information (avatar, display name, bio) - - - - - Important: Your data is stored on your chosen PDS (Personal Data Server). - This app does not store your content on our servers. - - - - - - How We Use Your Information - - - • To provide AT Protocol social networking features - - • To improve app performance and user experience - • To diagnose and fix technical issues - - - - - Data Sharing - - The App interacts with your chosen PDS and AppView services. Your posts and - profile information are shared according to the AT Protocol specification and - your privacy settings. - - - - - Your Rights - - You have the right to access, modify, or delete your data through your PDS. - You can also switch to a different PDS at any time while maintaining your - identity. - - - - - Contact - - For questions about this Privacy Policy, please contact: - - handleLinkPress('https://syu.is')}> - https://syu.is - - - - - Last Updated: December 3, 2025 - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - color: '#1d1d1f', - marginBottom: 12, - }, - subTitle: { - fontSize: 16, - fontWeight: '500', - color: '#1d1d1f', - marginTop: 12, - marginBottom: 8, - }, - paragraph: { - fontSize: 15, - lineHeight: 22, - color: '#3a3a3c', - marginBottom: 8, - }, - list: { - marginLeft: 8, - marginTop: 8, - }, - listItem: { - fontSize: 15, - lineHeight: 24, - color: '#3a3a3c', - }, - highlight: { - backgroundColor: '#fff3cd', - borderLeftWidth: 4, - borderLeftColor: '#ffc107', - padding: 12, - marginTop: 12, - borderRadius: 4, - }, - highlightText: { - fontSize: 14, - lineHeight: 20, - color: '#856404', - }, - link: { - fontSize: 15, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, - lastUpdated: { - fontSize: 13, - color: '#8e8e93', - fontStyle: 'italic', - }, -}) diff --git a/ios/PrivacyPolicy.screen.tsx b/ios/PrivacyPolicy.screen.tsx deleted file mode 100644 index df5d5c8..0000000 --- a/ios/PrivacyPolicy.screen.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {usePalette} from '#/lib/hooks/usePalette' -import { - type CommonNavigatorParams, - type NativeStackScreenProps, -} from '#/lib/routes/types' -import {s} from '#/lib/styles' -import {useSetMinimalShellMode} from '#/state/shell' -import {ScrollView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' -import {ViewHeader} from '../com/util/ViewHeader' -import PrivacyContent from '#/components/custom/PrivacyContent' - -type Props = NativeStackScreenProps -export const PrivacyPolicyScreen = (_props: Props) => { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - - - - ) -} diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 0000000..de45071 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,72 @@ +今回の./ios (social-app)開発の要点をまとめます。 + +1. MITのライセンスを遵守すること、iosアプリとして出品しても問題ないようにすること +https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE + +2. "Bluesky"という名称を使用しないこと。アイコンの変更。リンクの変更 + +3. selfhostでも動くこと。本来のsocial-appは動きませんので、これは不便なのでiosアプリに出品することにしました。なお、これはすでにpatchで実現しています。 + + +```sh +$ ./install.zsh pull +$ ./install.zsh patch +$ ./ios/setup.zsh +$ ./ios/preview.zsh +``` + +## 実装済み + +1. 最初の画面で、webではちゃんと私のサイトのロゴが表示されていますが、ios モバイル版では、未だにBluesky (icon)です。アカウント作成、サインイン、が表示されています。 + +2. 上のメニューバーにもBlueskyのロゴが表示されています。 + +3. サインイン後のホスティングプロバイダーで中身はsyu.isですが、表示は"Bluesky Social"になっています。これをsyu.isに変更してください。ios/webでコードは異なります。 + +4. チャット機能 +チャット機能は今回無効化するので、下メニューバーやプロフィール、設定画面に表示しないでください。 + +5. 設定ボタン(左カラム)を押すと、フィードバック、ヘルプが表示されますが、非表示にしてください。 + +6. 設定ボタン(左カラム)を押すと、フィード、リスト、保存済みの項目がありますが、これを削除してください。 + +7. 設定ボタン(左カラム)を押すと、下に利用規約、プライバシーポリシーが表示されますが、リンクがbsky.socialです。 +- /about/support/privacy-policy +- /about/support/tos +このページを独自に作って表示してください。 + +8. LOG 09:52:20 (logger) Poll latest failed { + "feed": "following", + "message": "Error: Could not find repo: did:plc:z72i7hdynmk6r22z27h6tvur" +} + +9. LOG 10:24:03 (metric) router:navigate + LOG 10:24:04 (dms-agent) init failed { + "safeMessage": "could not resolve iss did" +} + +9. 設定ボタン(左カラム)の一番下、利用規約やプライバシーポリシーが表示されいてるライセンスという項目を追加。ページを追加して、ライセンスの表示。 +https://github.com/bluesky-social/social-app +https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE + +10. アカウント作成時(create account)のページに"Having trouble?"で`Contact support`のリンクがありますが、これを削除してください。 + +11. スタートページ、つまり、`Create account`, `Sign in`があるページの一番下にライセンスページへのリンクを追加してください。また、footerに`© syui`を表示してください。このページのタイトル下にある文字`What's up?`の項目は削除。 + +12. スタートページのラインセンスリンクが機能しない。おそらくページ変遷に問題があるため。また、ライセンスページは上下が隠れて見えてしまうため、大きく上下に空間を開けること。 + +13. 利用規約、プライバシーポリシーのページの言語が日本語で書かれています。ラインセンスと同様に、英語を基本とし、日本語訳をその下に表示してください。 + +14. Settings/ 項目の非表示を追加。 +- Helpの非表示 +- Aboutのリンクを変更 + +## 壊れた実装 + +1. ログイン後のメイン画面、"Following"の項目(フィード)に表示されるものをシンプルにします。表示するのはFollowingのみで、以下のものを削除してください。 +- おすすめの削除 +- Discoverの削除 +- アカウントを探すの削除 + +2. 年齢保証、年齢確認ページがでてくるのを削除。誕生日を入力する処理を削除。アプリ配布国は限定します。 + diff --git a/ios/Support.screen.tsx b/ios/Support.screen.tsx deleted file mode 100644 index 3fddf92..0000000 --- a/ios/Support.screen.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {usePalette} from '#/lib/hooks/usePalette' -import { - type CommonNavigatorParams, - type NativeStackScreenProps, -} from '#/lib/routes/types' -import {s} from '#/lib/styles' -import {useSetMinimalShellMode} from '#/state/shell' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {ScrollView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' -import AppInfo from '#/components/custom/AppInfo' - -type Props = NativeStackScreenProps -export const SupportScreen = (_props: Props) => { - const pal = usePalette('default') - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - ) -} diff --git a/ios/app-icons/android_icon_core_aurora.png b/ios/app-icons/android_icon_core_aurora.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_aurora.png differ diff --git a/ios/app-icons/android_icon_core_bonfire.png b/ios/app-icons/android_icon_core_bonfire.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_bonfire.png differ diff --git a/ios/app-icons/android_icon_core_classic.png b/ios/app-icons/android_icon_core_classic.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_classic.png differ diff --git a/ios/app-icons/android_icon_core_flat_black.png b/ios/app-icons/android_icon_core_flat_black.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_black.png differ diff --git a/ios/app-icons/android_icon_core_flat_blue.png b/ios/app-icons/android_icon_core_flat_blue.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_blue.png differ diff --git a/ios/app-icons/android_icon_core_flat_white.png b/ios/app-icons/android_icon_core_flat_white.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_flat_white.png differ diff --git a/ios/app-icons/android_icon_core_midnight.png b/ios/app-icons/android_icon_core_midnight.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_midnight.png differ diff --git a/ios/app-icons/android_icon_core_sunrise.png b/ios/app-icons/android_icon_core_sunrise.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_sunrise.png differ diff --git a/ios/app-icons/android_icon_core_sunset.png b/ios/app-icons/android_icon_core_sunset.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_core_sunset.png differ diff --git a/ios/app-icons/android_icon_default_next.png b/ios/app-icons/android_icon_default_next.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_default_next.png differ diff --git a/ios/app-icons/android_icon_legacy_dark.png b/ios/app-icons/android_icon_legacy_dark.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_legacy_dark.png differ diff --git a/ios/app-icons/android_icon_legacy_light.png b/ios/app-icons/android_icon_legacy_light.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/android_icon_legacy_light.png differ diff --git a/ios/app-icons/ios_icon_core_aurora.png b/ios/app-icons/ios_icon_core_aurora.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_aurora.png differ diff --git a/ios/app-icons/ios_icon_core_bonfire.png b/ios/app-icons/ios_icon_core_bonfire.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_bonfire.png differ diff --git a/ios/app-icons/ios_icon_core_classic.png b/ios/app-icons/ios_icon_core_classic.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_classic.png differ diff --git a/ios/app-icons/ios_icon_core_flat_black.png b/ios/app-icons/ios_icon_core_flat_black.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_black.png differ diff --git a/ios/app-icons/ios_icon_core_flat_blue.png b/ios/app-icons/ios_icon_core_flat_blue.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_blue.png differ diff --git a/ios/app-icons/ios_icon_core_flat_white.png b/ios/app-icons/ios_icon_core_flat_white.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_flat_white.png differ diff --git a/ios/app-icons/ios_icon_core_midnight.png b/ios/app-icons/ios_icon_core_midnight.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_midnight.png differ diff --git a/ios/app-icons/ios_icon_core_sunrise.png b/ios/app-icons/ios_icon_core_sunrise.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_sunrise.png differ diff --git a/ios/app-icons/ios_icon_core_sunset.png b/ios/app-icons/ios_icon_core_sunset.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_core_sunset.png differ diff --git a/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png b/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png differ diff --git a/ios/app-icons/ios_icon_default.icon/icon.json b/ios/app-icons/ios_icon_default.icon/icon.json new file mode 100644 index 0000000..8a681cb --- /dev/null +++ b/ios/app-icons/ios_icon_default.icon/icon.json @@ -0,0 +1,31 @@ +{ + "fill" : { + "automatic-gradient" : "srgb:0.00000,0.41569,1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "fill" : "none", + "glass" : false, + "image-name" : "iOS transparent.png", + "name" : "iOS transparent" + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/ios/app-icons/ios_icon_default.icon/icon.png b/ios/app-icons/ios_icon_default.icon/icon.png new file mode 100644 index 0000000..4c67271 Binary files /dev/null and b/ios/app-icons/ios_icon_default.icon/icon.png differ diff --git a/ios/app-icons/ios_icon_default_next.png b/ios/app-icons/ios_icon_default_next.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_default_next.png differ diff --git a/ios/app-icons/ios_icon_legacy_dark.png b/ios/app-icons/ios_icon_legacy_dark.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_legacy_dark.png differ diff --git a/ios/app-icons/ios_icon_legacy_light.png b/ios/app-icons/ios_icon_legacy_light.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/app-icons/ios_icon_legacy_light.png differ diff --git a/ios/app.config.patch.js b/ios/app.config.patch.js deleted file mode 100644 index 136a4d9..0000000 --- a/ios/app.config.patch.js +++ /dev/null @@ -1,9 +0,0 @@ -// Aiat app configuration overrides -module.exports = { - name: 'Aiat', - slug: 'aiat', - scheme: 'aiat', - owner: 'syui', // Your Expo account - bundleIdentifier: 'ai.syui.at', - // Icon will be set separately -} diff --git a/ios/bin/build.zsh b/ios/bin/build.zsh deleted file mode 100755 index d1fe8ea..0000000 --- a/ios/bin/build.zsh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/zsh -set -e - -d=~/ai/at/repos/social-app -APP_NAME=Aiat -PKG=aiat -TEAM_NAME= -TEAM_ID= -CERT="Apple Distribution: ${TEAM_NAME} (${TEAM_ID})" -MAIL=user@example.com -KEY_CHAIN=EXAMPLE - -cd $d -# npx expo prebuild --clean -# cd ios && pod install && cd .. - -## アーカイブ -xcodebuild -workspace ios/${PKG}.xcworkspace \ - -scheme ${PKG} \ - -configuration Release \ - -archivePath build/${APP_NAME}.xcarchive \ - -allowProvisioningUpdates \ - archive - -cd build - -# IPA作成 -rm -rf Payload ${APP_NAME}.ipa -mkdir -p Payload -cp -R ${APP_NAME}.xcarchive/Products/Applications/${PKG}.app Payload/ -cp ../store.mobileprovision Payload/${PKG}.app/embedded.mobileprovision - -# entitlements抽出 -security cms -D -i Payload/${PKG}.app/embedded.mobileprovision > /tmp/profile.plist -/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist - -codesign -f -s "$CERT" Payload/${PKG}.app/Frameworks/*.framework 2>/dev/null || true -codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${PKG}.app - -zip -r ${APP_NAME}.ipa Payload - -xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${MAIL}" -p "@keychain:${KEY_CHAIN}" - -echo "Upload complete" diff --git a/ios/bin/install.zsh b/ios/bin/install.zsh deleted file mode 100644 index 2e372c7..0000000 --- a/ios/bin/install.zsh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/zsh - -if [ "$1" = "social-app-custom" ];then - at-social-app-custom-pages - at-social-app-custom-screens - at-social-app-aiat-config - at-social-app-aiat-logo - at-origin-social-app - exit -fi - -function at-social-app-custom-pages() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "copying custom components to social-app" - - # Create components directory if not exists - mkdir -p ${d_}/src/components/custom - - # Copy custom components - cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/ - cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/ - - echo "custom components copied successfully" -} - -function at-social-app-aiat-config() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying Aiat configuration" - - # Update app.config.js - cd ${d_} - - # Backup original - cp app.config.js app.config.js.orig - - # Apply changes using sed - sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js - sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js - sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js - sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js - sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js - - # Update package.json name - sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json - - echo "Aiat configuration applied" -} - -function at-social-app-aiat-logo() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying Aiat logo" - - # Create logo directory if not exists - mkdir -p ${custom}/assets - - # Copy logo if exists in custom folder - if [ -f ${custom}/assets/icon.png ]; then - cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png - echo "Aiat logo applied" - else - echo "Warning: Logo file not found at ${custom}/assets/icon.png" - echo "Please add your logo file there" - fi -} - -function at-social-app-custom-screens() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying custom screens" - - # Copy custom screen files - cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx - cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx - cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/ - - echo "custom screens applied" -} - - diff --git a/ios/icon.png b/ios/icon.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/icon.png differ diff --git a/ios/patching/001-social-app-ios-config.patch b/ios/patching/001-social-app-ios-config.patch new file mode 100644 index 0000000..6526b61 --- /dev/null +++ b/ios/patching/001-social-app-ios-config.patch @@ -0,0 +1,150 @@ +diff --git a/Dockerfile b/Dockerfile +index 371e8402c..2e139503e 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -66,7 +66,8 @@ RUN \. "$NVM_DIR/nvm.sh" && \ + echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \ + echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \ + npm install --global yarn && \ +- yarn && \ ++ yarn config set registry https://registry.npmjs.org/ && \ ++ yarn install --frozen-lockfile --network-timeout 100000 && \ + yarn intl:build 2>&1 | tee i18n.log && \ + if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \ + SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web +diff --git a/app.config.js b/app.config.js +index 246d8abd3..a6582864b 100644 +--- a/app.config.js ++++ b/app.config.js +@@ -33,8 +33,8 @@ module.exports = function (_config) { + return { + expo: { + version: VERSION, +- name: 'Bluesky', +- slug: 'bluesky', ++ name: 'Aiat', ++ slug: 'aiat', + scheme: 'bluesky', + owner: 'blueskysocial', + runtimeVersion: { +@@ -45,15 +45,20 @@ module.exports = function (_config) { + primaryColor: '#1083fe', + newArchEnabled: false, + ios: { ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, ++ }, + supportsTablet: false, +- bundleIdentifier: 'xyz.blueskyweb.app', ++ bundleIdentifier: 'ai.syui.at', + config: { + usesNonExemptEncryption: false, + }, + icon: + PLATFORM === 'web' // web build doesn't like .icon files + ? './assets/app-icons/ios_icon_default_next.png' +- : './assets/app-icons/ios_icon_default.icon', ++ : './assets/app-icons/ios_icon_default_next.png', + infoPlist: { + UIBackgroundModes: ['remote-notification'], + NSCameraUsageDescription: +@@ -113,7 +118,7 @@ module.exports = function (_config) { + entitlements: { + 'com.apple.developer.kernel.increased-memory-limit': true, + 'com.apple.developer.kernel.extended-virtual-addressing': true, +- 'com.apple.security.application-groups': 'group.app.bsky', ++ 'com.apple.security.application-groups': 'group.ai.syui.at', + }, + privacyManifests: { + NSPrivacyCollectedDataTypes: [ +@@ -182,7 +187,7 @@ module.exports = function (_config) { + backgroundColor: '#006AFF', + }, + googleServicesFile: './google-services.json', +- package: 'xyz.blueskyweb.app', ++ package: 'ai.syui.at', + intentFilters: [ + { + action: 'VIEW', +@@ -220,7 +225,7 @@ module.exports = function (_config) { + checkAutomatically: 'NEVER', + }, + plugins: [ +- 'expo-video', ++ 'expo-video', './plugins/withCodeSignEntitlements.js', + 'expo-localization', + 'expo-web-browser', + [ +@@ -239,6 +244,11 @@ module.exports = function (_config) { + 'expo-build-properties', + { + ios: { ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, ++ }, + deploymentTarget: '15.1', + buildReactNativeFromSource: true, + }, +@@ -264,7 +274,6 @@ module.exports = function (_config) { + networkInstrumentation: true, + }, + ], +- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js', + './plugins/withGradleJVMHeapSizeIncrease.js', + './plugins/withAndroidManifestLargeHeapPlugin.js', + './plugins/withAndroidManifestFCMIconPlugin.js', +@@ -296,6 +305,11 @@ module.exports = function (_config) { + 'expo-splash-screen', + { + ios: { ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, ++ }, + enableFullScreenImage_legacy: true, + backgroundColor: '#ffffff', + image: './assets/splash.png', +@@ -394,29 +408,30 @@ module.exports = function (_config) { + build: { + experimental: { + ios: { ++ infoPlist: { ++ NSAppTransportSecurity: { ++ NSAllowsArbitraryLoads: true, ++ }, ++ }, + appExtensions: [ + { + targetName: 'Share-with-Bluesky', +- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky', ++ bundleIdentifier: 'ai.syui.at.Share-with-Bluesky', + entitlements: { + 'com.apple.security.application-groups': [ +- 'group.app.bsky', ++ 'group.ai.syui.at', + ], + }, + }, + { + targetName: 'BlueskyNSE', +- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE', ++ bundleIdentifier: 'ai.syui.at.BlueskyNSE', + entitlements: { + 'com.apple.security.application-groups': [ +- 'group.app.bsky', ++ 'group.ai.syui.at', + ], + }, + }, +- { +- targetName: 'BlueskyClip', +- bundleIdentifier: 'xyz.blueskyweb.app.AppClip', +- }, + ], + }, + }, diff --git a/ios/patching/002-social-app-ios-lib.patch b/ios/patching/002-social-app-ios-lib.patch new file mode 100644 index 0000000..0ef4ec5 --- /dev/null +++ b/ios/patching/002-social-app-ios-lib.patch @@ -0,0 +1,79 @@ +diff --git a/src/lib/constants.ts b/src/lib/constants.ts +index 231447b4f..33c51cc0a 100644 +--- a/src/lib/constants.ts ++++ b/src/lib/constants.ts +@@ -7,12 +7,12 @@ import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' + export const LOCAL_DEV_SERVICE = + Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' + export const STAGING_SERVICE = 'https://staging.bsky.dev' +-export const BSKY_SERVICE = 'https://bsky.social' +-export const BSKY_SERVICE_DID = 'did:web:bsky.social' +-export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app' ++export const BSKY_SERVICE = 'https://syu.is' ++export const BSKY_SERVICE_DID = 'did:web:syu.is' ++export const PUBLIC_BSKY_SERVICE = 'https://bsky.syu.is' + export const DEFAULT_SERVICE = BSKY_SERVICE +-const HELP_DESK_LANG = 'en-us' +-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}` ++const HELP_DESK_LANG = 'ja-jp' ++export const HELP_DESK_URL = 'https://syu.is/about/support/help' + export const EMBED_SERVICE = 'https://embed.bsky.app' + export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` + export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' +@@ -209,8 +209,8 @@ export const urls = { + }, + } + +-export const PUBLIC_APPVIEW = 'https://api.bsky.app' +-export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app' ++export const PUBLIC_APPVIEW = 'https://bsky.syu.is' ++export const PUBLIC_APPVIEW_DID = 'did:web:bsky.syu.is' + export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev' + + export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same +@@ -236,8 +236,8 @@ export const BLUESKY_MOD_SERVICE_HEADERS = { + } + + export const webLinks = { +- tos: `https://bsky.social/about/support/tos`, +- privacy: `https://bsky.social/about/support/privacy-policy`, ++ tos: `https://syu.is/about/support/tos`, ++ privacy: `https://syu.is/about/support/privacy-policy`, + community: `https://bsky.social/about/support/community-guidelines`, + communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`, + } +diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx +index 860e841eb..a595b0868 100644 +--- a/src/lib/statsig/statsig.tsx ++++ b/src/lib/statsig/statsig.tsx +@@ -265,6 +265,7 @@ export async function tryFetchGates( + } + + export function initialize() { ++ if (!SDK_KEY) return Promise.resolve() + return Statsig.initialize(SDK_KEY, null, createStatsigOptions([])) + } + +@@ -309,6 +310,9 @@ export function Provider({children}: {children: React.ReactNode}) { + return () => clearInterval(id) + }, [handleIntervalTick]) + ++ if (!SDK_KEY) { ++ return {children} ++ } + return ( + + 0), }} + accessibilityRole="image" + /> + ) : ( +@@ -619,7 +619,7 @@ export {PreviewableUserAvatar} + // -prf + function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string { + return isEnabled +- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/') ++ ? uri.replace('https://cdn.web.syu.is/img/avatar/plain/', 'https://bsky.syu.is/img/avatar/plain/') + : uri + } + +diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx +index d7208df13..b711f71c7 100644 +--- a/src/view/icons/Logo.tsx ++++ b/src/view/icons/Logo.tsx +@@ -1,75 +1,17 @@ + import React from 'react' +-import {type TextProps} from 'react-native' +-import Svg, { +- Defs, +- LinearGradient, +- Path, +- type PathProps, +- Stop, +- type SvgProps, +-} from 'react-native-svg' + import {Image} from 'expo-image' ++import {flatten} from '#/alf' + +-import {useKawaiiMode} from '#/state/preferences/kawaii' +-import {flatten, useTheme} from '#/alf' +- +-const ratio = 57 / 64 +- +-type Props = { +- fill?: PathProps['fill'] +- style?: TextProps['style'] +-} & Omit +- +-export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) { +- const t = useTheme() +- const {fill, ...rest} = props +- const gradient = fill === 'sky' +- const styles = flatten(props.style) +- const _fill = gradient +- ? 'url(#sky)' +- : fill || styles?.color || t.palette.primary_500 +- // @ts-ignore it's fiiiiine +- const size = parseInt(rest.width || 32, 10) +- +- const isKawaii = useKawaiiMode() +- +- if (isKawaii) { +- return ( +- 100 +- ? require('../../../assets/kawaii.png') +- : require('../../../assets/kawaii_smol.png') +- } +- accessibilityLabel="Bluesky" +- accessibilityHint="" +- accessibilityIgnoresInvertColors +- style={[{height: size, aspectRatio: 1.4}]} +- /> +- ) +- } +- ++export const Logo = React.forwardRef(function LogoImpl(props: any, ref) { ++ const {width, style} = props ++ // @ts-ignore ++ const size = parseInt(width || 32, 10) + return ( +- +- {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..b2ea406 --- /dev/null +++ b/ios/patching/006-social-app-ios-shell.patch @@ -0,0 +1,713 @@ +diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx +index 9915d0a2d..c200a7c67 100644 +--- a/src/components/dialogs/BirthDateSettings.tsx ++++ b/src/components/dialogs/BirthDateSettings.tsx +@@ -163,7 +163,7 @@ function BirthdayInner({ + + You must be at least 13 years old to use Bluesky. Read our{' '} + + Terms of Service + {' '} +diff --git a/src/components/dialogs/ServerInput.tsx b/src/components/dialogs/ServerInput.tsx +index d7c02bb9f..fda1dfe4a 100644 +--- a/src/components/dialogs/ServerInput.tsx ++++ b/src/components/dialogs/ServerInput.tsx +@@ -165,7 +165,7 @@ function DialogInner({ + + Bluesky is an open network where you can choose your own + provider. If you're new here, we recommend sticking with the +- default Bluesky Social option. ++ default syu.is option. + + + +diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx +index ed2a6cfb7..2f387b4a8 100644 +--- a/src/view/shell/Drawer.tsx ++++ b/src/view/shell/Drawer.tsx +@@ -1,60 +1,50 @@ +-import React, {type ComponentProps, type JSX} from 'react' +-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native' +-import {useSafeAreaInsets} from 'react-native-safe-area-context' +-import {msg, Plural, plural, Trans} from '@lingui/macro' +-import {useLingui} from '@lingui/react' +-import {StackActions, useNavigation} from '@react-navigation/native' +- +-import {useActorStatus} from '#/lib/actor-status' +-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' +-import {type PressableScale} from '#/lib/custom-animations/PressableScale' +-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' +-import {getTabState, TabState} from '#/lib/routes/helpers' +-import {type NavigationProp} from '#/lib/routes/types' +-import {sanitizeHandle} from '#/lib/strings/handles' +-import {colors} from '#/lib/styles' +-import {isWeb} from '#/platform/detection' +-import {emitSoftReset} from '#/state/events' +-import {useKawaiiMode} from '#/state/preferences/kawaii' +-import {useUnreadNotifications} from '#/state/queries/notifications/unread' +-import {useProfileQuery} from '#/state/queries/profile' +-import {type SessionAccount, useSession} from '#/state/session' +-import {useSetDrawerOpen} from '#/state/shell' +-import {formatCount} from '#/view/com/util/numeric/format' +-import {UserAvatar} from '#/view/com/util/UserAvatar' +-import {NavSignupCard} from '#/view/shell/NavSignupCard' +-import {atoms as a, tokens, useTheme, web} from '#/alf' +-import {Button, ButtonIcon, ButtonText} from '#/components/Button' +-import {Divider} from '#/components/Divider' ++import React, { type ComponentProps, type JSX } from 'react' ++import { Linking, ScrollView, TouchableOpacity, View } from 'react-native' ++import { useSafeAreaInsets } from 'react-native-safe-area-context' ++import { msg, Plural, plural, Trans } from '@lingui/macro' ++import { useLingui } from '@lingui/react' ++import { StackActions, useNavigation } from '@react-navigation/native' ++ ++import { useActorStatus } from '#/lib/actor-status' ++import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants' ++import { type PressableScale } from '#/lib/custom-animations/PressableScale' ++import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState' ++import { getTabState, TabState } from '#/lib/routes/helpers' ++import { type NavigationProp } from '#/lib/routes/types' ++import { sanitizeHandle } from '#/lib/strings/handles' ++import { colors } from '#/lib/styles' ++import { isWeb } from '#/platform/detection' ++import { emitSoftReset } from '#/state/events' ++import { useKawaiiMode } from '#/state/preferences/kawaii' ++import { useUnreadNotifications } from '#/state/queries/notifications/unread' ++import { useProfileQuery } from '#/state/queries/profile' ++import { type SessionAccount, useSession } from '#/state/session' ++import { useSetDrawerOpen } from '#/state/shell' ++import { formatCount } from '#/view/com/util/numeric/format' ++import { UserAvatar } from '#/view/com/util/UserAvatar' ++import { NavSignupCard } from '#/view/shell/NavSignupCard' ++import { atoms as a, tokens, useTheme, web } from '#/alf' ++import { Button } from '#/components/Button' ++import { Divider } from '#/components/Divider' + import { + Bell_Filled_Corner0_Rounded as BellFilled, + Bell_Stroke2_Corner0_Rounded as Bell, + } from '#/components/icons/Bell' +-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' +-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList' +-import { +- Hashtag_Filled_Corner0_Rounded as HashtagFilled, +- Hashtag_Stroke2_Corner0_Rounded as Hashtag, +-} from '#/components/icons/Hashtag' + import { + HomeOpen_Filled_Corner0_Rounded as HomeFilled, + HomeOpen_Stoke2_Corner0_Rounded as Home, + } from '#/components/icons/HomeOpen' +-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass' +-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2' +-import { +- Message_Stroke2_Corner0_Rounded as Message, +- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled, +-} from '#/components/icons/Message' +-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2' ++import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass' ++import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2' ++import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2' + import { + UserCircle_Filled_Corner0_Rounded as UserCircleFilled, + UserCircle_Stroke2_Corner0_Rounded as UserCircle, + } from '#/components/icons/UserCircle' +-import {InlineLinkText} from '#/components/Link' +-import {Text} from '#/components/Typography' +-import {useSimpleVerificationState} from '#/components/verification' +-import {VerificationCheck} from '#/components/verification/VerificationCheck' ++import { InlineLinkText } from '#/components/Link' ++import { Text } from '#/components/Typography' ++import { useSimpleVerificationState } from '#/components/verification' ++import { VerificationCheck } from '#/components/verification/VerificationCheck' + + const iconWidth = 26 + +@@ -65,11 +55,11 @@ let DrawerProfileCard = ({ + account: SessionAccount + onPressProfile: () => void + }): React.ReactNode => { +- const {_, i18n} = useLingui() ++ const { _, i18n } = useLingui() + const t = useTheme() +- const {data: profile} = useProfileQuery({did: account.did}) +- const verification = useSimpleVerificationState({profile}) +- const {isActive: live} = useActorStatus(profile) ++ const { data: profile } = useProfileQuery({ did: account.did }) ++ const verification = useSimpleVerificationState({ profile }) ++ const { isActive: live } = useActorStatus(profile) + + return ( + ): React.ReactNode => { ++let DrawerContent = ({ }: React.PropsWithoutRef<{}>): React.ReactNode => { + const t = useTheme() + const insets = useSafeAreaInsets() + const setDrawerOpen = useSetDrawerOpen() +@@ -150,27 +139,20 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + const { + isAtHome, + isAtSearch, +- isAtFeeds, +- isAtBookmarks, + isAtNotifications, + isAtMyProfile, +- isAtMessages, + } = useNavigationTabState() +- const {hasSession, currentAccount} = useSession() +- +- // events +- // = ++ const { hasSession, currentAccount } = useSession() + + const onPressTab = React.useCallback( + (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { + const state = navigation.getState() + setDrawerOpen(false) + if (isWeb) { +- // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh + if (tab === 'MyProfile') { +- navigation.navigate('Profile', {name: currentAccount!.handle}) ++ navigation.navigate('Profile', { name: currentAccount!.handle }) + } else { +- // @ts-expect-error struggles with string unions, apparently ++ // @ts-expect-error struggles with string unions + navigation.navigate(tab) + } + } else { +@@ -178,21 +160,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + if (tabState === TabState.InsideAtRoot) { + emitSoftReset() + } else if (tabState === TabState.Inside) { +- // find the correct navigator in which to pop-to-top +- const target = state.routes.find(route => route.name === `${tab}Tab`) +- ?.state?.key ++ const target = state.routes.find(route => route.name === `${tab}Tab`)?.state?.key + if (target) { +- // if we found it, trigger pop-to-top +- navigation.dispatch({ +- ...StackActions.popToTop(), +- target, +- }) ++ navigation.dispatch({ ...StackActions.popToTop(), target }) + } else { +- // fallback: reset navigation +- navigation.reset({ +- index: 0, +- routes: [{name: `${tab}Tab`}], +- }) ++ navigation.reset({ index: 0, routes: [{ name: `${tab}Tab` }] }) + } + } else { + navigation.navigate(`${tab}Tab`) +@@ -203,76 +175,21 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + ) + + const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) +- +- const onPressSearch = React.useCallback( +- () => onPressTab('Search'), +- [onPressTab], +- ) +- +- const onPressMessages = React.useCallback( +- () => onPressTab('Messages'), +- [onPressTab], +- ) +- +- const onPressNotifications = React.useCallback( +- () => onPressTab('Notifications'), +- [onPressTab], +- ) +- +- const onPressProfile = React.useCallback(() => { +- onPressTab('MyProfile') +- }, [onPressTab]) +- +- const onPressMyFeeds = React.useCallback(() => { +- navigation.navigate('Feeds') +- setDrawerOpen(false) +- }, [navigation, setDrawerOpen]) +- +- const onPressLists = React.useCallback(() => { +- navigation.navigate('Lists') +- setDrawerOpen(false) +- }, [navigation, setDrawerOpen]) +- +- const onPressBookmarks = React.useCallback(() => { +- navigation.navigate('Bookmarks') +- setDrawerOpen(false) +- }, [navigation, setDrawerOpen]) +- ++ const onPressSearch = React.useCallback(() => onPressTab('Search'), [onPressTab]) ++ const onPressNotifications = React.useCallback(() => onPressTab('Notifications'), [onPressTab]) ++ const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab]) + const onPressSettings = React.useCallback(() => { + navigation.navigate('Settings') + setDrawerOpen(false) + }, [navigation, setDrawerOpen]) + +- const onPressFeedback = React.useCallback(() => { +- Linking.openURL( +- FEEDBACK_FORM_URL({ +- email: currentAccount?.email, +- handle: currentAccount?.handle, +- }), +- ) +- }, [currentAccount]) +- +- const onPressHelp = React.useCallback(() => { +- Linking.openURL(HELP_DESK_URL) +- }, []) +- +- // rendering +- // = +- + return ( + + ++ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}> + + {hasSession && currentAccount ? ( + ): React.ReactNode => { + + + )} +- + + + +@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + <> + + +- + +- +- +- + ): React.ReactNode => { + ) : ( + <> + +- + + + )} +@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + + + +- +- + + ) + } + DrawerContent = React.memo(DrawerContent) +-export {DrawerContent} +- +-let DrawerFooter = ({ +- onPressFeedback, +- onPressHelp, +-}: { +- onPressFeedback: () => void +- onPressHelp: () => void +-}): React.ReactNode => { +- const {_} = useLingui() +- const insets = useSafeAreaInsets() +- return ( +- +- +- +- +- ) +-} +-DrawerFooter = React.memo(DrawerFooter) ++export { DrawerContent } + + interface MenuItemProps extends ComponentProps { + icon: JSX.Element +@@ -400,7 +250,7 @@ let SearchMenuItem = ({ + isActive: boolean + onPress: () => void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + return ( + void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + return ( + void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg`Chat`)} +- bold={isActive} +- onPress={onPress} +- /> +- ) +-} +-ChatMenuItem = React.memo(ChatMenuItem) +- + let NotificationsMenuItem = ({ + isActive, + onPress, +@@ -478,7 +302,7 @@ let NotificationsMenuItem = ({ + isActive: boolean + onPress: () => void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + const numUnreadNotifications = useUnreadNotifications() + return ( +@@ -495,11 +319,11 @@ let NotificationsMenuItem = ({ + numUnreadNotifications === '' + ? '' + : _( +- msg`${plural(numUnreadNotifications ?? 0, { +- one: '# unread item', +- other: '# unread items', +- })}` || '', +- ) ++ msg`${plural(numUnreadNotifications ?? 0, { ++ one: '# unread item', ++ other: '# unread items', ++ })}` || '', ++ ) + } + count={numUnreadNotifications} + bold={isActive} +@@ -509,72 +333,6 @@ let NotificationsMenuItem = ({ + } + NotificationsMenuItem = React.memo(NotificationsMenuItem) + +-let FeedsMenuItem = ({ +- isActive, +- onPress, +-}: { +- isActive: boolean +- onPress: () => void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg`Feeds`)} +- bold={isActive} +- onPress={onPress} +- /> +- ) +-} +-FeedsMenuItem = React.memo(FeedsMenuItem) +- +-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- +- return ( +- } +- label={_(msg`Lists`)} +- onPress={onPress} +- /> +- ) +-} +-ListsMenuItem = React.memo(ListsMenuItem) +- +-let BookmarksMenuItem = ({ +- isActive, +- onPress, +-}: { +- isActive: boolean +- onPress: () => void +-}): React.ReactNode => { +- const {_} = useLingui() +- const t = useTheme() +- +- return ( +- +- ) : ( +- +- ) +- } +- label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))} +- onPress={onPress} +- /> +- ) +-} +-BookmarksMenuItem = React.memo(BookmarksMenuItem) +- + let ProfileMenuItem = ({ + isActive, + onPress, +@@ -582,7 +340,7 @@ let ProfileMenuItem = ({ + isActive: boolean + onPress: () => void + }): React.ReactNode => { +- const {_} = useLingui() ++ const { _ } = useLingui() + const t = useTheme() + return ( + void}): React.ReactNode => { +- const {_} = useLingui() ++let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => { ++ const { _ } = useLingui() + const t = useTheme() + return ( + void}): React.ReactNode => { + } + SettingsMenuItem = React.memo(SettingsMenuItem) + +-function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) { ++function MenuItem({ icon, label, count, bold, onPress }: MenuItemProps) { + const t = useTheme() + return ( + +- +- +- ) +-} +-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 ( +