diff --git a/.github/workflows/cf-pages.yml b/.github/workflows/cf-pages.yml new file mode 100644 index 0000000..85694ae --- /dev/null +++ b/.github/workflows/cf-pages.yml @@ -0,0 +1,30 @@ +name: Deploy to Cloudflare Pages + +on: + push: + branches: + - main + paths: + - 'html/**' + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy to Cloudflare Pages + uses: cloudflare/pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} + directory: html + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + wranglerVersion: '3' diff --git a/.gitignore b/.gitignore index 0efae0e..daca568 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ repos .claude deploy.yml claude.md +embedded.mobileprovision +.env +html.zip diff --git a/README.md b/README.md index 6b1798c..e11bdb2 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,24 @@ $ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=a } } ``` + +## build + +```sh +# build +./install.zsh + +# build social-app +./install.zsh pull;./install.zsh patch;./install.zsh build social-app;./install.zsh push social-app +--- +# server +./install.zsh +--- +# social-app ios +# https://appstoreconnect.apple.com/ +# https://developer.apple.com/account/resources/profiles/list +./install.zsh pull;./ios/setup.zsh +./ios/build.zsh +``` + + diff --git a/compose.yml b/compose.yml index ca1a873..26c975e 100644 --- a/compose.yml +++ b/compose.yml @@ -86,6 +86,7 @@ services: depends_on: database: condition: service_healthy + #command: ["/bigsky", "--crawl-insecure-ws"] social-app: ports: diff --git a/envs/feed b/envs/feed index 60f82c3..1a2464e 100644 --- a/envs/feed +++ b/envs/feed @@ -3,5 +3,5 @@ FEEDGEN_LISTENHOST=0.0.0.0 FEEDGEN_SQLITE_LOCATION=/data/db.sqlite FEEDGEN_HOSTNAME=feed.syu.is FEEDGEN_PUBLISHER_DID=did:plc:6qyecktefllvenje24fcxnie -FEEDGEN_SUBSCRIPTION_ENDPOINT=ws://bgs:2470 FEEDGEN_SERVICE_DID=did:web:feed.syu.is +FEEDGEN_JETSTREAM_URL=ws://jetstream:6008/subscribe diff --git a/envs/jetstream b/envs/jetstream index 324bbd7..950a3c9 100644 --- a/envs/jetstream +++ b/envs/jetstream @@ -1,4 +1,4 @@ -JETSTREAM_WS_URL=wss://bgs.${host}/xrpc/com.atproto.sync.subscribeRepos +JETSTREAM_WS_URL=ws://bgs.${host}/xrpc/com.atproto.sync.subscribeRepos JETSTREAM_DATA_DIR=/data JETSTREAM_LISTEN_ADDR=:6008 JETSTREAM_METRICS_LISTEN_ADDR=:6009 diff --git a/html/about/support/app.html b/html/about/support/app.html new file mode 100644 index 0000000..241d905 --- /dev/null +++ b/html/about/support/app.html @@ -0,0 +1,135 @@ + + + + + + App Info - Aiat + + + + +
+ ← Back to syu.is +
+ +
+ Aiat +
Aiat
+
v1.111.0
+
+ +
+

Aiat is a social networking application based on AT Protocol. Connect with your community on syu.is.

+
+ +
+
App Information
+
+
+
Version
+
1.111.0
+
+
+
Category
+
Social
+
+
+
Supported OS
+
iOS 26.0+
+
+
+
Price
+
Free
+
+
+
+ +
+
Developer
+
syui
+ + +
+ +
+
Bitcoin
+
+ + 3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr + copy +
+
+ + + + + + diff --git a/html/about/support/help.html b/html/about/support/help.html new file mode 100644 index 0000000..72da246 --- /dev/null +++ b/html/about/support/help.html @@ -0,0 +1,100 @@ + + + + + + Help - syu.is + + + + +
+ ← Back to syu.is +

Help Center

+
+ +

About syu.is

+

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

+ +

Frequently Asked Questions

+ +
+

What is the AT Protocol?

+

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

+
+ +
+

How do I create an account?

+

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

+
+ +
+

How do I reset my password?

+

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

+
+ +
+

How do I delete my account?

+

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

+
+ +
+

How do I report abuse or inappropriate content?

+

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

+
+ +

Contact

+
+

For additional support or questions:

+ +
+ +

Related Links

+ + + + + diff --git a/html/about/support/license.html b/html/about/support/license.html new file mode 100644 index 0000000..c61217a --- /dev/null +++ b/html/about/support/license.html @@ -0,0 +1,66 @@ + + + + + + License - syu.is + + + + +
+ ← Back to syu.is +

License

+
+ +

Aiat (iOS/Android App)

+

This application is based on the Bluesky Social App, which is open source software.

+ +

Open Source Licenses

+

This app uses the following open source software:

+ +

Bluesky Social App

+

Licensed under the MIT License

+

https://github.com/bluesky-social/social-app

+ +

AT Protocol

+

Licensed under the MIT License / Apache 2.0

+

https://github.com/bluesky-social/atproto

+ +

Third Party Libraries

+

This application includes various third-party libraries, each with their own licenses. For a complete list, please see the application's source code repository.

+ + + + diff --git a/html/about/support/privacy.html b/html/about/support/privacy.html new file mode 100644 index 0000000..9ab6925 --- /dev/null +++ b/html/about/support/privacy.html @@ -0,0 +1,92 @@ + + + + + + Privacy Policy - syu.is + + + + +
+ ← Back to syu.is +

Privacy Policy

+
+ +

1. Introduction

+

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

+ +

2. Information We Collect

+

We collect the following types of information:

+ + +

3. How We Use Your Information

+

We use your information to:

+ + +

4. Data Sharing

+

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

+ +

5. Data Security

+

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

+ +

6. Your Rights

+

You have the right to:

+ + +

7. Cookies

+

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

+ +

8. Changes to This Policy

+

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

+ +

9. Contact

+

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

+ + + + diff --git a/html/about/support/tos.html b/html/about/support/tos.html new file mode 100644 index 0000000..3784cb1 --- /dev/null +++ b/html/about/support/tos.html @@ -0,0 +1,84 @@ + + + + + + Terms of Service - syu.is + + + + +
+ ← Back to syu.is +

Terms of Service

+
+ +

1. Introduction

+

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

+ +

2. Service Description

+

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

+ +

3. User Responsibilities

+

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

+ + +

4. Content Guidelines

+

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

+ + +

5. Privacy

+

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

+ +

6. Disclaimer

+

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

+ +

7. Changes to Terms

+

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

+ +

8. Contact

+

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

+ + + + diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..a9f880a --- /dev/null +++ b/html/index.html @@ -0,0 +1,135 @@ + + + + + + App Info - Aiat + + + + +
+ ← Back to syu.is +
+ +
+ Aiat +
Aiat
+
v1.111.0
+
+ +
+

Aiat is a social networking application based on AT Protocol. Connect with your community on syu.is.

+
+ +
+
App Information
+
+
+
Version
+
1.111.2
+
+
+
Category
+
Social
+
+
+
Supported OS
+
iOS 26.0+
+
+
+
Price
+
Free
+
+
+
+ +
+
Developer
+
syui
+ + +
+ +
+
Bitcoin
+
+ + 3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr + copy +
+
+ + + + + + diff --git a/html/static/app.png b/html/static/app.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/html/static/app.png differ diff --git a/html/static/favicon.png b/html/static/favicon.png new file mode 100644 index 0000000..2227ba3 Binary files /dev/null and b/html/static/favicon.png differ diff --git a/install.zsh b/install.zsh index 58cdca6..d5afe8e 100755 --- a/install.zsh +++ b/install.zsh @@ -1,5 +1,23 @@ #!/bin/zsh +# Sed compatibility wrapper +function sediment() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + + +# Patch compatibility wrapper +function patchment() { + # -f : Force. Do not ask questions. (Standard in GNU and BSD patch) + # -N : Ignore patches that seem to be reversed or already applied (Forward) + # But we control these flags in the caller. + patch "$@" +} + function at-repos-env() { APP_PASSWORD=xxx host=syu.is @@ -34,6 +52,12 @@ function at-repos-env() { name=${host%%.*} domain=${host##*.} dport=5000 + + typeset -A PINNED_COMMITS + PINNED_COMMITS=( + [indigo]="d49b454196351c988ceb5ce1f5e21b689487b5ab" + [atproto]="104e6ed37b0589cc000109dc76316be35b2257e1" + ) } # Arrays for patch management @@ -82,6 +106,10 @@ function at-repos-pull() { echo $repo if [ -d $d/repos/${repo##*/} ];then cd $d/repos/${repo##*/} + # Clean up before pull: reset changes, remove .orig files and untracked patch-created files + git checkout -- . + find . -name "*.orig" -type f -delete 2>/dev/null + git clean -fd 2>/dev/null git stash -u if ! git pull;then rm -rf $d/repos/${repo##*/} @@ -98,32 +126,24 @@ function at-repos-pull() { cd $d } -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" - f=$dt/lib/constants.ts - sed -i "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f - sed -i "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f - sed -i "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f - sed -i "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f - sed -i "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f - - f=$dt/view/icons/Logotype.tsx - o=$d/icons/Logotype.tsx - cp -rf $o $f - 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 - 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" +function at-repos-checkout-pinned() { + echo "๐Ÿ”’ Checking out pinned commits..." + cd $d/repos + for repo_name pinned_commit in ${(kv)PINNED_COMMITS}; do + if [ -n "$pinned_commit" ] && [ -d "$d/repos/$repo_name" ]; then + echo " ๐Ÿ“Œ $repo_name -> $pinned_commit" + cd $d/repos/$repo_name + git fetch origin + git checkout $pinned_commit + cd $d/repos + fi + done + cd $d } +function at-repos-social-app-ios-patch() { + $d/ios/setup.zsh +} # Common patch function with status detection function apply-patch() { @@ -139,7 +159,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 +168,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 +305,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 } @@ -317,6 +338,9 @@ function at-repos-build-docker-atproto() { for ((i=1; i<=${#services}; i++)); do service=${services[$i]} docker compose build --no-cache $service + if [ "$service" = "ozone" ]; then + docker compose build --no-cache ${service}-web + fi done else docker compose build --no-cache $1 @@ -338,12 +362,11 @@ function at-repos-push-reset() { } function at-repos-push-docker() { - if [ -z "$1" ];then - for ((i=1; i<=${#services}; i++)); do - service=${services[$i]} + if [ -z "$1" ] || [ "$1" = "push" ]; then + for service in "${services[@]}"; do docker tag at-${service}:latest localhost:${dport}/${service}:latest docker push localhost:${dport}/${service}:latest - if [ "$service" == "ozone" ];then + if [ "$service" = "ozone" ]; then docker tag at-${service}-web:latest localhost:${dport}/${service}-web:latest docker push localhost:${dport}/${service}-web:latest fi @@ -388,28 +411,55 @@ function at-repos-reset-bgs-db() { echo "โš™๏ธ Updating Slurp Config..." docker exec -i $dp psql -U postgres -d bgs -c "UPDATE slurp_configs SET new_subs_disabled = false, new_pds_per_day_limit = 1000 WHERE id = 1;" - echo "๐Ÿ”— Registering Trusted Domain & Resetting Repos..." + # host=pds:3000 + echo "๐Ÿ”— Registering Trusted Domain..." # Retry loop for addTrustedDomain as BGS might still be warming up for i in {1..5}; do if curl -f -X POST "https://bgs.${host}/admin/pds/addTrustedDomain?domain=${host}" -H "Authorization: Bearer ${BGS_ADMIN_KEY}"; then + echo "" echo "โœ… Trusted domain registered" break fi - echo "Bot failed to contact BGS (attempt $i/5)... waiting 5s" + echo "Failed to contact BGS (attempt $i/5)... waiting 5s" sleep 5 done + echo "๐Ÿ”— Requesting PDS Crawl..." + # Request BGS to crawl the PDS - this registers the PDS and starts subscription + for i in {1..5}; do + result=$(curl -s -X POST "https://bgs.${host}/admin/pds/requestCrawl" \ + -H "Authorization: Bearer ${BGS_ADMIN_KEY}" \ + -H "Content-Type: application/json" \ + -d "{\"hostname\":\"{$host}\"}" \ + -w "%{http_code}" -o /dev/null) + if [ "$result" = "200" ]; then + echo "โœ… PDS crawl requested successfully" + break + fi + echo "Failed to request crawl (attempt $i/5, status: $result)... waiting 5s" + sleep 5 + done + + echo "โณ Waiting 5s for BGS to connect to PDS..." + sleep 5 + + echo "๐Ÿ”„ Triggering repo sync for existing users..." for ((i=1; i<=${#handles}; i++)); do handle=${handles[$i]} - did=`curl -sL "https://${host}/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did` - if [ ! -z "$did" ] && [ "$did" != "null" ]; then - echo "Resetting repo: $handle ($did)" - curl -X POST "https://bgs.${host}/admin/repo/reset?did=${did}" \ - -H "Authorization: Bearer ${BGS_ADMIN_KEY}" + did=$(curl -sL "https://${host}/xrpc/com.atproto.repo.describeRepo?repo=${handle}" | jq -r .did) + if [ -n "$did" ] && [ "$did" != "null" ]; then + echo " Syncing repo: $handle ($did)" + # Use takedown=false to trigger a resync without actually taking down + curl -s -X POST "https://bgs.${host}/admin/repo/takedown?did=${did}&takedown=false" \ + -H "Authorization: Bearer ${BGS_ADMIN_KEY}" || true else - echo "Skipping reset for $handle (DID not found)" + echo " Skipping $handle (DID not found)" fi done + + echo "" + echo "โœ… BGS reset complete!" + echo " PDS should now be subscribed and syncing repos." } function at-repos-feed-generator-start-push() { @@ -480,7 +530,7 @@ case "$1" in exit ;; patch) - at-repos-social-app-avatar-write + at-repos-social-app-ios-patch at-repos-patch-apply-all at-repos-ozone-patch show-failed-patches @@ -520,7 +570,8 @@ case "`cat /etc/hostname`" in *) at-repos-clone at-repos-pull - at-repos-social-app-avatar-write + at-repos-checkout-pinned + at-repos-social-app-ios-patch at-repos-patch-apply-all at-repos-ozone-patch show-failed-patches diff --git a/ios/.env.example b/ios/.env.example new file mode 100644 index 0000000..c0a09ce --- /dev/null +++ b/ios/.env.example @@ -0,0 +1,17 @@ +APP_NAME="Aiat" +REPO_DIR="../repos/social-app" +APP_SLUG="aiat" +APP_SCHEME="syui" +APP_GROUP="group.ai.syui.at" +APP_MAIL=user@example.com +APP_KEYCHAIN=@keychain:KEYCHAIN_NAME +BUNDLE_ID="ai.syui.at" +SERVICE_URL="https://syu.is" +HELP_URL="https://syu.is/about/support/help" +PRIVACY_URL="https://syu.is/about/support/privacy-policy" +TERMS_URL="https://syu.is/about/support/tos" +REPO_DIR="../repos/social-app" +CONFIG_FILE="$REPO_DIR/app.config.js" +CONSTANTS_FILE="$REPO_DIR/src/lib/constants.ts" +IOS_CERTIFICATE_NAME="Apple Distribution: $TEAM($TEAM_ID)" +PDS_HOST=syu.is diff --git a/ios/.keep b/ios/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/ios/AppInfo.tsx b/ios/AppInfo.tsx deleted file mode 100644 index 6242b12..0000000 --- a/ios/AppInfo.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -interface AppInfoProps { - onLinkPress?: (url: string) => void -} - -export default function AppInfo({onLinkPress}: AppInfoProps) { - const handleLinkPress = (url: string) => { - if (onLinkPress) { - onLinkPress(url) - } else { - Linking.openURL(url) - } - } - - return ( - - - About This App - - This is a customized AT Protocol social networking client. It allows you to - connect to any Personal Data Server (PDS) and participate in the decentralized - social network. - - - - - Key Features - - โ€ข Connect to any AT Protocol PDS - โ€ข Post text, images, and videos - โ€ข Follow users and view timelines - โ€ข Customize feeds and moderation settings - โ€ข Direct messaging support - - - - - Open Source - - This application is based on the Bluesky social-app, licensed under the MIT - License. The original source code is available at: - - - handleLinkPress('https://github.com/bluesky-social/social-app') - }> - github.com/bluesky-social/social-app - - - - - AT Protocol - - This app uses the AT Protocol (Authenticated Transfer Protocol), an open and - decentralized standard for social applications. - - handleLinkPress('https://atproto.com')}> - atproto.com - - - - - License - - Copyright 2023โ€“2025 Bluesky Social PBC - - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software. - - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. - - - - - Contact - handleLinkPress('https://syu.is')}> - https://syu.is - - - - - Version 1.0.0 - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - color: '#1d1d1f', - marginBottom: 12, - }, - paragraph: { - fontSize: 15, - lineHeight: 22, - color: '#3a3a3c', - marginBottom: 8, - }, - list: { - marginLeft: 8, - marginTop: 8, - }, - listItem: { - fontSize: 15, - lineHeight: 24, - color: '#3a3a3c', - }, - link: { - fontSize: 15, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, - versionText: { - fontSize: 13, - color: '#8e8e93', - fontStyle: 'italic', - }, -}) diff --git a/ios/LicenseNotice.tsx b/ios/LicenseNotice.tsx deleted file mode 100644 index 496d367..0000000 --- a/ios/LicenseNotice.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -export default function LicenseNotice() { - return ( - - Open Source Licenses - - - Bluesky Social App - MIT License - Copyright 2023โ€“2025 Bluesky Social PBC - - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - - - Linking.openURL('https://github.com/bluesky-social/social-app') - }> - View Source Code - - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 16, - }, - title: { - fontSize: 24, - fontWeight: 'bold', - marginBottom: 20, - color: '#1d1d1f', - }, - section: { - marginBottom: 24, - padding: 16, - backgroundColor: '#f5f5f7', - borderRadius: 8, - }, - projectName: { - fontSize: 18, - fontWeight: '600', - marginBottom: 8, - color: '#1d1d1f', - }, - license: { - fontSize: 14, - fontWeight: '500', - color: '#007aff', - marginBottom: 4, - }, - copyright: { - fontSize: 13, - color: '#3a3a3c', - marginBottom: 12, - }, - licenseText: { - fontSize: 12, - lineHeight: 18, - color: '#3a3a3c', - marginBottom: 12, - }, - link: { - fontSize: 14, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, -}) diff --git a/ios/PrivacyContent.tsx b/ios/PrivacyContent.tsx deleted file mode 100644 index d780104..0000000 --- a/ios/PrivacyContent.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React from 'react' -import {View, Text, StyleSheet, Pressable, Linking} from 'react-native' - -interface PrivacyContentProps { - onLinkPress?: (url: string) => void -} - -export default function PrivacyContent({onLinkPress}: PrivacyContentProps) { - const handleLinkPress = (url: string) => { - if (onLinkPress) { - onLinkPress(url) - } else { - Linking.openURL(url) - } - } - - return ( - - - Introduction - - This Privacy Policy explains how this AT Protocol client application - (hereinafter referred to as "the App") handles personal information. - Please read this policy carefully before using the App. - - - - - Information We Collect - - The App may collect and use the following information: - - - 1. Information Collected Automatically - - โ€ข Device information (model, OS version) - โ€ข App usage data (sessions, features used) - โ€ข Crash logs and performance data - - - 2. Information Provided by Users - - - โ€ข DID (Decentralized Identifier) and handle for authentication - - โ€ข Posts, media, and social interactions - โ€ข Profile information (avatar, display name, bio) - - - - - Important: Your data is stored on your chosen PDS (Personal Data Server). - This app does not store your content on our servers. - - - - - - How We Use Your Information - - - โ€ข To provide AT Protocol social networking features - - โ€ข To improve app performance and user experience - โ€ข To diagnose and fix technical issues - - - - - Data Sharing - - The App interacts with your chosen PDS and AppView services. Your posts and - profile information are shared according to the AT Protocol specification and - your privacy settings. - - - - - Your Rights - - You have the right to access, modify, or delete your data through your PDS. - You can also switch to a different PDS at any time while maintaining your - identity. - - - - - Contact - - For questions about this Privacy Policy, please contact: - - handleLinkPress('https://syu.is')}> - https://syu.is - - - - - Last Updated: December 3, 2025 - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - color: '#1d1d1f', - marginBottom: 12, - }, - subTitle: { - fontSize: 16, - fontWeight: '500', - color: '#1d1d1f', - marginTop: 12, - marginBottom: 8, - }, - paragraph: { - fontSize: 15, - lineHeight: 22, - color: '#3a3a3c', - marginBottom: 8, - }, - list: { - marginLeft: 8, - marginTop: 8, - }, - listItem: { - fontSize: 15, - lineHeight: 24, - color: '#3a3a3c', - }, - highlight: { - backgroundColor: '#fff3cd', - borderLeftWidth: 4, - borderLeftColor: '#ffc107', - padding: 12, - marginTop: 12, - borderRadius: 4, - }, - highlightText: { - fontSize: 14, - lineHeight: 20, - color: '#856404', - }, - link: { - fontSize: 15, - color: '#007aff', - textDecorationLine: 'underline', - marginTop: 8, - }, - lastUpdated: { - fontSize: 13, - color: '#8e8e93', - fontStyle: 'italic', - }, -}) diff --git a/ios/PrivacyPolicy.screen.tsx b/ios/PrivacyPolicy.screen.tsx deleted file mode 100644 index df5d5c8..0000000 --- a/ios/PrivacyPolicy.screen.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {usePalette} from '#/lib/hooks/usePalette' -import { - type CommonNavigatorParams, - type NativeStackScreenProps, -} from '#/lib/routes/types' -import {s} from '#/lib/styles' -import {useSetMinimalShellMode} from '#/state/shell' -import {ScrollView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' -import {ViewHeader} from '../com/util/ViewHeader' -import PrivacyContent from '#/components/custom/PrivacyContent' - -type Props = NativeStackScreenProps -export const PrivacyPolicyScreen = (_props: Props) => { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - - - - ) -} diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 0000000..c08d5da --- /dev/null +++ b/ios/README.md @@ -0,0 +1,9 @@ +ไปŠๅ›žใฎ./ios (social-app)้–‹็™บใฎ่ฆ็‚นใ‚’ใพใจใ‚ใพใ™ใ€‚ + +1. MITใฎใƒฉใ‚คใ‚ปใƒณใ‚นใ‚’้ตๅฎˆใ™ใ‚‹ใ“ใจใ€iosใ‚ขใƒ—ใƒชใจใ—ใฆๅ‡บๅ“ใ—ใฆใ‚‚ๅ•้กŒใชใ„ใ‚ˆใ†ใซใ™ใ‚‹ใ“ใจ +https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/LICENSE + +2. "Bluesky"ใจใ„ใ†ๅ็งฐใ‚’ไฝฟ็”จใ—ใชใ„ใ“ใจใ€‚ใ‚ขใ‚คใ‚ณใƒณใฎๅค‰ๆ›ดใ€‚ใƒชใƒณใ‚ฏใฎๅค‰ๆ›ด + +3. selfhostใงใ‚‚ๅ‹•ใใ“ใจใ€‚ใ“ใ‚Œใฏใ™ใงใซpatchใงๅฎŸ็พใ—ใฆใ„ใพใ™ใ€‚ + diff --git a/ios/Support.screen.tsx b/ios/Support.screen.tsx deleted file mode 100644 index 3fddf92..0000000 --- a/ios/Support.screen.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {usePalette} from '#/lib/hooks/usePalette' -import { - type CommonNavigatorParams, - type NativeStackScreenProps, -} from '#/lib/routes/types' -import {s} from '#/lib/styles' -import {useSetMinimalShellMode} from '#/state/shell' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {ScrollView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' -import AppInfo from '#/components/custom/AppInfo' - -type Props = NativeStackScreenProps -export const SupportScreen = (_props: Props) => { - const pal = usePalette('default') - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - ) -} diff --git a/ios/app.config.patch.js b/ios/app.config.patch.js deleted file mode 100644 index 136a4d9..0000000 --- a/ios/app.config.patch.js +++ /dev/null @@ -1,9 +0,0 @@ -// Aiat app configuration overrides -module.exports = { - name: 'Aiat', - slug: 'aiat', - scheme: 'aiat', - owner: 'syui', // Your Expo account - bundleIdentifier: 'ai.syui.at', - // Icon will be set separately -} diff --git a/ios/assets/app-icons/android_icon_core_aurora.png b/ios/assets/app-icons/android_icon_core_aurora.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_aurora.png differ diff --git a/ios/assets/app-icons/android_icon_core_bonfire.png b/ios/assets/app-icons/android_icon_core_bonfire.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_bonfire.png differ diff --git a/ios/assets/app-icons/android_icon_core_classic.png b/ios/assets/app-icons/android_icon_core_classic.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_classic.png differ diff --git a/ios/assets/app-icons/android_icon_core_flat_black.png b/ios/assets/app-icons/android_icon_core_flat_black.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_black.png differ diff --git a/ios/assets/app-icons/android_icon_core_flat_blue.png b/ios/assets/app-icons/android_icon_core_flat_blue.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_blue.png differ diff --git a/ios/assets/app-icons/android_icon_core_flat_white.png b/ios/assets/app-icons/android_icon_core_flat_white.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_flat_white.png differ diff --git a/ios/assets/app-icons/android_icon_core_midnight.png b/ios/assets/app-icons/android_icon_core_midnight.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_midnight.png differ diff --git a/ios/assets/app-icons/android_icon_core_sunrise.png b/ios/assets/app-icons/android_icon_core_sunrise.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_sunrise.png differ diff --git a/ios/assets/app-icons/android_icon_core_sunset.png b/ios/assets/app-icons/android_icon_core_sunset.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_core_sunset.png differ diff --git a/ios/assets/app-icons/android_icon_default_next.png b/ios/assets/app-icons/android_icon_default_next.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_default_next.png differ diff --git a/ios/assets/app-icons/android_icon_legacy_dark.png b/ios/assets/app-icons/android_icon_legacy_dark.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_legacy_dark.png differ diff --git a/ios/assets/app-icons/android_icon_legacy_light.png b/ios/assets/app-icons/android_icon_legacy_light.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/android_icon_legacy_light.png differ diff --git a/ios/assets/app-icons/ios_icon_core_aurora.png b/ios/assets/app-icons/ios_icon_core_aurora.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_aurora.png differ diff --git a/ios/assets/app-icons/ios_icon_core_bonfire.png b/ios/assets/app-icons/ios_icon_core_bonfire.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_bonfire.png differ diff --git a/ios/assets/app-icons/ios_icon_core_classic.png b/ios/assets/app-icons/ios_icon_core_classic.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_classic.png differ diff --git a/ios/assets/app-icons/ios_icon_core_flat_black.png b/ios/assets/app-icons/ios_icon_core_flat_black.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_black.png differ diff --git a/ios/assets/app-icons/ios_icon_core_flat_blue.png b/ios/assets/app-icons/ios_icon_core_flat_blue.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_blue.png differ diff --git a/ios/assets/app-icons/ios_icon_core_flat_white.png b/ios/assets/app-icons/ios_icon_core_flat_white.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_flat_white.png differ diff --git a/ios/assets/app-icons/ios_icon_core_midnight.png b/ios/assets/app-icons/ios_icon_core_midnight.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_midnight.png differ diff --git a/ios/assets/app-icons/ios_icon_core_sunrise.png b/ios/assets/app-icons/ios_icon_core_sunrise.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_sunrise.png differ diff --git a/ios/assets/app-icons/ios_icon_core_sunset.png b/ios/assets/app-icons/ios_icon_core_sunset.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_core_sunset.png differ diff --git a/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png b/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default.icon/Assets/iOS transparent.png differ diff --git a/ios/assets/app-icons/ios_icon_default.icon/icon.json b/ios/assets/app-icons/ios_icon_default.icon/icon.json new file mode 100644 index 0000000..8a681cb --- /dev/null +++ b/ios/assets/app-icons/ios_icon_default.icon/icon.json @@ -0,0 +1,31 @@ +{ + "fill" : { + "automatic-gradient" : "srgb:0.00000,0.41569,1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "fill" : "none", + "glass" : false, + "image-name" : "iOS transparent.png", + "name" : "iOS transparent" + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/ios/assets/app-icons/ios_icon_default.icon/icon.png b/ios/assets/app-icons/ios_icon_default.icon/icon.png new file mode 100644 index 0000000..4c67271 Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default.icon/icon.png differ diff --git a/ios/assets/app-icons/ios_icon_default_next.png b/ios/assets/app-icons/ios_icon_default_next.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_default_next.png differ diff --git a/ios/assets/app-icons/ios_icon_legacy_dark.png b/ios/assets/app-icons/ios_icon_legacy_dark.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_legacy_dark.png differ diff --git a/ios/assets/app-icons/ios_icon_legacy_light.png b/ios/assets/app-icons/ios_icon_legacy_light.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/app-icons/ios_icon_legacy_light.png differ diff --git a/ios/assets/favicons/apple-touch-icon.png b/ios/assets/favicons/apple-touch-icon.png new file mode 100644 index 0000000..143348f Binary files /dev/null and b/ios/assets/favicons/apple-touch-icon.png differ diff --git a/ios/assets/favicons/favicon-16x16.png b/ios/assets/favicons/favicon-16x16.png new file mode 100644 index 0000000..07aa7a8 Binary files /dev/null and b/ios/assets/favicons/favicon-16x16.png differ diff --git a/ios/assets/favicons/favicon-32x32.png b/ios/assets/favicons/favicon-32x32.png new file mode 100644 index 0000000..2227ba3 Binary files /dev/null and b/ios/assets/favicons/favicon-32x32.png differ diff --git a/ios/assets/favicons/favicon.png b/ios/assets/favicons/favicon.png new file mode 100644 index 0000000..2227ba3 Binary files /dev/null and b/ios/assets/favicons/favicon.png differ diff --git a/ios/assets/logo.png b/ios/assets/logo.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/assets/logo.png differ diff --git a/ios/bin/build.zsh b/ios/bin/build.zsh deleted file mode 100755 index d1fe8ea..0000000 --- a/ios/bin/build.zsh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/zsh -set -e - -d=~/ai/at/repos/social-app -APP_NAME=Aiat -PKG=aiat -TEAM_NAME= -TEAM_ID= -CERT="Apple Distribution: ${TEAM_NAME} (${TEAM_ID})" -MAIL=user@example.com -KEY_CHAIN=EXAMPLE - -cd $d -# npx expo prebuild --clean -# cd ios && pod install && cd .. - -## ใ‚ขใƒผใ‚ซใ‚คใƒ– -xcodebuild -workspace ios/${PKG}.xcworkspace \ - -scheme ${PKG} \ - -configuration Release \ - -archivePath build/${APP_NAME}.xcarchive \ - -allowProvisioningUpdates \ - archive - -cd build - -# IPAไฝœๆˆ -rm -rf Payload ${APP_NAME}.ipa -mkdir -p Payload -cp -R ${APP_NAME}.xcarchive/Products/Applications/${PKG}.app Payload/ -cp ../store.mobileprovision Payload/${PKG}.app/embedded.mobileprovision - -# entitlementsๆŠฝๅ‡บ -security cms -D -i Payload/${PKG}.app/embedded.mobileprovision > /tmp/profile.plist -/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist - -codesign -f -s "$CERT" Payload/${PKG}.app/Frameworks/*.framework 2>/dev/null || true -codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${PKG}.app - -zip -r ${APP_NAME}.ipa Payload - -xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${MAIL}" -p "@keychain:${KEY_CHAIN}" - -echo "Upload complete" diff --git a/ios/bin/install.zsh b/ios/bin/install.zsh deleted file mode 100644 index 2e372c7..0000000 --- a/ios/bin/install.zsh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/zsh - -if [ "$1" = "social-app-custom" ];then - at-social-app-custom-pages - at-social-app-custom-screens - at-social-app-aiat-config - at-social-app-aiat-logo - at-origin-social-app - exit -fi - -function at-social-app-custom-pages() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "copying custom components to social-app" - - # Create components directory if not exists - mkdir -p ${d_}/src/components/custom - - # Copy custom components - cp ${custom}/PrivacyContent.tsx ${d_}/src/components/custom/ - cp ${custom}/AppInfo.tsx ${d_}/src/components/custom/ - - echo "custom components copied successfully" -} - -function at-social-app-aiat-config() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying Aiat configuration" - - # Update app.config.js - cd ${d_} - - # Backup original - cp app.config.js app.config.js.orig - - # Apply changes using sed - sed -i "s/name: 'Bluesky'/name: 'Aiat'/g" app.config.js - sed -i "s/slug: 'bluesky'/slug: 'aiat'/g" app.config.js - sed -i "s/scheme: 'bluesky'/scheme: 'aiat'/g" app.config.js - sed -i "s/owner: 'blueskysocial'/owner: 'syui'/g" app.config.js - sed -i "s/bundleIdentifier: 'xyz.blueskyweb.app'/bundleIdentifier: 'ai.syui.at'/g" app.config.js - - # Update package.json name - sed -i 's/"name": "bsky.app"/"name": "aiat"/g' package.json - - echo "Aiat configuration applied" -} - -function at-social-app-aiat-logo() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying Aiat logo" - - # Create logo directory if not exists - mkdir -p ${custom}/assets - - # Copy logo if exists in custom folder - if [ -f ${custom}/assets/icon.png ]; then - cp ${custom}/assets/icon.png ${d_}/assets/app-icons/ios_icon_default_next.png - echo "Aiat logo applied" - else - echo "Warning: Logo file not found at ${custom}/assets/icon.png" - echo "Please add your logo file there" - fi -} - -function at-social-app-custom-screens() { - d_=$d/repos/social-app - custom=$d/social-app-custom - - echo "applying custom screens" - - # Copy custom screen files - cp ${custom}/PrivacyPolicy.screen.tsx ${d_}/src/view/screens/PrivacyPolicy.tsx - cp ${custom}/Support.screen.tsx ${d_}/src/view/screens/Support.tsx - cp ${custom}/LicenseNotice.tsx ${d_}/src/components/custom/ - - echo "custom screens applied" -} - - diff --git a/ios/build.zsh b/ios/build.zsh new file mode 100755 index 0000000..a379cff --- /dev/null +++ b/ios/build.zsh @@ -0,0 +1,202 @@ +#!/bin/zsh +set -e + +SCRIPT_DIR=${0:a:h} +cd "$SCRIPT_DIR" +source .env + +function sediment() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +# ็ตถๅฏพใƒ‘ใ‚นใซๅค‰ๆ› +REPO_DIR="$SCRIPT_DIR/../repos/social-app" +APP_NAME="Aiat" +WORKSPACE="$REPO_DIR/ios/${APP_NAME}.xcworkspace" +SCHEME="$APP_NAME" +BUILD_DIR="$REPO_DIR/build" +MOBILEPROVISION="$REPO_DIR/embedded.mobileprovision" +ASSETS_DIR="$SCRIPT_DIR/assets" + +echo "Running iOS preview workflow..." +cd "$REPO_DIR" + +# 0. Environment Setup (Fix Node Version) +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +echo "Checking Node version..." +if command -v nvm >/dev/null; then + nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)" +else + echo "nvm not found, using system node: $(node -v)" +fi + +# 1. Install dependencies +echo "1. Installing dependencies (yarn)..." +yarn install + +# 1.5. Copy assets +echo "1.5. Copying assets..." +if [ -d "$ASSETS_DIR" ]; then + cp -rf "$ASSETS_DIR/"* "$REPO_DIR/assets/" + echo "โœ… Copied all assets (including logo.png, logo-1024.png)" +else + echo "โš ๏ธ Warning: $ASSETS_DIR not found" +fi + +function cleanup_build { + # 1.8. Update package.json version (prevent App Store version conflict) + echo "1.8. Updating package.json version..." + if [ -n "$APP_VERSION" ]; then + # Use node to update version in package.json (already in REPO_DIR) + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + pkg.version = '$APP_VERSION'; + fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + echo " โœ… Set version to $APP_VERSION" + else + echo " โš ๏ธ APP_VERSION not set in .env" + fi + + # 1.9. Update buildNumber (CFBundleVersion) with current timestamp + echo "1.9. Updating buildNumber..." + local build_number=$(date +%y%m%d%H%M%S) + sediment "s/buildNumber: '[0-9]*'/buildNumber: '${build_number}'/" "./app.config.js" + echo " โœ… Set buildNumber to $build_number" + + # 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 (Automated) + echo "4. Configuring Xcode Signing..." + XCODE_PROJ="ios/${APP_NAME}.xcodeproj" + if [ ! -d "$XCODE_PROJ" ]; then + XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1) + fi + PBXPROJ="$XCODE_PROJ/project.pbxproj" + + # Set DEVELOPMENT_TEAM in pbxproj + if [ -n "$DEVELOPMENT_TEAM" ]; then + echo " Setting DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM" + sediment "s/PRODUCT_BUNDLE_IDENTIFIER = /DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM; PRODUCT_BUNDLE_IDENTIFIER = /g" "$PBXPROJ" + sediment "s/DEVELOPMENT_TEAM = \"\";/DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM;/g" "$PBXPROJ" + sediment "s/DEVELOPMENT_TEAM = ;/DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM;/g" "$PBXPROJ" + fi + + # Create/Update entitlements file with App Group + ENTITLEMENTS_FILE="ios/${APP_NAME}/${APP_NAME}.entitlements" + if [ -n "$APP_GROUP" ]; then + echo " Setting APP_GROUP=$APP_GROUP" + cat > "$ENTITLEMENTS_FILE" << EOF + + + + + aps-environment + production + com.apple.security.application-groups + + ${APP_GROUP} + + + +EOF + if ! grep -q "CODE_SIGN_ENTITLEMENTS" "$PBXPROJ"; then + sediment "s/DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM;/DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM; CODE_SIGN_ENTITLEMENTS = ${APP_NAME}\\/${APP_NAME}.entitlements;/g" "$PBXPROJ" + fi + fi + + echo "โœ… Signing configured automatically" + + # (Old manual step - commented out) + # open "$XCODE_PROJ" + # read +} + +case $1 in + i) + cleanup_build + ;; +esac + +echo "Building $APP_NAME for App Store upload..." + +# ใƒ“ใƒซใƒ‰ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชไฝœๆˆ +mkdir -p "$BUILD_DIR" + +# ใ‚ขใƒผใ‚ซใ‚คใƒ–๏ผˆ่ฉณ็ดฐใƒญใ‚ฐๅ‡บๅŠ›๏ผ‰ +xcodebuild -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -configuration Release \ + -archivePath "$BUILD_DIR/${APP_NAME}.xcarchive" \ + -allowProvisioningUpdates \ + DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM" \ + archive 2>&1 | tee "$BUILD_DIR/build.log" + +# ใ‚ขใƒผใ‚ซใ‚คใƒ–ๆˆๅŠŸ็ขบ่ช +if [ ! -d "$BUILD_DIR/${APP_NAME}.xcarchive" ]; then + echo "Error: Archive failed. Check $BUILD_DIR/build.log for details" + exit 1 +fi + +cd "$BUILD_DIR" + + +# IPAไฝœๆˆ +rm -rf Payload ${APP_NAME}.ipa +mkdir -p Payload +cp -R ${APP_NAME}.xcarchive/Products/Applications/${APP_NAME}.app Payload/ + +# store.mobileprovisionใฎๅญ˜ๅœจ็ขบ่ชใจใ‚ณใƒ”ใƒผ +# https://developer.apple.com/account/resources/profiles/list +if [ ! -f "$MOBILEPROVISION" ]; then + echo "Error: store.mobileprovision not found at $MOBILEPROVISION" + exit 1 +fi + +cp "$MOBILEPROVISION" Payload/${APP_NAME}.app/embedded.mobileprovision + +# entitlementsๆŠฝๅ‡บ +security cms -D -i Payload/${APP_NAME}.app/embedded.mobileprovision > /tmp/profile.plist +/usr/libexec/PlistBuddy -x -c "Print :Entitlements" /tmp/profile.plist > /tmp/entitlements.plist + +# ็ฝฒๅ +CERT="$IOS_CERTIFICATE_NAME" + +# Frameworksใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใŒๅญ˜ๅœจใ™ใ‚‹ๅ ดๅˆใฎใฟ็ฝฒๅ +if [ -d "Payload/${APP_NAME}.app/Frameworks" ]; then + for framework in Payload/${APP_NAME}.app/Frameworks/*.framework; do + if [ -e "$framework" ]; then + echo "Signing $framework" + codesign -f -s "$CERT" "$framework" + fi + done +fi + +# ใ‚ขใƒ—ใƒชๆœฌไฝ“ใซ็ฝฒๅ +codesign -f -s "$CERT" --entitlements /tmp/entitlements.plist Payload/${APP_NAME}.app + +# IPAไฝœๆˆ +zip -r ${APP_NAME}.ipa Payload + +# ใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰ +xcrun altool --upload-app -f ${APP_NAME}.ipa -t ios -u "${APP_MAIL}" -p "${APP_KEYCHAIN}" + +echo "Upload complete: ${APP_NAME}.ipa" diff --git a/ios/icon.png b/ios/icon.png new file mode 100644 index 0000000..f8f45fb Binary files /dev/null and b/ios/icon.png differ diff --git a/ios/patching/001-social-app-ios-config.patch b/ios/patching/001-social-app-ios-config.patch new file mode 100644 index 0000000..b132480 --- /dev/null +++ b/ios/patching/001-social-app-ios-config.patch @@ -0,0 +1,166 @@ +diff --git a/app.config.js b/app.config.js +index 246d8abd3..ed8f7b2b2 100644 +--- a/app.config.js ++++ b/app.config.js +@@ -18,10 +18,7 @@ module.exports = function (_config) { + const IS_DEV = !IS_TESTFLIGHT || !IS_PRODUCTION + + const ASSOCIATED_DOMAINS = [ +- 'applinks:bsky.app', +- 'applinks:staging.bsky.app', +- 'appclips:bsky.app', +- 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes ++ 'applinks:syu.is', + // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port. + ...(IS_DEV || IS_TESTFLIGHT ? [] : []), + ] +@@ -33,27 +30,25 @@ module.exports = function (_config) { + return { + expo: { + version: VERSION, +- name: 'Bluesky', +- slug: 'bluesky', +- scheme: 'bluesky', ++ name: 'Aiat', ++ slug: 'aiat', ++ scheme: 'syui', + owner: 'blueskysocial', + runtimeVersion: { + policy: 'appVersion', + }, +- icon: './assets/app-icons/ios_icon_default_next.png', ++ icon: './assets/logo.png', + userInterfaceStyle: 'automatic', + primaryColor: '#1083fe', + newArchEnabled: false, + ios: { + supportsTablet: false, +- bundleIdentifier: 'xyz.blueskyweb.app', ++ bundleIdentifier: 'ai.syui.at', ++ buildNumber: '__BUILD_NUMBER__', + config: { + usesNonExemptEncryption: false, + }, +- icon: +- PLATFORM === 'web' // web build doesn't like .icon files +- ? './assets/app-icons/ios_icon_default_next.png' +- : './assets/app-icons/ios_icon_default.icon', ++ icon: './assets/logo.png', + infoPlist: { + UIBackgroundModes: ['remote-notification'], + NSCameraUsageDescription: +@@ -113,7 +107,7 @@ module.exports = function (_config) { + entitlements: { + 'com.apple.developer.kernel.increased-memory-limit': true, + 'com.apple.developer.kernel.extended-virtual-addressing': true, +- 'com.apple.security.application-groups': 'group.app.bsky', ++ 'com.apple.security.application-groups': 'group.ai.syui.at', + }, + privacyManifests: { + NSPrivacyCollectedDataTypes: [ +@@ -175,14 +169,14 @@ module.exports = function (_config) { + barStyle: 'light-content', + }, + android: { +- icon: './assets/app-icons/android_icon_default_next.png', ++ icon: './assets/logo.png', + adaptiveIcon: { + foregroundImage: './assets/icon-android-foreground.png', + monochromeImage: './assets/icon-android-monochrome.png', + backgroundColor: '#006AFF', + }, + googleServicesFile: './google-services.json', +- package: 'xyz.blueskyweb.app', ++ package: 'ai.syui.at', + intentFilters: [ + { + action: 'VIEW', +@@ -190,7 +184,7 @@ module.exports = function (_config) { + data: [ + { + scheme: 'https', +- host: 'bsky.app', ++ host: 'syu.is', + }, + IS_DEV && { + scheme: 'http', +@@ -213,9 +207,9 @@ module.exports = function (_config) { + : undefined, + codeSigningMetadata: UPDATES_ENABLED + ? { +- keyid: 'main', +- alg: 'rsa-v1_5-sha256', +- } ++ keyid: 'main', ++ alg: 'rsa-v1_5-sha256', ++ } + : undefined, + checkAutomatically: 'NEVER', + }, +@@ -225,7 +219,7 @@ module.exports = function (_config) { + 'expo-web-browser', + [ + 'react-native-edge-to-edge', +- {android: {enforceNavigationBarContrast: false}}, ++ { android: { enforceNavigationBarContrast: false } }, + ], + USE_SENTRY && [ + '@sentry/react-native/expo', +@@ -264,7 +258,6 @@ module.exports = function (_config) { + networkInstrumentation: true, + }, + ], +- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js', + './plugins/withGradleJVMHeapSizeIncrease.js', + './plugins/withAndroidManifestLargeHeapPlugin.js', + './plugins/withAndroidManifestFCMIconPlugin.js', +@@ -272,8 +265,6 @@ module.exports = function (_config) { + './plugins/withAndroidStylesAccentColorPlugin.js', + './plugins/withAndroidDayNightThemePlugin.js', + './plugins/withAndroidNoJitpackPlugin.js', +- './plugins/shareExtension/withShareExtensions.js', +- './plugins/notificationsExtension/withNotificationsExtension.js', + [ + 'expo-font', + { +@@ -386,7 +377,7 @@ module.exports = function (_config) { + }, + }, + ], +- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}], ++ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }], + ['expo-location'], + ].filter(Boolean), + extra: { +@@ -394,30 +385,7 @@ module.exports = function (_config) { + build: { + experimental: { + ios: { +- appExtensions: [ +- { +- targetName: 'Share-with-Bluesky', +- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky', +- entitlements: { +- 'com.apple.security.application-groups': [ +- 'group.app.bsky', +- ], +- }, +- }, +- { +- targetName: 'BlueskyNSE', +- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE', +- entitlements: { +- 'com.apple.security.application-groups': [ +- 'group.app.bsky', +- ], +- }, +- }, +- { +- targetName: 'BlueskyClip', +- bundleIdentifier: 'xyz.blueskyweb.app.AppClip', +- }, +- ], ++ appExtensions: [], + }, + }, + }, diff --git a/ios/patching/002-social-app-ios-lib.patch b/ios/patching/002-social-app-ios-lib.patch new file mode 100644 index 0000000..ee04f7b --- /dev/null +++ b/ios/patching/002-social-app-ios-lib.patch @@ -0,0 +1,217 @@ +diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts +index 7a0d72d91..93554dc3e 100644 +--- a/src/lib/api/feed/home.ts ++++ b/src/lib/api/feed/home.ts +@@ -45,7 +45,7 @@ export class HomeFeedAPI implements FeedAPI { + this.following = new FollowingFeedAPI({agent}) + this.discover = new CustomFeedAPI({ + agent, +- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, ++ feedParams: {feed: PROD_DEFAULT_FEED('app')}, + }) + this.userInterests = userInterests + } +@@ -54,7 +54,7 @@ export class HomeFeedAPI implements FeedAPI { + this.following = new FollowingFeedAPI({agent: this.agent}) + this.discover = new CustomFeedAPI({ + agent: this.agent, +- feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, ++ feedParams: {feed: PROD_DEFAULT_FEED('app')}, + userInterests: this.userInterests, + }) + this.usingDiscover = false +diff --git a/src/lib/constants.ts b/src/lib/constants.ts +index 231447b4f..a44b3da05 100644 +--- a/src/lib/constants.ts ++++ b/src/lib/constants.ts +@@ -7,12 +7,12 @@ import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' + export const LOCAL_DEV_SERVICE = + Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' + export const STAGING_SERVICE = 'https://staging.bsky.dev' +-export const BSKY_SERVICE = 'https://bsky.social' +-export const BSKY_SERVICE_DID = 'did:web:bsky.social' +-export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app' ++export const BSKY_SERVICE = 'https://syu.is' ++export const BSKY_SERVICE_DID = 'did:web:syu.is' ++export const PUBLIC_BSKY_SERVICE = 'https://bsky.syu.is' + export const DEFAULT_SERVICE = BSKY_SERVICE +-const HELP_DESK_LANG = 'en-us' +-export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}` ++const HELP_DESK_LANG = 'ja-jp' ++export const HELP_DESK_URL = 'https://syu.is/about/support/help' + export const EMBED_SERVICE = 'https://embed.bsky.app' + export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` + export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' +@@ -79,19 +79,17 @@ export function IS_PROD_SERVICE(url?: string) { + } + + export const PROD_DEFAULT_FEED = (rkey: string) => +- `at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}` ++ `at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/${rkey}` + + export const STAGING_DEFAULT_FEED = (rkey: string) => + `at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/${rkey}` + + export const PROD_FEEDS = [ +- `feedgen|${PROD_DEFAULT_FEED('whats-hot')}`, +- `feedgen|${PROD_DEFAULT_FEED('thevids')}`, ++ `feedgen|${PROD_DEFAULT_FEED('app')}`, + ] + + export const STAGING_FEEDS = [ +- `feedgen|${STAGING_DEFAULT_FEED('whats-hot')}`, +- `feedgen|${STAGING_DEFAULT_FEED('thevids')}`, ++ `feedgen|${STAGING_DEFAULT_FEED('app')}`, + ] + + export const POST_IMG_MAX = { +@@ -129,7 +127,7 @@ export const LANG_DROPDOWN_HITSLOP = {top: 10, bottom: 10, left: 4, right: 4} + export const BACK_HITSLOP = HITSLOP_30 + export const MAX_POST_LINES = 25 + +-export const BSKY_APP_ACCOUNT_DID = 'did:plc:z72i7hdynmk6r22z27h6tvur' ++export const BSKY_APP_ACCOUNT_DID = 'did:plc:6qyecktefllvenje24fcxnie' + + export const BSKY_FEED_OWNER_DIDS = [ + BSKY_APP_ACCOUNT_DID, +@@ -138,9 +136,9 @@ export const BSKY_FEED_OWNER_DIDS = [ + ] + + export const DISCOVER_FEED_URI = +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' ++ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app' + export const VIDEO_FEED_URI = + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/thevids' + export const STAGING_VIDEO_FEED_URI = + 'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids' + export const VIDEO_FEED_URIS = [VIDEO_FEED_URI, STAGING_VIDEO_FEED_URI] +@@ -209,8 +207,8 @@ export const urls = { + }, + } + +-export const PUBLIC_APPVIEW = 'https://api.bsky.app' +-export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app' ++export const PUBLIC_APPVIEW = 'https://bsky.syu.is' ++export const PUBLIC_APPVIEW_DID = 'did:web:bsky.syu.is' + export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev' + + export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same +@@ -236,8 +234,8 @@ export const BLUESKY_MOD_SERVICE_HEADERS = { + } + + export const webLinks = { +- tos: `https://bsky.social/about/support/tos`, +- privacy: `https://bsky.social/about/support/privacy-policy`, ++ tos: `https://syu.is/about/support/tos`, ++ privacy: `https://syu.is/about/support/privacy-policy`, + community: `https://bsky.social/about/support/community-guidelines`, + communityDeprecated: `https://bsky.social/about/support/community-guidelines-deprecated`, + } +diff --git a/src/lib/demo.ts b/src/lib/demo.ts +index 5ead62c9d..7c80dfe15 100644 +--- a/src/lib/demo.ts ++++ b/src/lib/demo.ts +@@ -1,7 +1,7 @@ + import {type AppBskyFeedGetFeed} from '@atproto/api' + import {subDays, subMinutes} from 'date-fns' + +-const DID = `did:plc:z72i7hdynmk6r22z27h6tvur` ++const DID = `did:plc:6qyecktefllvenje24fcxnie` + const NOW = new Date() + const POST_1_DATE = subMinutes(NOW, 2).toISOString() + const POST_2_DATE = subMinutes(NOW, 4).toISOString() +diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts +index 6088e2806..0f6787a4d 100644 +--- a/src/lib/strings/url-helpers.ts ++++ b/src/lib/strings/url-helpers.ts +@@ -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) { +@@ -338,7 +338,7 @@ export function createProxiedUrl(url: string): string { + return url + } + +- return `https://go.bsky.app/redirect?u=${encodeURIComponent(url)}` ++ return url + } + + export function isShortLink(url: string): boolean { +diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts +index de1e92533..3d1566800 100644 +--- a/src/state/queries/feed.ts ++++ b/src/state/queries/feed.ts +@@ -201,14 +201,6 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) { + // for the ones we know need it + // -prf + export const KNOWN_AUTHED_ONLY_FEEDS = [ +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app +- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed +- 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed +- 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow +- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz +- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky +- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz +- 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why + ] + + type GetPopularFeedsOptions = {limit?: number; enabled?: boolean} +diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts +index 0cf6ab546..399e592bc 100644 +--- a/src/state/queries/preferences/index.ts ++++ b/src/state/queries/preferences/index.ts +@@ -270,7 +270,7 @@ export function useReplaceForYouWithDiscoverFeedMutation() { + await agent.addSavedFeeds([ + { + type: 'feed', +- value: PROD_DEFAULT_FEED('whats-hot'), ++ value: PROD_DEFAULT_FEED('app'), + pinned: true, + }, + ]) +diff --git a/src/view/com/posts/FeedShutdownMsg.tsx b/src/view/com/posts/FeedShutdownMsg.tsx +index 620382175..928480da2 100644 +--- a/src/view/com/posts/FeedShutdownMsg.tsx ++++ b/src/view/com/posts/FeedShutdownMsg.tsx +@@ -32,7 +32,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + f => f.value === feedUri && f.pinned, + ) + const discoverFeedConfig = preferences?.savedFeeds?.find( +- f => f.value === PROD_DEFAULT_FEED('whats-hot'), ++ f => f.value === PROD_DEFAULT_FEED('app'), + ) + const hasFeedPinned = Boolean(feedConfig) + const hasDiscoverPinned = Boolean(discoverFeedConfig?.pinned) +@@ -44,7 +44,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + Toast.show(_(msg`Removed from your feeds`)) + } + if (hasDiscoverPinned) { +- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`) ++ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`) + } + } catch (err: any) { + Toast.show( +@@ -63,7 +63,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + forYouFeedConfig: feedConfig, + discoverFeedConfig, + }) +- setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`) ++ setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('app')}`) + Toast.show(_(msg`The feed has been replaced with Discover.`)) + } catch (err: any) { + Toast.show( +@@ -100,7 +100,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) { + This feed is no longer online. We are showing{' '} + + Discover + {' '} + diff --git a/ios/patching/003-social-app-ios-view.patch b/ios/patching/003-social-app-ios-view.patch new file mode 100644 index 0000000..aa0cc29 --- /dev/null +++ b/ios/patching/003-social-app-ios-view.patch @@ -0,0 +1,213 @@ +diff --git a/src/Splash.tsx b/src/Splash.tsx +index 47e70b375..616f351ed 100644 +--- a/src/Splash.tsx ++++ b/src/Splash.tsx +@@ -15,8 +15,8 @@ import Animated, { + withTiming, + } from 'react-native-reanimated' + import {useSafeAreaInsets} from 'react-native-safe-area-context' +-import Svg, {Path, type SvgProps} from 'react-native-svg' + import {Image} from 'expo-image' ++import {type SvgProps} from 'react-native-svg' + import * as SplashScreen from 'expo-splash-screen' + + import {Logotype} from '#/view/icons/Logotype' +@@ -29,21 +29,18 @@ const darkSplashImageUri = RNImage.resolveAssetSource( + darkSplashImagePointer, + ).uri + +-export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) { +- const width = 1000 +- const height = width * (67 / 64) ++export const Logo = React.forwardRef(function LogoImpl(props: SvgProps & {fill?: string}, ref) { ++ const size = 1000 ++ // @ts-ignore + return ( +- +- +- ++ source={require('../assets/logo.png')} ++ style={[{width: size, height: size}, props.style]} ++ contentFit="contain" ++ accessibilityLabel="Logo" ++ /> + ) + }) + +diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx +index 8a9e51a33..65d643b89 100644 +--- a/src/view/com/util/UserAvatar.tsx ++++ b/src/view/com/util/UserAvatar.tsx +@@ -444,7 +444,7 @@ let EditableUserAvatar = ({ + 0), }} + accessibilityRole="image" + /> + ) : ( +@@ -618,9 +618,8 @@ export {PreviewableUserAvatar} + // manually string-replace to use the smaller ones + // -prf + function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string { +- return isEnabled +- ? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/') +- : uri ++ // syu.is: avatars are served directly from bsky.syu.is, no CDN transformation needed ++ return uri + } + + const styles = StyleSheet.create({ +diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx +index d7208df13..2763800ac 100644 +--- a/src/view/icons/Logo.tsx ++++ b/src/view/icons/Logo.tsx +@@ -1,75 +1,17 @@ + import React from 'react' +-import {type TextProps} from 'react-native' +-import Svg, { +- Defs, +- LinearGradient, +- Path, +- type PathProps, +- Stop, +- type SvgProps, +-} from 'react-native-svg' + import {Image} from 'expo-image' ++import {flatten} from '#/alf' + +-import {useKawaiiMode} from '#/state/preferences/kawaii' +-import {flatten, useTheme} from '#/alf' +- +-const ratio = 57 / 64 +- +-type Props = { +- fill?: PathProps['fill'] +- style?: TextProps['style'] +-} & Omit +- +-export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) { +- const t = useTheme() +- const {fill, ...rest} = props +- const gradient = fill === 'sky' +- const styles = flatten(props.style) +- const _fill = gradient +- ? 'url(#sky)' +- : fill || styles?.color || t.palette.primary_500 +- // @ts-ignore it's fiiiiine +- const size = parseInt(rest.width || 32, 10) +- +- const isKawaii = useKawaiiMode() +- +- if (isKawaii) { +- return ( +- 100 +- ? require('../../../assets/kawaii.png') +- : require('../../../assets/kawaii_smol.png') +- } +- accessibilityLabel="Bluesky" +- accessibilityHint="" +- accessibilityIgnoresInvertColors +- style={[{height: size, aspectRatio: 1.4}]} +- /> +- ) +- } +- ++export const Logo = React.forwardRef(function LogoImpl(props: any, ref) { ++ const {width, style} = props ++ // @ts-ignore ++ const size = parseInt(width || 32, 10) + return ( +- +- {gradient && ( +- +- +- +- +- +- +- )} +- +- +- ++ + ) + }) +diff --git a/src/view/icons/Logotype.tsx b/src/view/icons/Logotype.tsx +index 270c913fc..a60ffe07c 100644 +--- a/src/view/icons/Logotype.tsx ++++ b/src/view/icons/Logotype.tsx +@@ -1,28 +1,22 @@ +-import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg' +- +-import {usePalette} from '#/lib/hooks/usePalette' +- +-const ratio = 17 / 64 +- +-export function Logotype({ +- fill, +- ...rest +-}: {fill?: PathProps['fill']} & SvgProps) { +- const pal = usePalette('default') +- // @ts-ignore it's fiiiiine +- const size = parseInt(rest.width || 32) ++import React from 'react' ++import {Text} from 'react-native' ++import {useTheme, atoms as a} from '#/alf' + ++export function Logotype({width, fill, style}: any) { ++ const t = useTheme() ++ const fontSize = width ? parseInt(width) / 3.5 : 22 ++ + return ( +- +- +- ++ ++ Aiat ++ + ) + } diff --git a/ios/patching/004-social-app-ios-core.patch b/ios/patching/004-social-app-ios-core.patch new file mode 100644 index 0000000..1f61904 --- /dev/null +++ b/ios/patching/004-social-app-ios-core.patch @@ -0,0 +1,72 @@ +diff --git a/src/App.native.tsx b/src/App.native.tsx +index fb3008627..539ebc055 100644 +--- a/src/App.native.tsx ++++ b/src/App.native.tsx +@@ -92,7 +92,7 @@ if (isAndroid) { + * Begin geolocation ASAP + */ + Geo.resolve() +-prefetchAgeAssuranceConfig() ++// // // prefetchAgeAssuranceConfig() + + function InnerApp() { + const [isReady, setIsReady] = React.useState(false) +diff --git a/src/routes.ts b/src/routes.ts +index 1ed913bb2..c80340edb 100644 +--- a/src/routes.ts ++++ b/src/routes.ts +@@ -71,8 +71,8 @@ export const router = new Router({ + MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous', + // support + Support: '/support', +- PrivacyPolicy: '/support/privacy', +- TermsOfService: '/support/tos', ++ PrivacyPolicy: 'https://syu.is/about/support/privacy-policy', ++ TermsOfService: 'https://syu.is/about/support/tos', + CommunityGuidelines: '/support/community-guidelines', + CopyrightPolicy: '/support/copyright', + // hashtags +diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts +index 5c8ce3b97..ee85beb08 100644 +--- a/src/state/session/agent.ts ++++ b/src/state/session/agent.ts +@@ -47,7 +47,8 @@ export function createPublicAgent() { + configureModerationForGuest() // Side effect but only relevant for tests + + const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) +- agent.configureProxy(BLUESKY_PROXY_HEADER.get()) ++ // Disable proxy for self-hosted environments ++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get()) + return agent + } + +@@ -88,7 +89,8 @@ export async function createAgentAndResume( + // after session is attached + const aa = prefetchAgeAssuranceData({agent}) + +- agent.configureProxy(BLUESKY_PROXY_HEADER.get()) ++ // Disable proxy for self-hosted environments ++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get()) + + return agent.prepare({ + resolvers: [gates, moderation, aa], +@@ -127,7 +129,8 @@ export async function createAgentAndLogin( + const moderation = configureModerationForAccount(agent, account) + const aa = prefetchAgeAssuranceData({agent}) + +- agent.configureProxy(BLUESKY_PROXY_HEADER.get()) ++ // Disable proxy for self-hosted environments ++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get()) + + return agent.prepare({ + resolvers: [gates, moderation, aa], +@@ -299,7 +302,8 @@ export async function createAgentAndCreateAccount( + logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`}) + } + +- agent.configureProxy(BLUESKY_PROXY_HEADER.get()) ++ // Disable proxy for self-hosted environments ++ // agent.configureProxy(BLUESKY_PROXY_HEADER.get()) + + return agent.prepare({ + resolvers: [gates, moderation, aa], diff --git a/ios/patching/005-social-app-ios-screens.patch b/ios/patching/005-social-app-ios-screens.patch new file mode 100644 index 0000000..492e263 --- /dev/null +++ b/ios/patching/005-social-app-ios-screens.patch @@ -0,0 +1,593 @@ +diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx +index 6b8257b91..48ba7909e 100644 +--- a/src/screens/Settings/AboutSettings.tsx ++++ b/src/screens/Settings/AboutSettings.tsx +@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) { + + + + + +@@ -88,7 +88,7 @@ export function AboutSettingsScreen({}: Props) { + + + + + +diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx +index 77f219e55..53f5e0cc0 100644 +--- a/src/screens/Takendown.tsx ++++ b/src/screens/Takendown.tsx +@@ -217,10 +217,10 @@ export function Takendown() { + + Your account was found to be in violation of the{' '} + +- Bluesky Social Terms of Service ++ syu.is Terms of Service + + . You have been sent an email outlining the specific violation + and suspension period, if applicable. You can appeal this +diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx +index e058e2883..8daf41089 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,90 @@ import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' + import * as Layout from '#/components/Layout' + import {useDemoMode} from '#/storage/hooks/demo-mode' + ++const SYU_IS_FEED_URI = 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app' ++ ++const DEFAULT_PINNED_FEEDS: any[] = [{ ++ feedDescriptor: 'following', ++ displayName: 'Following', ++ id: 'following', ++ uri: 'following', ++ type: 'feed', ++ savedFeed: undefined, ++ pinned: true, ++ route: { href: '/', name: 'Home', params: {} }, ++ cid: '', ++ avatar: '', ++ creatorDid: '', ++ creatorHandle: '', ++}, { ++ feedDescriptor: `feedgen|${SYU_IS_FEED_URI}`, ++ displayName: 'Feeds', ++ id: SYU_IS_FEED_URI, ++ uri: SYU_IS_FEED_URI, ++ type: 'feed', ++ savedFeed: { ++ type: 'feed', ++ value: SYU_IS_FEED_URI, ++ pinned: true, ++ }, ++ pinned: true, ++ route: { href: '/', name: 'Home', params: {} }, ++ cid: '', ++ avatar: '', ++ creatorDid: '', ++ creatorHandle: '', ++}] ++ + type Props = NativeStackScreenProps + export function HomeScreen(props: Props) { + const {setShowLoggedOut} = useLoggedOutViewControls() + const {data: preferences} = usePreferencesQuery() + const {currentAccount} = useSession() +- const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = +- usePinnedFeedsInfos() ++ const {data: pinnedFeedInfos} = usePinnedFeedsInfos() ++ ++ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any ++ // Use user's pinned feeds when logged in and available, otherwise use defaults ++ const safePinnedFeedInfos = !currentAccount ++ ? DEFAULT_PINNED_FEEDS.filter(f => f.feedDescriptor !== 'following') ++ : (pinnedFeedInfos && pinnedFeedInfos.length > 0) ++ ? pinnedFeedInfos ++ : DEFAULT_PINNED_FEEDS + + React.useEffect(() => { + if (isWeb && !currentAccount) { + const getParams = new URLSearchParams(window.location.search) + const splash = getParams.get('splash') +- if (splash === 'true') { +- setShowLoggedOut(true) +- return +- } ++ if (splash === 'true') { setShowLoggedOut(true); return } + } +- + const params = props.route.params +- if ( +- currentAccount && +- props.route.name === 'Start' && +- params?.name && +- params?.rkey +- ) { +- props.navigation.navigate('StarterPack', { +- rkey: params.rkey, +- name: params.name, +- }) ++ if (currentAccount && props.route.name === 'Start' && params?.name && params?.rkey) { ++ props.navigation.navigate('StarterPack', { rkey: params.rkey, name: params.name }) + } +- }, [ +- currentAccount, +- props.navigation, +- props.route.name, +- props.route.params, +- setShowLoggedOut, +- ]) ++ }, [currentAccount, props.navigation, props.route.name, props.route.params, setShowLoggedOut]) + +- if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { +- return ( +- +- +- +- ) +- } else { +- return ( +- +- +- +- +- +- ) +- } ++ return ( ++ ++ ++ ++ ) + } + +-function HomeScreenReady({ +- preferences, +- pinnedFeedInfos, +-}: Props & { +- preferences: UsePreferencesQueryResponse +- pinnedFeedInfos: SavedFeedSourceInfo[] +-}) { +- const allFeeds = React.useMemo( +- () => pinnedFeedInfos.map(f => f.feedDescriptor), +- [pinnedFeedInfos], +- ) +- const maybeRawSelectedFeed: FeedDescriptor | undefined = +- useSelectedFeed() ?? allFeeds[0] ++function HomeScreenReady({preferences, pinnedFeedInfos}: any) { ++ const allFeeds = React.useMemo(() => pinnedFeedInfos.map(f => f.feedDescriptor), [pinnedFeedInfos]) ++ const maybeRawSelectedFeed = useSelectedFeed() ?? allFeeds[0] + const setSelectedFeed = useSetSelectedFeed() + const maybeFoundIndex = allFeeds.indexOf(maybeRawSelectedFeed) + const selectedIndex = Math.max(0, maybeFoundIndex) +- const maybeSelectedFeed: FeedDescriptor | undefined = allFeeds[selectedIndex] ++ const maybeSelectedFeed = allFeeds[selectedIndex] + const requestNotificationsPermission = useRequestNotificationsPermission() + + useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName) + useOTAUpdates() +- +- React.useEffect(() => { +- requestNotificationsPermission('Home') +- }, [requestNotificationsPermission]) ++ React.useEffect(() => { requestNotificationsPermission('Home') }, [requestNotificationsPermission]) + + const pagerRef = React.useRef(null) + const lastPagerReportedIndexRef = React.useRef(selectedIndex) + React.useLayoutEffect(() => { +- // Since the pager is not a controlled component, adjust it imperatively +- // if the selected index gets out of sync with what it last reported. +- // This is supposed to only happen on the web when you use the right nav. + if (selectedIndex !== lastPagerReportedIndexRef.current) { + lastPagerReportedIndexRef.current = selectedIndex + pagerRef.current?.setPage(selectedIndex) +@@ -138,205 +120,43 @@ function HomeScreenReady({ + + const {hasSession} = useSession() + const setMinimalShellMode = useSetMinimalShellMode() +- useFocusEffect( +- React.useCallback(() => { +- setMinimalShellMode(false) +- }, [setMinimalShellMode]), +- ) ++ 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] ++ const onPageSelected = React.useCallback((index) => { ++ setMinimalShellMode(false) ++ const maybeFeed = allFeeds[index] ++ lastPagerReportedIndexRef.current = index ++ setSelectedFeed(maybeFeed) ++ }, [setSelectedFeed, setMinimalShellMode, allFeeds]) + +- // 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], +- ) ++ 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) => { ++ return ++ }, [onPressSelected, pinnedFeedInfos]) + +- const renderTabBar = React.useCallback( +- (props: RenderTabBarFnProps) => { +- if (demoMode) { +- return ( +- +- ) +- } +- return ( +- +- ) +- }, +- [onPressSelected, pinnedFeedInfos, demoMode], +- ) +- +- const renderFollowingEmptyState = React.useCallback(() => { +- return +- }, []) ++ const renderFollowingEmptyState = React.useCallback(() => , []) ++ const renderCustomFeedEmptyState = React.useCallback(() => , []) + +- const renderCustomFeedEmptyState = React.useCallback(() => { +- return +- }, []) ++ const homeFeedParams = React.useMemo(() => ({ ++ mergeFeedEnabled: false, mergeFeedSources: [] ++ }), [preferences]) + +- const homeFeedParams = React.useMemo(() => { +- return { +- mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), +- mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled +- ? preferences.savedFeeds +- .filter(f => f.type === 'feed' || f.type === 'list') +- .map(f => f.value) +- : [], +- } +- }, [preferences]) +- +- if (demoMode) { +- return ( +- +- +- +- +- ) +- } +- +- return hasSession ? ( +- +- {pinnedFeedInfos.length ? ( +- pinnedFeedInfos.map((feedInfo, index) => { ++ return ( ++ ++ {pinnedFeedInfos.map((feedInfo, index) => { + const feed = feedInfo.feedDescriptor + if (feed === 'following') { +- return ( +- +- ) ++ return + } +- const savedFeedConfig = feedInfo.savedFeed +- return ( +- +- ) +- }) +- ) : ( +- +- )} +- +- ) : ( +- +- ++ return ++ })} + + ) + } +- +-const styles = StyleSheet.create({ +- loading: { +- height: '100%', +- alignContent: 'center', +- justifyContent: 'center', +- paddingBottom: 100, +- }, +-}) ++const styles = StyleSheet.create({ loading: { height: '100%', alignContent: 'center', justifyContent: 'center', paddingBottom: 100 } }) +diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx +index a89eaadc4..1da393f03 100644 +--- a/src/view/screens/PrivacyPolicy.tsx ++++ b/src/view/screens/PrivacyPolicy.tsx +@@ -1,52 +1,13 @@ + import React from 'react' +-import {View} from 'react-native' +-import {msg, Trans} from '@lingui/macro' +-import {useLingui} from '@lingui/react' +-import {useFocusEffect} from '@react-navigation/native' +- +-import {usePalette} from '#/lib/hooks/usePalette' +-import { +- type CommonNavigatorParams, +- type NativeStackScreenProps, +-} from '#/lib/routes/types' +-import {s} from '#/lib/styles' +-import {useSetMinimalShellMode} from '#/state/shell' +-import {TextLink} from '#/view/com/util/Link' +-import {Text} from '#/view/com/util/text/Text' +-import {ScrollView} from '#/view/com/util/Views' ++import { WebView } from 'react-native-webview' + import * as Layout from '#/components/Layout' +-import {ViewHeader} from '../com/util/ViewHeader' +- +-type Props = NativeStackScreenProps +-export const PrivacyPolicyScreen = (_props: Props) => { +- const pal = usePalette('default') +- const {_} = useLingui() +- const setMinimalShellMode = useSetMinimalShellMode() +- +- useFocusEffect( +- React.useCallback(() => { +- setMinimalShellMode(false) +- }, [setMinimalShellMode]), +- ) ++import {useSetTitle} from '#/lib/hooks/useSetTitle' + ++export function PrivacyPolicyScreen() { ++ useSetTitle('Privacy Policy') + return ( + +- +- +- +- +- +- The Privacy Policy has been moved to{' '} +- +- +- +- +- +- ++ + + ) + } +diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx +index d843c713c..b81767bd5 100644 +--- a/src/view/screens/TermsOfService.tsx ++++ b/src/view/screens/TermsOfService.tsx +@@ -1,50 +1,13 @@ + import React from 'react' +-import {View} from 'react-native' +-import {msg, Trans} from '@lingui/macro' +-import {useLingui} from '@lingui/react' +-import {useFocusEffect} from '@react-navigation/native' +- +-import {usePalette} from '#/lib/hooks/usePalette' +-import { +- type CommonNavigatorParams, +- type NativeStackScreenProps, +-} from '#/lib/routes/types' +-import {s} from '#/lib/styles' +-import {useSetMinimalShellMode} from '#/state/shell' +-import {TextLink} from '#/view/com/util/Link' +-import {Text} from '#/view/com/util/text/Text' +-import {ScrollView} from '#/view/com/util/Views' ++import { WebView } from 'react-native-webview' + import * as Layout from '#/components/Layout' +-import {ViewHeader} from '../com/util/ViewHeader' +- +-type Props = NativeStackScreenProps +-export const TermsOfServiceScreen = (_props: Props) => { +- const pal = usePalette('default') +- const setMinimalShellMode = useSetMinimalShellMode() +- const {_} = useLingui() +- +- useFocusEffect( +- React.useCallback(() => { +- setMinimalShellMode(false) +- }, [setMinimalShellMode]), +- ) ++import {useSetTitle} from '#/lib/hooks/useSetTitle' + ++export function TermsOfServiceScreen() { ++ useSetTitle('Terms of Service') + return ( + +- +- +- +- +- The Terms of Service have been moved to{' '} +- +- +- +- +- ++ + + ) + } diff --git a/ios/patching/006-social-app-ios-shell.patch b/ios/patching/006-social-app-ios-shell.patch new file mode 100644 index 0000000..399df6f --- /dev/null +++ b/ios/patching/006-social-app-ios-shell.patch @@ -0,0 +1,56 @@ +diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx +index f76147ccf..36b4d7de1 100644 +--- a/src/view/shell/Drawer.tsx ++++ b/src/view/shell/Drawer.tsx +@@ -292,17 +292,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => { + <> + + +- + + +- +- + +- ++{/* Feedback button removed for syu.is */} + +- +- +- You can also discover new Custom Feeds to follow. +- +- + + + ) +@@ -98,13 +45,4 @@ const styles = StyleSheet.create({ + marginLeft: 'auto', + marginRight: 'auto', + }, +- emptyBtn: { +- marginVertical: 20, +- flexDirection: 'row', +- alignItems: 'center', +- justifyContent: 'space-between', +- paddingVertical: 18, +- paddingHorizontal: 24, +- borderRadius: 30, +- }, + }) +diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx +index e3c84d782..efb55d406 100644 +--- a/src/view/com/posts/FollowingEndOfFeed.tsx ++++ b/src/view/com/posts/FollowingEndOfFeed.tsx +@@ -1,36 +1,13 @@ + import React from 'react' + import {Dimensions, StyleSheet, View} from 'react-native' +-import { +- FontAwesomeIcon, +- type FontAwesomeIconStyle, +-} from '@fortawesome/react-native-fontawesome' + import {Trans} from '@lingui/macro' +-import {useNavigation} from '@react-navigation/native' + + import {usePalette} from '#/lib/hooks/usePalette' +-import {type NavigationProp} from '#/lib/routes/types' + import {s} from '#/lib/styles' +-import {isWeb} from '#/platform/detection' +-import {Button} from '../util/forms/Button' + import {Text} from '../util/text/Text' + + export function FollowingEndOfFeed() { + const pal = usePalette('default') +- const palInverted = usePalette('inverted') +- const navigation = useNavigation() +- +- const onPressFindAccounts = React.useCallback(() => { +- if (isWeb) { +- navigation.navigate('Search', {}) +- } else { +- navigation.navigate('SearchTab') +- navigation.popToTop() +- } +- }, [navigation]) +- +- const onPressDiscoverFeeds = React.useCallback(() => { +- navigation.navigate('Feeds') +- }, [navigation]) + + return ( + + + +- +- You've reached the end of your feed! Find some more accounts to +- follow. +- +- +- +- +- +- You can also discover new Custom Feeds to follow. ++ You've reached the end of your feed! + +- + + + ) +@@ -93,13 +37,4 @@ const styles = StyleSheet.create({ + width: '100%', + maxWidth: 460, + }, +- emptyBtn: { +- marginVertical: 20, +- flexDirection: 'row', +- alignItems: 'center', +- justifyContent: 'space-between', +- paddingVertical: 18, +- paddingHorizontal: 24, +- borderRadius: 30, +- }, + }) +diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx +index 4f25468c9..a72a10b80 100644 +--- a/src/view/com/posts/PostFeed.tsx ++++ b/src/view/com/posts/PostFeed.tsx +@@ -766,7 +766,7 @@ let PostFeed = ({ + } else if (row.type === 'feedShutdownMsg') { + return + } else if (row.type === 'interstitialFollows') { +- return ++ return null + } else if (row.type === 'interstitialProgressGuide') { + return + } else if (row.type === 'ageAssuranceBanner') { diff --git a/ios/patching/022-social-app-ios-bskyweb-support-pages.patch b/ios/patching/022-social-app-ios-bskyweb-support-pages.patch new file mode 100644 index 0000000..0c983ac --- /dev/null +++ b/ios/patching/022-social-app-ios-bskyweb-support-pages.patch @@ -0,0 +1,51 @@ +diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go +index 790f211ee..ec05a8bcd 100644 +--- a/bskyweb/cmd/bskyweb/server.go ++++ b/bskyweb/cmd/bskyweb/server.go +@@ -317,6 +317,12 @@ func serve(cctx *cli.Context) error { + e.GET("/support/tos", server.WebGeneric) + e.GET("/support/community-guidelines", server.WebGeneric) + e.GET("/support/copyright", server.WebGeneric) ++ // about/support pages (syu.is specific) ++ e.GET("/about/support/tos", server.WebAboutTOS) ++ e.GET("/about/support/privacy-policy", server.WebAboutPrivacy) ++ e.GET("/about/support/help", server.WebAboutHelp) ++ e.GET("/about/support/license", server.WebAboutLicense) ++ e.GET("/about/support/app", server.WebAboutApp) + e.GET("/intent/compose", server.WebGeneric) + e.GET("/intent/verify-email", server.WebGeneric) + e.GET("/intent/age-assurance", server.WebGeneric) +@@ -825,3 +831,33 @@ func (srv *Server) serveSitemapRequest(c echo.Context, url, sitemapType string) + + return nil + } ++ ++// Handler for About TOS page (syu.is specific) ++func (srv *Server) WebAboutTOS(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-tos.html", data) ++} ++ ++// Handler for About Privacy Policy page (syu.is specific) ++func (srv *Server) WebAboutPrivacy(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-privacy.html", data) ++} ++ ++// Handler for About Help page (syu.is specific) ++func (srv *Server) WebAboutHelp(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-help.html", data) ++} ++ ++// Handler for About License page (syu.is specific) ++func (srv *Server) WebAboutLicense(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-license.html", data) ++} ++ ++// Handler for About App page (syu.is specific) ++func (srv *Server) WebAboutApp(c echo.Context) error { ++ data := srv.NewTemplateContext() ++ return c.Render(http.StatusOK, "about-app.html", data) ++} diff --git a/ios/patching/023-social-app-ios-disable-dm.patch b/ios/patching/023-social-app-ios-disable-dm.patch new file mode 100644 index 0000000..2f2236e --- /dev/null +++ b/ios/patching/023-social-app-ios-disable-dm.patch @@ -0,0 +1,70 @@ +diff --git a/src/state/messages/events/index.tsx b/src/state/messages/events/index.tsx +index 2ff0784ae..dc314ecc5 100644 +--- a/src/state/messages/events/index.tsx ++++ b/src/state/messages/events/index.tsx +@@ -10,13 +10,7 @@ const MessagesEventBusContext = React.createContext( + MessagesEventBusContext.displayName = 'MessagesEventBusContext' + + export function useMessagesEventBus() { +- const ctx = React.useContext(MessagesEventBusContext) +- if (!ctx) { +- throw new Error( +- 'useMessagesEventBus must be used within a MessagesEventBusProvider', +- ) +- } +- return ctx ++ return React.useContext(MessagesEventBusContext) + } + + export function MessagesEventBusProvider({ +@@ -24,18 +18,11 @@ export function MessagesEventBusProvider({ + }: { + children: React.ReactNode + }) { +- const {currentAccount} = useSession() +- +- if (!currentAccount) { +- return ( +- +- {children} +- +- ) +- } +- ++ // DM functionality is disabled for syu.is + return ( +- {children} ++ ++ {children} ++ + ) + } + +diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx +index c5457d1cb..5bc37bdce 100644 +--- a/src/state/queries/messages/list-conversations.tsx ++++ b/src/state/queries/messages/list-conversations.tsx +@@ -74,17 +74,12 @@ export function useListConvos() { + + const empty = {accepted: [], request: []} + export function ListConvosProvider({children}: {children: React.ReactNode}) { +- const {hasSession} = useSession() +- +- if (!hasSession) { +- return ( +- +- {children} +- +- ) +- } +- +- return {children} ++ // DM functionality is disabled for syu.is - always return empty ++ return ( ++ ++ {children} ++ ++ ) + } + + export function ListConvosProviderInner({ diff --git a/ios/patching/024-social-app-ios-disable-external-services.patch b/ios/patching/024-social-app-ios-disable-external-services.patch new file mode 100644 index 0000000..66bd786 --- /dev/null +++ b/ios/patching/024-social-app-ios-disable-external-services.patch @@ -0,0 +1,15 @@ +diff --git a/src/env/common.ts b/src/env/common.ts +--- a/src/env/common.ts ++++ b/src/env/common.ts +@@ -107,9 +107,8 @@ export const GCP_PROJECT_ID: number = + /** + * URLs for the app config web worker. Can be a + * locally running server, see `env.example` for more. ++ * Disabled for self-hosted environment to avoid CORS errors + */ + export const BAPP_CONFIG_DEV_URL = process.env.BAPP_CONFIG_DEV_URL + export const BAPP_CONFIG_PROD_URL = `https://ip.bsky.app` +-export const BAPP_CONFIG_URL = IS_DEV +- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_PROD_URL) +- : BAPP_CONFIG_PROD_URL ++export const BAPP_CONFIG_URL = null diff --git a/ios/patching/025-social-app-ios-bskyweb-title.patch b/ios/patching/025-social-app-ios-bskyweb-title.patch new file mode 100644 index 0000000..9b674e6 --- /dev/null +++ b/ios/patching/025-social-app-ios-bskyweb-title.patch @@ -0,0 +1,91 @@ +diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html +--- a/bskyweb/templates/base.html ++++ b/bskyweb/templates/base.html +@@ -7,9 +7,9 @@ + +- +- +- {%- block head_title -%}Bluesky{%- endblock -%} ++ ++ ++ {%- block head_title -%}syu.is{%- endblock -%} + + + +@@ -121,7 +121,7 @@ +