ai/at
1
0

add ios social-app

This commit is contained in:
2025-12-06 21:14:08 +09:00
parent 948551c185
commit a62cd95209
59 changed files with 3625 additions and 637 deletions

View File

@@ -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
}

13
ios/.env Normal file
View File

@@ -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"

View File

@@ -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 (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>About This App</Text>
<Text style={styles.paragraph}>
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.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Key Features</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Connect to any AT Protocol PDS</Text>
<Text style={styles.listItem}> Post text, images, and videos</Text>
<Text style={styles.listItem}> Follow users and view timelines</Text>
<Text style={styles.listItem}> Customize feeds and moderation settings</Text>
<Text style={styles.listItem}> Direct messaging support</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Open Source</Text>
<Text style={styles.paragraph}>
This application is based on the Bluesky social-app, licensed under the MIT
License. The original source code is available at:
</Text>
<Pressable
onPress={() =>
handleLinkPress('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>github.com/bluesky-social/social-app</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>AT Protocol</Text>
<Text style={styles.paragraph}>
This app uses the AT Protocol (Authenticated Transfer Protocol), an open and
decentralized standard for social applications.
</Text>
<Pressable onPress={() => handleLinkPress('https://atproto.com')}>
<Text style={styles.link}>atproto.com</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>License</Text>
<Text style={styles.paragraph}>
Copyright 20232025 Bluesky Social PBC
</Text>
<Text style={styles.paragraph}>
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.
</Text>
<Text style={styles.paragraph}>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.versionText}>Version 1.0.0</Text>
</View>
</View>
)
}
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',
},
})

View File

@@ -1,95 +0,0 @@
import React from 'react'
import {View, Text, StyleSheet, Pressable, Linking} from 'react-native'
export default function LicenseNotice() {
return (
<View style={styles.container}>
<Text style={styles.title}>Open Source Licenses</Text>
<View style={styles.section}>
<Text style={styles.projectName}>Bluesky Social App</Text>
<Text style={styles.license}>MIT License</Text>
<Text style={styles.copyright}>Copyright 20232025 Bluesky Social PBC</Text>
<Text style={styles.licenseText}>
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:
</Text>
<Text style={styles.licenseText}>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</Text>
<Text style={styles.licenseText}>
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.
</Text>
<Pressable
onPress={() =>
Linking.openURL('https://github.com/bluesky-social/social-app')
}>
<Text style={styles.link}>View Source Code</Text>
</Pressable>
</View>
</View>
)
}
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,
},
})

View File

@@ -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 (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Introduction</Text>
<Text style={styles.paragraph}>
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.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Information We Collect</Text>
<Text style={styles.paragraph}>
The App may collect and use the following information:
</Text>
<Text style={styles.subTitle}>1. Information Collected Automatically</Text>
<View style={styles.list}>
<Text style={styles.listItem}> Device information (model, OS version)</Text>
<Text style={styles.listItem}> App usage data (sessions, features used)</Text>
<Text style={styles.listItem}> Crash logs and performance data</Text>
</View>
<Text style={styles.subTitle}>2. Information Provided by Users</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
DID (Decentralized Identifier) and handle for authentication
</Text>
<Text style={styles.listItem}> Posts, media, and social interactions</Text>
<Text style={styles.listItem}> Profile information (avatar, display name, bio)</Text>
</View>
<View style={styles.highlight}>
<Text style={styles.highlightText}>
Important: Your data is stored on your chosen PDS (Personal Data Server).
This app does not store your content on our servers.
</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>How We Use Your Information</Text>
<View style={styles.list}>
<Text style={styles.listItem}>
To provide AT Protocol social networking features
</Text>
<Text style={styles.listItem}> To improve app performance and user experience</Text>
<Text style={styles.listItem}> To diagnose and fix technical issues</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Data Sharing</Text>
<Text style={styles.paragraph}>
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.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Your Rights</Text>
<Text style={styles.paragraph}>
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.
</Text>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contact</Text>
<Text style={styles.paragraph}>
For questions about this Privacy Policy, please contact:
</Text>
<Pressable onPress={() => handleLinkPress('https://syu.is')}>
<Text style={styles.link}>https://syu.is</Text>
</Pressable>
</View>
<View style={styles.section}>
<Text style={styles.lastUpdated}>Last Updated: December 3, 2025</Text>
</View>
</View>
)
}
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',
},
})

View File

@@ -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<CommonNavigatorParams, 'PrivacyPolicy'>
export const PrivacyPolicyScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`Privacy Policy`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<PrivacyContent />
</View>
<View style={s.footerSpacer} />
</ScrollView>
</Layout.Screen>
)
}

72
ios/README.md Normal file
View File

@@ -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. 年齢保証、年齢確認ページがでてくるのを削除。誕生日を入力する処理を削除。アプリ配布国は限定します。

View File

@@ -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<CommonNavigatorParams, 'Support'>
export const SupportScreen = (_props: Props) => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
return (
<Layout.Screen>
<ViewHeader title={_(msg`App Info`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<AppInfo />
</ScrollView>
</Layout.Screen>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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"
}

BIN
ios/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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 <GateCache.Provider value={gateCache}>{children}</GateCache.Provider>
+ }
return (
<GateCache.Provider value={gateCache}>
<StatsigProvider
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 6088e2806..7c903763d 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -53,7 +53,7 @@ export function toNiceDomain(url: string): string {
try {
const urlp = new URL(url)
if (`https://${urlp.host}` === BSKY_SERVICE) {
- return 'Bluesky Social'
+ return 'syu.is'
}
return urlp.host ? urlp.host : url
} catch (e) {

View File

@@ -0,0 +1,164 @@
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 8a9e51a33..cb0eb7b71 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -444,7 +444,7 @@ let EditableUserAvatar = ({
<HighPriorityImage
testID="userAvatarImage"
style={aviStyle}
- source={{uri: avatar}}
+ source={{ uri: hackModifyThumbnailPath(avatar, 1 > 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<SvgProps, 'style'>
-
-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 (
- <Image
- source={
- size > 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 (
- <Svg
- fill="none"
- // @ts-ignore it's fiiiiine
- ref={ref}
- viewBox="0 0 64 57"
- {...rest}
- style={[{width: size, height: size * ratio}, styles]}>
- {gradient && (
- <Defs>
- <LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
- <Stop offset="0" stopColor="#0A7AFF" stopOpacity="1" />
- <Stop offset="1" stopColor="#59B9FF" stopOpacity="1" />
- </LinearGradient>
- </Defs>
- )}
-
- <Path
- fill={_fill}
- d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"
- />
- </Svg>
+ <Image
+ source={require('../../../assets/icon.png')}
+ style={[{width: size, height: size}, flatten(style)]}
+ contentFit="contain"
+ accessibilityLabel="Logo"
+ />
)
})
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 (
- <Svg
- fill="none"
- viewBox="0 0 64 17"
- {...rest}
- width={size}
- height={Number(size) * ratio}>
- <Path
- fill={fill || pal.text.color}
- d="M8.478 6.252c1.503.538 2.3 1.78 2.3 3.172 0 2.356-1.576 3.785-4.6 3.785H0V0h5.974c2.875 0 4.267 1.466 4.267 3.413 0 1.3-.594 2.245-1.763 2.839Zm-2.69-4.193H2.504v3.45h3.284c1.28 0 1.967-.667 1.967-1.78 0-1.02-.705-1.67-1.967-1.67Zm-3.284 9.072h3.544c1.41 0 2.17-.65 2.17-1.818 0-1.224-.723-1.837-2.17-1.837H2.504v3.655ZM14.251 13.209h-2.337V0h2.337v13.209ZM22.001 8.998V3.636h2.338v9.573h-2.263v-1.392c-.724 1.076-1.726 1.614-3.006 1.614-2.022 0-3.34-1.224-3.34-3.45V3.636h2.338v5.955c0 1.206.594 1.818 1.8 1.818 1.132 0 2.133-.835 2.133-2.411ZM34.979 8.59v.556h-7.161c.167 1.651 1.076 2.467 2.486 2.467 1.076 0 1.8-.463 2.189-1.372h2.244c-.5 1.947-2.17 3.19-4.452 3.19-1.428 0-2.579-.463-3.45-1.372-.872-.91-1.318-2.115-1.318-3.637 0-1.502.427-2.708 1.299-3.636.872-.909 2.004-1.372 3.432-1.372 1.447 0 2.597.482 3.45 1.428.854.946 1.28 2.208 1.28 3.747Zm-4.75-3.358c-1.28 0-2.17.742-2.393 2.281h4.805c-.204-1.391-1.057-2.281-2.411-2.281ZM40.16 13.469c-2.783 0-4.249-1.095-4.379-3.303h2.282c.13 1.188.724 1.633 2.134 1.633 1.261 0 1.892-.39 1.892-1.15 0-.687-.445-1.02-1.874-1.262l-1.094-.185c-2.097-.353-3.136-1.318-3.136-2.894 0-1.8 1.429-2.894 3.97-2.894 2.728 0 4.138 1.075 4.23 3.246h-2.207c-.056-1.169-.742-1.577-2.023-1.577-1.113 0-1.67.371-1.67 1.113 0 .668.483.965 1.596 1.169l1.206.186c2.32.426 3.32 1.28 3.32 2.912 0 1.93-1.557 3.006-4.247 3.006ZM54.667 13.209h-2.671l-2.783-4.453-1.447 1.447v3.006h-2.3V0h2.3v7.606l3.896-3.97h2.783l-3.618 3.618 3.84 5.955ZM60.772 6.048l.78-2.412H64l-3.692 10.352c-.39 1.057-.872 1.818-1.484 2.245-.612.426-1.484.63-2.634.63-.39 0-.724-.018-1.02-.055V14.97h.89c1.057 0 1.577-.65 1.577-1.54 0-.445-.149-1.094-.446-1.929l-2.746-7.866h2.487l.779 2.393c.575 1.8 1.076 3.58 1.521 5.343.408-1.521.928-3.302 1.54-5.324Z"
- />
- </Svg>
+ <Text style={[
+ a.font_bold,
+ {
+ fontSize,
+ color: fill || t.palette.primary_500,
+ letterSpacing: -0.5
+ },
+ style
+ ]}>
+ Aiat
+ </Text>
)
}

View File

@@ -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<AllNavigatableRoutes>({
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],

View File

@@ -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) {
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms of Service`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
@@ -88,7 +88,7 @@ export function AboutSettingsScreen({}: Props) {
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy"
label={_(msg`Privacy Policy`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
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() {
<Trans>
Your account was found to be in violation of the{' '}
<InlineLinkText
- label={_(msg`Bluesky Social Terms of Service`)}
- to="https://bsky.social/about/support/tos"
+ label={_(msg`syu.is Terms of Service`)}
+ to="https://syu.is/about/support/tos"
style={[a.text_md, a.leading_normal]}
overridePresentation>
- Bluesky Social Terms of Service
+ syu.is Terms of Service
</InlineLinkText>
. 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<HomeTabNavigatorParams, 'Home' | 'Start'>
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 (
- <Layout.Screen testID="HomeScreen">
- <HomeScreenReady
- {...props}
- preferences={preferences}
- pinnedFeedInfos={pinnedFeedInfos}
- />
- </Layout.Screen>
- )
- } else {
- return (
- <Layout.Screen>
- <Layout.Center style={styles.loading}>
- <ActivityIndicator size="large" />
- </Layout.Center>
- </Layout.Screen>
- )
- }
+ return (
+ <Layout.Screen testID="HomeScreen">
+ <HomeScreenReady {...props} preferences={safePreferences} pinnedFeedInfos={safePinnedFeedInfos as any} />
+ </Layout.Screen>
+ )
}
-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<PagerRef>(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 (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- // @ts-ignore
- feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
- />
- )
- }
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- feeds={pinnedFeedInfos}
- />
- )
- },
- [onPressSelected, pinnedFeedInfos, demoMode],
- )
-
- const renderFollowingEmptyState = React.useCallback(() => {
- return <FollowingEmptyState />
- }, [])
-
- const renderCustomFeedEmptyState = React.useCallback(() => {
- return <CustomFeedEmptyState />
- }, [])
-
- const homeFeedParams = React.useMemo<FeedParams>(() => {
- 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 (
- <Pager
- ref={pagerRef}
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}
- initialPage={selectedIndex}>
- <FeedPage
- testID="demoFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed="demo"
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- </Pager>
- )
- }
-
- return hasSession ? (
- <Pager
- key={allFeeds.join(',')}
- ref={pagerRef}
- testID="homeScreen"
- initialPage={selectedIndex}
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- {pinnedFeedInfos.length ? (
- pinnedFeedInfos.map((feedInfo, index) => {
+ const renderTabBar = React.useCallback((props) => {
+ return <HomeHeader key="FEEDS_TAB_BAR" {...props} testID="homeScreenFeedTabs" onPressSelected={onPressSelected} feeds={pinnedFeedInfos} />
+ }, [onPressSelected, pinnedFeedInfos])
+
+ const renderFollowingEmptyState = React.useCallback(() => <FollowingEmptyState />, [])
+ const renderCustomFeedEmptyState = React.useCallback(() => <CustomFeedEmptyState />, [])
+
+ const homeFeedParams = React.useMemo(() => ({
+ mergeFeedEnabled: false, mergeFeedSources: []
+ }), [preferences])
+
+ return (
+ <Pager ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}>
+ {pinnedFeedInfos.map((feedInfo, index) => {
const feed = feedInfo.feedDescriptor
if (feed === 'following') {
- return (
- <FeedPage
- key={feed}
- testID="followingFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- feedParams={homeFeedParams}
- renderEmptyState={renderFollowingEmptyState}
- renderEndOfFeed={FollowingEndOfFeed}
- feedInfo={feedInfo}
- />
- )
+ return <FeedPage key={feed} testID="followingFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} feedParams={homeFeedParams} renderEmptyState={renderFollowingEmptyState} renderEndOfFeed={FollowingEndOfFeed} feedInfo={feedInfo} />
}
- const savedFeedConfig = feedInfo.savedFeed
- return (
- <FeedPage
- key={feed}
- testID="customFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- renderEmptyState={renderCustomFeedEmptyState}
- savedFeedConfig={savedFeedConfig}
- feedInfo={feedInfo}
- />
- )
- })
- ) : (
- <NoFeedsPinned preferences={preferences} />
- )}
- </Pager>
- ) : (
- <Pager
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
+ return <FeedPage key={feed} testID="customFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} renderEmptyState={renderCustomFeedEmptyState} savedFeedConfig={feedInfo.savedFeed} feedInfo={feedInfo} />
+ })}
</Pager>
)
}
-
-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<CommonNavigatorParams, 'PrivacyPolicy'>
-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 (
<Layout.Screen>
- <ViewHeader title={_(msg`Privacy Policy`)} />
- <ScrollView style={[s.hContentRegion, pal.view]}>
- <View style={[s.p20]}>
- <Text style={pal.text}>
- <Trans>
- The Privacy Policy has been moved to{' '}
- <TextLink
- style={pal.link}
- href="https://bsky.social/about/support/privacy-policy"
- text="bsky.social/about/support/privacy-policy"
- />
- </Trans>
- </Text>
- </View>
- <View style={s.footerSpacer} />
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/privacy-policy' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}
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<CommonNavigatorParams, 'TermsOfService'>
-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 (
<Layout.Screen>
- <ViewHeader title={_(msg`Terms of Service`)} />
- <ScrollView style={[s.hContentRegion, pal.view]}>
- <View style={[s.p20]}>
- <Text style={pal.text}>
- <Trans>The Terms of Service have been moved to</Trans>{' '}
- <TextLink
- style={pal.link}
- href="https://bsky.social/about/support/tos"
- text="bsky.social/about/support/tos"
- />
- </Text>
- </View>
- <View style={s.footerSpacer} />
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/tos' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}

View File

@@ -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({
<Trans>
You must be at least 13 years old to use Bluesky. Read our{' '}
<SimpleInlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms of Service`)}>
Terms of Service
</SimpleInlineLinkText>{' '}
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({
<Trans>
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.
</Trans>
</Admonition>
</View>
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 (
<TouchableOpacity
@@ -81,7 +71,6 @@ let DrawerProfileCard = ({
<UserAvatar
size={52}
avatar={profile?.avatar}
- // See https://github.com/bluesky-social/social-app/pull/1801:
usePlainRNImage={true}
type={profile?.associated?.labeler ? 'labeler' : 'user'}
live={live}
@@ -140,9 +129,9 @@ let DrawerProfileCard = ({
)
}
DrawerProfileCard = React.memo(DrawerProfileCard)
-export {DrawerProfileCard}
+export { DrawerProfileCard }
-let DrawerContent = ({}: React.PropsWithoutRef<{}>): 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 (
<View
testID="drawer"
style={[a.flex_1, a.border_r, t.atoms.bg, t.atoms.border_contrast_low]}>
<ScrollView
style={[a.flex_1]}
- contentContainerStyle={[
- {
- paddingTop: Math.max(
- insets.top + a.pt_xl.paddingTop,
- a.pt_xl.paddingTop,
- ),
- },
- ]}>
+ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
<View style={[a.px_xl]}>
{hasSession && currentAccount ? (
<DrawerProfileCard
@@ -284,7 +201,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<NavSignupCard />
</View>
)}
-
<Divider style={[a.mt_xl, a.mb_sm]} />
</View>
@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<>
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
- <ChatMenuItem isActive={isAtMessages} onPress={onPressMessages} />
<NotificationsMenuItem
isActive={isAtNotifications}
onPress={onPressNotifications}
/>
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
- <ListsMenuItem onPress={onPressLists} />
- <BookmarksMenuItem
- isActive={isAtBookmarks}
- onPress={onPressBookmarks}
- />
<ProfileMenuItem
isActive={isAtMyProfile}
onPress={onPressProfile}
@@ -312,7 +221,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
) : (
<>
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
</>
)}
@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<ExtraLinks />
</View>
</ScrollView>
-
- <DrawerFooter
- onPressFeedback={onPressFeedback}
- onPressHelp={onPressHelp}
- />
</View>
)
}
DrawerContent = React.memo(DrawerContent)
-export {DrawerContent}
-
-let DrawerFooter = ({
- onPressFeedback,
- onPressHelp,
-}: {
- onPressFeedback: () => void
- onPressHelp: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const insets = useSafeAreaInsets()
- return (
- <View
- style={[
- a.flex_row,
- a.gap_sm,
- a.flex_wrap,
- a.pl_xl,
- a.pt_md,
- {
- paddingBottom: Math.max(
- insets.bottom + tokens.space.xs,
- tokens.space.xl,
- ),
- },
- ]}>
- <Button
- label={_(msg`Send feedback`)}
- size="small"
- variant="solid"
- color="secondary"
- onPress={onPressFeedback}>
- <ButtonIcon icon={Message} position="left" />
- <ButtonText>
- <Trans>Feedback</Trans>
- </ButtonText>
- </Button>
- <Button
- label={_(msg`Get help`)}
- size="small"
- variant="outline"
- color="secondary"
- onPress={onPressHelp}
- style={{
- backgroundColor: 'transparent',
- }}>
- <ButtonText>
- <Trans>Help</Trans>
- </ButtonText>
- </Button>
- </View>
- )
-}
-DrawerFooter = React.memo(DrawerFooter)
+export { DrawerContent }
interface MenuItemProps extends ComponentProps<typeof PressableScale> {
icon: JSX.Element
@@ -400,7 +250,7 @@ let SearchMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -426,7 +276,7 @@ let HomeMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -445,32 +295,6 @@ let HomeMenuItem = ({
}
HomeMenuItem = React.memo(HomeMenuItem)
-let ChatMenuItem = ({
- isActive,
- onPress,
-}: {
- isActive: boolean
- onPress: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
- return (
- <MenuItem
- icon={
- isActive ? (
- <MessageFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Message style={[t.atoms.text]} width={iconWidth} />
- )
- }
- 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 (
- <MenuItem
- icon={
- isActive ? (
- <HashtagFilled width={iconWidth} style={[t.atoms.text]} />
- ) : (
- <Hashtag width={iconWidth} style={[t.atoms.text]} />
- )
- }
- label={_(msg`Feeds`)}
- bold={isActive}
- onPress={onPress}
- />
- )
-}
-FeedsMenuItem = React.memo(FeedsMenuItem)
-
-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
-
- return (
- <MenuItem
- icon={<List style={[t.atoms.text]} width={iconWidth} />}
- 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 (
- <MenuItem
- icon={
- isActive ? (
- <BookmarkFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Bookmark style={[t.atoms.text]} width={iconWidth} />
- )
- }
- 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 (
<MenuItem
@@ -600,8 +358,8 @@ let ProfileMenuItem = ({
}
ProfileMenuItem = React.memo(ProfileMenuItem)
-let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
+let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => {
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -613,7 +371,7 @@ let SettingsMenuItem = ({onPress}: {onPress: () => 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 (
<Button
@@ -621,7 +379,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
onPress={onPress}
accessibilityRole="tab"
label={label}>
- {({hovered, pressed}) => (
+ {({ hovered, pressed }) => (
<View
style={[
a.flex_1,
@@ -640,7 +398,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
a.absolute,
a.inset_0,
a.align_end,
- {top: -4, right: a.gap_sm.gap * -1},
+ { top: -4, right: a.gap_sm.gap * -1 },
]}>
<View
style={[
@@ -686,37 +444,23 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
}
function ExtraLinks() {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
const kawaii = useKawaiiMode()
+ const navigation = useNavigation<NavigationProp>()
return (
<View style={[a.flex_col, a.gap_md, a.flex_wrap]}>
- <InlineLinkText
- style={[a.text_md]}
- label={_(msg`Terms of Service`)}
- to="https://bsky.social/about/support/tos">
- <Trans>Terms of Service</Trans>
- </InlineLinkText>
- <InlineLinkText
- style={[a.text_md]}
- to="https://bsky.social/about/support/privacy-policy"
- label={_(msg`Privacy Policy`)}>
- <Trans>Privacy Policy</Trans>
- </InlineLinkText>
- {kawaii && (
- <Text style={t.atoms.text_contrast_medium}>
- <Trans>
- Logo by{' '}
- <InlineLinkText
- style={[a.text_md]}
- to="/profile/sawaratsuki.bsky.social"
- label="@sawaratsuki.bsky.social">
- @sawaratsuki.bsky.social
- </InlineLinkText>
- </Trans>
+ <TouchableOpacity onPress={() => navigation.navigate('TermsOfService')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ <Trans>Terms of Service</Trans>
</Text>
- )}
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => navigation.navigate('PrivacyPolicy')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ <Trans>Privacy Policy</Trans>
+ </Text>
+ </TouchableOpacity>
</View>
)
}
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 779ebda68..bfd9b70fa 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -198,38 +198,6 @@ export function BottomBar({navigation}: BottomTabBarProps) {
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
/>
- <Btn
- testID="bottomBarMessagesBtn"
- icon={
- isAtMessages ? (
- <MessageFilled
- width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
- />
- ) : (
- <Message
- width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
- />
- )
- }
- onPress={onPressMessages}
- notificationCount={numUnreadMessages.numUnread}
- hasNew={numUnreadMessages.hasNew}
- accessible={true}
- accessibilityRole="tab"
- accessibilityLabel={_(msg`Chat`)}
- accessibilityHint={
- numUnreadMessages.count > 0
- ? _(
- msg`${plural(numUnreadMessages.numUnread ?? 0, {
- one: '# unread item',
- other: '# unread items',
- })}` || '',
- )
- : ''
- }
- />
<Btn
testID="bottomBarNotificationsBtn"
icon={
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
index 1d097fc9a..e11a3a202 100644
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -109,13 +109,13 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
</>
)}
<InlineLinkText
- to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy"
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
{' • '}
<InlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms`)}>
{_(msg`Terms`)}
</InlineLinkText>

View File

@@ -0,0 +1,138 @@
diff --git a/plugins/notificationsExtension/withNotificationsExtension.js b/plugins/notificationsExtension/withNotificationsExtension.js
index 6a00cfd23..f91decc08 100644
--- a/plugins/notificationsExtension/withNotificationsExtension.js
+++ b/plugins/notificationsExtension/withNotificationsExtension.js
@@ -10,7 +10,7 @@ const EXTENSION_NAME = 'BlueskyNSE'
const EXTENSION_CONTROLLER_NAME = 'NotificationService'
const withNotificationsExtension = config => {
- const soundFiles = ['dm.aiff']
+ const soundFiles = []
return withPlugins(config, [
// IOS
diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts
index 104328330..c992a21de 100644
--- a/src/ageAssurance/util.ts
+++ b/src/ageAssurance/util.ts
@@ -2,87 +2,32 @@ import {useMemo} from 'react'
import {
ageAssuranceRuleIDs as ids,
type AppBskyAgeassuranceDefs,
- getAgeAssuranceRegionConfig,
} from '@atproto/api'
-
-import {getAge} from '#/lib/strings/time'
-import {useAgeAssuranceDataContext} from '#/ageAssurance/data'
import {AgeAssuranceAccess} from '#/ageAssurance/types'
import {type Geolocation, useGeolocation} from '#/geolocation'
+import {useAgeAssuranceDataContext} from '#/ageAssurance/data'
-const DEFAULT_MIN_AGE = 13
-
-/**
- * Get age assurance region config based on geolocation, with fallback to
- * app defaults if no region config is found.
- *
- * See {@link getAgeAssuranceRegionConfig} for the generic option, which can
- * return undefined if the geolocation does not match any AA region.
- */
export function getAgeAssuranceRegionConfigWithFallback(
config: AppBskyAgeassuranceDefs.Config,
geolocation: Geolocation,
): AppBskyAgeassuranceDefs.ConfigRegion {
- const region = getAgeAssuranceRegionConfig(config, {
- countryCode: geolocation.countryCode ?? '',
- regionCode: geolocation.regionCode,
- })
-
- return (
- region || {
- countryCode: '*',
- regionCode: undefined,
- rules: [
- {
- $type: ids.IfDeclaredOverAge,
- age: DEFAULT_MIN_AGE,
- access: AgeAssuranceAccess.Full,
- },
- {
- $type: ids.Default,
- access: AgeAssuranceAccess.None,
- },
- ],
- }
- )
+ return {
+ countryCode: '*',
+ regionCode: undefined,
+ rules: [{ $type: ids.Default, access: AgeAssuranceAccess.Full }],
+ }
}
-/**
- * Hook to get the age assurance region config based on current geolocation.
- * Does not fall-back to our app defaults. If no config is found, returns
- * undefined, which indicates no regional age assurance rules apply.
- */
export function useAgeAssuranceRegionConfig() {
const geolocation = useGeolocation()
const {config} = useAgeAssuranceDataContext()
- return useMemo(() => {
- if (!config) return
- // use generic helper, we want to potentially return undefined
- return getAgeAssuranceRegionConfig(config, {
- countryCode: geolocation.countryCode ?? '',
- regionCode: geolocation.regionCode,
- })
- }, [config, geolocation])
+ return useMemo(() => ({
+ countryCode: '*',
+ regionCode: undefined,
+ rules: [{ $type: ids.Default, access: AgeAssuranceAccess.Full }],
+ }), [config, geolocation])
}
-/**
- * Some users may have erroneously set their birth date to the current date
- * if one wasn't set on their account. We previously didn't do validation on
- * the bday dialog, and it defaulted to the current date. This bug _has_ been
- * seen in production, so we need to check for it where possible.
- */
-export function isLegacyBirthdateBug(birthDate: string) {
- return ['2025', '2024', '2023'].includes((birthDate || '').slice(0, 4))
-}
-
-/**
- * Returns whether the user is under the minimum age required to use the app.
- * This applies to all regions.
- */
-export function isUserUnderMinimumAge(birthDate: string) {
- return getAge(new Date(birthDate)) < DEFAULT_MIN_AGE
-}
-
-export function isUserUnderAdultAge(birthDate: string) {
- return getAge(new Date(birthDate)) < 18
-}
+export function isLegacyBirthdateBug(birthDate: string) { return false }
+export function isUserUnderMinimumAge(birthDate: string) { return false }
+export function isUserUnderAdultAge(birthDate: string) { return false }
diff --git a/src/components/PolicyUpdateOverlay/updates/202508/index.tsx b/src/components/PolicyUpdateOverlay/updates/202508/index.tsx
index 8365057e8..59c8506a2 100644
--- a/src/components/PolicyUpdateOverlay/updates/202508/index.tsx
+++ b/src/components/PolicyUpdateOverlay/updates/202508/index.tsx
@@ -26,12 +26,12 @@ export function Content({state}: {state: PolicyUpdateState}) {
const links = {
terms: {
overridePresentation: false,
- to: `https://bsky.social/about/support/tos`,
+ to: `https://syu.is/about/support/tos`,
label: _(msg`Terms of Service`),
},
privacy: {
overridePresentation: false,
- to: `https://bsky.social/about/support/privacy-policy`,
+ to: `https://syu.is/about/support/privacy-policy`,
label: _(msg`Privacy Policy`),
},
copyright: {

View File

@@ -0,0 +1,122 @@
diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
index a89eaadc4..19f5b4107 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<CommonNavigatorParams, 'PrivacyPolicy'>
-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 (
<Layout.Screen>
- <ViewHeader title={_(msg`Privacy Policy`)} />
- <ScrollView style={[s.hContentRegion, pal.view]}>
- <View style={[s.p20]}>
- <Text style={pal.text}>
- <Trans>
- The Privacy Policy has been moved to{' '}
- <TextLink
- style={pal.link}
- href="https://bsky.social/about/support/privacy-policy"
- text="bsky.social/about/support/privacy-policy"
- />
- </Trans>
- </Text>
- </View>
- <View style={s.footerSpacer} />
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/privacy-policy' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}
diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
index d843c713c..b96f112fc 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<CommonNavigatorParams, 'TermsOfService'>
-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 (
<Layout.Screen>
- <ViewHeader title={_(msg`Terms of Service`)} />
- <ScrollView style={[s.hContentRegion, pal.view]}>
- <View style={[s.p20]}>
- <Text style={pal.text}>
- <Trans>The Terms of Service have been moved to</Trans>{' '}
- <TextLink
- style={pal.link}
- href="https://bsky.social/about/support/tos"
- text="bsky.social/about/support/tos"
- />
- </Text>
- </View>
- <View style={s.footerSpacer} />
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/tos' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}

View File

@@ -0,0 +1,668 @@
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index fa33a9d56..642076b96 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -50,6 +50,7 @@ import {
snoozeEmailConfirmationPrompt,
} from '#/state/shell/reminders'
import {CommunityGuidelinesScreen} from '#/view/screens/CommunityGuidelines'
+import {LicenseScreen} from '#/view/screens/License'
import {CopyrightPolicyScreen} from '#/view/screens/CopyrightPolicy'
import {DebugModScreen} from '#/view/screens/DebugMod'
import {FeedsScreen} from '#/view/screens/Feeds'
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index c315a8341..9b2f50a83 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -39,6 +39,7 @@ export type CommonNavigatorParams = {
Support: undefined
PrivacyPolicy: undefined
TermsOfService: undefined
+ License: undefined
CommunityGuidelines: undefined
CopyrightPolicy: undefined
LanguageSettings: undefined
diff --git a/src/routes.ts b/src/routes.ts
index 1ed913bb2..77ea6deca 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -71,8 +71,8 @@ export const router = new Router<AllNavigatableRoutes>({
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/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index ed2a6cfb7..982c40b55 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 (
<TouchableOpacity
@@ -81,7 +71,6 @@ let DrawerProfileCard = ({
<UserAvatar
size={52}
avatar={profile?.avatar}
- // See https://github.com/bluesky-social/social-app/pull/1801:
usePlainRNImage={true}
type={profile?.associated?.labeler ? 'labeler' : 'user'}
live={live}
@@ -140,9 +129,9 @@ let DrawerProfileCard = ({
)
}
DrawerProfileCard = React.memo(DrawerProfileCard)
-export {DrawerProfileCard}
+export { DrawerProfileCard }
-let DrawerContent = ({}: React.PropsWithoutRef<{}>): 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 (
<View
testID="drawer"
style={[a.flex_1, a.border_r, t.atoms.bg, t.atoms.border_contrast_low]}>
<ScrollView
style={[a.flex_1]}
- contentContainerStyle={[
- {
- paddingTop: Math.max(
- insets.top + a.pt_xl.paddingTop,
- a.pt_xl.paddingTop,
- ),
- },
- ]}>
+ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
<View style={[a.px_xl]}>
{hasSession && currentAccount ? (
<DrawerProfileCard
@@ -284,7 +201,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<NavSignupCard />
</View>
)}
-
<Divider style={[a.mt_xl, a.mb_sm]} />
</View>
@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<>
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
- <ChatMenuItem isActive={isAtMessages} onPress={onPressMessages} />
<NotificationsMenuItem
isActive={isAtNotifications}
onPress={onPressNotifications}
/>
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
- <ListsMenuItem onPress={onPressLists} />
- <BookmarksMenuItem
- isActive={isAtBookmarks}
- onPress={onPressBookmarks}
- />
<ProfileMenuItem
isActive={isAtMyProfile}
onPress={onPressProfile}
@@ -312,7 +221,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
) : (
<>
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
</>
)}
@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<ExtraLinks />
</View>
</ScrollView>
-
- <DrawerFooter
- onPressFeedback={onPressFeedback}
- onPressHelp={onPressHelp}
- />
</View>
)
}
DrawerContent = React.memo(DrawerContent)
-export {DrawerContent}
-
-let DrawerFooter = ({
- onPressFeedback,
- onPressHelp,
-}: {
- onPressFeedback: () => void
- onPressHelp: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const insets = useSafeAreaInsets()
- return (
- <View
- style={[
- a.flex_row,
- a.gap_sm,
- a.flex_wrap,
- a.pl_xl,
- a.pt_md,
- {
- paddingBottom: Math.max(
- insets.bottom + tokens.space.xs,
- tokens.space.xl,
- ),
- },
- ]}>
- <Button
- label={_(msg`Send feedback`)}
- size="small"
- variant="solid"
- color="secondary"
- onPress={onPressFeedback}>
- <ButtonIcon icon={Message} position="left" />
- <ButtonText>
- <Trans>Feedback</Trans>
- </ButtonText>
- </Button>
- <Button
- label={_(msg`Get help`)}
- size="small"
- variant="outline"
- color="secondary"
- onPress={onPressHelp}
- style={{
- backgroundColor: 'transparent',
- }}>
- <ButtonText>
- <Trans>Help</Trans>
- </ButtonText>
- </Button>
- </View>
- )
-}
-DrawerFooter = React.memo(DrawerFooter)
+export { DrawerContent }
interface MenuItemProps extends ComponentProps<typeof PressableScale> {
icon: JSX.Element
@@ -400,7 +250,7 @@ let SearchMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -426,7 +276,7 @@ let HomeMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -445,32 +295,6 @@ let HomeMenuItem = ({
}
HomeMenuItem = React.memo(HomeMenuItem)
-let ChatMenuItem = ({
- isActive,
- onPress,
-}: {
- isActive: boolean
- onPress: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
- return (
- <MenuItem
- icon={
- isActive ? (
- <MessageFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Message style={[t.atoms.text]} width={iconWidth} />
- )
- }
- 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 (
- <MenuItem
- icon={
- isActive ? (
- <HashtagFilled width={iconWidth} style={[t.atoms.text]} />
- ) : (
- <Hashtag width={iconWidth} style={[t.atoms.text]} />
- )
- }
- label={_(msg`Feeds`)}
- bold={isActive}
- onPress={onPress}
- />
- )
-}
-FeedsMenuItem = React.memo(FeedsMenuItem)
-
-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
-
- return (
- <MenuItem
- icon={<List style={[t.atoms.text]} width={iconWidth} />}
- 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 (
- <MenuItem
- icon={
- isActive ? (
- <BookmarkFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Bookmark style={[t.atoms.text]} width={iconWidth} />
- )
- }
- 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 (
<MenuItem
@@ -600,8 +358,8 @@ let ProfileMenuItem = ({
}
ProfileMenuItem = React.memo(ProfileMenuItem)
-let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
+let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => {
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -613,7 +371,7 @@ let SettingsMenuItem = ({onPress}: {onPress: () => 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 (
<Button
@@ -621,7 +379,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
onPress={onPress}
accessibilityRole="tab"
label={label}>
- {({hovered, pressed}) => (
+ {({ hovered, pressed }) => (
<View
style={[
a.flex_1,
@@ -640,7 +398,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
a.absolute,
a.inset_0,
a.align_end,
- {top: -4, right: a.gap_sm.gap * -1},
+ { top: -4, right: a.gap_sm.gap * -1 },
]}>
<View
style={[
@@ -686,37 +444,28 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
}
function ExtraLinks() {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
const kawaii = useKawaiiMode()
+ const navigation = useNavigation<NavigationProp>()
return (
<View style={[a.flex_col, a.gap_md, a.flex_wrap]}>
- <InlineLinkText
- style={[a.text_md]}
- label={_(msg`Terms of Service`)}
- to="https://bsky.social/about/support/tos">
- <Trans>Terms of Service</Trans>
- </InlineLinkText>
- <InlineLinkText
- style={[a.text_md]}
- to="https://bsky.social/about/support/privacy-policy"
- label={_(msg`Privacy Policy`)}>
- <Trans>Privacy Policy</Trans>
- </InlineLinkText>
- {kawaii && (
- <Text style={t.atoms.text_contrast_medium}>
- <Trans>
- Logo by{' '}
- <InlineLinkText
- style={[a.text_md]}
- to="/profile/sawaratsuki.bsky.social"
- label="@sawaratsuki.bsky.social">
- @sawaratsuki.bsky.social
- </InlineLinkText>
- </Trans>
+ <TouchableOpacity onPress={() => navigation.navigate('TermsOfService')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ <Trans>Terms of Service</Trans>
</Text>
- )}
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => navigation.navigate('PrivacyPolicy')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ <Trans>Privacy Policy</Trans>
+ </Text>
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => navigation.navigate('License')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ <Trans>License</Trans>
+ </Text>
+ </TouchableOpacity>
</View>
)
}

View File

@@ -0,0 +1,25 @@
diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx
index aa6cd4156..37c7a38b0 100644
--- a/src/screens/Signup/index.tsx
+++ b/src/screens/Signup/index.tsx
@@ -211,20 +211,6 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
a.align_center,
]}>
<AppLanguageDropdown />
- <Text
- style={[
- a.flex_1,
- t.atoms.text_contrast_medium,
- !gtMobile && a.text_md,
- ]}>
- <Trans>Having trouble?</Trans>{' '}
- <InlineLinkText
- label={_(msg`Contact support`)}
- to={FEEDBACK_FORM_URL({email: state.email})}
- style={[!gtMobile && a.text_md]}>
- <Trans>Contact support</Trans>
- </InlineLinkText>
- </Text>
</View>
</View>
</ScreenTransition>

View File

@@ -0,0 +1,36 @@
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 6b8257b91..e21b4900d 100644
--- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx
@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) {
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms of Service`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
@@ -88,13 +88,21 @@ export function AboutSettingsScreen({}: Props) {
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy"
label={_(msg`Privacy Policy`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
<Trans>Privacy Policy</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
+ <SettingsList.LinkItem
+ to="/support/license"
+ label={_(msg`License`)}>
+ <SettingsList.ItemIcon icon={NewspaperIcon} />
+ <SettingsList.ItemText>
+ <Trans>License</Trans>
+ </SettingsList.ItemText>
+ </SettingsList.LinkItem>
<SettingsList.LinkItem
to={STATUS_PAGE_URL}
label={_(msg`Status Page`)}>

View File

@@ -0,0 +1,21 @@
diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
index 1703036b5..42baa462c 100644
--- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx
@@ -231,16 +231,6 @@ export function SettingsScreen({}: Props) {
<Trans>Languages</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
- <SettingsList.PressableItem
- onPress={() => Linking.openURL(HELP_DESK_URL)}
- label={_(msg`Help`)}
- accessibilityHint={_(msg`Opens helpdesk in browser`)}>
- <SettingsList.ItemIcon icon={CircleQuestionIcon} />
- <SettingsList.ItemText>
- <Trans>Help</Trans>
- </SettingsList.ItemText>
- <SettingsList.Chevron />
- </SettingsList.PressableItem>
<SettingsList.LinkItem to="/settings/about" label={_(msg`About`)}>
<SettingsList.ItemIcon icon={BubbleInfoIcon} />
<SettingsList.ItemText>

View File

@@ -0,0 +1,31 @@
diff --git a/plugins/withCodeSignEntitlements.js b/plugins/withCodeSignEntitlements.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/withCodeSignEntitlements.js
@@ -0,0 +1,25 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+const { withXcodeProject } = require('@expo/config-plugins')
+
+const withCodeSignEntitlements = (config) => {
+ return withXcodeProject(config, (config) => {
+ const xcodeProject = config.modResults
+ const configurations = xcodeProject.pbxXCBuildConfigurationSection()
+
+ for (const key in configurations) {
+ const configuration = configurations[key]
+ if (
+ configuration.buildSettings &&
+ configuration.comment &&
+ !configuration.comment.includes('TEST')
+ ) {
+ configuration.buildSettings.CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION =
+ 'YES'
+ }
+ }
+
+ return config
+ })
+}
+
+module.exports = withCodeSignEntitlements

View File

@@ -0,0 +1,16 @@
diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
index 9a0a9c9d5..fa233c8ab 100644
--- a/src/ageAssurance/index.tsx
+++ b/src/ageAssurance/index.tsx
@@ -97,7 +97,10 @@ function InnerProvider({children}: {children: React.ReactNode}) {
return {
Access: AgeAssuranceAccess,
Status: AgeAssuranceStatus,
- state,
+ state: {
+ ...state,
+ access: AgeAssuranceAccess.Full,
+ },
flags: {
adultContentDisabled,
chatDisabled,

View File

@@ -0,0 +1,414 @@
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index e058e2883..03d7dc990 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,67 @@ 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<HomeTabNavigatorParams, 'Home' | 'Start'>
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 = [{
+ feedDescriptor: 'following',
+ displayName: 'Following',
+ id: 'following',
+ type: 'feed',
+ savedFeed: undefined,
+ pinned: true,
+ }]
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 (
- <Layout.Screen testID="HomeScreen">
- <HomeScreenReady
- {...props}
- preferences={preferences}
- pinnedFeedInfos={pinnedFeedInfos}
- />
- </Layout.Screen>
- )
- } else {
- return (
- <Layout.Screen>
- <Layout.Center style={styles.loading}>
- <ActivityIndicator size="large" />
- </Layout.Center>
- </Layout.Screen>
- )
- }
+ return (
+ <Layout.Screen testID="HomeScreen">
+ <HomeScreenReady {...props} preferences={safePreferences} pinnedFeedInfos={safePinnedFeedInfos as any} />
+ </Layout.Screen>
+ )
}
-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<PagerRef>(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 +97,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 (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- // @ts-ignore
- feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
- />
- )
- }
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- feeds={pinnedFeedInfos}
- />
- )
- },
- [onPressSelected, pinnedFeedInfos, demoMode],
- )
-
- const renderFollowingEmptyState = React.useCallback(() => {
- return <FollowingEmptyState />
- }, [])
-
- const renderCustomFeedEmptyState = React.useCallback(() => {
- return <CustomFeedEmptyState />
- }, [])
-
- const homeFeedParams = React.useMemo<FeedParams>(() => {
- 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 (
- <Pager
- ref={pagerRef}
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}
- initialPage={selectedIndex}>
- <FeedPage
- testID="demoFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed="demo"
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- </Pager>
- )
- }
-
- return hasSession ? (
- <Pager
- key={allFeeds.join(',')}
- ref={pagerRef}
- testID="homeScreen"
- initialPage={selectedIndex}
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- {pinnedFeedInfos.length ? (
- pinnedFeedInfos.map((feedInfo, index) => {
+ const renderTabBar = React.useCallback((props) => {
+ return <HomeHeader key="FEEDS_TAB_BAR" {...props} testID="homeScreenFeedTabs" onPressSelected={onPressSelected} feeds={pinnedFeedInfos} />
+ }, [onPressSelected, pinnedFeedInfos])
+
+ const renderFollowingEmptyState = React.useCallback(() => <FollowingEmptyState />, [])
+ const renderCustomFeedEmptyState = React.useCallback(() => <CustomFeedEmptyState />, [])
+
+ const homeFeedParams = React.useMemo(() => ({
+ mergeFeedEnabled: false, mergeFeedSources: []
+ }), [preferences])
+
+ return (
+ <Pager ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}>
+ {pinnedFeedInfos.map((feedInfo, index) => {
const feed = feedInfo.feedDescriptor
if (feed === 'following') {
- return (
- <FeedPage
- key={feed}
- testID="followingFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- feedParams={homeFeedParams}
- renderEmptyState={renderFollowingEmptyState}
- renderEndOfFeed={FollowingEndOfFeed}
- feedInfo={feedInfo}
- />
- )
+ return <FeedPage key={feed} testID="followingFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} feedParams={homeFeedParams} renderEmptyState={renderFollowingEmptyState} renderEndOfFeed={FollowingEndOfFeed} feedInfo={feedInfo} />
}
- const savedFeedConfig = feedInfo.savedFeed
- return (
- <FeedPage
- key={feed}
- testID="customFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- renderEmptyState={renderCustomFeedEmptyState}
- savedFeedConfig={savedFeedConfig}
- feedInfo={feedInfo}
- />
- )
- })
- ) : (
- <NoFeedsPinned preferences={preferences} />
- )}
- </Pager>
- ) : (
- <Pager
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
+ return <FeedPage key={feed} testID="customFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} renderEmptyState={renderCustomFeedEmptyState} savedFeedConfig={feedInfo.savedFeed} feedInfo={feedInfo} />
+ })}
</Pager>
)
}
-
-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 } })

86
ios/patching/License.tsx Normal file
View File

@@ -0,0 +1,86 @@
import React from 'react'
import { ScrollView } from 'react-native'
import * as Layout from '#/components/Layout'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
export function LicenseScreen() {
useSetTitle('License')
const t = useTheme()
return (
<Layout.Screen>
<ScrollView
style={[a.flex_1, {backgroundColor: t.palette.white}]}
contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
<Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>License</Text>
<Text style={[a.mb_md]}>
This application is based on Bluesky Social App.
</Text>
<Text style={[a.text_md, a.mb_md, {color: t.palette.primary_500}]}>
https://github.com/bluesky-social/social-app
</Text>
<Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text>
<Text style={[a.mb_md, {fontFamily: 'monospace'}]}>
Copyright (c) 2022-2025 Bluesky PBC
</Text>
<Text style={[a.mb_md]}>
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:
</Text>
<Text style={[a.mb_md]}>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</Text>
<Text style={[a.mb_md]}>
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.
</Text>
<Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}></Text>
<Text style={[a.mb_md]}>
使
/
</Text>
<Text style={[a.mb_md]}>
</Text>
<Text style={[a.mb_md]}>
使
</Text>
<Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
Original License: https://github.com/bluesky-social/social-app/blob/main/LICENSE
</Text>
</ScrollView>
</Layout.Screen>
)
}

54
ios/patching/README.md Normal file
View File

@@ -0,0 +1,54 @@
# iOS Social App Patches
このディレクトリには、iOS版social-appのカスタマイズパッチが含まれています。
## パッチファイル一覧
- `001-social-app-ios-config.patch` - Dockerfile, app.config.js の設定変更
- `002-social-app-ios-lib.patch` - lib/constants.ts, lib/statsig, lib/url-helpers の変更
- `003-social-app-ios-view.patch` - Logo, Logotype, UserAvatar の UI 変更
- `004-social-app-ios-core.patch` - agent.ts, App.native.tsx, routes.ts のコア変更
- `005-social-app-ios-screens.patch` - Settings, Home, Privacy, TOS 画面の変更
- `006-social-app-ios-shell.patch` - Drawer, BottomBar, RightNav, ServerInput などシェル変更
- `007-social-app-ios-misc.patch` - notifications, ageAssurance, PolicyUpdate などその他変更
- `008-social-app-ios-policy-tos-error.patch` - プライバシーポリシー・利用規約の英語/日本語二言語化、DIDエラー処理
- `009-social-app-ios-license.patch` - ライセンスページの追加Drawer, Navigation, routes, types
- `010-social-app-ios-remove-contact-support.patch` - アカウント作成時の「Contact support」リンクを削除
- `011-social-app-ios-splash-license-footer.patch` - スプラッシュ画面の「What's up?」削除、© syui フッター追加
- `012-social-app-ios-settings-about-help.patch` - About 設定のリンク修正syu.is に変更)
- `013-social-app-ios-settings-remove-help.patch` - Settings から Help 項目を削除
- `License.tsx` - ライセンス表示画面(新規ファイル)
## 使用方法
### パッチの適用
```bash
cd /Users/syui/ai/at/ios
./setup.zsh patch
```
### リポジトリのリセット
```bash
cd /Users/syui/ai/at/ios
./setup.zsh reset
```
### すべてのパッチを適用(デフォルト)
```bash
cd /Users/syui/ai/at/ios
./setup.zsh
```
## パッチの更新方法
repos/social-app で変更を加えた後:
```bash
cd /Users/syui/ai/at/repos/social-app
git diff [ファイル名] > /Users/syui/ai/at/ios/patching/新しいパッチ.patch
```
その後、`setup.zsh``PATCH_FILES_IOS` 配列に新しいパッチファイル名を追加してください。

72
ios/preview.zsh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/zsh
set -e
d=${0:a:h}
cd $d
source $d/.env
xcrun simctl uninstall booted $BUNDLE_ID
echo "Running iOS preview workflow..."
cd "$REPO_DIR"
# 0. Environment Setup (Fix Node Version)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
echo "Checking Node version..."
if command -v nvm >/dev/null; then
nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)"
else
echo "nvm not found, using system node: $(node -v)"
fi
# 1. Install dependencies
echo "1. Installing dependencies (yarn)..."
yarn install
# 2. Prebuild (Generate ios directory)
echo "2. Running Expo Prebuild..."
# Clean old ios folder to remove old entitlements/AppClip targets
rm -rf ios
npx expo prebuild --platform ios --clean
# 3. CocoaPods
echo "3. Installing CocoaPods..."
# Ensure PATH includes Homebrew ruby gems if needed
export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
cd ios
pod install
cd ..
# 4. Signing (Manual Step)
echo "4. Opening Xcode for Signing..."
XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
# Fallback search if variable name logic differs
if [ ! -d "$XCODE_PROJ" ]; then
XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
fi
open "$XCODE_PROJ"
echo "========================================================"
echo " [ACTION REQUIRED] "
echo " Xcode opened ($XCODE_PROJ)."
echo " 1. Go to 'Signing & Capabilities' tab."
echo " 2. Select your Team."
echo " 3. Verify 'App Clip' target is gone."
echo " 4. Ensure no red errors exist."
echo " Press ENTER here once you are done to continue building."
echo "========================================================"
read
# 5. Run
echo "5. Building and Running..."
# If user wants specific device ID, uncomment below, otherwise let Expo ask/pick boot simulator
case $1 in
d|devicei)
npx expo run:ios --device "$DEVICE_ID" --configuration Release
;;
*)
npx expo run:ios
;;
esac

182
ios/setup.zsh Executable file
View File

@@ -0,0 +1,182 @@
#!/bin/zsh
cd ${0:a:h}
# iOS Social App Patch Setup Script
# Usage: ./ios/setup.zsh [patch|reset]
# Arrays for patch management
typeset -a FAILED_PATCHES
# Patch file lists for iOS
typeset -a PATCH_FILES_IOS
PATCH_FILES_IOS=(
"001-social-app-ios-config.patch"
"002-social-app-ios-lib.patch"
"003-social-app-ios-view.patch"
"004-social-app-ios-core.patch"
"005-social-app-ios-screens.patch"
"006-social-app-ios-shell.patch"
"007-social-app-ios-misc.patch"
"008-social-app-ios-policy-tos-error.patch"
"009-social-app-ios-license.patch"
"010-social-app-ios-remove-contact-support.patch"
"011-social-app-ios-splash-license-footer.patch"
"012-social-app-ios-settings-about-help.patch"
"013-social-app-ios-settings-remove-help.patch"
"019-social-app-ios-entitlements-plugin.patch"
"020-social-app-ios-bypass-age-assurance.patch"
)
function ios-env() {
d=${0:a:h:h}
patching_dir=${0:a:h}/patching
target_dir=$d/repos/social-app
}
# Common patch function with status detection
function apply-patch() {
local patch_name=$1
local target_dir=$2
local patch_file=$3
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Patch: ${patch_name}"
echo " Target: ${target_dir}"
echo " File: ${patch_file}"
pushd ${target_dir} > /dev/null
# Check if patch is already applied (reverse dry-run succeeds)
if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
echo "✅ Already applied - skipping"
popd > /dev/null
echo ""
return 0
fi
# Check if patch can be applied (forward dry-run succeeds)
if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
echo "🔧 Applying patch..."
if patch -p1 < ${patch_file}; then
echo "✅ Applied successfully"
popd > /dev/null
echo ""
return 0
else
echo "❌ Failed to apply"
FAILED_PATCHES+=("${patch_name} (${patch_file})")
popd > /dev/null
echo ""
return 1
fi
else
echo "⚠️ Cannot apply - file may have been modified"
echo " Please check manually"
FAILED_PATCHES+=("${patch_name} (${patch_file}) - file modified")
popd > /dev/null
echo ""
return 1
fi
}
function show-failed-patches() {
if [ ${#FAILED_PATCHES[@]} -eq 0 ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ All patches applied successfully!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
return 0
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ FAILED PATCHES SUMMARY"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "The following patches could not be applied:"
echo ""
for failed_patch in "${FAILED_PATCHES[@]}"; do
echo "${failed_patch}"
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
}
# Helper function for applying patches
function patch-apply() {
local name=$1
local patch_file=$2
apply-patch "${name}" "$target_dir" "$patching_dir/${patch_file}"
}
# Auto-apply patches from list
function ios-patch-apply-all() {
for filename in "${PATCH_FILES_IOS[@]}"; do
local title="${filename%.*}"
patch-apply "$title" "$filename"
done
}
# Copy new files that aren't in patches
function ios-copy-new-files() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📁 Copying new files..."
# Copy License.tsx
if [ -f "$patching_dir/License.tsx" ]; then
mkdir -p "$target_dir/src/view/screens"
cp "$patching_dir/License.tsx" "$target_dir/src/view/screens/License.tsx"
echo "✅ Copied License.tsx"
fi
# Copy logo.png to icon.png if it doesn't exist
if [ -f "$target_dir/assets/logo.png" ] && [ ! -f "$target_dir/assets/icon.png" ]; then
cp "$target_dir/assets/logo.png" "$target_dir/assets/icon.png"
echo "✅ Created assets/icon.png from assets/logo.png"
fi
echo ""
}
function ios-setup-clone() {
if [ ! -d $target_dir ]; then
echo "Error: social-app repository not found at $target_dir"
echo "Please run install.zsh first to clone repositories"
return 1
fi
echo "Repository found: $target_dir"
}
function ios-setup-reset() {
echo "Resetting social-app repository..."
cd $target_dir
git stash
git checkout .
echo "Reset complete"
}
# Main execution
ios-env
case "$1" in
patch)
ios-setup-clone
ios-patch-apply-all
ios-copy-new-files
show-failed-patches
exit
;;
reset)
ios-setup-reset
exit
;;
*)
ios-setup-clone
ios-patch-apply-all
ios-copy-new-files
show-failed-patches
;;
esac

4
task.md Normal file
View File

@@ -0,0 +1,4 @@
## Rules & Constraints
- Do not reformat existing code unless explicitly requested.
- Maintain existing import styles (newlines, sorting).
- Keep patches minimal (clean deltas).