ai/at
1
0

add ios social-app

This commit is contained in:
2025-12-06 21:14:08 +09:00
parent 948551c185
commit ea589afe41
67 changed files with 3966 additions and 640 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ repos
.claude
deploy.yml
claude.md
store.mobileprovision
.env

View File

@@ -65,3 +65,23 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=a
}
}
```
## build
- https://appstoreconnect.apple.com/
- https://developer.apple.com/account/resources/profiles/list
```sh
# build
./install.zsh
# build social-app
./install.zsh pull;./install.zsh patch;./install.zsh build social-app;./install.zsh push social-app
---
# server
./install.zsh
---
# social-app ios
./install.zsh pull;./ios/setup.zsh
./ios/build.zsh
```

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
@@ -98,33 +116,33 @@ function at-repos-pull() {
cd $d
}
function at-repos-social-app-ios-patch() {
$d/ios/setup.zsh
}
function at-repos-social-app-avatar-write() {
did_admin=did:plc:6qyecktefllvenje24fcxnie
dt=$d/repos/social-app/src
cd $dt
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g"
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g"
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/syu.is/${host}/g"
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/web.syu.is/web.${host}/g"
f=$dt/lib/constants.ts
sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
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/icons/Logotype.tsx
o=$d/icons/Logotype.tsx
cp -rf $o $f
f=$dt/view/com/util/UserAvatar.tsx
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/view/com/util/UserAvatar.tsx -o $f
sed -i "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
sed -i "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
sed -i "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
sediment "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
sediment "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
sediment "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/lib/strings/url-helpers.ts -o $dt/lib/strings/url-helpers.ts
sed -i "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sed -i "s/${did_admin}/${did}/g"
sediment "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sediment "s/${did_admin}/${did}/g"
}
# Common patch function with status detection
function apply-patch() {
local patch_name=$1
@@ -139,7 +157,8 @@ function apply-patch() {
pushd ${target_dir} > /dev/null
# Check if patch is already applied (reverse dry-run succeeds)
if patch --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
# Use -f to force dry-run to fail instead of asking questions if unapplied
if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
echo "✅ Already applied - skipping"
popd > /dev/null
echo ""
@@ -147,9 +166,9 @@ function apply-patch() {
fi
# Check if patch can be applied (forward dry-run succeeds)
if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
if patch -f --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
echo "🔧 Applying patch..."
if patch -p1 < ${patch_file}; then
if patch -f -p1 < ${patch_file}; then
echo "✅ Applied successfully"
popd > /dev/null
echo ""
@@ -284,29 +303,29 @@ function at-repos-ozone-patch() {
fi
# Replace process.env with env()
sed -i 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
sed -i 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
sediment 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
sediment 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
# Add missing SOCIAL_APP_DOMAIN constant after SOCIAL_APP_URL
sed -i '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
sediment '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
export const SOCIAL_APP_DOMAIN =\
env('\''NEXT_PUBLIC_SOCIAL_APP_DOMAIN'\'') || '\''bsky.app'\''\
}' lib/constants.ts 2>/dev/null || true
# Fix multiline process.env patterns
sed -i '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
sediment '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
s/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
sed -i '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
sediment '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
s/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
/^ \.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
}' lib/constants.ts 2>/dev/null || true
sed -i '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
sediment '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
s/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/export const HIGH_PROFILE_FOLLOWER_THRESHOLD = env('\''NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'\'')/
/^ \.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD$/d
}' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || ''
sed -i "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
popd > /dev/null
}
@@ -480,7 +499,7 @@ case "$1" in
exit
;;
patch)
at-repos-social-app-avatar-write
at-repos-social-app-ios-patch
at-repos-patch-apply-all
at-repos-ozone-patch
show-failed-patches
@@ -520,7 +539,8 @@ case "`cat /etc/hostname`" in
*)
at-repos-clone
at-repos-pull
at-repos-social-app-avatar-write
at-repos-social-app-ios-patch
#at-repos-social-app-avatar-write
at-repos-patch-apply-all
at-repos-ozone-patch
show-failed-patches

17
ios/.env.example Normal file
View File

@@ -0,0 +1,17 @@
APP_NAME="Aiat"
REPO_DIR="../repos/social-app"
APP_SLUG="aiat"
APP_SCHEME="syui"
APP_GROUP="group.ai.syui.at"
APP_MAIL=user@example.com
APP_KEYCHAIN=@keychain:KEYCHAIN_NAME
BUNDLE_ID="ai.syui.at"
SERVICE_URL="https://syu.is"
HELP_URL="https://syu.is/about/support/help"
PRIVACY_URL="https://syu.is/about/support/privacy-policy"
TERMS_URL="https://syu.is/about/support/tos"
REPO_DIR="../repos/social-app"
CONFIG_FILE="$REPO_DIR/app.config.js"
CONSTANTS_FILE="$REPO_DIR/src/lib/constants.ts"
IOS_CERTIFICATE_NAME="Apple Distribution: $TEAM($TEAM_ID)"
PDS_HOST=syu.is

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

93
ios/README.md Normal file
View File

@@ -0,0 +1,93 @@
今回の./ios (social-app)開発の要点をまとめます。
1. MITのライセンスを遵守すること、iosアプリとして出品しても問題ないようにすること
https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
2. "Bluesky"という名称を使用しないこと。アイコンの変更。リンクの変更
3. selfhostでも動くこと。本来のsocial-appは動きませんので、これは不便なのでiosアプリに出品することにしました。なお、これはすでにpatchで実現しています。
```sh
$ ./install.zsh pull
$ ./install.zsh patch
$ ./ios/setup.zsh
$ ./ios/preview.zsh
```
## App Storeへのアップロード
```sh
# 1. 事前にXcodeでサイニング設定を完了させる
# 2. store.mobileprovision を repos/social-app/ に配置
# 3. キーチェーンに AC_PASSWORD を登録
$ cd /Users/syui/ai/at/ios
$ ./build.sh
```
**必要な準備:**
- Apple Distribution証明書: `Apple Distribution: syutaro inagaki (WN6KD5ZT49)`
- App Store用Provisioning Profile: `store.mobileprovision`
- App-Specific Password: キーチェーンに `AC_PASSWORD` として登録
```sh
# App-Specific Passwordの登録
security add-generic-password -a "syui@syui.ai" -w "your-app-specific-password" -s "AC_PASSWORD"
```
## 実装済み
1. 最初の画面で、webではちゃんと私のサイトのロゴが表示されていますが、ios モバイル版では、未だにBluesky (icon)です。アカウント作成、サインイン、が表示されています。
2. 上のメニューバーにもBlueskyのロゴが表示されています。
3. サインイン後のホスティングプロバイダーで中身はsyu.isですが、表示は"Bluesky Social"になっています。これをsyu.isに変更してください。ios/webでコードは異なります。
4. チャット機能
チャット機能は今回無効化するので、下メニューバーやプロフィール、設定画面に表示しないでください。
5. 設定ボタン(左カラム)を押すと、フィードバック、ヘルプが表示されますが、非表示にしてください。
6. 設定ボタン(左カラム)を押すと、フィード、リスト、保存済みの項目がありますが、これを削除してください。
7. 設定ボタン(左カラム)を押すと、下に利用規約、プライバシーポリシーが表示されますが、リンクがbsky.socialです。
- /about/support/privacy-policy
- /about/support/tos
このページを独自に作って表示してください。
8. LOG 09:52:20 (logger) Poll latest failed {
"feed": "following",
"message": "Error: Could not find repo: did:plc:z72i7hdynmk6r22z27h6tvur"
}
9. LOG 10:24:03 (metric) router:navigate
LOG 10:24:04 (dms-agent) init failed {
"safeMessage": "could not resolve iss did"
}
9. 設定ボタン(左カラム)の一番下、利用規約やプライバシーポリシーが表示されいてるライセンスという項目を追加。ページを追加して、ライセンスの表示。
https://github.com/bluesky-social/social-app
https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
10. アカウント作成時(create account)のページに"Having trouble?"で`Contact support`のリンクがありますが、これを削除してください。
11. スタートページ、つまり、`Create account`, `Sign in`があるページの一番下にライセンスページへのリンクを追加してください。また、footerに`© syui`を表示してください。このページのタイトル下にある文字`What's up?`の項目は削除。
12. スタートページのラインセンスリンクが機能しない。おそらくページ変遷に問題があるため。また、ライセンスページは上下が隠れて見えてしまうため、大きく上下に空間を開けること。
13. 利用規約、プライバシーポリシーのページの言語が日本語で書かれています。ラインセンスと同様に、英語を基本とし、日本語訳をその下に表示してください。
14. Settings/ 項目の非表示を追加。
- Helpの非表示
- Aboutのリンクを変更
## 壊れた実装
1. ログイン後のメイン画面、"Following"の項目(フィード)に表示されるものをシンプルにします。表示するのはFollowingのみで、以下のものを削除してください。
- おすすめの削除
- Discoverの削除
- アカウントを探すの削除
2. 年齢保証、年齢確認ページがでてくるのを削除。誕生日を入力する処理を削除。アプリ配布国は限定します。

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

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
}

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

BIN
ios/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

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

129
ios/build.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/zsh
set -e
SCRIPT_DIR=${0:a:h}
cd "$SCRIPT_DIR"
source .env
# 絶対パスに変換
REPO_DIR="$SCRIPT_DIR/../repos/social-app"
APP_NAME="Aiat"
WORKSPACE="$REPO_DIR/ios/${APP_NAME}.xcworkspace"
SCHEME="$APP_NAME"
BUILD_DIR="$REPO_DIR/build"
MOBILEPROVISION="$SCRIPT_DIR/store.mobileprovision"
ASSETS_DIR="$SCRIPT_DIR/assets"
echo "Running iOS preview workflow..."
cd "$REPO_DIR"
# 0. Environment Setup (Fix Node Version)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
echo "Checking Node version..."
if command -v nvm >/dev/null; then
nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)"
else
echo "nvm not found, using system node: $(node -v)"
fi
# 1. Install dependencies
echo "1. Installing dependencies (yarn)..."
yarn install
# 1.5. Copy assets
echo "1.5. Copying assets..."
if [ -d "$ASSETS_DIR" ]; then
cp -rf "$ASSETS_DIR/"* "$REPO_DIR/assets/"
echo "✅ Copied all assets (including logo.png, logo-1024.png)"
else
echo "⚠️ Warning: $ASSETS_DIR not found"
fi
# 2. Prebuild (Generate ios directory)
echo "2. Running Expo Prebuild..."
# Clean old ios folder to remove old entitlements/AppClip targets
rm -rf ios
npx expo prebuild --platform ios --clean
# 3. CocoaPods
echo "3. Installing CocoaPods..."
# Ensure PATH includes Homebrew ruby gems if needed
export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
cd ios
pod install
cd ..
# 4. Signing (Manual Step)
echo "4. Opening Xcode for Signing..."
XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
# Fallback search if variable name logic differs
if [ ! -d "$XCODE_PROJ" ]; then
XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
fi
open "$XCODE_PROJ"
read
echo "Building $APP_NAME for App Store upload..."
# ビルドディレクトリ作成
mkdir -p "$BUILD_DIR"
# アーカイブ(詳細ログ出力)
xcodebuild -workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-archivePath "$BUILD_DIR/${APP_NAME}.xcarchive" \
-allowProvisioningUpdates \
archive 2>&1 | tee "$BUILD_DIR/build.log"
# アーカイブ成功確認
if [ ! -d "$BUILD_DIR/${APP_NAME}.xcarchive" ]; then
echo "Error: Archive failed. Check $BUILD_DIR/build.log for details"
exit 1
fi
cd "$BUILD_DIR"
# IPA作成
rm -rf Payload ${APP_NAME}.ipa
mkdir -p Payload
cp -R ${APP_NAME}.xcarchive/Products/Applications/${APP_NAME}.app Payload/
# store.mobileprovisionの存在確認とコピー
# https://developer.apple.com/account/resources/profiles/list
if [ ! -f "$MOBILEPROVISION" ]; then
echo "Error: store.mobileprovision not found at $MOBILEPROVISION"
exit 1
fi
cp "$MOBILEPROVISION" Payload/${APP_NAME}.app/embedded.mobileprovision
# entitlements抽出
security cms -D -i Payload/${APP_NAME}.app/embedded.mobileprovision > /tmp/profile.plist
/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist
# 署名
CERT="$IOS_CERTIFICATE_NAME"
# Frameworksディレクトリが存在する場合のみ署名
if [ -d "Payload/${APP_NAME}.app/Frameworks" ]; then
for framework in Payload/${APP_NAME}.app/Frameworks/*.framework; do
if [ -e "$framework" ]; then
echo "Signing $framework"
codesign -f -s "$CERT" "$framework"
fi
done
fi
# アプリ本体に署名
codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${APP_NAME}.app
# IPA作成
zip -r ${APP_NAME}.ipa Payload
# アップロード
xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${APP_MAIL}" -p "${APP_KEYCHAIN}"
echo "Upload complete: ${APP_NAME}.ipa"

BIN
ios/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,166 @@
diff --git a/app.config.js b/app.config.js
index 246d8abd3..ed8f7b2b2 100644
--- a/app.config.js
+++ b/app.config.js
@@ -18,10 +18,7 @@ module.exports = function (_config) {
const IS_DEV = !IS_TESTFLIGHT || !IS_PRODUCTION
const ASSOCIATED_DOMAINS = [
- 'applinks:bsky.app',
- 'applinks:staging.bsky.app',
- 'appclips:bsky.app',
- 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes
+ 'applinks:syu.is',
// When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
...(IS_DEV || IS_TESTFLIGHT ? [] : []),
]
@@ -33,27 +30,25 @@ module.exports = function (_config) {
return {
expo: {
version: VERSION,
- name: 'Bluesky',
- slug: 'bluesky',
- scheme: 'bluesky',
+ name: 'Aiat',
+ slug: 'aiat',
+ scheme: 'syui',
owner: 'blueskysocial',
runtimeVersion: {
policy: 'appVersion',
},
- icon: './assets/app-icons/ios_icon_default_next.png',
+ icon: './assets/logo.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
ios: {
supportsTablet: false,
- bundleIdentifier: 'xyz.blueskyweb.app',
+ bundleIdentifier: 'ai.syui.at',
+ buildNumber: '__BUILD_NUMBER__',
config: {
usesNonExemptEncryption: false,
},
- icon:
- PLATFORM === 'web' // web build doesn't like .icon files
- ? './assets/app-icons/ios_icon_default_next.png'
- : './assets/app-icons/ios_icon_default.icon',
+ icon: './assets/logo.png',
infoPlist: {
UIBackgroundModes: ['remote-notification'],
NSCameraUsageDescription:
@@ -113,7 +107,7 @@ module.exports = function (_config) {
entitlements: {
'com.apple.developer.kernel.increased-memory-limit': true,
'com.apple.developer.kernel.extended-virtual-addressing': true,
- 'com.apple.security.application-groups': 'group.app.bsky',
+ 'com.apple.security.application-groups': 'group.ai.syui.at',
},
privacyManifests: {
NSPrivacyCollectedDataTypes: [
@@ -175,14 +169,14 @@ module.exports = function (_config) {
barStyle: 'light-content',
},
android: {
- icon: './assets/app-icons/android_icon_default_next.png',
+ icon: './assets/logo.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-monochrome.png',
backgroundColor: '#006AFF',
},
googleServicesFile: './google-services.json',
- package: 'xyz.blueskyweb.app',
+ package: 'ai.syui.at',
intentFilters: [
{
action: 'VIEW',
@@ -190,7 +184,7 @@ module.exports = function (_config) {
data: [
{
scheme: 'https',
- host: 'bsky.app',
+ host: 'syu.is',
},
IS_DEV && {
scheme: 'http',
@@ -213,9 +207,9 @@ module.exports = function (_config) {
: undefined,
codeSigningMetadata: UPDATES_ENABLED
? {
- keyid: 'main',
- alg: 'rsa-v1_5-sha256',
- }
+ keyid: 'main',
+ alg: 'rsa-v1_5-sha256',
+ }
: undefined,
checkAutomatically: 'NEVER',
},
@@ -225,7 +219,7 @@ module.exports = function (_config) {
'expo-web-browser',
[
'react-native-edge-to-edge',
- {android: {enforceNavigationBarContrast: false}},
+ { android: { enforceNavigationBarContrast: false } },
],
USE_SENTRY && [
'@sentry/react-native/expo',
@@ -264,7 +258,6 @@ module.exports = function (_config) {
networkInstrumentation: true,
},
],
- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
'./plugins/withGradleJVMHeapSizeIncrease.js',
'./plugins/withAndroidManifestLargeHeapPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js',
@@ -272,8 +265,6 @@ module.exports = function (_config) {
'./plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidDayNightThemePlugin.js',
'./plugins/withAndroidNoJitpackPlugin.js',
- './plugins/shareExtension/withShareExtensions.js',
- './plugins/notificationsExtension/withNotificationsExtension.js',
[
'expo-font',
{
@@ -386,7 +377,7 @@ module.exports = function (_config) {
},
},
],
- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
+ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }],
['expo-location'],
].filter(Boolean),
extra: {
@@ -394,30 +385,7 @@ module.exports = function (_config) {
build: {
experimental: {
ios: {
- appExtensions: [
- {
- targetName: 'Share-with-Bluesky',
- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky',
- entitlements: {
- 'com.apple.security.application-groups': [
- 'group.app.bsky',
- ],
- },
- },
- {
- targetName: 'BlueskyNSE',
- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE',
- entitlements: {
- 'com.apple.security.application-groups': [
- 'group.app.bsky',
- ],
- },
- },
- {
- targetName: 'BlueskyClip',
- bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
- },
- ],
+ appExtensions: [],
},
},
},

View File

@@ -0,0 +1,209 @@
diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts
index 7a0d72d91..93554dc3e 100644
--- a/src/lib/api/feed/home.ts
+++ b/src/lib/api/feed/home.ts
@@ -45,7 +45,7 @@ export class HomeFeedAPI implements FeedAPI {
this.following = new FollowingFeedAPI({agent})
this.discover = new CustomFeedAPI({
agent,
- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
+ feedParams: {feed: PROD_DEFAULT_FEED('app')},
})
this.userInterests = userInterests
}
@@ -54,7 +54,7 @@ export class HomeFeedAPI implements FeedAPI {
this.following = new FollowingFeedAPI({agent: this.agent})
this.discover = new CustomFeedAPI({
agent: this.agent,
- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
+ feedParams: {feed: PROD_DEFAULT_FEED('app')},
userInterests: this.userInterests,
})
this.usingDiscover = false
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 231447b4f..a44b3da05 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -7,12 +7,12 @@ import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env'
export const LOCAL_DEV_SERVICE =
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
export const STAGING_SERVICE = 'https://staging.bsky.dev'
-export const BSKY_SERVICE = 'https://bsky.social'
-export const BSKY_SERVICE_DID = 'did:web:bsky.social'
-export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
+export const BSKY_SERVICE = 'https://syu.is'
+export const BSKY_SERVICE_DID = 'did:web:syu.is'
+export const PUBLIC_BSKY_SERVICE = 'https://bsky.syu.is'
export const DEFAULT_SERVICE = BSKY_SERVICE
-const HELP_DESK_LANG = 'en-us'
-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
+const HELP_DESK_LANG = 'ja-jp'
+export const HELP_DESK_URL = 'https://syu.is/about/support/help'
export const EMBED_SERVICE = 'https://embed.bsky.app'
export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
@@ -79,19 +79,17 @@ export function IS_PROD_SERVICE(url?: string) {
}
export const PROD_DEFAULT_FEED = (rkey: string) =>
- `at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}`
+ `at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/${rkey}`
export const STAGING_DEFAULT_FEED = (rkey: string) =>
`at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/${rkey}`
export const PROD_FEEDS = [
- `feedgen|${PROD_DEFAULT_FEED('whats-hot')}`,
- `feedgen|${PROD_DEFAULT_FEED('thevids')}`,
+ `feedgen|${PROD_DEFAULT_FEED('app')}`,
]
export const STAGING_FEEDS = [
- `feedgen|${STAGING_DEFAULT_FEED('whats-hot')}`,
- `feedgen|${STAGING_DEFAULT_FEED('thevids')}`,
+ `feedgen|${STAGING_DEFAULT_FEED('app')}`,
]
export const POST_IMG_MAX = {
@@ -129,7 +127,7 @@ export const LANG_DROPDOWN_HITSLOP = {top: 10, bottom: 10, left: 4, right: 4}
export const BACK_HITSLOP = HITSLOP_30
export const MAX_POST_LINES = 25
-export const BSKY_APP_ACCOUNT_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur'
+export const BSKY_APP_ACCOUNT_DID = 'did:plc:6qyecktefllvenje24fcxnie'
export const BSKY_FEED_OWNER_DIDS = [
BSKY_APP_ACCOUNT_DID,
@@ -138,9 +136,9 @@ export const BSKY_FEED_OWNER_DIDS = [
]
export const DISCOVER_FEED_URI =
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app'
export const VIDEO_FEED_URI =
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/thevids'
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app'
export const STAGING_VIDEO_FEED_URI =
'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids'
export const VIDEO_FEED_URIS = [VIDEO_FEED_URI, STAGING_VIDEO_FEED_URI]
@@ -209,8 +207,8 @@ export const urls = {
},
}
-export const PUBLIC_APPVIEW = 'https://api.bsky.app'
-export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'
+export const PUBLIC_APPVIEW = 'https://bsky.syu.is'
+export const PUBLIC_APPVIEW_DID = 'did:web:bsky.syu.is'
export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev'
export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same
@@ -236,8 +234,8 @@ export const BLUESKY_MOD_SERVICE_HEADERS = {
}
export const webLinks = {
- tos: `https://bsky.social/about/support/tos`,
- privacy: `https://bsky.social/about/support/privacy-policy`,
+ tos: `https://syu.is/about/support/tos`,
+ privacy: `https://syu.is/about/support/privacy-policy`,
community: `https://bsky.social/about/support/community-guidelines`,
communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`,
}
diff --git a/src/lib/demo.ts b/src/lib/demo.ts
index 5ead62c9d..7c80dfe15 100644
--- a/src/lib/demo.ts
+++ b/src/lib/demo.ts
@@ -1,7 +1,7 @@
import {type AppBskyFeedGetFeed} from '@atproto/api'
import {subDays, subMinutes} from 'date-fns'
-const DID = `did:plc:z72i7hdynmk6r22z27h6tvur`
+const DID = `did:plc:6qyecktefllvenje24fcxnie`
const NOW = new Date()
const POST_1_DATE = subMinutes(NOW, 2).toISOString()
const POST_2_DATE = subMinutes(NOW, 4).toISOString()
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 6088e2806..0f6787a4d 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -338,7 +338,7 @@ export function createProxiedUrl(url: string): string {
return url
}
- return `https://go.bsky.app/redirect?u=${encodeURIComponent(url)}`
+ return url
}
export function isShortLink(url: string): boolean {
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index de1e92533..3d1566800 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -201,14 +201,7 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
// for the ones we know need it
// -prf
export const KNOWN_AUTHED_ONLY_FEEDS = [
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app
- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed
- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed
- 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow
- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky
- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz
- 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app', // app feed, by syu.is
]
type GetPopularFeedsOptions = {limit?: number; enabled?: boolean}
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 0cf6ab546..399e592bc 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -270,7 +270,7 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
await agent.addSavedFeeds([
{
type: 'feed',
- value: PROD_DEFAULT_FEED('whats-hot'),
+ value: PROD_DEFAULT_FEED('app'),
pinned: true,
},
])
diff --git a/src/view/com/posts/FeedShutdownMsg.tsx b/src/view/com/posts/FeedShutdownMsg.tsx
index 620382175..928480da2 100644
--- a/src/view/com/posts/FeedShutdownMsg.tsx
+++ b/src/view/com/posts/FeedShutdownMsg.tsx
@@ -32,7 +32,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
f => f.value === feedUri && f.pinned,
)
const discoverFeedConfig = preferences?.savedFeeds?.find(
- f => f.value === PROD_DEFAULT_FEED('whats-hot'),
+ f => f.value === PROD_DEFAULT_FEED('app'),
)
const hasFeedPinned = Boolean(feedConfig)
const hasDiscoverPinned = Boolean(discoverFeedConfig?.pinned)
@@ -44,7 +44,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
Toast.show(_(msg`Removed from your feeds`))
}
if (hasDiscoverPinned) {
- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`)
+ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`)
}
} catch (err: any) {
Toast.show(
@@ -63,7 +63,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
forYouFeedConfig: feedConfig,
discoverFeedConfig,
})
- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`)
+ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`)
Toast.show(_(msg`The feed has been replaced with Discover.`))
} catch (err: any) {
Toast.show(
@@ -100,7 +100,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
This feed is no longer online. We are showing{' '}
<InlineLinkText
label={_(msg`The Discover feed`)}
- to="/profile/bsky.app/feed/whats-hot"
+ to="/profile/did:plc:6qyecktefllvenje24fcxnie/feed/app"
style={[a.text_md]}>
Discover
</InlineLinkText>{' '}

View File

@@ -0,0 +1,213 @@
diff --git a/src/Splash.tsx b/src/Splash.tsx
index 47e70b375..616f351ed 100644
--- a/src/Splash.tsx
+++ b/src/Splash.tsx
@@ -15,8 +15,8 @@ import Animated, {
withTiming,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import Svg, {Path, type SvgProps} from 'react-native-svg'
import {Image} from 'expo-image'
+import {type SvgProps} from 'react-native-svg'
import * as SplashScreen from 'expo-splash-screen'
import {Logotype} from '#/view/icons/Logotype'
@@ -29,21 +29,18 @@ const darkSplashImageUri = RNImage.resolveAssetSource(
darkSplashImagePointer,
).uri
-export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) {
- const width = 1000
- const height = width * (67 / 64)
+export const Logo = React.forwardRef(function LogoImpl(props: SvgProps & {fill?: string}, ref) {
+ const size = 1000
+ // @ts-ignore
return (
- <Svg
- fill="none"
- // @ts-ignore it's fiiiiine
+ <Image
+ // @ts-ignore
ref={ref}
- viewBox="0 0 64 66"
- style={[{width, height}, props.style]}>
- <Path
- fill={props.fill || '#fff'}
- d="M13.873 3.77C21.21 9.243 29.103 20.342 32 26.3v15.732c0-.335-.13.043-.41.858-1.512 4.414-7.418 21.642-20.923 7.87-7.111-7.252-3.819-14.503 9.125-16.692-7.405 1.252-15.73-.817-18.014-8.93C1.12 22.804 0 8.431 0 6.488 0-3.237 8.579-.18 13.873 3.77ZM50.127 3.77C42.79 9.243 34.897 20.342 32 26.3v15.732c0-.335.13.043.41.858 1.512 4.414 7.418 21.642 20.923 7.87 7.111-7.252 3.819-14.503-9.125-16.692 7.405 1.252 15.73-.817 18.014-8.93C62.88 22.804 64 8.431 64 6.488 64-3.237 55.422-.18 50.127 3.77Z"
- />
- </Svg>
+ source={require('../assets/logo.png')}
+ style={[{width: size, height: size}, props.style]}
+ contentFit="contain"
+ accessibilityLabel="Logo"
+ />
)
})
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 8a9e51a33..65d643b89 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -444,7 +444,7 @@ let EditableUserAvatar = ({
<HighPriorityImage
testID="userAvatarImage"
style={aviStyle}
- source={{uri: avatar}}
+ source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}
accessibilityRole="image"
/>
) : (
@@ -618,9 +618,8 @@ export {PreviewableUserAvatar}
// manually string-replace to use the smaller ones
// -prf
function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string {
- return isEnabled
- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/')
- : uri
+ // syu.is: avatars are served directly from bsky.syu.is, no CDN transformation needed
+ return uri
}
const styles = StyleSheet.create({
diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx
index d7208df13..2763800ac 100644
--- a/src/view/icons/Logo.tsx
+++ b/src/view/icons/Logo.tsx
@@ -1,75 +1,17 @@
import React from 'react'
-import {type TextProps} from 'react-native'
-import Svg, {
- Defs,
- LinearGradient,
- Path,
- type PathProps,
- Stop,
- type SvgProps,
-} from 'react-native-svg'
import {Image} from 'expo-image'
+import {flatten} from '#/alf'
-import {useKawaiiMode} from '#/state/preferences/kawaii'
-import {flatten, useTheme} from '#/alf'
-
-const ratio = 57 / 64
-
-type Props = {
- fill?: PathProps['fill']
- style?: TextProps['style']
-} & Omit<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/logo.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,717 @@
diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx
index 9915d0a2d..4ae51215d 100644
--- a/src/components/dialogs/BirthDateSettings.tsx
+++ b/src/components/dialogs/BirthDateSettings.tsx
@@ -163,7 +163,7 @@ function BirthdayInner({
<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..1dc7f9227 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -1,60 +1,50 @@
-import React, {type ComponentProps, type JSX} from 'react'
-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {msg, Plural, plural, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {StackActions, useNavigation} from '@react-navigation/native'
-
-import {useActorStatus} from '#/lib/actor-status'
-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
-import {type PressableScale} from '#/lib/custom-animations/PressableScale'
-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
-import {getTabState, TabState} from '#/lib/routes/helpers'
-import {type NavigationProp} from '#/lib/routes/types'
-import {sanitizeHandle} from '#/lib/strings/handles'
-import {colors} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
-import {emitSoftReset} from '#/state/events'
-import {useKawaiiMode} from '#/state/preferences/kawaii'
-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
-import {useProfileQuery} from '#/state/queries/profile'
-import {type SessionAccount, useSession} from '#/state/session'
-import {useSetDrawerOpen} from '#/state/shell'
-import {formatCount} from '#/view/com/util/numeric/format'
-import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {NavSignupCard} from '#/view/shell/NavSignupCard'
-import {atoms as a, tokens, useTheme, web} from '#/alf'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import {Divider} from '#/components/Divider'
+import React, { type ComponentProps, type JSX } from 'react'
+import { Linking, ScrollView, TouchableOpacity, View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import { msg, Plural, plural, Trans } from '@lingui/macro'
+import { useLingui } from '@lingui/react'
+import { StackActions, useNavigation } from '@react-navigation/native'
+
+import { useActorStatus } from '#/lib/actor-status'
+import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants'
+import { type PressableScale } from '#/lib/custom-animations/PressableScale'
+import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState'
+import { getTabState, TabState } from '#/lib/routes/helpers'
+import { type NavigationProp } from '#/lib/routes/types'
+import { sanitizeHandle } from '#/lib/strings/handles'
+import { colors } from '#/lib/styles'
+import { isWeb } from '#/platform/detection'
+import { emitSoftReset } from '#/state/events'
+import { useKawaiiMode } from '#/state/preferences/kawaii'
+import { useUnreadNotifications } from '#/state/queries/notifications/unread'
+import { useProfileQuery } from '#/state/queries/profile'
+import { type SessionAccount, useSession } from '#/state/session'
+import { useSetDrawerOpen } from '#/state/shell'
+import { formatCount } from '#/view/com/util/numeric/format'
+import { UserAvatar } from '#/view/com/util/UserAvatar'
+import { NavSignupCard } from '#/view/shell/NavSignupCard'
+import { atoms as a, tokens, useTheme, web } from '#/alf'
+import { Button } from '#/components/Button'
+import { Divider } from '#/components/Divider'
import {
Bell_Filled_Corner0_Rounded as BellFilled,
Bell_Stroke2_Corner0_Rounded as Bell,
} from '#/components/icons/Bell'
-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
-import {
- Hashtag_Filled_Corner0_Rounded as HashtagFilled,
- Hashtag_Stroke2_Corner0_Rounded as Hashtag,
-} from '#/components/icons/Hashtag'
import {
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
HomeOpen_Stoke2_Corner0_Rounded as Home,
} from '#/components/icons/HomeOpen'
-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
-import {
- Message_Stroke2_Corner0_Rounded as Message,
- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
-} from '#/components/icons/Message'
-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
+import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass'
+import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2'
+import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2'
import {
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
} from '#/components/icons/UserCircle'
-import {InlineLinkText} from '#/components/Link'
-import {Text} from '#/components/Typography'
-import {useSimpleVerificationState} from '#/components/verification'
-import {VerificationCheck} from '#/components/verification/VerificationCheck'
+import { InlineLinkText } from '#/components/Link'
+import { Text } from '#/components/Typography'
+import { useSimpleVerificationState } from '#/components/verification'
+import { VerificationCheck } from '#/components/verification/VerificationCheck'
const iconWidth = 26
@@ -65,11 +55,11 @@ let DrawerProfileCard = ({
account: SessionAccount
onPressProfile: () => void
}): React.ReactNode => {
- const {_, i18n} = useLingui()
+ const { _, i18n } = useLingui()
const t = useTheme()
- const {data: profile} = useProfileQuery({did: account.did})
- const verification = useSimpleVerificationState({profile})
- const {isActive: live} = useActorStatus(profile)
+ const { data: profile } = useProfileQuery({ did: account.did })
+ const verification = useSimpleVerificationState({ profile })
+ const { isActive: live } = useActorStatus(profile)
return (
<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,26 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
}
function ExtraLinks() {
- 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={() => Linking.openURL('https://syu.is/about/support/tos')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ Terms of Service
</Text>
- )}
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => Linking.openURL('https://syu.is/about/support/privacy-policy')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ Privacy Policy
+ </Text>
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => navigation.navigate('License')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ License
+ </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..1ad7026a2 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,174 @@
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index fa33a9d56..13af087c2 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -62,6 +62,7 @@ import {NotFoundScreen} from '#/view/screens/NotFound'
import {NotificationsScreen} from '#/view/screens/Notifications'
import {PostThreadScreen} from '#/view/screens/PostThread'
import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy'
+import {LicenseScreen} from '#/view/screens/License'
import {ProfileScreen} from '#/view/screens/Profile'
import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy'
import {Storybook} from '#/view/screens/Storybook'
@@ -335,6 +336,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
getComponent={() => TermsOfServiceScreen}
options={{title: title(msg`Terms of Service`)}}
/>
+ <Stack.Screen
+ name="License"
+ getComponent={() => LicenseScreen}
+ options={{title: title(msg`License`)}}
+ />
<Stack.Screen
name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen}
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/view/screens/License.tsx b/src/view/screens/License.tsx
new file mode 100644
index 000000000..87f52a972
--- /dev/null
+++ b/src/view/screens/License.tsx
@@ -0,0 +1,132 @@
+import React from 'react'
+import { ScrollView, Text as RNText, StyleSheet } from 'react-native'
+import * as Layout from '#/components/Layout'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+
+export function LicenseScreen() {
+ useSetTitle('License')
+ const t = useTheme()
+
+ return (
+ <Layout.Screen>
+ <Layout.Header.Outer>
+ <Layout.Header.BackButton />
+ <Layout.Header.Content>
+ <Layout.Header.TitleText>License</Layout.Header.TitleText>
+ </Layout.Header.Content>
+ <Layout.Header.Slot />
+ </Layout.Header.Outer>
+ <Layout.Content>
+ <ScrollView
+ style={[a.flex_1]}
+ contentContainerStyle={[a.p_lg, a.pt_xl, a.pb_5xl]}>
+ <RNText style={styles.text}>
+ This application is based on Bluesky Social App.
+ </RNText>
+
+ <RNText style={styles.link}>
+ https://github.com/bluesky-social/social-app
+ </RNText>
+
+ <RNText style={styles.sectionTitle}>MIT License</RNText>
+
+ <RNText style={styles.mono}>
+ Copyright (c) 2022-2025 Bluesky PBC
+ </RNText>
+
+ <RNText style={styles.text}>
+ 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:
+ </RNText>
+
+ <RNText style={styles.text}>
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ </RNText>
+
+ <RNText style={styles.text}>
+ 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.
+ </RNText>
+
+ <RNText style={styles.sectionTitle2}>日本語訳(参考)</RNText>
+
+ <RNText style={styles.text}>
+ 本ソフトウェアおよび関連文書ファイル(以下「ソフトウェア」)のコピーを取得する
+ すべての人に対し、ソフトウェアを無制限に扱うことを無償で許可します。これには、
+ ソフトウェアのコピーを使用、複製、変更、結合、公開、配布、サブライセンス、
+ および/または販売する権利、ならびにソフトウェアを提供する相手にそうした行為を
+ 許可する権利が含まれますが、これらに限定されません。
+ </RNText>
+
+ <RNText style={styles.text}>
+ 上記の著作権表示および本許諾表示を、ソフトウェアのすべてのコピーまたは
+ 重要な部分に記載するものとします。
+ </RNText>
+
+ <RNText style={styles.text}>
+ ソフトウェアは「現状のまま」で提供され、明示黙示を問わず、商品性、特定目的への
+ 適合性、および権利非侵害についての保証を含む、いかなる種類の保証もなされません。
+ いかなる場合においても、作者または著作権者は、契約行為、不法行為、またはそれ以外で
+ あろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用または
+ その他の扱いによって生じる一切の請求、損害、その他の義務について責任を負わないものとします。
+ </RNText>
+
+ <RNText style={styles.footer}>
+ Original License: https://github.com/bluesky-social/social-app/blob/main/LICENSE
+ </RNText>
+ </ScrollView>
+ </Layout.Content>
+ </Layout.Screen>
+ )
+}
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginBottom: 16,
+ },
+ text: {
+ fontSize: 14,
+ marginBottom: 12,
+ lineHeight: 20,
+ },
+ link: {
+ fontSize: 14,
+ marginBottom: 12,
+ color: '#0066cc',
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginTop: 16,
+ marginBottom: 12,
+ },
+ sectionTitle2: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginTop: 24,
+ marginBottom: 12,
+ },
+ mono: {
+ fontSize: 14,
+ marginBottom: 12,
+ fontFamily: 'monospace',
+ },
+ footer: {
+ fontSize: 12,
+ marginTop: 24,
+ color: '#666666',
+ },
+})

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,22 @@
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
index 3442d1bdf..dd2d1fdfb 100644
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -102,6 +102,17 @@ export const SplashScreen = ({
<AppLanguageDropdown />
</View>
</View>
+ <View
+ style={[
+ a.px_lg,
+ a.pb_xl,
+ a.justify_center,
+ a.align_center,
+ ]}>
+ <Text style={[a.text_xs, t.atoms.text_contrast_low]}>
+ © syui
+ </Text>
+ </View>
<View style={{height: insets.bottom}} />
</ErrorBoundary>
</Animated.View>

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,44 @@
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/plugins/withCodeSignEntitlements.js b/plugins/withCodeSignEntitlements.js
new file mode 100644
index 000000000..b03b6bd68
--- /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,30 @@
diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
index 9a0a9c9d5..5a6563e52 100644
--- a/src/ageAssurance/index.tsx
+++ b/src/ageAssurance/index.tsx
@@ -88,19 +88,16 @@ function InnerProvider({children}: {children: React.ReactNode}) {
return (
<AgeAssuranceStateContext.Provider
value={useMemo(() => {
- const chatDisabled = state.access !== AgeAssuranceAccess.Full
- const isUnderage = data?.birthdate
- ? isUserUnderAdultAge(data.birthdate)
- : true
- const adultContentDisabled =
- state.access !== AgeAssuranceAccess.Full || isUnderage
return {
Access: AgeAssuranceAccess,
Status: AgeAssuranceStatus,
- state,
+ state: {
+ ...state,
+ access: AgeAssuranceAccess.Full,
+ },
flags: {
- adultContentDisabled,
- chatDisabled,
+ adultContentDisabled: false,
+ chatDisabled: false,
},
}
}, [state, data])}>

View File

@@ -0,0 +1,247 @@
diff --git a/src/view/com/posts/DiscoverFallbackHeader.tsx b/src/view/com/posts/DiscoverFallbackHeader.tsx
index e35a33aaf..a36f84ae0 100644
--- a/src/view/com/posts/DiscoverFallbackHeader.tsx
+++ b/src/view/com/posts/DiscoverFallbackHeader.tsx
@@ -7,37 +7,5 @@ import {TextLink} from '../util/Link'
import {Text} from '../util/text/Text'
export function DiscoverFallbackHeader() {
- const pal = usePalette('default')
- return (
- <View
- style={[
- {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: 12,
- paddingHorizontal: 12,
- borderTopWidth: 1,
- },
- pal.border,
- pal.viewLight,
- ]}>
- <View style={{width: 68, paddingLeft: 12}}>
- <InfoCircleIcon size={36} style={pal.textLight} strokeWidth={1.5} />
- </View>
- <View style={{flex: 1}}>
- <Text type="md" style={pal.text}>
- <Trans>
- We ran out of posts from your follows. Here's the latest from{' '}
- <TextLink
- type="md-medium"
- href="/profile/bsky.app/feed/whats-hot"
- text="Discover"
- style={pal.link}
- />
- .
- </Trans>
- </Text>
- </View>
- </View>
- )
+ return null
}
diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
index 352cc1dc0..f477521af 100644
--- a/src/view/com/posts/FollowingEmptyState.tsx
+++ b/src/view/com/posts/FollowingEmptyState.tsx
@@ -1,37 +1,14 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
-import {
- FontAwesomeIcon,
- type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro'
-import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {MagnifyingGlassIcon} from '#/lib/icons'
-import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
-import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
export function FollowingEmptyState() {
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (isWeb) {
- navigation.navigate('Search', {})
- } else {
- navigation.navigate('SearchTab')
- navigation.popToTop()
- }
- }, [navigation])
-
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
return (
<View style={styles.container}>
@@ -45,36 +22,6 @@ export function FollowingEmptyState() {
happening.
</Trans>
</Text>
- <Button
- type="inverted"
- style={styles.emptyBtn}
- onPress={onPressFindAccounts}>
- <Text type="lg-medium" style={palInverted.text}>
- <Trans>Find accounts to follow</Trans>
- </Text>
- <FontAwesomeIcon
- icon="angle-right"
- style={palInverted.text as FontAwesomeIconStyle}
- size={14}
- />
- </Button>
-
- <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
- <Trans>You can also discover new Custom Feeds to follow.</Trans>
- </Text>
- <Button
- type="inverted"
- style={[styles.emptyBtn, s.mt10]}
- onPress={onPressDiscoverFeeds}>
- <Text type="lg-medium" style={palInverted.text}>
- <Trans>Discover new custom feeds</Trans>
- </Text>
- <FontAwesomeIcon
- icon="angle-right"
- style={palInverted.text as FontAwesomeIconStyle}
- size={14}
- />
- </Button>
</View>
</View>
)
@@ -98,13 +45,4 @@ const styles = StyleSheet.create({
marginLeft: 'auto',
marginRight: 'auto',
},
- emptyBtn: {
- marginVertical: 20,
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingVertical: 18,
- paddingHorizontal: 24,
- borderRadius: 30,
- },
})
diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx
index e3c84d782..efb55d406 100644
--- a/src/view/com/posts/FollowingEndOfFeed.tsx
+++ b/src/view/com/posts/FollowingEndOfFeed.tsx
@@ -1,36 +1,13 @@
import React from 'react'
import {Dimensions, StyleSheet, View} from 'react-native'
-import {
- FontAwesomeIcon,
- type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro'
-import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
-import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
-import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
export function FollowingEndOfFeed() {
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (isWeb) {
- navigation.navigate('Search', {})
- } else {
- navigation.navigate('SearchTab')
- navigation.popToTop()
- }
- }, [navigation])
-
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
return (
<View
@@ -41,41 +18,8 @@ export function FollowingEndOfFeed() {
]}>
<View style={styles.inner}>
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
- <Trans>
- You've reached the end of your feed! Find some more accounts to
- follow.
- </Trans>
- </Text>
- <Button
- type="inverted"
- style={styles.emptyBtn}
- onPress={onPressFindAccounts}>
- <Text type="lg-medium" style={palInverted.text}>
- <Trans>Find accounts to follow</Trans>
- </Text>
- <FontAwesomeIcon
- icon="angle-right"
- style={palInverted.text as FontAwesomeIconStyle}
- size={14}
- />
- </Button>
-
- <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
- <Trans>You can also discover new Custom Feeds to follow.</Trans>
+ <Trans>You've reached the end of your feed!</Trans>
</Text>
- <Button
- type="inverted"
- style={[styles.emptyBtn, s.mt10]}
- onPress={onPressDiscoverFeeds}>
- <Text type="lg-medium" style={palInverted.text}>
- <Trans>Discover new custom feeds</Trans>
- </Text>
- <FontAwesomeIcon
- icon="angle-right"
- style={palInverted.text as FontAwesomeIconStyle}
- size={14}
- />
- </Button>
</View>
</View>
)
@@ -93,13 +37,4 @@ const styles = StyleSheet.create({
width: '100%',
maxWidth: 460,
},
- emptyBtn: {
- marginVertical: 20,
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingVertical: 18,
- paddingHorizontal: 24,
- borderRadius: 30,
- },
})
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
index 4f25468c9..a72a10b80 100644
--- a/src/view/com/posts/PostFeed.tsx
+++ b/src/view/com/posts/PostFeed.tsx
@@ -766,7 +766,7 @@ let PostFeed = ({
} else if (row.type === 'feedShutdownMsg') {
return <FeedShutdownMsg feedUri={feedUriOrActorDid} />
} else if (row.type === 'interstitialFollows') {
- return <SuggestedFollows feed={feed} />
+ return null
} else if (row.type === 'interstitialProgressGuide') {
return <ProgressGuide />
} else if (row.type === 'ageAssuranceBanner') {

View File

@@ -0,0 +1,329 @@
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index ec5261dee..c670cf75a 100644
--- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go
@@ -300,6 +300,10 @@ func serve(cctx *cli.Context) error {
e.GET("/support/tos", server.WebGeneric)
e.GET("/support/community-guidelines", server.WebGeneric)
e.GET("/support/copyright", server.WebGeneric)
+ // about/support pages (syu.is specific)
+ e.GET("/about/support/tos", server.WebAboutTOS)
+ e.GET("/about/support/privacy-policy", server.WebAboutPrivacy)
+ e.GET("/about/support/help", server.WebAboutHelp)
e.GET("/intent/compose", server.WebGeneric)
e.GET("/intent/verify-email", server.WebGeneric)
e.GET("/intent/age-assurance", server.WebGeneric)
@@ -753,3 +757,21 @@ func (srv *Server) WebIpCC(c echo.Context) error {
}
return c.JSON(200, outResponse)
}
+
+// Handler for About TOS page (syu.is specific)
+func (srv *Server) WebAboutTOS(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-tos.html", data)
+}
+
+// Handler for About Privacy Policy page (syu.is specific)
+func (srv *Server) WebAboutPrivacy(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-privacy.html", data)
+}
+
+// Handler for About Help page (syu.is specific)
+func (srv *Server) WebAboutHelp(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-help.html", data)
+}
diff --git a/bskyweb/templates/about-help.html b/bskyweb/templates/about-help.html
new file mode 100644
index 000000000..d37db25c5
--- /dev/null
+++ b/bskyweb/templates/about-help.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Help - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ h3 { font-size: 16px; margin: 16px 0 8px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ .faq-item { margin-bottom: 24px; }
+ .contact-box { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
+ @media (prefers-color-scheme: dark) {
+ .contact-box { background: #1a1a1a; }
+ }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Help Center</h1>
+ </div>
+
+ <h2>About syu.is</h2>
+ <p>syu.is is a social networking service built on the AT Protocol (Authenticated Transfer Protocol). It allows users to share content, connect with others, and participate in a decentralized social network.</p>
+
+ <h2>Frequently Asked Questions</h2>
+
+ <div class="faq-item">
+ <h3>What is the AT Protocol?</h3>
+ <p>The AT Protocol is a decentralized social networking protocol that allows users to own their data and identity. It enables federation between different services while maintaining user control.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I create an account?</h3>
+ <p>You can create an account by downloading the app or visiting the website. You'll need to provide an email address and choose a username.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I reset my password?</h3>
+ <p>You can reset your password through the login screen by selecting "Forgot Password" and following the instructions sent to your email.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I delete my account?</h3>
+ <p>You can delete your account through Settings &gt; Account. Please note that account deletion is permanent and cannot be undone.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I report abuse or inappropriate content?</h3>
+ <p>You can report content by using the report function available on each post. Our moderation team will review reports and take appropriate action.</p>
+ </div>
+
+ <h2>Contact</h2>
+ <div class="contact-box">
+ <p>For additional support or questions:</p>
+ <ul>
+ <li>GitHub: <a href="https://github.com/syui" target="_blank">github.com/syui</a></li>
+ </ul>
+ </div>
+
+ <h2>Related Links</h2>
+ <ul>
+ <li><a href="/about/support/tos">Terms of Service</a></li>
+ <li><a href="/about/support/privacy-policy">Privacy Policy</a></li>
+ <li><a href="https://atproto.com" target="_blank">AT Protocol Documentation</a></li>
+ </ul>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>
diff --git a/bskyweb/templates/about-privacy.html b/bskyweb/templates/about-privacy.html
new file mode 100644
index 000000000..14a1168ad
--- /dev/null
+++ b/bskyweb/templates/about-privacy.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Privacy Policy - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Privacy Policy</h1>
+ </div>
+
+ <h2>1. Introduction</h2>
+ <p>This Privacy Policy explains how syu.is collects, uses, and protects your personal information when you use our service.</p>
+
+ <h2>2. Information We Collect</h2>
+ <p>We collect the following types of information:</p>
+ <ul>
+ <li><strong>Account Information:</strong> Email address, username, and profile information you provide</li>
+ <li><strong>Content:</strong> Posts, messages, and other content you create on the platform</li>
+ <li><strong>Usage Data:</strong> Information about how you interact with our service</li>
+ <li><strong>Device Information:</strong> Browser type, operating system, and device identifiers</li>
+ </ul>
+
+ <h2>3. How We Use Your Information</h2>
+ <p>We use your information to:</p>
+ <ul>
+ <li>Provide and maintain our service</li>
+ <li>Improve and personalize your experience</li>
+ <li>Communicate with you about the service</li>
+ <li>Ensure security and prevent abuse</li>
+ </ul>
+
+ <h2>4. Data Sharing</h2>
+ <p>As part of the AT Protocol federation, your public content may be shared with other servers in the network. We do not sell your personal information to third parties.</p>
+
+ <h2>5. Data Security</h2>
+ <p>We implement appropriate security measures to protect your personal information. However, no method of transmission over the Internet is 100% secure.</p>
+
+ <h2>6. Your Rights</h2>
+ <p>You have the right to:</p>
+ <ul>
+ <li>Access your personal data</li>
+ <li>Request correction of your data</li>
+ <li>Request deletion of your account</li>
+ <li>Export your data</li>
+ </ul>
+
+ <h2>7. Cookies</h2>
+ <p>We use cookies and similar technologies to maintain your session and improve your experience.</p>
+
+ <h2>8. Changes to This Policy</h2>
+ <p>We may update this Privacy Policy from time to time. We will notify you of any significant changes.</p>
+
+ <h2>9. Contact</h2>
+ <p>For privacy-related questions, please visit our <a href="/about/support/help">Help page</a>.</p>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>
diff --git a/bskyweb/templates/about-tos.html b/bskyweb/templates/about-tos.html
new file mode 100644
index 000000000..db5d82f5c
--- /dev/null
+++ b/bskyweb/templates/about-tos.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Terms of Service - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Terms of Service</h1>
+ </div>
+
+ <h2>1. Introduction</h2>
+ <p>Welcome to syu.is. By using our service, you agree to these terms. Please read them carefully.</p>
+
+ <h2>2. Service Description</h2>
+ <p>syu.is is a social networking service built on the AT Protocol. We provide a platform for users to share content and connect with others.</p>
+
+ <h2>3. User Responsibilities</h2>
+ <p>As a user of syu.is, you agree to:</p>
+ <ul>
+ <li>Provide accurate information when creating an account</li>
+ <li>Keep your account credentials secure</li>
+ <li>Not use the service for illegal activities</li>
+ <li>Respect other users and their content</li>
+ <li>Comply with applicable laws and regulations</li>
+ </ul>
+
+ <h2>4. Content Guidelines</h2>
+ <p>Users are responsible for the content they post. Prohibited content includes:</p>
+ <ul>
+ <li>Illegal content</li>
+ <li>Harassment or abuse</li>
+ <li>Spam or misleading information</li>
+ <li>Content that violates others' rights</li>
+ </ul>
+
+ <h2>5. Privacy</h2>
+ <p>Your privacy is important to us. Please review our <a href="/about/support/privacy-policy">Privacy Policy</a> to understand how we handle your data.</p>
+
+ <h2>6. Disclaimer</h2>
+ <p>The service is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the service.</p>
+
+ <h2>7. Changes to Terms</h2>
+ <p>We may update these terms from time to time. Continued use of the service after changes constitutes acceptance of the new terms.</p>
+
+ <h2>8. Contact</h2>
+ <p>For questions about these terms, please visit our <a href="/about/support/help">Help page</a>.</p>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>

View File

@@ -0,0 +1,70 @@
diff --git a/src/state/messages/events/index.tsx b/src/state/messages/events/index.tsx
index 2ff0784ae..dc314ecc5 100644
--- a/src/state/messages/events/index.tsx
+++ b/src/state/messages/events/index.tsx
@@ -10,13 +10,7 @@ const MessagesEventBusContext = React.createContext<MessagesEventBus | null>(
MessagesEventBusContext.displayName = 'MessagesEventBusContext'
export function useMessagesEventBus() {
- const ctx = React.useContext(MessagesEventBusContext)
- if (!ctx) {
- throw new Error(
- 'useMessagesEventBus must be used within a MessagesEventBusProvider',
- )
- }
- return ctx
+ return React.useContext(MessagesEventBusContext)
}
export function MessagesEventBusProvider({
@@ -24,18 +18,11 @@ export function MessagesEventBusProvider({
}: {
children: React.ReactNode
}) {
- const {currentAccount} = useSession()
-
- if (!currentAccount) {
- return (
- <MessagesEventBusContext.Provider value={null}>
- {children}
- </MessagesEventBusContext.Provider>
- )
- }
-
+ // DM functionality is disabled for syu.is
return (
- <MessagesEventBusProviderInner>{children}</MessagesEventBusProviderInner>
+ <MessagesEventBusContext.Provider value={null}>
+ {children}
+ </MessagesEventBusContext.Provider>
)
}
diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx
index c5457d1cb..5bc37bdce 100644
--- a/src/state/queries/messages/list-conversations.tsx
+++ b/src/state/queries/messages/list-conversations.tsx
@@ -74,17 +74,12 @@ export function useListConvos() {
const empty = {accepted: [], request: []}
export function ListConvosProvider({children}: {children: React.ReactNode}) {
- const {hasSession} = useSession()
-
- if (!hasSession) {
- return (
- <ListConvosContext.Provider value={empty}>
- {children}
- </ListConvosContext.Provider>
- )
- }
-
- return <ListConvosProviderInner>{children}</ListConvosProviderInner>
+ // DM functionality is disabled for syu.is - always return empty
+ return (
+ <ListConvosContext.Provider value={empty}>
+ {children}
+ </ListConvosContext.Provider>
+ )
}
export function ListConvosProviderInner({

View File

@@ -0,0 +1,15 @@
diff --git a/src/env/common.ts b/src/env/common.ts
--- a/src/env/common.ts
+++ b/src/env/common.ts
@@ -107,9 +107,8 @@ export const GCP_PROJECT_ID: number =
/**
* URLs for the app config web worker. Can be a
* locally running server, see `env.example` for more.
+ * Disabled for self-hosted environment to avoid CORS errors
*/
export const BAPP_CONFIG_DEV_URL = process.env.BAPP_CONFIG_DEV_URL
export const BAPP_CONFIG_PROD_URL = `https://ip.bsky.app`
-export const BAPP_CONFIG_URL = IS_DEV
- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_PROD_URL)
- : BAPP_CONFIG_PROD_URL
+export const BAPP_CONFIG_URL = null

View File

@@ -0,0 +1,91 @@
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -7,9 +7,9 @@
<!--
Preconnect to essential domains
-->
- <link rel="preconnect" href="https://bsky.social">
- <link rel="preconnect" href="https://go.bsky.app">
- <title>{%- block head_title -%}Bluesky{%- endblock -%}</title>
+ <link rel="preconnect" href="https://syu.is">
+ <link rel="preconnect" href="https://bsky.syu.is">
+ <title>{%- block head_title -%}syu.is{%- endblock -%}</title>
<!-- Hello Humans! API docs at https://atproto.com -->
@@ -121,7 +121,7 @@
<noscript>
<h1 lang="en">JavaScript Required</h1>
<p lang="en">This is a heavily interactive web application, and JavaScript is required. Simple HTML interfaces are possible, but that is not what this is.
- <p lang="en">Learn more about Bluesky at <a href="https://bsky.social">bsky.social</a> and <a href="https://atproto.com">atproto.com</a>.
+ <p lang="en">Learn more at <a href="https://syu.is">syu.is</a> and <a href="https://atproto.com">atproto.com</a>.
{% block noscript_extra %}{% endblock %}
</noscript>
{% endblock -%}
diff --git a/bskyweb/templates/home.html b/bskyweb/templates/home.html
--- a/bskyweb/templates/home.html
+++ b/bskyweb/templates/home.html
@@ -1,17 +1,17 @@
{% extends "base.html" %}
-{% block head_title %}Bluesky{% endblock %}
+{% block head_title %}syu.is{% endblock %}
{% block html_head_extra -%}
- <meta property="og:title" content="Bluesky" />
- <meta name="twitter:title" content="Bluesky" />
+ <meta property="og:title" content="syu.is" />
+ <meta name="twitter:title" content="syu.is" />
<meta name="description" content="Social media as it should be. Find your community among millions of users, unleash your creativity, and have some fun again." />
<meta name="og:description" content="Social media as it should be. Find your community among millions of users, unleash your creativity, and have some fun again." />
<meta name="twitter:description" content="Social media as it should be. Find your community among millions of users, unleash your creativity, and have some fun again." />
- <meta property="og:url" content="https://bsky.app" />
- <meta name="twitter:url" content="https://bsky.app" />
- <link rel="canonical" href="https://bsky.app" />
+ <meta property="og:url" content="https://syu.is" />
+ <meta name="twitter:url" content="https://syu.is" />
+ <link rel="canonical" href="https://syu.is" />
<meta property="og:image" content="https://bsky.app/static/social-card-default-gradient.png" />
diff --git a/bskyweb/templates/error.html b/bskyweb/templates/error.html
--- a/bskyweb/templates/error.html
+++ b/bskyweb/templates/error.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% block head_title %}Error {{ statusCode }} - Bluesky{% endblock %}
+{% block head_title %}Error {{ statusCode }} - syu.is{% endblock %}
{% block noscript_extra %}
{%- if statusCode == 404 %}
diff --git a/bskyweb/templates/starterpack.html b/bskyweb/templates/starterpack.html
--- a/bskyweb/templates/starterpack.html
+++ b/bskyweb/templates/starterpack.html
@@ -17,8 +17,8 @@
<meta property="og:title" content="{{ title }}" />
<meta name="twitter:title" content="{{ title }}" />
{%- else -%}
- <meta property="og:title" content="Bluesky" />
- <meta name="twitter:title" content="Bluesky" />
+ <meta property="og:title" content="syu.is" />
+ <meta name="twitter:title" content="syu.is" />
{% endif -%}
<meta name="description" content="Join the conversation" />
<meta name="og:description" content="Join the conversation" />
diff --git a/web/index.html b/web/index.html
--- a/web/index.html
+++ b/web/index.html
@@ -14,8 +14,8 @@
<!--
Preconnect to essential domains
-->
- <link rel="preconnect" href="https://bsky.social">
- <link rel="preconnect" href="https://go.bsky.app">
+ <link rel="preconnect" href="https://syu.is">
+ <link rel="preconnect" href="https://bsky.syu.is">
<title>%WEB_TITLE%</title>
<link rel="preload" as="font" type="font/woff2" href="/static/media/InterVariable.c504db5c06caaf7cdfba.woff2" crossorigin>

View File

@@ -0,0 +1,15 @@
diff --git a/src/components/dialogs/ServerInput.tsx b/src/components/dialogs/ServerInput.tsx
--- a/src/components/dialogs/ServerInput.tsx
+++ b/src/components/dialogs/ServerInput.tsx
@@ -144,9 +144,9 @@
<SegmentedControl.Item
testID="bskyServiceSelectBtn"
value={BSKY_SERVICE}
- label={_(msg`Bluesky`)}>
+ label={_(msg`syu.is`)}>
<SegmentedControl.ItemText>
- {_(msg`Bluesky`)}
+ {_(msg`syu.is`)}
</SegmentedControl.ItemText>
</SegmentedControl.Item>
<SegmentedControl.Item

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

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

@@ -0,0 +1,62 @@
# iOS Social App Patches
このディレクトリには、iOS版social-appのカスタマイズパッチが含まれています。
## パッチファイル一覧
- `001-social-app-ios-config.patch` - app.config.js の設定変更アプリ名、Bundle ID、アイコンパス、ドメイン等
- `002-social-app-ios-lib.patch` - lib/constants.ts, lib/statsig, lib/url-helpers の変更
- `003-social-app-ios-view.patch` - Logo, Logotype, UserAvatar, Splash.tsx の UI 変更Bluesky 蝶ロゴを logo.png に変更)
- `004-social-app-ios-core.patch` - agent.ts, App.native.tsx, routes.ts のコア変更
- `005-social-app-ios-screens.patch` - Settings, Home, Privacy, TOS 画面の変更
- `006-social-app-ios-shell.patch` - Drawer, BottomBar, RightNav, ServerInput などシェル変更
- `007-social-app-ios-misc.patch` - notifications, ageAssurance, PolicyUpdate などその他変更
- `008-social-app-ios-policy-tos-error.patch` - プライバシーポリシー・利用規約をネイティブコンポーネントで表示WebView から ScrollView + Text へ変更)
- `009-social-app-ios-license.patch` - ライセンスページの追加Drawer, Navigation, routes, types
- `010-social-app-ios-remove-contact-support.patch` - アカウント作成時の「Contact support」リンクを削除
- `011-social-app-ios-splash-license-footer.patch` - スプラッシュ画面の「What's up?」削除、© syui フッター追加
- `012-social-app-ios-settings-about-help.patch` - About 設定と routes.ts のリンクを内部ルートに変更(/support/tos, /support/privacy-policy, /support/license
- `013-social-app-ios-settings-remove-help.patch` - Settings から Help 項目を削除
- `019-social-app-ios-entitlements-plugin.patch` - iOS entitlements プラグイン設定
- `020-social-app-ios-bypass-age-assurance.patch` - 年齢確認を完全に無効化access を Full に固定、chatDisabled と adultContentDisabled を false に固定)
- `021-social-app-ios-clean-feed.patch` - Following フィードのシンプル化DiscoverFallbackHeader の (i) アイコンと Discover リンク削除、SuggestedFollows インタースティシャル無効化、おすすめボタン削除)
- `License.tsx` - ライセンス表示画面(新規ファイル)
## 使用方法
### パッチの適用
```bash
cd /Users/syui/ai/at/ios
./setup.zsh patch
```
**注意**: setup.zsh が自動的に以下を実行します:
- パッチファイルの適用
- License.tsx のコピー
- Xcode AppIcon を logo.png から 1024x1024 にリサイズして配置GraphicsMagick または sips を使用)
### リポジトリのリセット
```bash
cd /Users/syui/ai/at/ios
./setup.zsh reset
```
### すべてのパッチを適用(デフォルト)
```bash
cd /Users/syui/ai/at/ios
./setup.zsh
```
## パッチの更新方法
repos/social-app で変更を加えた後:
```bash
cd /Users/syui/ai/at/repos/social-app
git diff [ファイル名] > /Users/syui/ai/at/ios/patching/新しいパッチ.patch
```
その後、`setup.zsh``PATCH_FILES_IOS` 配列に新しいパッチファイル名を追加してください。

82
ios/preview.zsh Executable file
View File

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

223
ios/setup.zsh Executable file
View File

@@ -0,0 +1,223 @@
#!/bin/zsh
cd ${0:a:h}
# iOS Social App Patch Setup Script
# Usage: ./ios/setup.zsh [patch|reset]
# Cross-platform sed (macOS vs Linux)
function sediment() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
# 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"
"009-social-app-ios-license.patch"
"010-social-app-ios-remove-contact-support.patch"
"011-social-app-ios-splash-license-footer.patch"
"013-social-app-ios-settings-remove-help.patch"
"019-social-app-ios-entitlements-plugin.patch"
"020-social-app-ios-bypass-age-assurance.patch"
"021-social-app-ios-clean-feed.patch"
"022-social-app-ios-bskyweb-support-pages.patch"
"023-social-app-ios-disable-dm.patch"
"024-social-app-ios-disable-external-services.patch"
"025-social-app-ios-bskyweb-title.patch"
"026-social-app-ios-serverinput-label.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}"
}
# Generate build number from timestamp (YYMMDDHHMMSS)
function ios-generate-build-number() {
local build_number=$(date +%y%m%d%H%M%S)
local config_patch="$patching_dir/001-social-app-ios-config.patch"
if [ -f "$config_patch" ]; then
# Replace placeholder with timestamp
sediment "s/__BUILD_NUMBER__/${build_number}/" "$config_patch"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔢 Build number: ${build_number}"
echo ""
fi
}
# Restore placeholder after patching (for git cleanliness)
function ios-restore-build-placeholder() {
local config_patch="$patching_dir/001-social-app-ios-config.patch"
if [ -f "$config_patch" ]; then
# Restore placeholder for next build
sediment "s/buildNumber: '[0-9]*'/buildNumber: '__BUILD_NUMBER__'/" "$config_patch"
fi
}
# Auto-apply patches from list
function ios-patch-apply-all() {
for filename in "${PATCH_FILES_IOS[@]}"; do
local title="${filename%.*}"
patch-apply "$title" "$filename"
done
}
# Copy new files that aren't in patches
function ios-copy-new-files() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📁 Copying new files..."
# Copy all assets from ios/assets/ to repos/social-app/assets/
if [ -d "$d/ios/assets" ]; then
cp -rf "$d/ios/assets/"* "$target_dir/assets/"
echo "✅ Copied all assets (including logo.png, app-icons)"
fi
# Copy License.tsx
if [ -f "$patching_dir/License.tsx" ]; then
mkdir -p "$target_dir/src/view/screens"
cp "$patching_dir/License.tsx" "$target_dir/src/view/screens/License.tsx"
echo "✅ Copied License.tsx"
fi
echo ""
}
function ios-setup-clone() {
if [ ! -d $target_dir ]; then
echo "Error: social-app repository not found at $target_dir"
echo "Please run install.zsh first to clone repositories"
return 1
fi
echo "Repository found: $target_dir"
}
function ios-setup-reset() {
echo "Resetting social-app repository..."
cd $target_dir
git stash
git checkout .
echo "Reset complete"
}
# Main execution
ios-env
case "$1" in
patch)
ios-setup-clone
ios-generate-build-number
ios-patch-apply-all
ios-restore-build-placeholder
ios-copy-new-files
show-failed-patches
exit
;;
reset)
ios-setup-reset
exit
;;
*)
ios-setup-clone
ios-generate-build-number
ios-patch-apply-all
ios-restore-build-placeholder
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).