add ios social-app
66
install.zsh
@@ -1,5 +1,23 @@
|
||||
#!/bin/zsh
|
||||
|
||||
# Sed compatibility wrapper
|
||||
function sediment() {
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "$@"
|
||||
else
|
||||
sed -i "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Patch compatibility wrapper
|
||||
function patchment() {
|
||||
# -f : Force. Do not ask questions. (Standard in GNU and BSD patch)
|
||||
# -N : Ignore patches that seem to be reversed or already applied (Forward)
|
||||
# But we control these flags in the caller.
|
||||
patch "$@"
|
||||
}
|
||||
|
||||
function at-repos-env() {
|
||||
APP_PASSWORD=xxx
|
||||
host=syu.is
|
||||
@@ -102,26 +120,23 @@ function at-repos-social-app-avatar-write() {
|
||||
did_admin=did:plc:6qyecktefllvenje24fcxnie
|
||||
dt=$d/repos/social-app/src
|
||||
cd $dt
|
||||
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/syu.is/${host}/g"
|
||||
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sed -i "s/web.syu.is/web.${host}/g"
|
||||
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/syu.is/${host}/g"
|
||||
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/web.syu.is/web.${host}/g"
|
||||
f=$dt/lib/constants.ts
|
||||
sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
|
||||
sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
|
||||
sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
|
||||
sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
|
||||
sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +154,8 @@ function apply-patch() {
|
||||
pushd ${target_dir} > /dev/null
|
||||
|
||||
# Check if patch is already applied (reverse dry-run succeeds)
|
||||
if patch --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
|
||||
# Use -f to force dry-run to fail instead of asking questions if unapplied
|
||||
if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
|
||||
echo "✅ Already applied - skipping"
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
@@ -147,9 +163,9 @@ function apply-patch() {
|
||||
fi
|
||||
|
||||
# Check if patch can be applied (forward dry-run succeeds)
|
||||
if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
|
||||
if patch -f --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
|
||||
echo "🔧 Applying patch..."
|
||||
if patch -p1 < ${patch_file}; then
|
||||
if patch -f -p1 < ${patch_file}; then
|
||||
echo "✅ Applied successfully"
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
@@ -284,29 +300,29 @@ function at-repos-ozone-patch() {
|
||||
fi
|
||||
|
||||
# Replace process.env with env()
|
||||
sed -i 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
|
||||
sed -i 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
|
||||
sediment 's/process\.env\.\(NEXT_PUBLIC_[A-Z_]*\)/env('\''\1'\'')/g' lib/constants.ts 2>/dev/null || true
|
||||
sediment 's/process\.env\.NODE_ENV/env('\''NODE_ENV'\'')/g' lib/constants.ts 2>/dev/null || true
|
||||
# Add missing SOCIAL_APP_DOMAIN constant after SOCIAL_APP_URL
|
||||
sed -i '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
|
||||
sediment '/^export const SOCIAL_APP_URL =/,/^$/{ /^$/a\
|
||||
export const SOCIAL_APP_DOMAIN =\
|
||||
env('\''NEXT_PUBLIC_SOCIAL_APP_DOMAIN'\'') || '\''bsky.app'\''\
|
||||
|
||||
}' lib/constants.ts 2>/dev/null || true
|
||||
# Fix multiline process.env patterns
|
||||
sed -i '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
|
||||
sediment '/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 7$/ {
|
||||
s/^export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
|
||||
/^ \.NEXT_PUBLIC_NEW_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
|
||||
}' lib/constants.ts 2>/dev/null || true
|
||||
sed -i '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
|
||||
sediment '/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/,/^ : 30$/ {
|
||||
s/^export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = process\.env$/export const YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS = env('\''NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS'\'')/
|
||||
/^ \.NEXT_PUBLIC_YOUNG_ACCOUNT_MARKER_THRESHOLD_IN_DAYS$/d
|
||||
}' lib/constants.ts 2>/dev/null || true
|
||||
sed -i '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
|
||||
sediment '/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/,/^ : Infinity$/ {
|
||||
s/^export const HIGH_PROFILE_FOLLOWER_THRESHOLD = process\.env$/export const HIGH_PROFILE_FOLLOWER_THRESHOLD = env('\''NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD'\'')/
|
||||
/^ \.NEXT_PUBLIC_HIGH_PROFILE_FOLLOWER_THRESHOLD$/d
|
||||
}' lib/constants.ts 2>/dev/null || true
|
||||
# Fix parseInt() to handle undefined by adding || ''
|
||||
sed -i "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
|
||||
sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
|
||||
13
ios/.env
Normal file
@@ -0,0 +1,13 @@
|
||||
APP_NAME="Aiat"
|
||||
REPO_DIR="../repos/social-app"
|
||||
APP_SLUG="aiat"
|
||||
APP_SCHEME="syui"
|
||||
BUNDLE_ID="ai.syui.at"
|
||||
APP_GROUP="group.ai.syui.at"
|
||||
SERVICE_URL="https://syu.is"
|
||||
HELP_URL="https://syu.is/about/support/help"
|
||||
PRIVACY_URL="https://syu.is/about/support/privacy-policy"
|
||||
TERMS_URL="https://syu.is/about/support/tos"
|
||||
REPO_DIR="../repos/social-app"
|
||||
CONFIG_FILE="$REPO_DIR/app.config.js"
|
||||
CONSTANTS_FILE="$REPO_DIR/src/lib/constants.ts"
|
||||
134
ios/AppInfo.tsx
@@ -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 2023–2025 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',
|
||||
},
|
||||
})
|
||||
@@ -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 2023–2025 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,
|
||||
},
|
||||
})
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import PrivacyContent from '#/components/custom/PrivacyContent'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
|
||||
export const PrivacyPolicyScreen = (_props: Props) => {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
setMinimalShellMode(false)
|
||||
}, [setMinimalShellMode]),
|
||||
)
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Privacy Policy`)} />
|
||||
<ScrollView style={[s.hContentRegion, pal.view]}>
|
||||
<View style={[s.p20]}>
|
||||
<PrivacyContent />
|
||||
</View>
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
72
ios/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
今回の./ios (social-app)開発の要点をまとめます。
|
||||
|
||||
1. MITのライセンスを遵守すること、iosアプリとして出品しても問題ないようにすること
|
||||
https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
|
||||
|
||||
2. "Bluesky"という名称を使用しないこと。アイコンの変更。リンクの変更
|
||||
|
||||
3. selfhostでも動くこと。本来のsocial-appは動きませんので、これは不便なのでiosアプリに出品することにしました。なお、これはすでにpatchで実現しています。
|
||||
|
||||
|
||||
```sh
|
||||
$ ./install.zsh pull
|
||||
$ ./install.zsh patch
|
||||
$ ./ios/setup.zsh
|
||||
$ ./ios/preview.zsh
|
||||
```
|
||||
|
||||
## 実装済み
|
||||
|
||||
1. 最初の画面で、webではちゃんと私のサイトのロゴが表示されていますが、ios モバイル版では、未だにBluesky (icon)です。アカウント作成、サインイン、が表示されています。
|
||||
|
||||
2. 上のメニューバーにもBlueskyのロゴが表示されています。
|
||||
|
||||
3. サインイン後のホスティングプロバイダーで中身はsyu.isですが、表示は"Bluesky Social"になっています。これをsyu.isに変更してください。ios/webでコードは異なります。
|
||||
|
||||
4. チャット機能
|
||||
チャット機能は今回無効化するので、下メニューバーやプロフィール、設定画面に表示しないでください。
|
||||
|
||||
5. 設定ボタン(左カラム)を押すと、フィードバック、ヘルプが表示されますが、非表示にしてください。
|
||||
|
||||
6. 設定ボタン(左カラム)を押すと、フィード、リスト、保存済みの項目がありますが、これを削除してください。
|
||||
|
||||
7. 設定ボタン(左カラム)を押すと、下に利用規約、プライバシーポリシーが表示されますが、リンクがbsky.socialです。
|
||||
- /about/support/privacy-policy
|
||||
- /about/support/tos
|
||||
このページを独自に作って表示してください。
|
||||
|
||||
8. LOG 09:52:20 (logger) Poll latest failed {
|
||||
"feed": "following",
|
||||
"message": "Error: Could not find repo: did:plc:z72i7hdynmk6r22z27h6tvur"
|
||||
}
|
||||
|
||||
9. LOG 10:24:03 (metric) router:navigate
|
||||
LOG 10:24:04 (dms-agent) init failed {
|
||||
"safeMessage": "could not resolve iss did"
|
||||
}
|
||||
|
||||
9. 設定ボタン(左カラム)の一番下、利用規約やプライバシーポリシーが表示されいてるライセンスという項目を追加。ページを追加して、ライセンスの表示。
|
||||
https://github.com/bluesky-social/social-app
|
||||
https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE
|
||||
|
||||
10. アカウント作成時(create account)のページに"Having trouble?"で`Contact support`のリンクがありますが、これを削除してください。
|
||||
|
||||
11. スタートページ、つまり、`Create account`, `Sign in`があるページの一番下にライセンスページへのリンクを追加してください。また、footerに`© syui`を表示してください。このページのタイトル下にある文字`What's up?`の項目は削除。
|
||||
|
||||
12. スタートページのラインセンスリンクが機能しない。おそらくページ変遷に問題があるため。また、ライセンスページは上下が隠れて見えてしまうため、大きく上下に空間を開けること。
|
||||
|
||||
13. 利用規約、プライバシーポリシーのページの言語が日本語で書かれています。ラインセンスと同様に、英語を基本とし、日本語訳をその下に表示してください。
|
||||
|
||||
14. Settings/ 項目の非表示を追加。
|
||||
- Helpの非表示
|
||||
- Aboutのリンクを変更
|
||||
|
||||
## 壊れた実装
|
||||
|
||||
1. ログイン後のメイン画面、"Following"の項目(フィード)に表示されるものをシンプルにします。表示するのはFollowingのみで、以下のものを削除してください。
|
||||
- おすすめの削除
|
||||
- Discoverの削除
|
||||
- アカウントを探すの削除
|
||||
|
||||
2. 年齢保証、年齢確認ページがでてくるのを削除。誕生日を入力する処理を削除。アプリ配布国は限定します。
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
BIN
ios/app-icons/android_icon_core_aurora.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_bonfire.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_classic.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_flat_black.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_flat_blue.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_flat_white.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_midnight.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_sunrise.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_core_sunset.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_default_next.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_legacy_dark.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/android_icon_legacy_light.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_aurora.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_bonfire.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_classic.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_flat_black.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_flat_blue.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_flat_white.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_midnight.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_sunrise.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_core_sunset.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_default.icon/Assets/iOS transparent.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
31
ios/app-icons/ios_icon_default.icon/icon.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
ios/app-icons/ios_icon_default.icon/icon.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
ios/app-icons/ios_icon_default_next.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_legacy_dark.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
ios/app-icons/ios_icon_legacy_light.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/zsh
|
||||
|
||||
if [ "$1" = "social-app-custom" ];then
|
||||
at-social-app-custom-pages
|
||||
at-social-app-custom-screens
|
||||
at-social-app-aiat-config
|
||||
at-social-app-aiat-logo
|
||||
at-origin-social-app
|
||||
exit
|
||||
fi
|
||||
|
||||
function at-social-app-custom-pages() {
|
||||
d_=$d/repos/social-app
|
||||
custom=$d/social-app-custom
|
||||
|
||||
echo "copying custom components to social-app"
|
||||
|
||||
# Create components directory if not exists
|
||||
mkdir -p ${d_}/src/components/custom
|
||||
|
||||
# Copy custom components
|
||||
cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/
|
||||
cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/
|
||||
|
||||
echo "custom components copied successfully"
|
||||
}
|
||||
|
||||
function at-social-app-aiat-config() {
|
||||
d_=$d/repos/social-app
|
||||
custom=$d/social-app-custom
|
||||
|
||||
echo "applying Aiat configuration"
|
||||
|
||||
# Update app.config.js
|
||||
cd ${d_}
|
||||
|
||||
# Backup original
|
||||
cp app.config.js app.config.js.orig
|
||||
|
||||
# Apply changes using sed
|
||||
sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js
|
||||
sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js
|
||||
sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js
|
||||
sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js
|
||||
sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js
|
||||
|
||||
# Update package.json name
|
||||
sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json
|
||||
|
||||
echo "Aiat configuration applied"
|
||||
}
|
||||
|
||||
function at-social-app-aiat-logo() {
|
||||
d_=$d/repos/social-app
|
||||
custom=$d/social-app-custom
|
||||
|
||||
echo "applying Aiat logo"
|
||||
|
||||
# Create logo directory if not exists
|
||||
mkdir -p ${custom}/assets
|
||||
|
||||
# Copy logo if exists in custom folder
|
||||
if [ -f ${custom}/assets/icon.png ]; then
|
||||
cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png
|
||||
echo "Aiat logo applied"
|
||||
else
|
||||
echo "Warning: Logo file not found at ${custom}/assets/icon.png"
|
||||
echo "Please add your logo file there"
|
||||
fi
|
||||
}
|
||||
|
||||
function at-social-app-custom-screens() {
|
||||
d_=$d/repos/social-app
|
||||
custom=$d/social-app-custom
|
||||
|
||||
echo "applying custom screens"
|
||||
|
||||
# Copy custom screen files
|
||||
cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx
|
||||
cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx
|
||||
cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/
|
||||
|
||||
echo "custom screens applied"
|
||||
}
|
||||
|
||||
|
||||
BIN
ios/icon.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
150
ios/patching/001-social-app-ios-config.patch
Normal file
@@ -0,0 +1,150 @@
|
||||
diff --git a/Dockerfile b/Dockerfile
|
||||
index 371e8402c..2e139503e 100644
|
||||
--- a/Dockerfile
|
||||
+++ b/Dockerfile
|
||||
@@ -66,7 +66,8 @@ RUN \. "$NVM_DIR/nvm.sh" && \
|
||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \
|
||||
echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \
|
||||
npm install --global yarn && \
|
||||
- yarn && \
|
||||
+ yarn config set registry https://registry.npmjs.org/ && \
|
||||
+ yarn install --frozen-lockfile --network-timeout 100000 && \
|
||||
yarn intl:build 2>&1 | tee i18n.log && \
|
||||
if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \
|
||||
SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web
|
||||
diff --git a/app.config.js b/app.config.js
|
||||
index 246d8abd3..a6582864b 100644
|
||||
--- a/app.config.js
|
||||
+++ b/app.config.js
|
||||
@@ -33,8 +33,8 @@ module.exports = function (_config) {
|
||||
return {
|
||||
expo: {
|
||||
version: VERSION,
|
||||
- name: 'Bluesky',
|
||||
- slug: 'bluesky',
|
||||
+ name: 'Aiat',
|
||||
+ slug: 'aiat',
|
||||
scheme: 'bluesky',
|
||||
owner: 'blueskysocial',
|
||||
runtimeVersion: {
|
||||
@@ -45,15 +45,20 @@ module.exports = function (_config) {
|
||||
primaryColor: '#1083fe',
|
||||
newArchEnabled: false,
|
||||
ios: {
|
||||
+ infoPlist: {
|
||||
+ NSAppTransportSecurity: {
|
||||
+ NSAllowsArbitraryLoads: true,
|
||||
+ },
|
||||
+ },
|
||||
supportsTablet: false,
|
||||
- bundleIdentifier: 'xyz.blueskyweb.app',
|
||||
+ bundleIdentifier: 'ai.syui.at',
|
||||
config: {
|
||||
usesNonExemptEncryption: false,
|
||||
},
|
||||
icon:
|
||||
PLATFORM === 'web' // web build doesn't like .icon files
|
||||
? './assets/app-icons/ios_icon_default_next.png'
|
||||
- : './assets/app-icons/ios_icon_default.icon',
|
||||
+ : './assets/app-icons/ios_icon_default_next.png',
|
||||
infoPlist: {
|
||||
UIBackgroundModes: ['remote-notification'],
|
||||
NSCameraUsageDescription:
|
||||
@@ -113,7 +118,7 @@ module.exports = function (_config) {
|
||||
entitlements: {
|
||||
'com.apple.developer.kernel.increased-memory-limit': true,
|
||||
'com.apple.developer.kernel.extended-virtual-addressing': true,
|
||||
- 'com.apple.security.application-groups': 'group.app.bsky',
|
||||
+ 'com.apple.security.application-groups': 'group.ai.syui.at',
|
||||
},
|
||||
privacyManifests: {
|
||||
NSPrivacyCollectedDataTypes: [
|
||||
@@ -182,7 +187,7 @@ module.exports = function (_config) {
|
||||
backgroundColor: '#006AFF',
|
||||
},
|
||||
googleServicesFile: './google-services.json',
|
||||
- package: 'xyz.blueskyweb.app',
|
||||
+ package: 'ai.syui.at',
|
||||
intentFilters: [
|
||||
{
|
||||
action: 'VIEW',
|
||||
@@ -220,7 +225,7 @@ module.exports = function (_config) {
|
||||
checkAutomatically: 'NEVER',
|
||||
},
|
||||
plugins: [
|
||||
- 'expo-video',
|
||||
+ 'expo-video', './plugins/withCodeSignEntitlements.js',
|
||||
'expo-localization',
|
||||
'expo-web-browser',
|
||||
[
|
||||
@@ -239,6 +244,11 @@ module.exports = function (_config) {
|
||||
'expo-build-properties',
|
||||
{
|
||||
ios: {
|
||||
+ infoPlist: {
|
||||
+ NSAppTransportSecurity: {
|
||||
+ NSAllowsArbitraryLoads: true,
|
||||
+ },
|
||||
+ },
|
||||
deploymentTarget: '15.1',
|
||||
buildReactNativeFromSource: true,
|
||||
},
|
||||
@@ -264,7 +274,6 @@ module.exports = function (_config) {
|
||||
networkInstrumentation: true,
|
||||
},
|
||||
],
|
||||
- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
|
||||
'./plugins/withGradleJVMHeapSizeIncrease.js',
|
||||
'./plugins/withAndroidManifestLargeHeapPlugin.js',
|
||||
'./plugins/withAndroidManifestFCMIconPlugin.js',
|
||||
@@ -296,6 +305,11 @@ module.exports = function (_config) {
|
||||
'expo-splash-screen',
|
||||
{
|
||||
ios: {
|
||||
+ infoPlist: {
|
||||
+ NSAppTransportSecurity: {
|
||||
+ NSAllowsArbitraryLoads: true,
|
||||
+ },
|
||||
+ },
|
||||
enableFullScreenImage_legacy: true,
|
||||
backgroundColor: '#ffffff',
|
||||
image: './assets/splash.png',
|
||||
@@ -394,29 +408,30 @@ module.exports = function (_config) {
|
||||
build: {
|
||||
experimental: {
|
||||
ios: {
|
||||
+ infoPlist: {
|
||||
+ NSAppTransportSecurity: {
|
||||
+ NSAllowsArbitraryLoads: true,
|
||||
+ },
|
||||
+ },
|
||||
appExtensions: [
|
||||
{
|
||||
targetName: 'Share-with-Bluesky',
|
||||
- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky',
|
||||
+ bundleIdentifier: 'ai.syui.at.Share-with-Bluesky',
|
||||
entitlements: {
|
||||
'com.apple.security.application-groups': [
|
||||
- 'group.app.bsky',
|
||||
+ 'group.ai.syui.at',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
targetName: 'BlueskyNSE',
|
||||
- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE',
|
||||
+ bundleIdentifier: 'ai.syui.at.BlueskyNSE',
|
||||
entitlements: {
|
||||
'com.apple.security.application-groups': [
|
||||
- 'group.app.bsky',
|
||||
+ 'group.ai.syui.at',
|
||||
],
|
||||
},
|
||||
},
|
||||
- {
|
||||
- targetName: 'BlueskyClip',
|
||||
- bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
|
||||
- },
|
||||
],
|
||||
},
|
||||
},
|
||||
79
ios/patching/002-social-app-ios-lib.patch
Normal file
@@ -0,0 +1,79 @@
|
||||
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
|
||||
index 231447b4f..33c51cc0a 100644
|
||||
--- a/src/lib/constants.ts
|
||||
+++ b/src/lib/constants.ts
|
||||
@@ -7,12 +7,12 @@ import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env'
|
||||
export const LOCAL_DEV_SERVICE =
|
||||
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
|
||||
export const STAGING_SERVICE = 'https://staging.bsky.dev'
|
||||
-export const BSKY_SERVICE = 'https://bsky.social'
|
||||
-export const BSKY_SERVICE_DID = 'did:web:bsky.social'
|
||||
-export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
|
||||
+export const BSKY_SERVICE = 'https://syu.is'
|
||||
+export const BSKY_SERVICE_DID = 'did:web:syu.is'
|
||||
+export const PUBLIC_BSKY_SERVICE = 'https://bsky.syu.is'
|
||||
export const DEFAULT_SERVICE = BSKY_SERVICE
|
||||
-const HELP_DESK_LANG = 'en-us'
|
||||
-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
|
||||
+const HELP_DESK_LANG = 'ja-jp'
|
||||
+export const HELP_DESK_URL = 'https://syu.is/about/support/help'
|
||||
export const EMBED_SERVICE = 'https://embed.bsky.app'
|
||||
export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
|
||||
export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download'
|
||||
@@ -209,8 +209,8 @@ export const urls = {
|
||||
},
|
||||
}
|
||||
|
||||
-export const PUBLIC_APPVIEW = 'https://api.bsky.app'
|
||||
-export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'
|
||||
+export const PUBLIC_APPVIEW = 'https://bsky.syu.is'
|
||||
+export const PUBLIC_APPVIEW_DID = 'did:web:bsky.syu.is'
|
||||
export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev'
|
||||
|
||||
export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same
|
||||
@@ -236,8 +236,8 @@ export const BLUESKY_MOD_SERVICE_HEADERS = {
|
||||
}
|
||||
|
||||
export const webLinks = {
|
||||
- tos: `https://bsky.social/about/support/tos`,
|
||||
- privacy: `https://bsky.social/about/support/privacy-policy`,
|
||||
+ tos: `https://syu.is/about/support/tos`,
|
||||
+ privacy: `https://syu.is/about/support/privacy-policy`,
|
||||
community: `https://bsky.social/about/support/community-guidelines`,
|
||||
communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`,
|
||||
}
|
||||
diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
|
||||
index 860e841eb..a595b0868 100644
|
||||
--- a/src/lib/statsig/statsig.tsx
|
||||
+++ b/src/lib/statsig/statsig.tsx
|
||||
@@ -265,6 +265,7 @@ export async function tryFetchGates(
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
+ if (!SDK_KEY) return Promise.resolve()
|
||||
return Statsig.initialize(SDK_KEY, null, createStatsigOptions([]))
|
||||
}
|
||||
|
||||
@@ -309,6 +310,9 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||
return () => clearInterval(id)
|
||||
}, [handleIntervalTick])
|
||||
|
||||
+ if (!SDK_KEY) {
|
||||
+ return <GateCache.Provider value={gateCache}>{children}</GateCache.Provider>
|
||||
+ }
|
||||
return (
|
||||
<GateCache.Provider value={gateCache}>
|
||||
<StatsigProvider
|
||||
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
|
||||
index 6088e2806..7c903763d 100644
|
||||
--- a/src/lib/strings/url-helpers.ts
|
||||
+++ b/src/lib/strings/url-helpers.ts
|
||||
@@ -53,7 +53,7 @@ export function toNiceDomain(url: string): string {
|
||||
try {
|
||||
const urlp = new URL(url)
|
||||
if (`https://${urlp.host}` === BSKY_SERVICE) {
|
||||
- return 'Bluesky Social'
|
||||
+ return 'syu.is'
|
||||
}
|
||||
return urlp.host ? urlp.host : url
|
||||
} catch (e) {
|
||||
164
ios/patching/003-social-app-ios-view.patch
Normal file
@@ -0,0 +1,164 @@
|
||||
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
|
||||
index 8a9e51a33..cb0eb7b71 100644
|
||||
--- a/src/view/com/util/UserAvatar.tsx
|
||||
+++ b/src/view/com/util/UserAvatar.tsx
|
||||
@@ -444,7 +444,7 @@ let EditableUserAvatar = ({
|
||||
<HighPriorityImage
|
||||
testID="userAvatarImage"
|
||||
style={aviStyle}
|
||||
- source={{uri: avatar}}
|
||||
+ source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}
|
||||
accessibilityRole="image"
|
||||
/>
|
||||
) : (
|
||||
@@ -619,7 +619,7 @@ export {PreviewableUserAvatar}
|
||||
// -prf
|
||||
function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string {
|
||||
return isEnabled
|
||||
- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/')
|
||||
+ ? uri.replace('https://cdn.web.syu.is/img/avatar/plain/', 'https://bsky.syu.is/img/avatar/plain/')
|
||||
: uri
|
||||
}
|
||||
|
||||
diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx
|
||||
index d7208df13..b711f71c7 100644
|
||||
--- a/src/view/icons/Logo.tsx
|
||||
+++ b/src/view/icons/Logo.tsx
|
||||
@@ -1,75 +1,17 @@
|
||||
import React from 'react'
|
||||
-import {type TextProps} from 'react-native'
|
||||
-import Svg, {
|
||||
- Defs,
|
||||
- LinearGradient,
|
||||
- Path,
|
||||
- type PathProps,
|
||||
- Stop,
|
||||
- type SvgProps,
|
||||
-} from 'react-native-svg'
|
||||
import {Image} from 'expo-image'
|
||||
+import {flatten} from '#/alf'
|
||||
|
||||
-import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||
-import {flatten, useTheme} from '#/alf'
|
||||
-
|
||||
-const ratio = 57 / 64
|
||||
-
|
||||
-type Props = {
|
||||
- fill?: PathProps['fill']
|
||||
- style?: TextProps['style']
|
||||
-} & Omit<SvgProps, 'style'>
|
||||
-
|
||||
-export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
|
||||
- const t = useTheme()
|
||||
- const {fill, ...rest} = props
|
||||
- const gradient = fill === 'sky'
|
||||
- const styles = flatten(props.style)
|
||||
- const _fill = gradient
|
||||
- ? 'url(#sky)'
|
||||
- : fill || styles?.color || t.palette.primary_500
|
||||
- // @ts-ignore it's fiiiiine
|
||||
- const size = parseInt(rest.width || 32, 10)
|
||||
-
|
||||
- const isKawaii = useKawaiiMode()
|
||||
-
|
||||
- if (isKawaii) {
|
||||
- return (
|
||||
- <Image
|
||||
- source={
|
||||
- size > 100
|
||||
- ? require('../../../assets/kawaii.png')
|
||||
- : require('../../../assets/kawaii_smol.png')
|
||||
- }
|
||||
- accessibilityLabel="Bluesky"
|
||||
- accessibilityHint=""
|
||||
- accessibilityIgnoresInvertColors
|
||||
- style={[{height: size, aspectRatio: 1.4}]}
|
||||
- />
|
||||
- )
|
||||
- }
|
||||
-
|
||||
+export const Logo = React.forwardRef(function LogoImpl(props: any, ref) {
|
||||
+ const {width, style} = props
|
||||
+ // @ts-ignore
|
||||
+ const size = parseInt(width || 32, 10)
|
||||
return (
|
||||
- <Svg
|
||||
- fill="none"
|
||||
- // @ts-ignore it's fiiiiine
|
||||
- ref={ref}
|
||||
- viewBox="0 0 64 57"
|
||||
- {...rest}
|
||||
- style={[{width: size, height: size * ratio}, styles]}>
|
||||
- {gradient && (
|
||||
- <Defs>
|
||||
- <LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
- <Stop offset="0" stopColor="#0A7AFF" stopOpacity="1" />
|
||||
- <Stop offset="1" stopColor="#59B9FF" stopOpacity="1" />
|
||||
- </LinearGradient>
|
||||
- </Defs>
|
||||
- )}
|
||||
-
|
||||
- <Path
|
||||
- fill={_fill}
|
||||
- d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"
|
||||
- />
|
||||
- </Svg>
|
||||
+ <Image
|
||||
+ source={require('../../../assets/icon.png')}
|
||||
+ style={[{width: size, height: size}, flatten(style)]}
|
||||
+ contentFit="contain"
|
||||
+ accessibilityLabel="Logo"
|
||||
+ />
|
||||
)
|
||||
})
|
||||
diff --git a/src/view/icons/Logotype.tsx b/src/view/icons/Logotype.tsx
|
||||
index 270c913fc..a60ffe07c 100644
|
||||
--- a/src/view/icons/Logotype.tsx
|
||||
+++ b/src/view/icons/Logotype.tsx
|
||||
@@ -1,28 +1,22 @@
|
||||
-import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg'
|
||||
-
|
||||
-import {usePalette} from '#/lib/hooks/usePalette'
|
||||
-
|
||||
-const ratio = 17 / 64
|
||||
-
|
||||
-export function Logotype({
|
||||
- fill,
|
||||
- ...rest
|
||||
-}: {fill?: PathProps['fill']} & SvgProps) {
|
||||
- const pal = usePalette('default')
|
||||
- // @ts-ignore it's fiiiiine
|
||||
- const size = parseInt(rest.width || 32)
|
||||
+import React from 'react'
|
||||
+import {Text} from 'react-native'
|
||||
+import {useTheme, atoms as a} from '#/alf'
|
||||
|
||||
+export function Logotype({width, fill, style}: any) {
|
||||
+ const t = useTheme()
|
||||
+ const fontSize = width ? parseInt(width) / 3.5 : 22
|
||||
+
|
||||
return (
|
||||
- <Svg
|
||||
- fill="none"
|
||||
- viewBox="0 0 64 17"
|
||||
- {...rest}
|
||||
- width={size}
|
||||
- height={Number(size) * ratio}>
|
||||
- <Path
|
||||
- fill={fill || pal.text.color}
|
||||
- d="M8.478 6.252c1.503.538 2.3 1.78 2.3 3.172 0 2.356-1.576 3.785-4.6 3.785H0V0h5.974c2.875 0 4.267 1.466 4.267 3.413 0 1.3-.594 2.245-1.763 2.839Zm-2.69-4.193H2.504v3.45h3.284c1.28 0 1.967-.667 1.967-1.78 0-1.02-.705-1.67-1.967-1.67Zm-3.284 9.072h3.544c1.41 0 2.17-.65 2.17-1.818 0-1.224-.723-1.837-2.17-1.837H2.504v3.655ZM14.251 13.209h-2.337V0h2.337v13.209ZM22.001 8.998V3.636h2.338v9.573h-2.263v-1.392c-.724 1.076-1.726 1.614-3.006 1.614-2.022 0-3.34-1.224-3.34-3.45V3.636h2.338v5.955c0 1.206.594 1.818 1.8 1.818 1.132 0 2.133-.835 2.133-2.411ZM34.979 8.59v.556h-7.161c.167 1.651 1.076 2.467 2.486 2.467 1.076 0 1.8-.463 2.189-1.372h2.244c-.5 1.947-2.17 3.19-4.452 3.19-1.428 0-2.579-.463-3.45-1.372-.872-.91-1.318-2.115-1.318-3.637 0-1.502.427-2.708 1.299-3.636.872-.909 2.004-1.372 3.432-1.372 1.447 0 2.597.482 3.45 1.428.854.946 1.28 2.208 1.28 3.747Zm-4.75-3.358c-1.28 0-2.17.742-2.393 2.281h4.805c-.204-1.391-1.057-2.281-2.411-2.281ZM40.16 13.469c-2.783 0-4.249-1.095-4.379-3.303h2.282c.13 1.188.724 1.633 2.134 1.633 1.261 0 1.892-.39 1.892-1.15 0-.687-.445-1.02-1.874-1.262l-1.094-.185c-2.097-.353-3.136-1.318-3.136-2.894 0-1.8 1.429-2.894 3.97-2.894 2.728 0 4.138 1.075 4.23 3.246h-2.207c-.056-1.169-.742-1.577-2.023-1.577-1.113 0-1.67.371-1.67 1.113 0 .668.483.965 1.596 1.169l1.206.186c2.32.426 3.32 1.28 3.32 2.912 0 1.93-1.557 3.006-4.247 3.006ZM54.667 13.209h-2.671l-2.783-4.453-1.447 1.447v3.006h-2.3V0h2.3v7.606l3.896-3.97h2.783l-3.618 3.618 3.84 5.955ZM60.772 6.048l.78-2.412H64l-3.692 10.352c-.39 1.057-.872 1.818-1.484 2.245-.612.426-1.484.63-2.634.63-.39 0-.724-.018-1.02-.055V14.97h.89c1.057 0 1.577-.65 1.577-1.54 0-.445-.149-1.094-.446-1.929l-2.746-7.866h2.487l.779 2.393c.575 1.8 1.076 3.58 1.521 5.343.408-1.521.928-3.302 1.54-5.324Z"
|
||||
- />
|
||||
- </Svg>
|
||||
+ <Text style={[
|
||||
+ a.font_bold,
|
||||
+ {
|
||||
+ fontSize,
|
||||
+ color: fill || t.palette.primary_500,
|
||||
+ letterSpacing: -0.5
|
||||
+ },
|
||||
+ style
|
||||
+ ]}>
|
||||
+ Aiat
|
||||
+ </Text>
|
||||
)
|
||||
}
|
||||
72
ios/patching/004-social-app-ios-core.patch
Normal 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],
|
||||
570
ios/patching/005-social-app-ios-screens.patch
Normal 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>
|
||||
)
|
||||
}
|
||||
713
ios/patching/006-social-app-ios-shell.patch
Normal file
@@ -0,0 +1,713 @@
|
||||
diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx
|
||||
index 9915d0a2d..c200a7c67 100644
|
||||
--- a/src/components/dialogs/BirthDateSettings.tsx
|
||||
+++ b/src/components/dialogs/BirthDateSettings.tsx
|
||||
@@ -163,7 +163,7 @@ function BirthdayInner({
|
||||
<Trans>
|
||||
You must be at least 13 years old to use Bluesky. Read our{' '}
|
||||
<SimpleInlineLinkText
|
||||
- to="https://bsky.social/about/support/tos"
|
||||
+ to="https://syu.is/about/support/tos"
|
||||
label={_(msg`Terms of Service`)}>
|
||||
Terms of Service
|
||||
</SimpleInlineLinkText>{' '}
|
||||
diff --git a/src/components/dialogs/ServerInput.tsx b/src/components/dialogs/ServerInput.tsx
|
||||
index d7c02bb9f..fda1dfe4a 100644
|
||||
--- a/src/components/dialogs/ServerInput.tsx
|
||||
+++ b/src/components/dialogs/ServerInput.tsx
|
||||
@@ -165,7 +165,7 @@ function DialogInner({
|
||||
<Trans>
|
||||
Bluesky is an open network where you can choose your own
|
||||
provider. If you're new here, we recommend sticking with the
|
||||
- default Bluesky Social option.
|
||||
+ default syu.is option.
|
||||
</Trans>
|
||||
</Admonition>
|
||||
</View>
|
||||
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
|
||||
index ed2a6cfb7..2f387b4a8 100644
|
||||
--- a/src/view/shell/Drawer.tsx
|
||||
+++ b/src/view/shell/Drawer.tsx
|
||||
@@ -1,60 +1,50 @@
|
||||
-import React, {type ComponentProps, type JSX} from 'react'
|
||||
-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native'
|
||||
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
-import {msg, Plural, plural, Trans} from '@lingui/macro'
|
||||
-import {useLingui} from '@lingui/react'
|
||||
-import {StackActions, useNavigation} from '@react-navigation/native'
|
||||
-
|
||||
-import {useActorStatus} from '#/lib/actor-status'
|
||||
-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
|
||||
-import {type PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
||||
-import {getTabState, TabState} from '#/lib/routes/helpers'
|
||||
-import {type NavigationProp} from '#/lib/routes/types'
|
||||
-import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
-import {colors} from '#/lib/styles'
|
||||
-import {isWeb} from '#/platform/detection'
|
||||
-import {emitSoftReset} from '#/state/events'
|
||||
-import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||
-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
-import {useProfileQuery} from '#/state/queries/profile'
|
||||
-import {type SessionAccount, useSession} from '#/state/session'
|
||||
-import {useSetDrawerOpen} from '#/state/shell'
|
||||
-import {formatCount} from '#/view/com/util/numeric/format'
|
||||
-import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
-import {NavSignupCard} from '#/view/shell/NavSignupCard'
|
||||
-import {atoms as a, tokens, useTheme, web} from '#/alf'
|
||||
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
-import {Divider} from '#/components/Divider'
|
||||
+import React, { type ComponentProps, type JSX } from 'react'
|
||||
+import { Linking, ScrollView, TouchableOpacity, View } from 'react-native'
|
||||
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
+import { msg, Plural, plural, Trans } from '@lingui/macro'
|
||||
+import { useLingui } from '@lingui/react'
|
||||
+import { StackActions, useNavigation } from '@react-navigation/native'
|
||||
+
|
||||
+import { useActorStatus } from '#/lib/actor-status'
|
||||
+import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants'
|
||||
+import { type PressableScale } from '#/lib/custom-animations/PressableScale'
|
||||
+import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState'
|
||||
+import { getTabState, TabState } from '#/lib/routes/helpers'
|
||||
+import { type NavigationProp } from '#/lib/routes/types'
|
||||
+import { sanitizeHandle } from '#/lib/strings/handles'
|
||||
+import { colors } from '#/lib/styles'
|
||||
+import { isWeb } from '#/platform/detection'
|
||||
+import { emitSoftReset } from '#/state/events'
|
||||
+import { useKawaiiMode } from '#/state/preferences/kawaii'
|
||||
+import { useUnreadNotifications } from '#/state/queries/notifications/unread'
|
||||
+import { useProfileQuery } from '#/state/queries/profile'
|
||||
+import { type SessionAccount, useSession } from '#/state/session'
|
||||
+import { useSetDrawerOpen } from '#/state/shell'
|
||||
+import { formatCount } from '#/view/com/util/numeric/format'
|
||||
+import { UserAvatar } from '#/view/com/util/UserAvatar'
|
||||
+import { NavSignupCard } from '#/view/shell/NavSignupCard'
|
||||
+import { atoms as a, tokens, useTheme, web } from '#/alf'
|
||||
+import { Button } from '#/components/Button'
|
||||
+import { Divider } from '#/components/Divider'
|
||||
import {
|
||||
Bell_Filled_Corner0_Rounded as BellFilled,
|
||||
Bell_Stroke2_Corner0_Rounded as Bell,
|
||||
} from '#/components/icons/Bell'
|
||||
-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
|
||||
-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
|
||||
-import {
|
||||
- Hashtag_Filled_Corner0_Rounded as HashtagFilled,
|
||||
- Hashtag_Stroke2_Corner0_Rounded as Hashtag,
|
||||
-} from '#/components/icons/Hashtag'
|
||||
import {
|
||||
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
-import {
|
||||
- Message_Stroke2_Corner0_Rounded as Message,
|
||||
- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
-} from '#/components/icons/Message'
|
||||
-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
|
||||
+import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass'
|
||||
+import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2'
|
||||
+import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2'
|
||||
import {
|
||||
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
|
||||
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
|
||||
} from '#/components/icons/UserCircle'
|
||||
-import {InlineLinkText} from '#/components/Link'
|
||||
-import {Text} from '#/components/Typography'
|
||||
-import {useSimpleVerificationState} from '#/components/verification'
|
||||
-import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
||||
+import { InlineLinkText } from '#/components/Link'
|
||||
+import { Text } from '#/components/Typography'
|
||||
+import { useSimpleVerificationState } from '#/components/verification'
|
||||
+import { VerificationCheck } from '#/components/verification/VerificationCheck'
|
||||
|
||||
const iconWidth = 26
|
||||
|
||||
@@ -65,11 +55,11 @@ let DrawerProfileCard = ({
|
||||
account: SessionAccount
|
||||
onPressProfile: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_, i18n} = useLingui()
|
||||
+ const { _, i18n } = useLingui()
|
||||
const t = useTheme()
|
||||
- const {data: profile} = useProfileQuery({did: account.did})
|
||||
- const verification = useSimpleVerificationState({profile})
|
||||
- const {isActive: live} = useActorStatus(profile)
|
||||
+ const { data: profile } = useProfileQuery({ did: account.did })
|
||||
+ const verification = useSimpleVerificationState({ profile })
|
||||
+ const { isActive: live } = useActorStatus(profile)
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -81,7 +71,6 @@ let DrawerProfileCard = ({
|
||||
<UserAvatar
|
||||
size={52}
|
||||
avatar={profile?.avatar}
|
||||
- // See https://github.com/bluesky-social/social-app/pull/1801:
|
||||
usePlainRNImage={true}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={live}
|
||||
@@ -140,9 +129,9 @@ let DrawerProfileCard = ({
|
||||
)
|
||||
}
|
||||
DrawerProfileCard = React.memo(DrawerProfileCard)
|
||||
-export {DrawerProfileCard}
|
||||
+export { DrawerProfileCard }
|
||||
|
||||
-let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
+let DrawerContent = ({ }: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const insets = useSafeAreaInsets()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
@@ -150,27 +139,20 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
const {
|
||||
isAtHome,
|
||||
isAtSearch,
|
||||
- isAtFeeds,
|
||||
- isAtBookmarks,
|
||||
isAtNotifications,
|
||||
isAtMyProfile,
|
||||
- isAtMessages,
|
||||
} = useNavigationTabState()
|
||||
- const {hasSession, currentAccount} = useSession()
|
||||
-
|
||||
- // events
|
||||
- // =
|
||||
+ const { hasSession, currentAccount } = useSession()
|
||||
|
||||
const onPressTab = React.useCallback(
|
||||
(tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
|
||||
const state = navigation.getState()
|
||||
setDrawerOpen(false)
|
||||
if (isWeb) {
|
||||
- // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
|
||||
if (tab === 'MyProfile') {
|
||||
- navigation.navigate('Profile', {name: currentAccount!.handle})
|
||||
+ navigation.navigate('Profile', { name: currentAccount!.handle })
|
||||
} else {
|
||||
- // @ts-expect-error struggles with string unions, apparently
|
||||
+ // @ts-expect-error struggles with string unions
|
||||
navigation.navigate(tab)
|
||||
}
|
||||
} else {
|
||||
@@ -178,21 +160,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
if (tabState === TabState.InsideAtRoot) {
|
||||
emitSoftReset()
|
||||
} else if (tabState === TabState.Inside) {
|
||||
- // find the correct navigator in which to pop-to-top
|
||||
- const target = state.routes.find(route => route.name === `${tab}Tab`)
|
||||
- ?.state?.key
|
||||
+ const target = state.routes.find(route => route.name === `${tab}Tab`)?.state?.key
|
||||
if (target) {
|
||||
- // if we found it, trigger pop-to-top
|
||||
- navigation.dispatch({
|
||||
- ...StackActions.popToTop(),
|
||||
- target,
|
||||
- })
|
||||
+ navigation.dispatch({ ...StackActions.popToTop(), target })
|
||||
} else {
|
||||
- // fallback: reset navigation
|
||||
- navigation.reset({
|
||||
- index: 0,
|
||||
- routes: [{name: `${tab}Tab`}],
|
||||
- })
|
||||
+ navigation.reset({ index: 0, routes: [{ name: `${tab}Tab` }] })
|
||||
}
|
||||
} else {
|
||||
navigation.navigate(`${tab}Tab`)
|
||||
@@ -203,76 +175,21 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
)
|
||||
|
||||
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
|
||||
-
|
||||
- const onPressSearch = React.useCallback(
|
||||
- () => onPressTab('Search'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressMessages = React.useCallback(
|
||||
- () => onPressTab('Messages'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressNotifications = React.useCallback(
|
||||
- () => onPressTab('Notifications'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressProfile = React.useCallback(() => {
|
||||
- onPressTab('MyProfile')
|
||||
- }, [onPressTab])
|
||||
-
|
||||
- const onPressMyFeeds = React.useCallback(() => {
|
||||
- navigation.navigate('Feeds')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
- const onPressLists = React.useCallback(() => {
|
||||
- navigation.navigate('Lists')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
- const onPressBookmarks = React.useCallback(() => {
|
||||
- navigation.navigate('Bookmarks')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
+ const onPressSearch = React.useCallback(() => onPressTab('Search'), [onPressTab])
|
||||
+ const onPressNotifications = React.useCallback(() => onPressTab('Notifications'), [onPressTab])
|
||||
+ const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab])
|
||||
const onPressSettings = React.useCallback(() => {
|
||||
navigation.navigate('Settings')
|
||||
setDrawerOpen(false)
|
||||
}, [navigation, setDrawerOpen])
|
||||
|
||||
- const onPressFeedback = React.useCallback(() => {
|
||||
- Linking.openURL(
|
||||
- FEEDBACK_FORM_URL({
|
||||
- email: currentAccount?.email,
|
||||
- handle: currentAccount?.handle,
|
||||
- }),
|
||||
- )
|
||||
- }, [currentAccount])
|
||||
-
|
||||
- const onPressHelp = React.useCallback(() => {
|
||||
- Linking.openURL(HELP_DESK_URL)
|
||||
- }, [])
|
||||
-
|
||||
- // rendering
|
||||
- // =
|
||||
-
|
||||
return (
|
||||
<View
|
||||
testID="drawer"
|
||||
style={[a.flex_1, a.border_r, t.atoms.bg, t.atoms.border_contrast_low]}>
|
||||
<ScrollView
|
||||
style={[a.flex_1]}
|
||||
- contentContainerStyle={[
|
||||
- {
|
||||
- paddingTop: Math.max(
|
||||
- insets.top + a.pt_xl.paddingTop,
|
||||
- a.pt_xl.paddingTop,
|
||||
- ),
|
||||
- },
|
||||
- ]}>
|
||||
+ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
|
||||
<View style={[a.px_xl]}>
|
||||
{hasSession && currentAccount ? (
|
||||
<DrawerProfileCard
|
||||
@@ -284,7 +201,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<NavSignupCard />
|
||||
</View>
|
||||
)}
|
||||
-
|
||||
<Divider style={[a.mt_xl, a.mb_sm]} />
|
||||
</View>
|
||||
|
||||
@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<>
|
||||
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
|
||||
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
|
||||
- <ChatMenuItem isActive={isAtMessages} onPress={onPressMessages} />
|
||||
<NotificationsMenuItem
|
||||
isActive={isAtNotifications}
|
||||
onPress={onPressNotifications}
|
||||
/>
|
||||
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
|
||||
- <ListsMenuItem onPress={onPressLists} />
|
||||
- <BookmarksMenuItem
|
||||
- isActive={isAtBookmarks}
|
||||
- onPress={onPressBookmarks}
|
||||
- />
|
||||
<ProfileMenuItem
|
||||
isActive={isAtMyProfile}
|
||||
onPress={onPressProfile}
|
||||
@@ -312,7 +221,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
) : (
|
||||
<>
|
||||
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
|
||||
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
|
||||
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
|
||||
</>
|
||||
)}
|
||||
@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<ExtraLinks />
|
||||
</View>
|
||||
</ScrollView>
|
||||
-
|
||||
- <DrawerFooter
|
||||
- onPressFeedback={onPressFeedback}
|
||||
- onPressHelp={onPressHelp}
|
||||
- />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
DrawerContent = React.memo(DrawerContent)
|
||||
-export {DrawerContent}
|
||||
-
|
||||
-let DrawerFooter = ({
|
||||
- onPressFeedback,
|
||||
- onPressHelp,
|
||||
-}: {
|
||||
- onPressFeedback: () => void
|
||||
- onPressHelp: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const insets = useSafeAreaInsets()
|
||||
- return (
|
||||
- <View
|
||||
- style={[
|
||||
- a.flex_row,
|
||||
- a.gap_sm,
|
||||
- a.flex_wrap,
|
||||
- a.pl_xl,
|
||||
- a.pt_md,
|
||||
- {
|
||||
- paddingBottom: Math.max(
|
||||
- insets.bottom + tokens.space.xs,
|
||||
- tokens.space.xl,
|
||||
- ),
|
||||
- },
|
||||
- ]}>
|
||||
- <Button
|
||||
- label={_(msg`Send feedback`)}
|
||||
- size="small"
|
||||
- variant="solid"
|
||||
- color="secondary"
|
||||
- onPress={onPressFeedback}>
|
||||
- <ButtonIcon icon={Message} position="left" />
|
||||
- <ButtonText>
|
||||
- <Trans>Feedback</Trans>
|
||||
- </ButtonText>
|
||||
- </Button>
|
||||
- <Button
|
||||
- label={_(msg`Get help`)}
|
||||
- size="small"
|
||||
- variant="outline"
|
||||
- color="secondary"
|
||||
- onPress={onPressHelp}
|
||||
- style={{
|
||||
- backgroundColor: 'transparent',
|
||||
- }}>
|
||||
- <ButtonText>
|
||||
- <Trans>Help</Trans>
|
||||
- </ButtonText>
|
||||
- </Button>
|
||||
- </View>
|
||||
- )
|
||||
-}
|
||||
-DrawerFooter = React.memo(DrawerFooter)
|
||||
+export { DrawerContent }
|
||||
|
||||
interface MenuItemProps extends ComponentProps<typeof PressableScale> {
|
||||
icon: JSX.Element
|
||||
@@ -400,7 +250,7 @@ let SearchMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -426,7 +276,7 @@ let HomeMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -445,32 +295,6 @@ let HomeMenuItem = ({
|
||||
}
|
||||
HomeMenuItem = React.memo(HomeMenuItem)
|
||||
|
||||
-let ChatMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <MessageFilled style={[t.atoms.text]} width={iconWidth} />
|
||||
- ) : (
|
||||
- <Message style={[t.atoms.text]} width={iconWidth} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg`Chat`)}
|
||||
- bold={isActive}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-ChatMenuItem = React.memo(ChatMenuItem)
|
||||
-
|
||||
let NotificationsMenuItem = ({
|
||||
isActive,
|
||||
onPress,
|
||||
@@ -478,7 +302,7 @@ let NotificationsMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
const numUnreadNotifications = useUnreadNotifications()
|
||||
return (
|
||||
@@ -495,11 +319,11 @@ let NotificationsMenuItem = ({
|
||||
numUnreadNotifications === ''
|
||||
? ''
|
||||
: _(
|
||||
- msg`${plural(numUnreadNotifications ?? 0, {
|
||||
- one: '# unread item',
|
||||
- other: '# unread items',
|
||||
- })}` || '',
|
||||
- )
|
||||
+ msg`${plural(numUnreadNotifications ?? 0, {
|
||||
+ one: '# unread item',
|
||||
+ other: '# unread items',
|
||||
+ })}` || '',
|
||||
+ )
|
||||
}
|
||||
count={numUnreadNotifications}
|
||||
bold={isActive}
|
||||
@@ -509,72 +333,6 @@ let NotificationsMenuItem = ({
|
||||
}
|
||||
NotificationsMenuItem = React.memo(NotificationsMenuItem)
|
||||
|
||||
-let FeedsMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <HashtagFilled width={iconWidth} style={[t.atoms.text]} />
|
||||
- ) : (
|
||||
- <Hashtag width={iconWidth} style={[t.atoms.text]} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg`Feeds`)}
|
||||
- bold={isActive}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-FeedsMenuItem = React.memo(FeedsMenuItem)
|
||||
-
|
||||
-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
-
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={<List style={[t.atoms.text]} width={iconWidth} />}
|
||||
- label={_(msg`Lists`)}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-ListsMenuItem = React.memo(ListsMenuItem)
|
||||
-
|
||||
-let BookmarksMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
-
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <BookmarkFilled style={[t.atoms.text]} width={iconWidth} />
|
||||
- ) : (
|
||||
- <Bookmark style={[t.atoms.text]} width={iconWidth} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-BookmarksMenuItem = React.memo(BookmarksMenuItem)
|
||||
-
|
||||
let ProfileMenuItem = ({
|
||||
isActive,
|
||||
onPress,
|
||||
@@ -582,7 +340,7 @@ let ProfileMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -600,8 +358,8 @@ let ProfileMenuItem = ({
|
||||
}
|
||||
ProfileMenuItem = React.memo(ProfileMenuItem)
|
||||
|
||||
-let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => {
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -613,7 +371,7 @@ let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
}
|
||||
SettingsMenuItem = React.memo(SettingsMenuItem)
|
||||
|
||||
-function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
+function MenuItem({ icon, label, count, bold, onPress }: MenuItemProps) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Button
|
||||
@@ -621,7 +379,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
onPress={onPress}
|
||||
accessibilityRole="tab"
|
||||
label={label}>
|
||||
- {({hovered, pressed}) => (
|
||||
+ {({ hovered, pressed }) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
@@ -640,7 +398,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.align_end,
|
||||
- {top: -4, right: a.gap_sm.gap * -1},
|
||||
+ { top: -4, right: a.gap_sm.gap * -1 },
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
@@ -686,37 +444,23 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
}
|
||||
|
||||
function ExtraLinks() {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
const kawaii = useKawaiiMode()
|
||||
+ const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
return (
|
||||
<View style={[a.flex_col, a.gap_md, a.flex_wrap]}>
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- label={_(msg`Terms of Service`)}
|
||||
- to="https://bsky.social/about/support/tos">
|
||||
- <Trans>Terms of Service</Trans>
|
||||
- </InlineLinkText>
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- to="https://bsky.social/about/support/privacy-policy"
|
||||
- label={_(msg`Privacy Policy`)}>
|
||||
- <Trans>Privacy Policy</Trans>
|
||||
- </InlineLinkText>
|
||||
- {kawaii && (
|
||||
- <Text style={t.atoms.text_contrast_medium}>
|
||||
- <Trans>
|
||||
- Logo by{' '}
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- to="/profile/sawaratsuki.bsky.social"
|
||||
- label="@sawaratsuki.bsky.social">
|
||||
- @sawaratsuki.bsky.social
|
||||
- </InlineLinkText>
|
||||
- </Trans>
|
||||
+ <TouchableOpacity onPress={() => navigation.navigate('TermsOfService')}>
|
||||
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
+ <Trans>Terms of Service</Trans>
|
||||
</Text>
|
||||
- )}
|
||||
+ </TouchableOpacity>
|
||||
+ <TouchableOpacity onPress={() => navigation.navigate('PrivacyPolicy')}>
|
||||
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
+ <Trans>Privacy Policy</Trans>
|
||||
+ </Text>
|
||||
+ </TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
|
||||
index 779ebda68..bfd9b70fa 100644
|
||||
--- a/src/view/shell/bottom-bar/BottomBar.tsx
|
||||
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
|
||||
@@ -198,38 +198,6 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||
accessibilityLabel={_(msg`Search`)}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
- <Btn
|
||||
- testID="bottomBarMessagesBtn"
|
||||
- icon={
|
||||
- isAtMessages ? (
|
||||
- <MessageFilled
|
||||
- width={iconWidth - 1}
|
||||
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
|
||||
- />
|
||||
- ) : (
|
||||
- <Message
|
||||
- width={iconWidth - 1}
|
||||
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
|
||||
- />
|
||||
- )
|
||||
- }
|
||||
- onPress={onPressMessages}
|
||||
- notificationCount={numUnreadMessages.numUnread}
|
||||
- hasNew={numUnreadMessages.hasNew}
|
||||
- accessible={true}
|
||||
- accessibilityRole="tab"
|
||||
- accessibilityLabel={_(msg`Chat`)}
|
||||
- accessibilityHint={
|
||||
- numUnreadMessages.count > 0
|
||||
- ? _(
|
||||
- msg`${plural(numUnreadMessages.numUnread ?? 0, {
|
||||
- one: '# unread item',
|
||||
- other: '# unread items',
|
||||
- })}` || '',
|
||||
- )
|
||||
- : ''
|
||||
- }
|
||||
- />
|
||||
<Btn
|
||||
testID="bottomBarNotificationsBtn"
|
||||
icon={
|
||||
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
|
||||
index 1d097fc9a..e11a3a202 100644
|
||||
--- a/src/view/shell/desktop/RightNav.tsx
|
||||
+++ b/src/view/shell/desktop/RightNav.tsx
|
||||
@@ -109,13 +109,13 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
</>
|
||||
)}
|
||||
<InlineLinkText
|
||||
- to="https://bsky.social/about/support/privacy-policy"
|
||||
+ to="https://syu.is/about/support/privacy-policy"
|
||||
label={_(msg`Privacy`)}>
|
||||
{_(msg`Privacy`)}
|
||||
</InlineLinkText>
|
||||
{' • '}
|
||||
<InlineLinkText
|
||||
- to="https://bsky.social/about/support/tos"
|
||||
+ to="https://syu.is/about/support/tos"
|
||||
label={_(msg`Terms`)}>
|
||||
{_(msg`Terms`)}
|
||||
</InlineLinkText>
|
||||
138
ios/patching/007-social-app-ios-misc.patch
Normal 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: {
|
||||
318
ios/patching/008-social-app-ios-policy-tos-error.patch
Normal file
@@ -0,0 +1,318 @@
|
||||
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
|
||||
index 4f25468c9..95b183dcc 100644
|
||||
--- a/src/view/com/posts/PostFeed.tsx
|
||||
+++ b/src/view/com/posts/PostFeed.tsx
|
||||
@@ -298,8 +298,13 @@ let PostFeed = ({
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
+ const errorMsg = String(e)
|
||||
+ // Skip errors for missing repos (deleted accounts)
|
||||
+ if (errorMsg.includes('Could not find repo')) {
|
||||
+ return
|
||||
+ }
|
||||
if (!isNetworkError(e)) {
|
||||
- logger.error('Poll latest failed', {feed, message: String(e)})
|
||||
+ logger.error('Poll latest failed', {feed, message: errorMsg})
|
||||
}
|
||||
}
|
||||
})
|
||||
diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
|
||||
index a89eaadc4..c3a8b6114 100644
|
||||
--- a/src/view/screens/PrivacyPolicy.tsx
|
||||
+++ b/src/view/screens/PrivacyPolicy.tsx
|
||||
@@ -1,51 +1,95 @@
|
||||
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 { ScrollView } from 'react-native'
|
||||
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'
|
||||
+import {atoms as a, useTheme} from '#/alf'
|
||||
+import {Text} from '#/components/Typography'
|
||||
+
|
||||
+export function PrivacyPolicyScreen() {
|
||||
+ useSetTitle('Privacy Policy')
|
||||
+ const t = useTheme()
|
||||
|
||||
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
|
||||
+ 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]}>Privacy Policy</Text>
|
||||
+
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ This application (hereinafter referred to as "the App") respects user privacy and is committed to protecting personal information.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>1. Information We Collect</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The App is a decentralized social network using the AT Protocol.
|
||||
+ Information posted by users is stored on the selected server (PDS).
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>2. Use of Information</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The collected information is used for the following purposes:
|
||||
+ {'\n'}- Providing and operating the service
|
||||
+ {'\n'}- User support
|
||||
+ {'\n'}- Service improvement
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>3. Third-Party Disclosure</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The App will not provide users' personal information to third parties without user consent, except as required by law.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>4. Security</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The App takes appropriate security measures to protect personal information.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>5. Contact</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ For questions regarding this Privacy Policy, please contact us through the support link in the settings screen.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_5xl, a.mb_md]}>日本語訳(参考)</Text>
|
||||
+
|
||||
+ <Text style={[a.text_xl, a.font_bold, a.mb_lg]}>プライバシーポリシー</Text>
|
||||
+
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリケーション(以下「本アプリ」)は、ユーザーのプライバシーを尊重し、個人情報の保護に努めます。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>1. 収集する情報</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリは、ATプロトコルを使用した分散型ソーシャルネットワークです。
|
||||
+ ユーザーが投稿した情報は、選択したサーバー(PDS)に保存されます。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>2. 情報の利用目的</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 収集した情報は、以下の目的で利用されます:
|
||||
+ {'\n'}- サービスの提供・運営
|
||||
+ {'\n'}- ユーザーサポート
|
||||
+ {'\n'}- サービスの改善
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>3. 情報の第三者提供</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリは、ユーザーの個人情報を、法令に基づく場合を除き、
|
||||
+ ユーザーの同意なく第三者に提供することはありません。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>4. セキュリティ</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリは、個人情報の保護のため、適切な安全対策を講じます。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>5. お問い合わせ</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ プライバシーポリシーに関するご質問は、設定画面のサポートリンクからお問い合わせください。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
|
||||
+ Last Updated: December 7, 2025
|
||||
+ </Text>
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
|
||||
index d843c713c..3d2323f73 100644
|
||||
--- a/src/view/screens/TermsOfService.tsx
|
||||
+++ b/src/view/screens/TermsOfService.tsx
|
||||
@@ -1,49 +1,110 @@
|
||||
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 { ScrollView } from 'react-native'
|
||||
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'
|
||||
+import {atoms as a, useTheme} from '#/alf'
|
||||
+import {Text} from '#/components/Typography'
|
||||
+
|
||||
+export function TermsOfServiceScreen() {
|
||||
+ useSetTitle('Terms of Service')
|
||||
+ const t = useTheme()
|
||||
|
||||
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
|
||||
+ 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]}>Terms of Service</Text>
|
||||
+
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ These Terms of Service (hereinafter referred to as "these Terms") set forth the conditions for using this application (hereinafter referred to as "the App").
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 1 (Application)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ These Terms shall apply to all relationships between users and the provider of the App regarding the use of the App.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 2 (User Registration)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ Use of the App requires an AT Protocol-compatible account.
|
||||
+ Registration information must be kept accurate and up-to-date.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 3 (Prohibited Acts)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ Users shall not engage in the following acts when using the App:
|
||||
+ {'\n'}- Acts that violate laws or public order and morals
|
||||
+ {'\n'}- Acts related to criminal activity
|
||||
+ {'\n'}- Acts that infringe upon the rights of other users or third parties
|
||||
+ {'\n'}- Acts that interfere with the operation of the App
|
||||
+ {'\n'}- Unauthorized access or attempts thereof
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 4 (Disclaimer)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The App is a decentralized service using the AT Protocol.
|
||||
+ The provider makes no warranties, express or implied, regarding the App.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 5 (Changes to Terms)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ The provider may change these Terms without obtaining user consent when deemed necessary.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Article 6 (Contact)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ For questions regarding these Terms, please contact us through the support link in the settings screen.
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_5xl, a.mb_md]}>日本語訳(参考)</Text>
|
||||
+
|
||||
+ <Text style={[a.text_xl, a.font_bold, a.mb_lg]}>利用規約</Text>
|
||||
+
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本利用規約(以下「本規約」)は、本アプリケーション(以下「本アプリ」)の利用条件を定めるものです。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第1条(適用)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本規約は、ユーザーと本アプリの提供者との間の本アプリの利用に関わる一切の関係に適用されます。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第2条(利用登録)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリの利用にあたっては、ATプロトコル対応のアカウントが必要です。
|
||||
+ 登録情報は正確かつ最新の状態に保つ必要があります。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第3条(禁止事項)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ ユーザーは、本アプリの利用にあたり、以下の行為をしてはなりません:
|
||||
+ {'\n'}- 法令または公序良俗に違反する行為
|
||||
+ {'\n'}- 犯罪行為に関連する行為
|
||||
+ {'\n'}- 他のユーザーまたは第三者の権利を侵害する行為
|
||||
+ {'\n'}- 本アプリの運営を妨害する行為
|
||||
+ {'\n'}- 不正アクセスまたはこれを試みる行為
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第4条(免責事項)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本アプリは、ATプロトコルを使用した分散型サービスです。
|
||||
+ 提供者は、本アプリに関して、明示的・黙示的を問わず、いかなる保証も行いません。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第5条(規約の変更)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 提供者は、必要と判断した場合、ユーザーの承諾を得ることなく本規約を変更できるものとします。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>第6条(お問い合わせ)</Text>
|
||||
+ <Text style={[a.mb_md]}>
|
||||
+ 本規約に関するご質問は、設定画面のサポートリンクからお問い合わせください。
|
||||
+ </Text>
|
||||
+
|
||||
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
|
||||
+ Last Updated: December 7, 2025
|
||||
+ </Text>
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
681
ios/patching/009-social-app-ios-license.patch
Normal file
@@ -0,0 +1,681 @@
|
||||
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/routes.ts b/src/routes.ts
|
||||
index 1ed913bb2..aa6fccc4e 100644
|
||||
--- a/src/routes.ts
|
||||
+++ b/src/routes.ts
|
||||
@@ -71,8 +71,9 @@ 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',
|
||||
+ License: '/support/license',
|
||||
CommunityGuidelines: '/support/community-guidelines',
|
||||
CopyrightPolicy: '/support/copyright',
|
||||
// hashtags
|
||||
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
|
||||
index ed2a6cfb7..982c40b55 100644
|
||||
--- a/src/view/shell/Drawer.tsx
|
||||
+++ b/src/view/shell/Drawer.tsx
|
||||
@@ -1,60 +1,50 @@
|
||||
-import React, {type ComponentProps, type JSX} from 'react'
|
||||
-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native'
|
||||
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
-import {msg, Plural, plural, Trans} from '@lingui/macro'
|
||||
-import {useLingui} from '@lingui/react'
|
||||
-import {StackActions, useNavigation} from '@react-navigation/native'
|
||||
-
|
||||
-import {useActorStatus} from '#/lib/actor-status'
|
||||
-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
|
||||
-import {type PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
||||
-import {getTabState, TabState} from '#/lib/routes/helpers'
|
||||
-import {type NavigationProp} from '#/lib/routes/types'
|
||||
-import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
-import {colors} from '#/lib/styles'
|
||||
-import {isWeb} from '#/platform/detection'
|
||||
-import {emitSoftReset} from '#/state/events'
|
||||
-import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||
-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
-import {useProfileQuery} from '#/state/queries/profile'
|
||||
-import {type SessionAccount, useSession} from '#/state/session'
|
||||
-import {useSetDrawerOpen} from '#/state/shell'
|
||||
-import {formatCount} from '#/view/com/util/numeric/format'
|
||||
-import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
-import {NavSignupCard} from '#/view/shell/NavSignupCard'
|
||||
-import {atoms as a, tokens, useTheme, web} from '#/alf'
|
||||
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
-import {Divider} from '#/components/Divider'
|
||||
+import React, { type ComponentProps, type JSX } from 'react'
|
||||
+import { Linking, ScrollView, TouchableOpacity, View } from 'react-native'
|
||||
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
+import { msg, Plural, plural, Trans } from '@lingui/macro'
|
||||
+import { useLingui } from '@lingui/react'
|
||||
+import { StackActions, useNavigation } from '@react-navigation/native'
|
||||
+
|
||||
+import { useActorStatus } from '#/lib/actor-status'
|
||||
+import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants'
|
||||
+import { type PressableScale } from '#/lib/custom-animations/PressableScale'
|
||||
+import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState'
|
||||
+import { getTabState, TabState } from '#/lib/routes/helpers'
|
||||
+import { type NavigationProp } from '#/lib/routes/types'
|
||||
+import { sanitizeHandle } from '#/lib/strings/handles'
|
||||
+import { colors } from '#/lib/styles'
|
||||
+import { isWeb } from '#/platform/detection'
|
||||
+import { emitSoftReset } from '#/state/events'
|
||||
+import { useKawaiiMode } from '#/state/preferences/kawaii'
|
||||
+import { useUnreadNotifications } from '#/state/queries/notifications/unread'
|
||||
+import { useProfileQuery } from '#/state/queries/profile'
|
||||
+import { type SessionAccount, useSession } from '#/state/session'
|
||||
+import { useSetDrawerOpen } from '#/state/shell'
|
||||
+import { formatCount } from '#/view/com/util/numeric/format'
|
||||
+import { UserAvatar } from '#/view/com/util/UserAvatar'
|
||||
+import { NavSignupCard } from '#/view/shell/NavSignupCard'
|
||||
+import { atoms as a, tokens, useTheme, web } from '#/alf'
|
||||
+import { Button } from '#/components/Button'
|
||||
+import { Divider } from '#/components/Divider'
|
||||
import {
|
||||
Bell_Filled_Corner0_Rounded as BellFilled,
|
||||
Bell_Stroke2_Corner0_Rounded as Bell,
|
||||
} from '#/components/icons/Bell'
|
||||
-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
|
||||
-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
|
||||
-import {
|
||||
- Hashtag_Filled_Corner0_Rounded as HashtagFilled,
|
||||
- Hashtag_Stroke2_Corner0_Rounded as Hashtag,
|
||||
-} from '#/components/icons/Hashtag'
|
||||
import {
|
||||
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
|
||||
HomeOpen_Stoke2_Corner0_Rounded as Home,
|
||||
} from '#/components/icons/HomeOpen'
|
||||
-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||
-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||
-import {
|
||||
- Message_Stroke2_Corner0_Rounded as Message,
|
||||
- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
|
||||
-} from '#/components/icons/Message'
|
||||
-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
|
||||
+import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass'
|
||||
+import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2'
|
||||
+import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2'
|
||||
import {
|
||||
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
|
||||
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
|
||||
} from '#/components/icons/UserCircle'
|
||||
-import {InlineLinkText} from '#/components/Link'
|
||||
-import {Text} from '#/components/Typography'
|
||||
-import {useSimpleVerificationState} from '#/components/verification'
|
||||
-import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
||||
+import { InlineLinkText } from '#/components/Link'
|
||||
+import { Text } from '#/components/Typography'
|
||||
+import { useSimpleVerificationState } from '#/components/verification'
|
||||
+import { VerificationCheck } from '#/components/verification/VerificationCheck'
|
||||
|
||||
const iconWidth = 26
|
||||
|
||||
@@ -65,11 +55,11 @@ let DrawerProfileCard = ({
|
||||
account: SessionAccount
|
||||
onPressProfile: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_, i18n} = useLingui()
|
||||
+ const { _, i18n } = useLingui()
|
||||
const t = useTheme()
|
||||
- const {data: profile} = useProfileQuery({did: account.did})
|
||||
- const verification = useSimpleVerificationState({profile})
|
||||
- const {isActive: live} = useActorStatus(profile)
|
||||
+ const { data: profile } = useProfileQuery({ did: account.did })
|
||||
+ const verification = useSimpleVerificationState({ profile })
|
||||
+ const { isActive: live } = useActorStatus(profile)
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -81,7 +71,6 @@ let DrawerProfileCard = ({
|
||||
<UserAvatar
|
||||
size={52}
|
||||
avatar={profile?.avatar}
|
||||
- // See https://github.com/bluesky-social/social-app/pull/1801:
|
||||
usePlainRNImage={true}
|
||||
type={profile?.associated?.labeler ? 'labeler' : 'user'}
|
||||
live={live}
|
||||
@@ -140,9 +129,9 @@ let DrawerProfileCard = ({
|
||||
)
|
||||
}
|
||||
DrawerProfileCard = React.memo(DrawerProfileCard)
|
||||
-export {DrawerProfileCard}
|
||||
+export { DrawerProfileCard }
|
||||
|
||||
-let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
+let DrawerContent = ({ }: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const insets = useSafeAreaInsets()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
@@ -150,27 +139,20 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
const {
|
||||
isAtHome,
|
||||
isAtSearch,
|
||||
- isAtFeeds,
|
||||
- isAtBookmarks,
|
||||
isAtNotifications,
|
||||
isAtMyProfile,
|
||||
- isAtMessages,
|
||||
} = useNavigationTabState()
|
||||
- const {hasSession, currentAccount} = useSession()
|
||||
-
|
||||
- // events
|
||||
- // =
|
||||
+ const { hasSession, currentAccount } = useSession()
|
||||
|
||||
const onPressTab = React.useCallback(
|
||||
(tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
|
||||
const state = navigation.getState()
|
||||
setDrawerOpen(false)
|
||||
if (isWeb) {
|
||||
- // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
|
||||
if (tab === 'MyProfile') {
|
||||
- navigation.navigate('Profile', {name: currentAccount!.handle})
|
||||
+ navigation.navigate('Profile', { name: currentAccount!.handle })
|
||||
} else {
|
||||
- // @ts-expect-error struggles with string unions, apparently
|
||||
+ // @ts-expect-error struggles with string unions
|
||||
navigation.navigate(tab)
|
||||
}
|
||||
} else {
|
||||
@@ -178,21 +160,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
if (tabState === TabState.InsideAtRoot) {
|
||||
emitSoftReset()
|
||||
} else if (tabState === TabState.Inside) {
|
||||
- // find the correct navigator in which to pop-to-top
|
||||
- const target = state.routes.find(route => route.name === `${tab}Tab`)
|
||||
- ?.state?.key
|
||||
+ const target = state.routes.find(route => route.name === `${tab}Tab`)?.state?.key
|
||||
if (target) {
|
||||
- // if we found it, trigger pop-to-top
|
||||
- navigation.dispatch({
|
||||
- ...StackActions.popToTop(),
|
||||
- target,
|
||||
- })
|
||||
+ navigation.dispatch({ ...StackActions.popToTop(), target })
|
||||
} else {
|
||||
- // fallback: reset navigation
|
||||
- navigation.reset({
|
||||
- index: 0,
|
||||
- routes: [{name: `${tab}Tab`}],
|
||||
- })
|
||||
+ navigation.reset({ index: 0, routes: [{ name: `${tab}Tab` }] })
|
||||
}
|
||||
} else {
|
||||
navigation.navigate(`${tab}Tab`)
|
||||
@@ -203,76 +175,21 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
)
|
||||
|
||||
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
|
||||
-
|
||||
- const onPressSearch = React.useCallback(
|
||||
- () => onPressTab('Search'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressMessages = React.useCallback(
|
||||
- () => onPressTab('Messages'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressNotifications = React.useCallback(
|
||||
- () => onPressTab('Notifications'),
|
||||
- [onPressTab],
|
||||
- )
|
||||
-
|
||||
- const onPressProfile = React.useCallback(() => {
|
||||
- onPressTab('MyProfile')
|
||||
- }, [onPressTab])
|
||||
-
|
||||
- const onPressMyFeeds = React.useCallback(() => {
|
||||
- navigation.navigate('Feeds')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
- const onPressLists = React.useCallback(() => {
|
||||
- navigation.navigate('Lists')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
- const onPressBookmarks = React.useCallback(() => {
|
||||
- navigation.navigate('Bookmarks')
|
||||
- setDrawerOpen(false)
|
||||
- }, [navigation, setDrawerOpen])
|
||||
-
|
||||
+ const onPressSearch = React.useCallback(() => onPressTab('Search'), [onPressTab])
|
||||
+ const onPressNotifications = React.useCallback(() => onPressTab('Notifications'), [onPressTab])
|
||||
+ const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab])
|
||||
const onPressSettings = React.useCallback(() => {
|
||||
navigation.navigate('Settings')
|
||||
setDrawerOpen(false)
|
||||
}, [navigation, setDrawerOpen])
|
||||
|
||||
- const onPressFeedback = React.useCallback(() => {
|
||||
- Linking.openURL(
|
||||
- FEEDBACK_FORM_URL({
|
||||
- email: currentAccount?.email,
|
||||
- handle: currentAccount?.handle,
|
||||
- }),
|
||||
- )
|
||||
- }, [currentAccount])
|
||||
-
|
||||
- const onPressHelp = React.useCallback(() => {
|
||||
- Linking.openURL(HELP_DESK_URL)
|
||||
- }, [])
|
||||
-
|
||||
- // rendering
|
||||
- // =
|
||||
-
|
||||
return (
|
||||
<View
|
||||
testID="drawer"
|
||||
style={[a.flex_1, a.border_r, t.atoms.bg, t.atoms.border_contrast_low]}>
|
||||
<ScrollView
|
||||
style={[a.flex_1]}
|
||||
- contentContainerStyle={[
|
||||
- {
|
||||
- paddingTop: Math.max(
|
||||
- insets.top + a.pt_xl.paddingTop,
|
||||
- a.pt_xl.paddingTop,
|
||||
- ),
|
||||
- },
|
||||
- ]}>
|
||||
+ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
|
||||
<View style={[a.px_xl]}>
|
||||
{hasSession && currentAccount ? (
|
||||
<DrawerProfileCard
|
||||
@@ -284,7 +201,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<NavSignupCard />
|
||||
</View>
|
||||
)}
|
||||
-
|
||||
<Divider style={[a.mt_xl, a.mb_sm]} />
|
||||
</View>
|
||||
|
||||
@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<>
|
||||
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
|
||||
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
|
||||
- <ChatMenuItem isActive={isAtMessages} onPress={onPressMessages} />
|
||||
<NotificationsMenuItem
|
||||
isActive={isAtNotifications}
|
||||
onPress={onPressNotifications}
|
||||
/>
|
||||
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
|
||||
- <ListsMenuItem onPress={onPressLists} />
|
||||
- <BookmarksMenuItem
|
||||
- isActive={isAtBookmarks}
|
||||
- onPress={onPressBookmarks}
|
||||
- />
|
||||
<ProfileMenuItem
|
||||
isActive={isAtMyProfile}
|
||||
onPress={onPressProfile}
|
||||
@@ -312,7 +221,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
) : (
|
||||
<>
|
||||
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
|
||||
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
|
||||
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
|
||||
</>
|
||||
)}
|
||||
@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
|
||||
<ExtraLinks />
|
||||
</View>
|
||||
</ScrollView>
|
||||
-
|
||||
- <DrawerFooter
|
||||
- onPressFeedback={onPressFeedback}
|
||||
- onPressHelp={onPressHelp}
|
||||
- />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
DrawerContent = React.memo(DrawerContent)
|
||||
-export {DrawerContent}
|
||||
-
|
||||
-let DrawerFooter = ({
|
||||
- onPressFeedback,
|
||||
- onPressHelp,
|
||||
-}: {
|
||||
- onPressFeedback: () => void
|
||||
- onPressHelp: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const insets = useSafeAreaInsets()
|
||||
- return (
|
||||
- <View
|
||||
- style={[
|
||||
- a.flex_row,
|
||||
- a.gap_sm,
|
||||
- a.flex_wrap,
|
||||
- a.pl_xl,
|
||||
- a.pt_md,
|
||||
- {
|
||||
- paddingBottom: Math.max(
|
||||
- insets.bottom + tokens.space.xs,
|
||||
- tokens.space.xl,
|
||||
- ),
|
||||
- },
|
||||
- ]}>
|
||||
- <Button
|
||||
- label={_(msg`Send feedback`)}
|
||||
- size="small"
|
||||
- variant="solid"
|
||||
- color="secondary"
|
||||
- onPress={onPressFeedback}>
|
||||
- <ButtonIcon icon={Message} position="left" />
|
||||
- <ButtonText>
|
||||
- <Trans>Feedback</Trans>
|
||||
- </ButtonText>
|
||||
- </Button>
|
||||
- <Button
|
||||
- label={_(msg`Get help`)}
|
||||
- size="small"
|
||||
- variant="outline"
|
||||
- color="secondary"
|
||||
- onPress={onPressHelp}
|
||||
- style={{
|
||||
- backgroundColor: 'transparent',
|
||||
- }}>
|
||||
- <ButtonText>
|
||||
- <Trans>Help</Trans>
|
||||
- </ButtonText>
|
||||
- </Button>
|
||||
- </View>
|
||||
- )
|
||||
-}
|
||||
-DrawerFooter = React.memo(DrawerFooter)
|
||||
+export { DrawerContent }
|
||||
|
||||
interface MenuItemProps extends ComponentProps<typeof PressableScale> {
|
||||
icon: JSX.Element
|
||||
@@ -400,7 +250,7 @@ let SearchMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -426,7 +276,7 @@ let HomeMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -445,32 +295,6 @@ let HomeMenuItem = ({
|
||||
}
|
||||
HomeMenuItem = React.memo(HomeMenuItem)
|
||||
|
||||
-let ChatMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <MessageFilled style={[t.atoms.text]} width={iconWidth} />
|
||||
- ) : (
|
||||
- <Message style={[t.atoms.text]} width={iconWidth} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg`Chat`)}
|
||||
- bold={isActive}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-ChatMenuItem = React.memo(ChatMenuItem)
|
||||
-
|
||||
let NotificationsMenuItem = ({
|
||||
isActive,
|
||||
onPress,
|
||||
@@ -478,7 +302,7 @@ let NotificationsMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
const numUnreadNotifications = useUnreadNotifications()
|
||||
return (
|
||||
@@ -495,11 +319,11 @@ let NotificationsMenuItem = ({
|
||||
numUnreadNotifications === ''
|
||||
? ''
|
||||
: _(
|
||||
- msg`${plural(numUnreadNotifications ?? 0, {
|
||||
- one: '# unread item',
|
||||
- other: '# unread items',
|
||||
- })}` || '',
|
||||
- )
|
||||
+ msg`${plural(numUnreadNotifications ?? 0, {
|
||||
+ one: '# unread item',
|
||||
+ other: '# unread items',
|
||||
+ })}` || '',
|
||||
+ )
|
||||
}
|
||||
count={numUnreadNotifications}
|
||||
bold={isActive}
|
||||
@@ -509,72 +333,6 @@ let NotificationsMenuItem = ({
|
||||
}
|
||||
NotificationsMenuItem = React.memo(NotificationsMenuItem)
|
||||
|
||||
-let FeedsMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <HashtagFilled width={iconWidth} style={[t.atoms.text]} />
|
||||
- ) : (
|
||||
- <Hashtag width={iconWidth} style={[t.atoms.text]} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg`Feeds`)}
|
||||
- bold={isActive}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-FeedsMenuItem = React.memo(FeedsMenuItem)
|
||||
-
|
||||
-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
-
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={<List style={[t.atoms.text]} width={iconWidth} />}
|
||||
- label={_(msg`Lists`)}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-ListsMenuItem = React.memo(ListsMenuItem)
|
||||
-
|
||||
-let BookmarksMenuItem = ({
|
||||
- isActive,
|
||||
- onPress,
|
||||
-}: {
|
||||
- isActive: boolean
|
||||
- onPress: () => void
|
||||
-}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
- const t = useTheme()
|
||||
-
|
||||
- return (
|
||||
- <MenuItem
|
||||
- icon={
|
||||
- isActive ? (
|
||||
- <BookmarkFilled style={[t.atoms.text]} width={iconWidth} />
|
||||
- ) : (
|
||||
- <Bookmark style={[t.atoms.text]} width={iconWidth} />
|
||||
- )
|
||||
- }
|
||||
- label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))}
|
||||
- onPress={onPress}
|
||||
- />
|
||||
- )
|
||||
-}
|
||||
-BookmarksMenuItem = React.memo(BookmarksMenuItem)
|
||||
-
|
||||
let ProfileMenuItem = ({
|
||||
isActive,
|
||||
onPress,
|
||||
@@ -582,7 +340,7 @@ let ProfileMenuItem = ({
|
||||
isActive: boolean
|
||||
onPress: () => void
|
||||
}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -600,8 +358,8 @@ let ProfileMenuItem = ({
|
||||
}
|
||||
ProfileMenuItem = React.memo(ProfileMenuItem)
|
||||
|
||||
-let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
- const {_} = useLingui()
|
||||
+let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => {
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -613,7 +371,7 @@ let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
|
||||
}
|
||||
SettingsMenuItem = React.memo(SettingsMenuItem)
|
||||
|
||||
-function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
+function MenuItem({ icon, label, count, bold, onPress }: MenuItemProps) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Button
|
||||
@@ -621,7 +379,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
onPress={onPress}
|
||||
accessibilityRole="tab"
|
||||
label={label}>
|
||||
- {({hovered, pressed}) => (
|
||||
+ {({ hovered, pressed }) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
@@ -640,7 +398,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.align_end,
|
||||
- {top: -4, right: a.gap_sm.gap * -1},
|
||||
+ { top: -4, right: a.gap_sm.gap * -1 },
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
@@ -686,37 +444,28 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
|
||||
}
|
||||
|
||||
function ExtraLinks() {
|
||||
- const {_} = useLingui()
|
||||
+ const { _ } = useLingui()
|
||||
const t = useTheme()
|
||||
const kawaii = useKawaiiMode()
|
||||
+ const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
return (
|
||||
<View style={[a.flex_col, a.gap_md, a.flex_wrap]}>
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- label={_(msg`Terms of Service`)}
|
||||
- to="https://bsky.social/about/support/tos">
|
||||
- <Trans>Terms of Service</Trans>
|
||||
- </InlineLinkText>
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- to="https://bsky.social/about/support/privacy-policy"
|
||||
- label={_(msg`Privacy Policy`)}>
|
||||
- <Trans>Privacy Policy</Trans>
|
||||
- </InlineLinkText>
|
||||
- {kawaii && (
|
||||
- <Text style={t.atoms.text_contrast_medium}>
|
||||
- <Trans>
|
||||
- Logo by{' '}
|
||||
- <InlineLinkText
|
||||
- style={[a.text_md]}
|
||||
- to="/profile/sawaratsuki.bsky.social"
|
||||
- label="@sawaratsuki.bsky.social">
|
||||
- @sawaratsuki.bsky.social
|
||||
- </InlineLinkText>
|
||||
- </Trans>
|
||||
+ <TouchableOpacity onPress={() => navigation.navigate('TermsOfService')}>
|
||||
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
+ <Trans>Terms of Service</Trans>
|
||||
</Text>
|
||||
- )}
|
||||
+ </TouchableOpacity>
|
||||
+ <TouchableOpacity onPress={() => navigation.navigate('PrivacyPolicy')}>
|
||||
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
+ <Trans>Privacy Policy</Trans>
|
||||
+ </Text>
|
||||
+ </TouchableOpacity>
|
||||
+ <TouchableOpacity onPress={() => navigation.navigate('License')}>
|
||||
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
+ <Trans>License</Trans>
|
||||
+ </Text>
|
||||
+ </TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
25
ios/patching/010-social-app-ios-remove-contact-support.patch
Normal 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>
|
||||
117
ios/patching/011-social-app-ios-splash-license-footer.patch
Normal file
@@ -0,0 +1,117 @@
|
||||
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
|
||||
index 3442d1bdf..2b059af52 100644
|
||||
--- a/src/view/com/auth/SplashScreen.tsx
|
||||
+++ b/src/view/com/auth/SplashScreen.tsx
|
||||
@@ -40,16 +40,6 @@ export const SplashScreen = ({
|
||||
<View style={[a.pb_sm, a.pt_5xl]}>
|
||||
<Logotype width={161} fill={t.atoms.text.color} />
|
||||
</View>
|
||||
-
|
||||
- <Text
|
||||
- style={[
|
||||
- a.text_md,
|
||||
- a.font_semi_bold,
|
||||
- t.atoms.text_contrast_medium,
|
||||
- a.text_center,
|
||||
- ]}>
|
||||
- <Trans>What's up?</Trans>
|
||||
- </Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -94,7 +84,7 @@ export const SplashScreen = ({
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.pt_md,
|
||||
- a.pb_2xl,
|
||||
+ a.pb_md,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
]}>
|
||||
@@ -102,6 +92,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>
|
||||
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
|
||||
index 22dd23d7f..08cd8aa33 100644
|
||||
--- a/src/view/com/auth/SplashScreen.web.tsx
|
||||
+++ b/src/view/com/auth/SplashScreen.web.tsx
|
||||
@@ -3,7 +3,9 @@ import {Pressable, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
+import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
+import {NavigationProp} from '#/lib/routes/types'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
||||
@@ -93,15 +95,6 @@ export const SplashScreen = ({
|
||||
<Logotype width={161} fill={t.atoms.text.color} />
|
||||
</View>
|
||||
)}
|
||||
-
|
||||
- <Text
|
||||
- style={[
|
||||
- a.text_md,
|
||||
- a.font_semi_bold,
|
||||
- t.atoms.text_contrast_medium,
|
||||
- ]}>
|
||||
- <Trans>What's up?</Trans>
|
||||
- </Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -150,7 +143,6 @@ export const SplashScreen = ({
|
||||
|
||||
function Footer() {
|
||||
const t = useTheme()
|
||||
- const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -168,26 +160,12 @@ function Footer() {
|
||||
a.flex_1,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
- <InlineLinkText
|
||||
- label={_(msg`Learn more about Bluesky`)}
|
||||
- to="https://bsky.social">
|
||||
- <Trans>Business</Trans>
|
||||
- </InlineLinkText>
|
||||
- <InlineLinkText
|
||||
- label={_(msg`Read the Bluesky blog`)}
|
||||
- to="https://bsky.social/about/blog">
|
||||
- <Trans>Blog</Trans>
|
||||
- </InlineLinkText>
|
||||
- <InlineLinkText
|
||||
- label={_(msg`See jobs at Bluesky`)}
|
||||
- to="https://bsky.social/about/join">
|
||||
- <Trans comment="Link to a page with job openings at Bluesky">
|
||||
- Jobs
|
||||
- </Trans>
|
||||
- </InlineLinkText>
|
||||
-
|
||||
<View style={a.flex_1} />
|
||||
|
||||
+ <Text style={[a.text_xs, t.atoms.text_contrast_low]}>
|
||||
+ © syui
|
||||
+ </Text>
|
||||
+
|
||||
<AppLanguageDropdown />
|
||||
</View>
|
||||
)
|
||||
31
ios/patching/012-social-app-ios-settings-about-help.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
|
||||
index 6b8257b91..bed851753 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="/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="/about/support/privacy-policy"
|
||||
label={_(msg`Privacy Policy`)}>
|
||||
<SettingsList.ItemIcon icon={NewspaperIcon} />
|
||||
<SettingsList.ItemText>
|
||||
@@ -96,7 +96,7 @@ export function AboutSettingsScreen({}: Props) {
|
||||
</SettingsList.ItemText>
|
||||
</SettingsList.LinkItem>
|
||||
<SettingsList.LinkItem
|
||||
- to={STATUS_PAGE_URL}
|
||||
+ to="https://syu.is/"
|
||||
label={_(msg`Status Page`)}>
|
||||
<SettingsList.ItemIcon icon={GlobeIcon} />
|
||||
<SettingsList.ItemText>
|
||||
21
ios/patching/013-social-app-ios-settings-remove-help.patch
Normal 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>
|
||||
31
ios/patching/019-social-app-ios-entitlements-plugin.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
diff --git a/plugins/withCodeSignEntitlements.js b/plugins/withCodeSignEntitlements.js
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
--- /dev/null
|
||||
+++ b/plugins/withCodeSignEntitlements.js
|
||||
@@ -0,0 +1,25 @@
|
||||
+/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
+const { withXcodeProject } = require('@expo/config-plugins')
|
||||
+
|
||||
+const withCodeSignEntitlements = (config) => {
|
||||
+ return withXcodeProject(config, (config) => {
|
||||
+ const xcodeProject = config.modResults
|
||||
+ const configurations = xcodeProject.pbxXCBuildConfigurationSection()
|
||||
+
|
||||
+ for (const key in configurations) {
|
||||
+ const configuration = configurations[key]
|
||||
+ if (
|
||||
+ configuration.buildSettings &&
|
||||
+ configuration.comment &&
|
||||
+ !configuration.comment.includes('TEST')
|
||||
+ ) {
|
||||
+ configuration.buildSettings.CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION =
|
||||
+ 'YES'
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return config
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+module.exports = withCodeSignEntitlements
|
||||
95
ios/patching/020-social-app-ios-bypass-age-assurance.patch
Normal file
@@ -0,0 +1,95 @@
|
||||
--- a/b/src/ageAssurance/index.tsx 2025-12-07 15:18:15
|
||||
+++ b/src/ageAssurance/index.tsx 2025-12-07 15:18:16
|
||||
@@ -1,10 +1,10 @@
|
||||
-import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
|
||||
+import { createContext, useCallback, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
-import {useGetAndRegisterPushToken} from '#/lib/notifications/notifications'
|
||||
-import {Provider as RedirectOverlayProvider} from '#/ageAssurance/components/RedirectOverlay'
|
||||
-import {AgeAssuranceDataProvider} from '#/ageAssurance/data'
|
||||
-import {useAgeAssuranceDataContext} from '#/ageAssurance/data'
|
||||
-import {logger} from '#/ageAssurance/logger'
|
||||
+import { useGetAndRegisterPushToken } from '#/lib/notifications/notifications'
|
||||
+import { Provider as RedirectOverlayProvider } from '#/ageAssurance/components/RedirectOverlay'
|
||||
+import { AgeAssuranceDataProvider } from '#/ageAssurance/data'
|
||||
+import { useAgeAssuranceDataContext } from '#/ageAssurance/data'
|
||||
+import { logger } from '#/ageAssurance/logger'
|
||||
import {
|
||||
useAgeAssuranceState,
|
||||
useOnAgeAssuranceAccessUpdate,
|
||||
@@ -14,7 +14,7 @@
|
||||
type AgeAssuranceState,
|
||||
AgeAssuranceStatus,
|
||||
} from '#/ageAssurance/types'
|
||||
-import {isUserUnderAdultAge} from '#/ageAssurance/util'
|
||||
+import { isUserUnderAdultAge } from '#/ageAssurance/util'
|
||||
|
||||
export {
|
||||
prefetchConfig as prefetchAgeAssuranceConfig,
|
||||
@@ -23,7 +23,7 @@
|
||||
usePatchOtherRequiredData as usePatchAgeAssuranceOtherRequiredData,
|
||||
usePatchServerState as usePatchAgeAssuranceServerState,
|
||||
} from '#/ageAssurance/data'
|
||||
-export {logger} from '#/ageAssurance/logger'
|
||||
+export { logger } from '#/ageAssurance/logger'
|
||||
|
||||
const AgeAssuranceStateContext = createContext<{
|
||||
Access: typeof AgeAssuranceAccess
|
||||
@@ -56,7 +56,7 @@
|
||||
return useContext(AgeAssuranceStateContext)
|
||||
}
|
||||
|
||||
-export function Provider({children}: {children: React.ReactNode}) {
|
||||
+export function Provider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<AgeAssuranceDataProvider>
|
||||
<InnerProvider>
|
||||
@@ -66,9 +66,9 @@
|
||||
)
|
||||
}
|
||||
|
||||
-function InnerProvider({children}: {children: React.ReactNode}) {
|
||||
+function InnerProvider({ children }: { children: React.ReactNode }) {
|
||||
const state = useAgeAssuranceState()
|
||||
- const {data} = useAgeAssuranceDataContext()
|
||||
+ const { data } = useAgeAssuranceDataContext()
|
||||
const getAndRegisterPushToken = useGetAndRegisterPushToken()
|
||||
|
||||
const handleAccessUpdate = useCallback(
|
||||
@@ -82,28 +82,25 @@
|
||||
useOnAgeAssuranceAccessUpdate(handleAccessUpdate)
|
||||
|
||||
useEffect(() => {
|
||||
- logger.debug(`useAgeAssuranceState`, {state})
|
||||
+ logger.debug(`useAgeAssuranceState`, { state })
|
||||
}, [state])
|
||||
|
||||
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])}>
|
||||
+ }, [state])}>
|
||||
{children}
|
||||
</AgeAssuranceStateContext.Provider>
|
||||
)
|
||||
86
ios/patching/License.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react'
|
||||
import { ScrollView } from 'react-native'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function LicenseScreen() {
|
||||
useSetTitle('License')
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ScrollView
|
||||
style={[a.flex_1, {backgroundColor: t.palette.white}]}
|
||||
contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
|
||||
<Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>License</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
This application is based on Bluesky Social App.
|
||||
</Text>
|
||||
|
||||
<Text style={[a.text_md, a.mb_md, {color: t.palette.primary_500}]}>
|
||||
https://github.com/bluesky-social/social-app
|
||||
</Text>
|
||||
|
||||
<Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text>
|
||||
|
||||
<Text style={[a.mb_md, {fontFamily: 'monospace'}]}>
|
||||
Copyright (c) 2022-2025 Bluesky PBC
|
||||
</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</Text>
|
||||
|
||||
<Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}>日本語訳(参考)</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
本ソフトウェアおよび関連文書ファイル(以下「ソフトウェア」)のコピーを取得する
|
||||
すべての人に対し、ソフトウェアを無制限に扱うことを無償で許可します。これには、
|
||||
ソフトウェアのコピーを使用、複製、変更、結合、公開、配布、サブライセンス、
|
||||
および/または販売する権利、ならびにソフトウェアを提供する相手にそうした行為を
|
||||
許可する権利が含まれますが、これらに限定されません。
|
||||
</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
上記の著作権表示および本許諾表示を、ソフトウェアのすべてのコピーまたは
|
||||
重要な部分に記載するものとします。
|
||||
</Text>
|
||||
|
||||
<Text style={[a.mb_md]}>
|
||||
ソフトウェアは「現状のまま」で提供され、明示黙示を問わず、商品性、特定目的への
|
||||
適合性、および権利非侵害についての保証を含む、いかなる種類の保証もなされません。
|
||||
いかなる場合においても、作者または著作権者は、契約行為、不法行為、またはそれ以外で
|
||||
あろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用または
|
||||
その他の扱いによって生じる一切の請求、損害、その他の義務について責任を負わないものとします。
|
||||
</Text>
|
||||
|
||||
<Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
|
||||
Original License: https://github.com/bluesky-social/social-app/blob/main/LICENSE
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
54
ios/patching/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# iOS Social App Patches
|
||||
|
||||
このディレクトリには、iOS版social-appのカスタマイズパッチが含まれています。
|
||||
|
||||
## パッチファイル一覧
|
||||
|
||||
- `001-social-app-ios-config.patch` - Dockerfile, app.config.js の設定変更
|
||||
- `002-social-app-ios-lib.patch` - lib/constants.ts, lib/statsig, lib/url-helpers の変更
|
||||
- `003-social-app-ios-view.patch` - Logo, Logotype, UserAvatar の UI 変更
|
||||
- `004-social-app-ios-core.patch` - agent.ts, App.native.tsx, routes.ts のコア変更
|
||||
- `005-social-app-ios-screens.patch` - Settings, Home, Privacy, TOS 画面の変更
|
||||
- `006-social-app-ios-shell.patch` - Drawer, BottomBar, RightNav, ServerInput などシェル変更
|
||||
- `007-social-app-ios-misc.patch` - notifications, ageAssurance, PolicyUpdate などその他変更
|
||||
- `008-social-app-ios-policy-tos-error.patch` - プライバシーポリシー・利用規約の英語/日本語二言語化、DIDエラー処理
|
||||
- `009-social-app-ios-license.patch` - ライセンスページの追加(Drawer, Navigation, routes, types)
|
||||
- `010-social-app-ios-remove-contact-support.patch` - アカウント作成時の「Contact support」リンクを削除
|
||||
- `011-social-app-ios-splash-license-footer.patch` - スプラッシュ画面の「What's up?」削除、© syui フッター追加
|
||||
- `012-social-app-ios-settings-about-help.patch` - About 設定のリンク修正(syu.is に変更)
|
||||
- `013-social-app-ios-settings-remove-help.patch` - Settings から Help 項目を削除
|
||||
- `License.tsx` - ライセンス表示画面(新規ファイル)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### パッチの適用
|
||||
|
||||
```bash
|
||||
cd /Users/syui/ai/at/ios
|
||||
./setup.zsh patch
|
||||
```
|
||||
|
||||
### リポジトリのリセット
|
||||
|
||||
```bash
|
||||
cd /Users/syui/ai/at/ios
|
||||
./setup.zsh reset
|
||||
```
|
||||
|
||||
### すべてのパッチを適用(デフォルト)
|
||||
|
||||
```bash
|
||||
cd /Users/syui/ai/at/ios
|
||||
./setup.zsh
|
||||
```
|
||||
|
||||
## パッチの更新方法
|
||||
|
||||
repos/social-app で変更を加えた後:
|
||||
|
||||
```bash
|
||||
cd /Users/syui/ai/at/repos/social-app
|
||||
git diff [ファイル名] > /Users/syui/ai/at/ios/patching/新しいパッチ.patch
|
||||
```
|
||||
|
||||
その後、`setup.zsh` の `PATCH_FILES_IOS` 配列に新しいパッチファイル名を追加してください。
|
||||
72
ios/preview.zsh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/zsh
|
||||
set -e
|
||||
d=${0:a:h}
|
||||
cd $d
|
||||
|
||||
source $d/.env
|
||||
|
||||
xcrun simctl uninstall booted $BUNDLE_ID
|
||||
|
||||
echo "Running iOS preview workflow..."
|
||||
cd "$REPO_DIR"
|
||||
|
||||
# 0. Environment Setup (Fix Node Version)
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
echo "Checking Node version..."
|
||||
if command -v nvm >/dev/null; then
|
||||
nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)"
|
||||
else
|
||||
echo "nvm not found, using system node: $(node -v)"
|
||||
fi
|
||||
|
||||
# 1. Install dependencies
|
||||
echo "1. Installing dependencies (yarn)..."
|
||||
yarn install
|
||||
|
||||
# 2. Prebuild (Generate ios directory)
|
||||
echo "2. Running Expo Prebuild..."
|
||||
# Clean old ios folder to remove old entitlements/AppClip targets
|
||||
rm -rf ios
|
||||
npx expo prebuild --platform ios --clean
|
||||
|
||||
# 3. CocoaPods
|
||||
echo "3. Installing CocoaPods..."
|
||||
# Ensure PATH includes Homebrew ruby gems if needed
|
||||
export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
|
||||
# 4. Signing (Manual Step)
|
||||
echo "4. Opening Xcode for Signing..."
|
||||
XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
|
||||
# Fallback search if variable name logic differs
|
||||
if [ ! -d "$XCODE_PROJ" ]; then
|
||||
XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
|
||||
fi
|
||||
|
||||
open "$XCODE_PROJ"
|
||||
echo "========================================================"
|
||||
echo " [ACTION REQUIRED] "
|
||||
echo " Xcode opened ($XCODE_PROJ)."
|
||||
echo " 1. Go to 'Signing & Capabilities' tab."
|
||||
echo " 2. Select your Team."
|
||||
echo " 3. Verify 'App Clip' target is gone."
|
||||
echo " 4. Ensure no red errors exist."
|
||||
echo " Press ENTER here once you are done to continue building."
|
||||
echo "========================================================"
|
||||
read
|
||||
|
||||
# 5. Run
|
||||
echo "5. Building and Running..."
|
||||
# If user wants specific device ID, uncomment below, otherwise let Expo ask/pick boot simulator
|
||||
|
||||
case $1 in
|
||||
d|devicei)
|
||||
npx expo run:ios --device "$DEVICE_ID" --configuration Release
|
||||
;;
|
||||
*)
|
||||
npx expo run:ios
|
||||
;;
|
||||
esac
|
||||
182
ios/setup.zsh
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/bin/zsh
|
||||
|
||||
cd ${0:a:h}
|
||||
|
||||
# iOS Social App Patch Setup Script
|
||||
# Usage: ./ios/setup.zsh [patch|reset]
|
||||
|
||||
# Arrays for patch management
|
||||
typeset -a FAILED_PATCHES
|
||||
|
||||
# Patch file lists for iOS
|
||||
typeset -a PATCH_FILES_IOS
|
||||
PATCH_FILES_IOS=(
|
||||
"001-social-app-ios-config.patch"
|
||||
"002-social-app-ios-lib.patch"
|
||||
"003-social-app-ios-view.patch"
|
||||
"004-social-app-ios-core.patch"
|
||||
"005-social-app-ios-screens.patch"
|
||||
"006-social-app-ios-shell.patch"
|
||||
"007-social-app-ios-misc.patch"
|
||||
"008-social-app-ios-policy-tos-error.patch"
|
||||
"009-social-app-ios-license.patch"
|
||||
"010-social-app-ios-remove-contact-support.patch"
|
||||
"011-social-app-ios-splash-license-footer.patch"
|
||||
"012-social-app-ios-settings-about-help.patch"
|
||||
"013-social-app-ios-settings-remove-help.patch"
|
||||
"019-social-app-ios-entitlements-plugin.patch"
|
||||
"020-social-app-ios-bypass-age-assurance.patch"
|
||||
)
|
||||
|
||||
function ios-env() {
|
||||
d=${0:a:h:h}
|
||||
patching_dir=${0:a:h}/patching
|
||||
target_dir=$d/repos/social-app
|
||||
}
|
||||
|
||||
# Common patch function with status detection
|
||||
function apply-patch() {
|
||||
local patch_name=$1
|
||||
local target_dir=$2
|
||||
local patch_file=$3
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📝 Patch: ${patch_name}"
|
||||
echo " Target: ${target_dir}"
|
||||
echo " File: ${patch_file}"
|
||||
|
||||
pushd ${target_dir} > /dev/null
|
||||
|
||||
# Check if patch is already applied (reverse dry-run succeeds)
|
||||
if patch -f --dry-run -p1 -R < ${patch_file} > /dev/null 2>&1; then
|
||||
echo "✅ Already applied - skipping"
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if patch can be applied (forward dry-run succeeds)
|
||||
if patch --dry-run -p1 < ${patch_file} > /dev/null 2>&1; then
|
||||
echo "🔧 Applying patch..."
|
||||
if patch -p1 < ${patch_file}; then
|
||||
echo "✅ Applied successfully"
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
return 0
|
||||
else
|
||||
echo "❌ Failed to apply"
|
||||
FAILED_PATCHES+=("${patch_name} (${patch_file})")
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Cannot apply - file may have been modified"
|
||||
echo " Please check manually"
|
||||
FAILED_PATCHES+=("${patch_name} (${patch_file}) - file modified")
|
||||
popd > /dev/null
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function show-failed-patches() {
|
||||
if [ ${#FAILED_PATCHES[@]} -eq 0 ]; then
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ All patches applied successfully!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "⚠️ FAILED PATCHES SUMMARY"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "The following patches could not be applied:"
|
||||
echo ""
|
||||
for failed_patch in "${FAILED_PATCHES[@]}"; do
|
||||
echo " ❌ ${failed_patch}"
|
||||
done
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Helper function for applying patches
|
||||
function patch-apply() {
|
||||
local name=$1
|
||||
local patch_file=$2
|
||||
apply-patch "${name}" "$target_dir" "$patching_dir/${patch_file}"
|
||||
}
|
||||
|
||||
# Auto-apply patches from list
|
||||
function ios-patch-apply-all() {
|
||||
for filename in "${PATCH_FILES_IOS[@]}"; do
|
||||
local title="${filename%.*}"
|
||||
patch-apply "$title" "$filename"
|
||||
done
|
||||
}
|
||||
|
||||
# Copy new files that aren't in patches
|
||||
function ios-copy-new-files() {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📁 Copying new files..."
|
||||
|
||||
# Copy License.tsx
|
||||
if [ -f "$patching_dir/License.tsx" ]; then
|
||||
mkdir -p "$target_dir/src/view/screens"
|
||||
cp "$patching_dir/License.tsx" "$target_dir/src/view/screens/License.tsx"
|
||||
echo "✅ Copied License.tsx"
|
||||
fi
|
||||
|
||||
# Copy logo.png to icon.png if it doesn't exist
|
||||
if [ -f "$target_dir/assets/logo.png" ] && [ ! -f "$target_dir/assets/icon.png" ]; then
|
||||
cp "$target_dir/assets/logo.png" "$target_dir/assets/icon.png"
|
||||
echo "✅ Created assets/icon.png from assets/logo.png"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
function ios-setup-clone() {
|
||||
if [ ! -d $target_dir ]; then
|
||||
echo "Error: social-app repository not found at $target_dir"
|
||||
echo "Please run install.zsh first to clone repositories"
|
||||
return 1
|
||||
fi
|
||||
echo "Repository found: $target_dir"
|
||||
}
|
||||
|
||||
function ios-setup-reset() {
|
||||
echo "Resetting social-app repository..."
|
||||
cd $target_dir
|
||||
git stash
|
||||
git checkout .
|
||||
echo "Reset complete"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
ios-env
|
||||
|
||||
case "$1" in
|
||||
patch)
|
||||
ios-setup-clone
|
||||
ios-patch-apply-all
|
||||
ios-copy-new-files
|
||||
show-failed-patches
|
||||
exit
|
||||
;;
|
||||
reset)
|
||||
ios-setup-reset
|
||||
exit
|
||||
;;
|
||||
*)
|
||||
ios-setup-clone
|
||||
ios-patch-apply-all
|
||||
ios-copy-new-files
|
||||
show-failed-patches
|
||||
;;
|
||||
esac
|
||||