ai/at
1
0

Compare commits

..

10 Commits

Author SHA1 Message Date
20c2bc8e6a fix install.zsh 2025-12-10 15:41:26 +09:00
e53fbba3bf fix pds-subscriberepos-no-auth patch 2025-12-10 15:41:26 +09:00
67f6691740 fix social-app ios patch rm create account 2025-12-10 12:54:38 +09:00
d345b8f62d add html 2025-12-09 23:03:59 +09:00
2a3f9060c5 fix social-app web icon 2025-12-09 19:08:36 +09:00
dea55e649a fix social-app ios patch 2025-12-09 17:48:50 +09:00
4ba39c15ad add social-app appinfo 2025-12-09 17:04:21 +09:00
25b6b39532 fix social-app ios patch 2025-12-09 13:54:43 +09:00
5b21fc6cac fix social-app ios host server 2025-12-08 16:18:51 +09:00
ea589afe41 add ios social-app 2025-12-08 15:12:20 +09:00
144 changed files with 3685 additions and 16460 deletions

View File

@@ -1,40 +0,0 @@
name: Deploy to Cloudflare Pages
on:
push:
branches:
- main
paths:
- 'web/**'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
working-directory: web
- name: Build
run: npm run build
working-directory: web
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy web/dist/aiat --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}

9
.gitignore vendored
View File

@@ -2,11 +2,6 @@ repos
.claude
deploy.yml
claude.md
embedded.mobileprovision
store.mobileprovision
.env
k8s/secrets.env
k8s/deploy.yml
web/dist
node_modules
package-lock.json
/tmp
html.zip

View File

@@ -1,15 +1,18 @@
# at
- https://github.com/bluesky-social/atproto
- https://github.com/bluesky-social/social-app
- https://github.com/bluesky-social/atproto/discussions/2026
|name|type|example|
|word|name|example|
|---|---|---|
|at|uri|at://example.com|
|@|user|@example.com|
|[at]proto|repo|`git@github.com:bluesky-social/atproto`|
|[at]mosphere|system|pds, bsky(appview), ozone, bgs, plc|
|[a]uthenticated [t]ransfer|protocol|[did](https://www.w3.org/TR/did-core/)|
- https://atproto.com/ja/guides/glossary
## account
- [ai@syu.is](https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie)
@@ -19,14 +22,27 @@
```sh
$ curl -sL syu.is/xrpc/_health
# latest
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/package.json
$ curl -sL https://raw.githubusercontent.com/bluesky-social/atproto/refs/heads/main/packages/pds/package.json |jq -r .version
```
```sh
$ handle=ai.syui.ai
$ curl -sL "syu.is/xrpc/com.atproto.repo.describeRepo?repo=${handle}" |jq -r .did
did:plc:6qyecktefllvenje24fcxnie
$ curl -sL "syu.is/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.feed.post&reverse=true&limit=1"
{"records":[{"uri":"at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.post/3l6s2riuouk2j","cid":"bafyreibjohl7va4upkibw5twaxdd4jg3l6rmfatu4dpjjfd5xkb2ijtlx4","value":{"text":"hello","$type":"app.bsky.feed.post","langs":["ja"],"createdAt":"2024-10-18T13:21:39.809Z"}}],"cursor":"3l6s2riuouk2j"}
```
## feed
> at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app
- https://syu.is/profile/did:plc:6qyecktefllvenje24fcxnie/feed/app
- https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app
- https://syu.is/profile/ai.syui.ai/feed/app
- https://feed.syu.is/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.generator/app
```json
{
@@ -69,3 +85,4 @@ $ curl -sL syu.is/xrpc/_health
./ios/build.zsh
```

View File

@@ -77,7 +77,7 @@ services:
- 2470:2470
build:
context: ./repos/indigo/
dockerfile: cmd/relay/Dockerfile
dockerfile: cmd/bigsky/Dockerfile
restart: always
env_file:
- ./envs/bgs
@@ -86,7 +86,6 @@ services:
depends_on:
database:
condition: service_healthy
#command: ["/bigsky", "--crawl-insecure-ws"]
social-app:
ports:
@@ -155,5 +154,3 @@ services:
- ./envs/feed
volumes:
- ./data/feed:/data/
depends_on:
- jetstream

View File

@@ -1,20 +0,0 @@
FROM node:20-alpine
WORKDIR /app
# Install dependencies for better-sqlite3
RUN apk add --no-cache python3 make g++
# Copy package files and install
COPY package.json yarn.lock ./
RUN yarn install
# Copy source
COPY . .
# Build TypeScript
RUN yarn build
EXPOSE 3000
CMD ["node", "dist/index.js"]

View File

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

View File

@@ -1,4 +1,4 @@
JETSTREAM_WS_URL=ws://bgs.${host}/xrpc/com.atproto.sync.subscribeRepos
JETSTREAM_WS_URL=wss://bgs.${host}/xrpc/com.atproto.sync.subscribeRepos
JETSTREAM_DATA_DIR=/data
JETSTREAM_LISTEN_ADDR=:6008
JETSTREAM_METRICS_LISTEN_ADDR=:6009

View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>Help - syu.is</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a1a1a;
background: #fff;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
body { background: #000; color: #e0e0e0; }
a { color: #6bb3ff; }
h1, h2, h3 { color: #fff; }
}
h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
h2 { font-size: 20px; margin: 24px 0 12px; }
h3 { font-size: 16px; margin: 16px 0 8px; }
p { margin-bottom: 16px; }
ul { margin: 0 0 16px 24px; }
li { margin-bottom: 8px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
.header { margin-bottom: 32px; }
.back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
.faq-item { margin-bottom: 24px; }
.contact-box { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
@media (prefers-color-scheme: dark) {
.contact-box { background: #1a1a1a; }
}
</style>
</head>
<body>
<div class="header">
<a href="/" class="back-link">&larr; Back to syu.is</a>
<h1>Help Center</h1>
</div>
<h2>About syu.is</h2>
<p>syu.is is a social networking service built on the AT Protocol (Authenticated Transfer Protocol). It allows users to share content, connect with others, and participate in a decentralized social network.</p>
<h2>Frequently Asked Questions</h2>
<div class="faq-item">
<h3>What is the AT Protocol?</h3>
<p>The AT Protocol is a decentralized social networking protocol that allows users to own their data and identity. It enables federation between different services while maintaining user control.</p>
</div>
<div class="faq-item">
<h3>How do I create an account?</h3>
<p>You can create an account by downloading the app or visiting the website. You'll need to provide an email address and choose a username.</p>
</div>
<div class="faq-item">
<h3>How do I reset my password?</h3>
<p>You can reset your password through the login screen by selecting "Forgot Password" and following the instructions sent to your email.</p>
</div>
<div class="faq-item">
<h3>How do I delete my account?</h3>
<p>You can delete your account through Settings &gt; Account. Please note that account deletion is permanent and cannot be undone.</p>
</div>
<div class="faq-item">
<h3>How do I report abuse or inappropriate content?</h3>
<p>You can report content by using the report function available on each post. Our moderation team will review reports and take appropriate action.</p>
</div>
<h2>Contact</h2>
<div class="contact-box">
<p>For additional support or questions:</p>
<ul>
<li>GitHub: <a href="https://github.com/syui" target="_blank">github.com/syui</a></li>
</ul>
</div>
<h2>Related Links</h2>
<ul>
<li><a href="/about/support/tos">Terms of Service</a></li>
<li><a href="/about/support/privacy-policy">Privacy Policy</a></li>
<li><a href="/about/support/license">License</a></li>
<li><a href="/about/support/app">App Info</a></li>

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>License - syu.is</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a1a1a;
background: #fff;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
body { background: #000; color: #e0e0e0; }
a { color: #6bb3ff; }
h1, h2, h3 { color: #fff; }
}
h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
h2 { font-size: 20px; margin: 24px 0 12px; }
p { margin-bottom: 16px; }
ul { margin: 0 0 16px 24px; }
li { margin-bottom: 8px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
.header { margin-bottom: 32px; }
.back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
pre { background: #f5f5f5; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
@media (prefers-color-scheme: dark) { pre { background: #1a1a1a; } }
</style>
</head>
<body>
<div class="header">
<a href="/" class="back-link">&larr; Back to syu.is</a>
<h1>License</h1>
</div>
<h2>Aiat (iOS/Android App)</h2>
<p>This application is based on the Bluesky Social App, which is open source software.</p>
<h2>Open Source Licenses</h2>
<p>This app uses the following open source software:</p>
<h3>Bluesky Social App</h3>
<p>Licensed under the MIT License</p>
<p><a href="https://github.com/bluesky-social/social-app" target="_blank">https://github.com/bluesky-social/social-app</a></p>
<h3>AT Protocol</h3>
<p>Licensed under the MIT License / Apache 2.0</p>
<p><a href="https://github.com/bluesky-social/atproto" target="_blank">https://github.com/bluesky-social/atproto</a></p>
<h2>Third Party Libraries</h2>
<p>This application includes various third-party libraries, each with their own licenses. For a complete list, please see the application's source code repository.</p>
<div class="footer">
<p>Last updated: 2025</p>
<p>&copy; syu.is</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>Privacy Policy - syu.is</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a1a1a;
background: #fff;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
body { background: #000; color: #e0e0e0; }
a { color: #6bb3ff; }
h1, h2, h3 { color: #fff; }
}
h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
h2 { font-size: 20px; margin: 24px 0 12px; }
p { margin-bottom: 16px; }
ul { margin: 0 0 16px 24px; }
li { margin-bottom: 8px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
.header { margin-bottom: 32px; }
.back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
</style>
</head>
<body>
<div class="header">
<a href="/" class="back-link">&larr; Back to syu.is</a>
<h1>Privacy Policy</h1>
</div>
<h2>1. Introduction</h2>
<p>This Privacy Policy explains how syu.is collects, uses, and protects your personal information when you use our service.</p>
<h2>2. Information We Collect</h2>
<p>We collect the following types of information:</p>
<ul>
<li><strong>Account Information:</strong> Email address, username, and profile information you provide</li>
<li><strong>Content:</strong> Posts, messages, and other content you create on the platform</li>
<li><strong>Usage Data:</strong> Information about how you interact with our service</li>
<li><strong>Device Information:</strong> Browser type, operating system, and device identifiers</li>
</ul>
<h2>3. How We Use Your Information</h2>
<p>We use your information to:</p>
<ul>
<li>Provide and maintain our service</li>
<li>Improve and personalize your experience</li>
<li>Communicate with you about the service</li>
<li>Ensure security and prevent abuse</li>
</ul>
<h2>4. Data Sharing</h2>
<p>As part of the AT Protocol federation, your public content may be shared with other servers in the network. We do not sell your personal information to third parties.</p>
<h2>5. Data Security</h2>
<p>We implement appropriate security measures to protect your personal information. However, no method of transmission over the Internet is 100% secure.</p>
<h2>6. Your Rights</h2>
<p>You have the right to:</p>
<ul>
<li>Access your personal data</li>
<li>Request correction of your data</li>
<li>Request deletion of your account</li>
<li>Export your data</li>
</ul>
<h2>7. Cookies</h2>
<p>We use cookies and similar technologies to maintain your session and improve your experience.</p>
<h2>8. Changes to This Policy</h2>
<p>We may update this Privacy Policy from time to time. We will notify you of any significant changes.</p>

View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>Terms of Service - syu.is</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a1a1a;
background: #fff;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
body { background: #000; color: #e0e0e0; }
a { color: #6bb3ff; }
h1, h2, h3 { color: #fff; }
}
h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
h2 { font-size: 20px; margin: 24px 0 12px; }
p { margin-bottom: 16px; }
ul { margin: 0 0 16px 24px; }
li { margin-bottom: 8px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
.header { margin-bottom: 32px; }
.back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
</style>
</head>
<body>
<div class="header">
<a href="/" class="back-link">&larr; Back to syu.is</a>
<h1>Terms of Service</h1>
</div>
<h2>1. Introduction</h2>
<p>Welcome to syu.is. By using our service, you agree to these terms. Please read them carefully.</p>
<h2>2. Service Description</h2>
<p>syu.is is a social networking service built on the AT Protocol. We provide a platform for users to share content and connect with others.</p>
<h2>3. User Responsibilities</h2>
<p>As a user of syu.is, you agree to:</p>
<ul>
<li>Provide accurate information when creating an account</li>
<li>Keep your account credentials secure</li>
<li>Not use the service for illegal activities</li>
<li>Respect other users and their content</li>
<li>Comply with applicable laws and regulations</li>
</ul>
<h2>4. Content Guidelines</h2>
<p>Users are responsible for the content they post. Prohibited content includes:</p>
<ul>
<li>Illegal content</li>
<li>Harassment or abuse</li>
<li>Spam or misleading information</li>
<li>Content that violates others' rights</li>
</ul>
<h2>5. Privacy</h2>
<p>Your privacy is important to us. Please review our <a href="/about/support/privacy-policy">Privacy Policy</a> to understand how we handle your data.</p>
<h2>6. Disclaimer</h2>
<p>The service is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the service.</p>
<h2>7. Changes to Terms</h2>
<p>We may update these terms from time to time. Continued use of the service after changes constitutes acceptance of the new terms.</p>
<h2>8. Contact</h2>

135
html/index.html Normal file
View File

@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>App Info - Aiat</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a1a1a;
background: #fff;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
body { background: #000; color: #e0e0e0; }
a { color: #6bb3ff; }
h1, h2, h3 { color: #fff; }
.section { background: #1a1a1a; }
.info-item { background: #2a2a2a; }
}
.header { margin-bottom: 32px; }
.back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; color: #0066cc; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
.app-header { text-align: center; margin-bottom: 32px; }
.app-icon { width: 80px; height: 80px; border-radius: 18px; margin-bottom: 12px; }
.app-name { font-size: 24px; font-weight: bold; margin-bottom: 4px; }
.app-version { font-size: 14px; color: #666; }
.section { background: #f5f5f5; border-radius: 16px; padding: 20px; margin-bottom: 16px; }
.section-title { font-size: 13px; font-weight: 600; color: #999; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
.description { font-size: 15px; line-height: 22px; }
.info-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.info-item { flex: 1; min-width: 45%; text-align: center; background: #e8e8e8; border-radius: 12px; padding: 12px; }
.info-label { font-size: 11px; color: #999; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.info-value { font-size: 16px; font-weight: 600; }
.developer-name { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
.link-row { display: flex; align-items: center; padding: 12px 0; border-top: 1px solid rgba(0,0,0,0.1); }
.link-icon { font-size: 14px; font-weight: 600; color: #666; width: 70px; }
.link-value { flex: 1; font-size: 14px; color: #0084ff; text-decoration: none; }
.link-value:hover { text-decoration: underline; }
.link-arrow { font-size: 16px; color: #ccc; }
.bitcoin-row { display: flex; align-items: center; background: rgba(247, 147, 26, 0.08); border-radius: 12px; padding: 14px; gap: 10px; }
.bitcoin-label { font-size: 18px; font-weight: 600; color: #f7931a; }
.bitcoin-address { flex: 1; font-size: 11px; font-family: monospace; color: #666; word-break: break-all; }
.copy-btn { font-size: 12px; color: #999; cursor: pointer; min-width: 50px; text-align: right; }
.copy-btn:hover { color: #0084ff; }
.footer { text-align: center; margin-top: 32px; padding-top: 20px; border-top: 1px solid #ddd; }
.copyright { font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="header">
<a href="/" class="back-link">&larr; Back to syu.is</a>
</div>
<div class="app-header">
<img src="/static/app.png" alt="Aiat" class="app-icon">
<div class="app-name">Aiat</div>
<div class="app-version">v1.111.0</div>
</div>
<div class="section">
<p class="description">Aiat is a social networking application based on AT Protocol. Connect with your community on syu.is.</p>
</div>
<div class="section">
<div class="section-title">App Information</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Version</div>
<div class="info-value">1.111.0</div>
</div>
<div class="info-item">
<div class="info-label">Category</div>
<div class="info-value">Social</div>
</div>
<div class="info-item">
<div class="info-label">Supported OS</div>
<div class="info-value">iOS 26.0+</div>
</div>
<div class="info-item">
<div class="info-label">Price</div>
<div class="info-value">Free</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Developer</div>
<div class="developer-name">syui</div>
<div class="link-row">
<span class="link-icon">GitHub</span>
<a href="https://github.com/syui" class="link-value" target="_blank">github.com/syui</a>
<span class="link-arrow"></span>
</div>
<div class="link-row">
<span class="link-icon">ATProto</span>
<a href="https://syu.is/syui" class="link-value" target="_blank">syu.is/syui</a>
<span class="link-arrow"></span>
</div>
</div>
<div class="section">
<div class="section-title">Bitcoin</div>
<div class="bitcoin-row">
<span class="bitcoin-label"></span>
<span class="bitcoin-address" id="btc-address">3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr</span>
<span class="copy-btn" onclick="copyBTC()">copy</span>
</div>
</div>
<div class="footer">
<p class="copyright">© syui</p>
</div>
<script>
function copyBTC() {
const addr = document.getElementById('btc-address').textContent;
navigator.clipboard.writeText(addr).then(() => {
const btn = document.querySelector('.copy-btn');
btn.textContent = 'copied!';
btn.style.color = '#4CAF50';
setTimeout(() => {
btn.textContent = 'copy';
btn.style.color = '';
}, 2000);
});
}
</script>
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1005 B

32
icon.svg Normal file
View File

@@ -0,0 +1,32 @@
<svg
viewBox="0 0 2821.6379 794.29016"
preserveAspectRatio="xMidYMid"
xmlns:svg="http://www.w3.org/2000/svg">
<g
transform="matrix(0.1,0,0,-0.1,-282.80153,1445)"
fill="#000000"
stroke="none"
>
<path
d="m 24787,14443 c -4,-3 -7,-224 -7,-490 v -483 h 545 545 v 490 490 h -538 c -296,0 -542,-3 -545,-7 z"
/>
<path
d="m 5190,13285 c -8,-3 -96,-12 -195,-20 -199,-16 -296,-32 -430,-70 -49,-14 -115,-32 -145,-40 -153,-39 -504,-198 -662,-301 -21,-13 -57,-36 -80,-51 -24,-16 -72,-50 -109,-78 -241,-182 -377,-315 -528,-517 -119,-158 -120,-160 -106,-188 23,-45 140,-140 560,-457 110,-84 319,-242 465,-353 146,-110 369,-279 495,-375 791,-600 723,-549 1049,-799 269,-207 398,-307 524,-403 l 114,-86 -49,-49 c -128,-131 -378,-258 -588,-299 -66,-13 -357,-13 -420,0 -115,23 -172,39 -202,54 -18,10 -37,17 -43,17 -24,0 -171,81 -255,141 -50,35 -146,121 -215,192 -69,70 -133,127 -142,127 -19,0 -153,-63 -177,-83 -9,-7 -54,-35 -101,-62 -47,-26 -110,-64 -140,-85 -30,-20 -97,-61 -148,-91 -51,-30 -107,-62 -124,-72 -18,-10 -57,-38 -87,-61 -70,-53 -252,-168 -446,-281 -82,-49 -158,-95 -168,-104 -16,-16 -14,-21 34,-106 165,-289 544,-666 867,-860 9,-6 35,-22 58,-37 36,-23 267,-138 349,-173 108,-47 160,-67 240,-93 150,-48 201,-62 228,-62 14,0 64,-9 109,-19 197,-46 302,-56 573,-56 197,1 281,5 345,17 47,9 110,19 141,22 31,3 75,13 99,21 23,8 56,15 72,15 17,0 51,7 77,15 25,8 80,24 121,36 41,12 125,41 185,66 61,25 124,50 140,56 17,7 89,42 160,78 113,58 177,98 395,246 82,56 273,232 403,371 127,136 237,271 237,291 0,12 -208,179 -425,342 -186,140 -1121,843 -1720,1294 -264,199 -589,444 -723,546 -134,101 -274,208 -312,237 -39,29 -70,58 -70,66 0,21 107,115 203,179 95,64 237,133 295,143 20,4 49,12 63,20 96,48 519,48 619,-1 14,-7 41,-16 60,-20 65,-13 262,-118 360,-191 99,-74 250,-230 372,-384 34,-44 70,-80 80,-80 9,0 39,15 67,33 28,17 70,44 94,58 23,15 121,76 217,136 96,60 254,156 350,213 96,56 272,160 390,230 118,70 230,135 248,144 41,21 41,45 -3,116 -18,30 -46,75 -61,100 -64,106 -252,348 -352,454 -106,112 -169,171 -293,274 -197,164 -310,235 -594,376 -215,106 -519,205 -735,240 -137,22 -574,51 -610,41 z"
/>
<path
d="m 29095,12736 c -421,-44 -744,-157 -975,-342 -259,-207 -396,-446 -465,-809 -16,-84 -23,-301 -14,-425 36,-518 257,-859 694,-1070 166,-80 284,-116 716,-215 449,-103 514,-121 646,-186 218,-107 308,-253 306,-494 -2,-303 -188,-477 -588,-551 -140,-26 -640,-27 -825,-1 -275,38 -486,94 -776,203 -94,35 -174,64 -178,64 -3,0 -6,-175 -6,-389 v -388 l 88,-41 c 404,-187 905,-282 1486,-282 675,0 1154,150 1465,459 196,194 311,434 362,756 20,121 17,476 -5,600 -89,517 -358,800 -945,994 -130,43 -241,71 -616,156 -137,31 -299,73 -360,92 -331,106 -455,246 -455,511 1,249 127,412 387,501 272,92 801,76 1269,-39 116,-28 326,-96 432,-138 l 52,-22 -2,406 -3,406 -66,28 c -154,66 -413,140 -604,174 -279,49 -756,69 -1020,42 z"
/>
<path
d="m 9630,12676 c 0,-2 403,-996 896,-2208 493,-1211 898,-2211 901,-2220 6,-21 -135,-386 -193,-499 -104,-202 -256,-324 -471,-380 -118,-30 -340,-43 -516,-29 -84,6 -185,17 -225,24 -40,7 -75,10 -77,7 -3,-2 -5,-163 -5,-357 v -353 l 63,-30 c 194,-93 493,-138 801,-120 414,23 683,115 937,319 174,140 357,402 474,680 26,62 1945,5159 1945,5167 0,2 -232,2 -516,1 l -517,-3 -556,-1550 c -485,-1350 -560,-1550 -578,-1553 -18,-3 -26,11 -62,105 -23,59 -295,758 -605,1553 l -562,1445 -567,3 c -312,1 -567,0 -567,-2 z"
/>
<path
d="m 15690,10867 c 0,-1970 -1,-1936 56,-2153 128,-497 461,-787 1014,-885 147,-26 440,-36 605,-20 413,40 834,200 1181,447 35,25 73,44 88,44 14,0 26,-1 26,-3 0,-2 20,-94 45,-205 25,-111 45,-204 45,-207 0,-3 209,-5 465,-5 h 465 v 2400 2400 h -535 -535 l -2,-1866 -3,-1866 -115,-50 c -425,-185 -743,-252 -1088,-227 -302,21 -472,109 -562,293 -79,161 -74,11 -77,1969 l -3,1747 h -535 -535 z"
/>
<path
d="m 24785,12668 c -3,-7 -4,-1086 -3,-2398 l 3,-2385 538,-3 537,-2 v 2400 2400 h -535 c -419,0 -537,-3 -540,-12 z"
/>
<path
d="m 21660,8275 v -545 h 565 565 v 545 545 h -565 -565 z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -52,10 +52,6 @@ function at-repos-env() {
name=${host%%.*}
domain=${host##*.}
dport=5000
typeset -A PINNED_COMMITS
PINNED_COMMITS=()
#PINNED_COMMITS=( [indigo]="d49b454196351c988ceb5ce1f5e21b689487b5ab" [atproto]="104e6ed37b0589cc000109dc76316be35b2257e1")
}
# Arrays for patch management
@@ -72,10 +68,11 @@ typeset -a PATCH_FILES
PATCH_FILES=(
"170-pds-oauth-same-site-fix.patch"
"8980-social-app-disable-proxy.diff"
"disable-statsig-sdk.diff"
"140-social-app-yarn-network-timeout.patch"
"130-atproto-ozone-enable-daemon-v2.patch"
"190-bgs-disable-ratelimit.patch"
"200-feed-generator-custom.patch"
"210-bgs-since-empty-fix.patch"
)
function at-repos-clone() {
@@ -120,25 +117,6 @@ function at-repos-pull() {
cd ..
fi
done
# Copy feed-generator Dockerfile if missing (removed by git checkout)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
fi
cd $d
}
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
}
@@ -146,6 +124,29 @@ function at-repos-social-app-ios-patch() {
$d/ios/setup.zsh
}
function at-repos-social-app-avatar-write() {
did_admin=did:plc:6qyecktefllvenje24fcxnie
dt=$d/repos/social-app/src
cd $dt
grep -R syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/syu.is/${host}/g"
grep -R web.syu.is .|cut -d : -f 1|sort -u|xargs sediment "s/web.syu.is/web.${host}/g"
f=$dt/lib/constants.ts
sediment "s#export const BSKY_SERVICE = 'https://bsky.social'#export const BSKY_SERVICE = 'https://${host}'#g" $f
sediment "s#export const BSKY_SERVICE_DID = 'did:web:bsky.social'#export const BSKY_SERVICE_DID = 'did:web:${host}'#g" $f
sediment "s#export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'#export const PUBLIC_BSKY_SERVICE = 'https://bsky.${host}'#g" $f
sediment "s#export const PUBLIC_APPVIEW = 'https://api.bsky.app'#export const PUBLIC_APPVIEW = 'https://bsky.${host}'#g" $f
sediment "s#export const PUBLIC_APPVIEW_DID = 'did:web:api.bsky.app'#export const PUBLIC_APPVIEW_DID = 'did:web:bsky.${host}'#g" $f
f=$dt/view/com/util/UserAvatar.tsx
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/view/com/util/UserAvatar.tsx -o $f
sediment "s#/img/avatar/plain/#https://cdn.web.syu.is/img/avatar/plain/#g" $f
sediment "s#/img/avatar_thumbnail/plain/#https://bsky.${host}/img/avatar/plain/#g" $f
sediment "s#source={{uri: avatar}}#source={{ uri: hackModifyThumbnailPath(avatar, 1 > 0), }}#g" $f
curl -sL https://raw.githubusercontent.com/bluesky-social/social-app/refs/heads/main/src/lib/strings/url-helpers.ts -o $dt/lib/strings/url-helpers.ts
sediment "s#https://go.web.syu.is/redirect?u=\${encodeURIComponent(url)}#\${url}#g" $dt/lib/strings/url-helpers.ts
grep -R $did_admin .|cut -d : -f 1|sort -u|xargs sediment "s/${did_admin}/${did}/g"
}
# Common patch function with status detection
function apply-patch() {
local patch_name=$1
@@ -267,21 +268,16 @@ function at-repos-patch-apply-all() {
local repo=""
# Determine repo from filename
# Note: check indigo/bgs BEFORE pds to avoid "newpds" matching "pds"
if [[ $filename == *"social-app"* || $filename == *"statsig"* ]]; then
repo="social-app"
elif [[ $filename == *"indigo"* || $filename == *"bgs"* ]]; then
repo="indigo"
elif [[ $filename == *"atproto"* ]]; then
repo="atproto"
elif [[ $filename == *"pds"* ]]; then
repo="atproto"
elif [[ $filename == *"indigo"* || $filename == *"bgs"* ]]; then
repo="indigo"
elif [[ $filename == *"feed"* ]]; then
repo="feed-generator"
# feed-generatorパッチ適用前に既存のDockerfileを削除upstreamと競合回避
if [[ $filename == "200-feed-generator-custom.patch" ]]; then
rm -f "$d/repos/feed-generator/Dockerfile"
fi
fi
patch-apply "$title" "$repo" "$filename"
@@ -334,169 +330,20 @@ export const SOCIAL_APP_DOMAIN =\
}' lib/constants.ts 2>/dev/null || true
# Fix parseInt() to handle undefined by adding || ''
sediment "s/parseInt(env('\([^']*\)'))/parseInt(env('\1') || '0')/g" lib/constants.ts 2>/dev/null || true
# Add next-runtime-env to package.json if missing (used by lib/constants.ts)
if ! grep -q 'next-runtime-env' package.json 2>/dev/null; then
echo "📦 Adding next-runtime-env to package.json..."
python3 -c "
import json
with open('package.json') as f:
d = json.load(f)
d.setdefault('dependencies', {})['next-runtime-env'] = '^1.6.2'
with open('package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
"
echo "✅ Added next-runtime-env"
fi
# Update @atproto/ozone in service/package.json to latest
echo "📦 Updating @atproto/ozone in service/package.json..."
local latest_ozone_ver
latest_ozone_ver=$(npm view @atproto/ozone version 2>/dev/null)
if [ -n "$latest_ozone_ver" ] && [ -f service/package.json ]; then
python3 -c "
import json, sys
ver = sys.argv[1]
with open('service/package.json') as f:
d = json.load(f)
old = d.get('dependencies', {}).get('@atproto/ozone', '')
d.setdefault('dependencies', {})['@atproto/ozone'] = ver
with open('service/package.json', 'w') as f:
json.dump(d, f, indent=2)
f.write('\n')
print(f'✅ @atproto/ozone: {old} -> {ver}')
" "$latest_ozone_ver"
fi
popd > /dev/null
}
function at-repos-feed-generator-newfiles() {
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Creating feed-generator new files..."
# Create app.ts
cat > $d/repos/feed-generator/src/algos/app.ts <<'EOF'
import { QueryParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
import { AppContext } from '../config'
// max 15 chars
export const shortname = 'app'
export const handler = async (ctx: AppContext, params: QueryParams) => {
let builder = ctx.db
.selectFrom('post')
.selectAll()
.orderBy('indexedAt', 'desc')
.orderBy('cid', 'desc')
.limit(params.limit)
if (params.cursor) {
const timeStr = new Date(parseInt(params.cursor, 10)).toISOString()
builder = builder.where('post.indexedAt', '<', timeStr)
}
const res = await builder.execute()
const feed = res.map((row) => ({
post: row.uri,
}))
let cursor: string | undefined
const last = res.at(-1)
if (last) {
cursor = new Date(last.indexedAt).getTime().toString(10)
}
return {
cursor,
feed,
}
}
EOF
echo "✅ Created src/algos/app.ts"
# Restore Dockerfile (removed during patch apply to avoid conflicts)
if [ ! -f $d/repos/feed-generator/Dockerfile ] && [ -f $d/docker/feed/Dockerfile ];then
cp -rf $d/docker/feed/Dockerfile $d/repos/feed-generator/
echo "✅ Restored Dockerfile"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
function at-repos-docker-verify() {
local service=$1
local image="at-${service}:latest"
local check_file=""
case $service in
pds) check_file="/app/services/pds/index.js" ;;
bsky) check_file="/app/services/bsky/api.js" ;;
ozone) check_file="/app/services/ozone/api.js" ;;
bgs) check_file="/relay" ;;
plc) check_file="/app/packages/server/dist/index.js" ;;
*) return 0 ;;
esac
local cid
cid=$(docker create --entrypoint "" "$image" true 2>&1)
if [ $? -ne 0 ]; then
echo " ❌ FAILED: cannot create container from $image"
echo " $cid"
return 1
fi
if docker cp "$cid:$check_file" /tmp/.docker-verify-tmp 2>/dev/null; then
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ✅ Verified: $check_file exists"
return 0
else
rm -f /tmp/.docker-verify-tmp
docker rm "$cid" > /dev/null 2>&1
echo " ❌ FAILED: $check_file not found in $image"
return 1
fi
}
function at-repos-build-docker-atproto() {
cd $d
local failed=()
docker image prune -a
if [ -z "$1" ];then
for ((i=1; i<=${#services}; i++)); do
service=${services[$i]}
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔨 Building: $service"
if ! docker compose build --no-cache $service; then
echo " ❌ Build failed: $service"
failed+=($service)
continue
fi
if ! at-repos-docker-verify $service; then
failed+=($service)
continue
fi
if [ "$service" = "ozone" ]; then
docker compose build --no-cache ${service}-web
fi
docker compose build --no-cache $service
done
else
echo "🔨 Building: $1"
if ! docker compose build --no-cache $1; then
echo "❌ Build failed: $1"
return 1
fi
if ! at-repos-docker-verify $1; then
return 1
fi
docker compose build --no-cache $1
fi
if [ ${#failed[@]} -gt 0 ]; then
echo ""
echo "❌ Failed builds: ${failed[*]}"
echo "⚠️ Do NOT push these images."
return 1
fi
echo ""
echo "✅ All builds verified successfully."
}
function at-repos-push-reset() {
@@ -514,41 +361,25 @@ function at-repos-push-reset() {
}
function at-repos-push-docker() {
local dtag=$(date +%Y%m%d)
if [ -z "$1" ] || [ "$1" = "push" ]; then
for service in "${services[@]}"; do
if ! at-repos-docker-verify $service; then
echo "⚠️ Skipping push: $service (verification failed)"
continue
fi
docker tag at-${service}:latest localhost:${dport}/${service}:latest
docker tag at-${service}:latest localhost:${dport}/${service}:${dtag}
docker push localhost:${dport}/${service}:latest
docker push localhost:${dport}/${service}:${dtag}
if [ "$service" = "ozone" ]; then
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:latest
docker tag at-${service}-web:latest localhost:${dport}/${service}-web:${dtag}
docker push localhost:${dport}/${service}-web:latest
docker push localhost:${dport}/${service}-web:${dtag}
fi
done
else
if ! at-repos-docker-verify $1; then
echo "❌ Push aborted: $1 (verification failed)"
return 1
fi
docker tag at-${1}:latest localhost:${dport}/${1}:latest
docker tag at-${1}:latest localhost:${dport}/${1}:${dtag}
docker push localhost:${dport}/${1}:latest
docker push localhost:${dport}/${1}:${dtag}
fi
echo "📦 Pushed with tags: latest, ${dtag}"
}
function at-repos-pull-docker() {
cd $d
docker image prune -a
docker compose up -d --pull always
echo "💡 Run 'docker image prune' manually to clean up old images."
}
function at-repos-reset-bgs-db() {
@@ -579,7 +410,6 @@ 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;"
# host=pds:3000
echo "🔗 Registering Trusted Domain..."
# Retry loop for addTrustedDomain as BGS might still be warming up
for i in {1..5}; do
@@ -690,179 +520,8 @@ curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $
https://${host}/xrpc/com.atproto.repo.putRecord
}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Patch creation helpers
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Find existing patches that modify the same file
# Usage: at-patch-find-conflicts <filepath> <patch-dir>
function at-patch-find-conflicts() {
local filepath="$1"
local patch_dir="$2"
local conflicts=()
if [ ! -d "$patch_dir" ]; then
return
fi
for pf in "$patch_dir"/*.patch(N) "$patch_dir"/*.diff(N); do
[ -f "$pf" ] || continue
if grep -q "^--- a/$filepath" "$pf" 2>/dev/null || grep -q "^+++ b/$filepath" "$pf" 2>/dev/null; then
conflicts+=("$(basename "$pf")")
fi
done
if [ ${#conflicts[@]} -gt 0 ]; then
echo "⚠️ This file is also modified by:"
for c in "${conflicts[@]}"; do
echo " - $c"
done
echo ""
echo " Ensure patches are applied in order before patch-begin."
echo " Baseline must reflect the post-earlier-patches state."
fi
}
# Save current file state as baseline for patch creation
# Usage: ./install.zsh patch-begin <repo> <file-path> [--ios]
# Example: ./install.zsh patch-begin social-app "src/screens/Profile/Header/ProfileHeaderStandard.tsx" --ios
function at-patch-begin() {
local repo="$1"
local filepath="$2"
local flag="$3"
if [ -z "$repo" ] || [ -z "$filepath" ]; then
echo "Usage: ./install.zsh patch-begin <repo> <file-path> [--ios]"
echo "Example: ./install.zsh patch-begin social-app \"src/screens/Profile/Header/ProfileHeaderStandard.tsx\" --ios"
return 1
fi
local full_path="$d/repos/$repo/$filepath"
if [ ! -f "$full_path" ]; then
echo "❌ File not found: $full_path"
return 1
fi
local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')"
cp "$full_path" "$tmp_file"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Baseline saved: $tmp_file"
echo " File: $full_path"
echo ""
# Check for conflicting patches
if [ "$flag" = "--ios" ]; then
at-patch-find-conflicts "$filepath" "$d/ios/patching"
else
at-patch-find-conflicts "$filepath" "$d/patching"
fi
echo "Next steps:"
echo " 1. Edit: $full_path"
echo " 2. Save: ./install.zsh patch-save <NNN-name.patch> $repo \"$filepath\" ${flag:---ios}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Generate patch from baseline diff
# Usage: ./install.zsh patch-save <patch-filename> <repo> <file-path> [--ios]
# Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app "src/path/to/file.tsx" --ios
function at-patch-save() {
local patch_filename="$1"
local repo="$2"
local filepath="$3"
local flag="$4"
if [ -z "$patch_filename" ] || [ -z "$repo" ] || [ -z "$filepath" ]; then
echo "Usage: ./install.zsh patch-save <patch-filename> <repo> <file-path> [--ios]"
echo "Example: ./install.zsh patch-save 042-social-app-ios-feature.patch social-app \"src/file.tsx\" --ios"
return 1
fi
local full_path="$d/repos/$repo/$filepath"
local tmp_file="/tmp/patch-base--$(echo "$filepath" | tr '/' '-')"
if [ ! -f "$tmp_file" ]; then
echo "❌ No baseline found. Run 'patch-begin' first."
return 1
fi
if [ ! -f "$full_path" ]; then
echo "❌ File not found: $full_path"
return 1
fi
# Determine output directory
local patch_dir="$d/patching"
if [ "$flag" = "--ios" ]; then
patch_dir="$d/ios/patching"
fi
# Generate diff with proper a/b paths
diff -u "$tmp_file" "$full_path" \
| sed "1s|--- .*|--- a/$filepath|" \
| sed "2s|+++ .*|+++ b/$filepath|" \
> "$patch_dir/$patch_filename"
local line_count
line_count=$(wc -l < "$patch_dir/$patch_filename" | tr -d ' ')
if [ "$line_count" -eq 0 ]; then
echo "⚠️ No differences found. Patch file is empty."
rm "$patch_dir/$patch_filename"
return 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Patch: $patch_dir/$patch_filename ($line_count lines)"
# Dry-run verify: restore baseline, test patch, restore edit
pushd "$d/repos/$repo" > /dev/null
cp "$full_path" /tmp/patch-edited-tmp
cp "$tmp_file" "$full_path"
if patch --dry-run -p1 < "$patch_dir/$patch_filename" > /dev/null 2>&1; then
echo "✅ Dry-run: OK"
else
echo "❌ Dry-run: FAILED"
patch --dry-run -p1 < "$patch_dir/$patch_filename" 2>&1 | head -5
fi
cp /tmp/patch-edited-tmp "$full_path"
rm -f /tmp/patch-edited-tmp
popd > /dev/null
rm -f "$tmp_file"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
# Verify all patches can be applied (full dry-run)
# Usage: ./install.zsh patch-check [--ios]
function at-patch-check() {
local flag="$1"
if [ "$flag" = "--ios" ]; then
echo "Checking iOS patches against: $d/repos/social-app"
pushd "$d/repos/social-app" > /dev/null
for pf in "$d/ios/patching"/*.patch; do
[ -f "$pf" ] || continue
local name="$(basename "$pf")"
if patch --dry-run -p1 < "$pf" > /dev/null 2>&1; then
echo "$name"
else
echo "$name"
fi
done
popd > /dev/null
else
echo "Checking server patches..."
for pf in "$d/patching"/*.patch "$d/patching"/*.diff; do
[ -f "$pf" ] || continue
echo " $(basename "$pf")"
done
fi
}
at-repos-env
case "$1" in
case "$1" in
pull)
at-repos-clone
at-repos-pull
@@ -872,22 +531,9 @@ case "$1" in
at-repos-social-app-ios-patch
at-repos-patch-apply-all
at-repos-ozone-patch
at-repos-feed-generator-newfiles
show-failed-patches
exit
;;
patch-begin)
at-patch-begin "$2" "$3" "$4"
exit
;;
patch-save)
at-patch-save "$2" "$3" "$4" "$5"
exit
;;
patch-check)
at-patch-check "$2"
exit
;;
build)
at-repos-build-docker-atproto $2
exit
@@ -913,7 +559,7 @@ esac
case "`cat /etc/hostname`" in
at)
if [ "$1" = "bgs-reset" ];then
# at-repos-reset-bgs-db
at-repos-reset-bgs-db
exit
fi
at-repos-pull-docker
@@ -922,11 +568,10 @@ case "`cat /etc/hostname`" in
*)
at-repos-clone
at-repos-pull
at-repos-checkout-pinned
at-repos-social-app-ios-patch
#at-repos-social-app-avatar-write
at-repos-patch-apply-all
at-repos-ozone-patch
at-repos-feed-generator-newfiles
show-failed-patches
at-repos-build-docker-atproto
at-repos-push-docker

0
ios/.keep Normal file
View File

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 821 B

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 821 B

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M237.9 461.4C237.9 463.4 235.6 465 232.7 465C229.4 465.3 227.1 463.7 227.1 461.4C227.1 459.4 229.4 457.8 232.3 457.8C235.3 457.5 237.9 459.1 237.9 461.4zM206.8 456.9C206.1 458.9 208.1 461.2 211.1 461.8C213.7 462.8 216.7 461.8 217.3 459.8C217.9 457.8 216 455.5 213 454.6C210.4 453.9 207.5 454.9 206.8 456.9zM251 455.2C248.1 455.9 246.1 457.8 246.4 460.1C246.7 462.1 249.3 463.4 252.3 462.7C255.2 462 257.2 460.1 256.9 458.1C256.6 456.2 253.9 454.9 251 455.2zM316.8 72C178.1 72 72 177.3 72 316C72 426.9 141.8 521.8 241.5 555.2C254.3 557.5 258.8 549.6 258.8 543.1C258.8 536.9 258.5 502.7 258.5 481.7C258.5 481.7 188.5 496.7 173.8 451.9C173.8 451.9 162.4 422.8 146 415.3C146 415.3 123.1 399.6 147.6 399.9C147.6 399.9 172.5 401.9 186.2 425.7C208.1 464.3 244.8 453.2 259.1 446.6C261.4 430.6 267.9 419.5 275.1 412.9C219.2 406.7 162.8 398.6 162.8 302.4C162.8 274.9 170.4 261.1 186.4 243.5C183.8 237 175.3 210.2 189 175.6C209.9 169.1 258 202.6 258 202.6C278 197 299.5 194.1 320.8 194.1C342.1 194.1 363.6 197 383.6 202.6C383.6 202.6 431.7 169 452.6 175.6C466.3 210.3 457.8 237 455.2 243.5C471.2 261.2 481 275 481 302.4C481 398.9 422.1 406.6 366.2 412.9C375.4 420.8 383.2 435.8 383.2 459.3C383.2 493 382.9 534.7 382.9 542.9C382.9 549.4 387.5 557.3 400.2 555C500.2 521.8 568 426.9 568 316C568 177.3 455.5 72 316.8 72zM169.2 416.9C167.9 417.9 168.2 420.2 169.9 422.1C171.5 423.7 173.8 424.4 175.1 423.1C176.4 422.1 176.1 419.8 174.4 417.9C172.8 416.3 170.5 415.6 169.2 416.9zM158.4 408.8C157.7 410.1 158.7 411.7 160.7 412.7C162.3 413.7 164.3 413.4 165 412C165.7 410.7 164.7 409.1 162.7 408.1C160.7 407.5 159.1 407.8 158.4 408.8zM190.8 444.4C189.2 445.7 189.8 448.7 192.1 450.6C194.4 452.9 197.3 453.2 198.6 451.6C199.9 450.3 199.3 447.3 197.3 445.4C195.1 443.1 192.1 442.8 190.8 444.4zM179.4 429.7C177.8 430.7 177.8 433.3 179.4 435.6C181 437.9 183.7 438.9 185 437.9C186.6 436.6 186.6 434 185 431.7C183.6 429.4 181 428.4 179.4 429.7z"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M453.2 112L523.8 112L369.6 288.2L551 528L409 528L297.7 382.6L170.5 528L99.8 528L264.7 339.5L90.8 112L236.4 112L336.9 244.9L453.2 112zM428.4 485.8L467.5 485.8L215.1 152L173.1 152L428.4 485.8z"/></svg>

Before

Width:  |  Height:  |  Size: 421 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M581.7 188.1C575.5 164.4 556.9 145.8 533.4 139.5C490.9 128 320.1 128 320.1 128C320.1 128 149.3 128 106.7 139.5C83.2 145.8 64.7 164.4 58.4 188.1C47 231 47 320.4 47 320.4C47 320.4 47 409.8 58.4 452.7C64.7 476.3 83.2 494.2 106.7 500.5C149.3 512 320.1 512 320.1 512C320.1 512 490.9 512 533.5 500.5C557 494.2 575.5 476.3 581.8 452.7C593.2 409.8 593.2 320.4 593.2 320.4C593.2 320.4 593.2 231 581.8 188.1zM264.2 401.6L264.2 239.2L406.9 320.4L264.2 401.6z"/></svg>

Before

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

138
ios/build.sh Executable file
View File

@@ -0,0 +1,138 @@
#!/bin/zsh
set -e
SCRIPT_DIR=${0:a:h}
cd "$SCRIPT_DIR"
source .env
# 絶対パスに変換
REPO_DIR="$SCRIPT_DIR/../repos/social-app"
APP_NAME="Aiat"
WORKSPACE="$REPO_DIR/ios/${APP_NAME}.xcworkspace"
SCHEME="$APP_NAME"
BUILD_DIR="$REPO_DIR/build"
MOBILEPROVISION="$SCRIPT_DIR/store.mobileprovision"
ASSETS_DIR="$SCRIPT_DIR/assets"
echo "Running iOS preview workflow..."
cd "$REPO_DIR"
# 0. Environment Setup (Fix Node Version)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
echo "Checking Node version..."
if command -v nvm >/dev/null; then
nvm use 22 || nvm use 20 || echo "Warning: Could not switch to Node 22/20. Current: $(node -v)"
else
echo "nvm not found, using system node: $(node -v)"
fi
# 1. Install dependencies
echo "1. Installing dependencies (yarn)..."
yarn install
# 1.5. Copy assets
echo "1.5. Copying assets..."
if [ -d "$ASSETS_DIR" ]; then
cp -rf "$ASSETS_DIR/"* "$REPO_DIR/assets/"
echo "✅ Copied all assets (including logo.png, logo-1024.png)"
else
echo "⚠️ Warning: $ASSETS_DIR not found"
fi
function cleanup_build {
# 2. Prebuild (Generate ios directory)
echo "2. Running Expo Prebuild..."
# Clean old ios folder to remove old entitlements/AppClip targets
rm -rf ios
npx expo prebuild --platform ios --clean
# 3. CocoaPods
echo "3. Installing CocoaPods..."
# Ensure PATH includes Homebrew ruby gems if needed
export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
cd ios
pod install
cd ..
# 4. Signing (Manual Step)
echo "4. Opening Xcode for Signing..."
XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
# Fallback search if variable name logic differs
if [ ! -d "$XCODE_PROJ" ]; then
XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
fi
open "$XCODE_PROJ"
read
}
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 \
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"

View File

@@ -1,212 +0,0 @@
#!/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..."
if [ -z "$1" ];then
cd ios
pod install
cd ..
fi
# 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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>production</string>
<key>com.apple.security.application-groups</key>
<array>
<string>${APP_GROUP}</string>
</array>
</dict>
</plist>
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)
echo "Skipping cleanup_build..."
;;
*)
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
# 親ディレクトリからコピーを試みる
PARENT_MOBILEPROVISION="$SCRIPT_DIR/../embedded.mobileprovision"
if [ -f "$PARENT_MOBILEPROVISION" ]; then
echo "Copying mobileprovision from $PARENT_MOBILEPROVISION to $MOBILEPROVISION"
cp "$PARENT_MOBILEPROVISION" "$MOBILEPROVISION"
else
echo "Error: store.mobileprovision not found at $MOBILEPROVISION or $PARENT_MOBILEPROVISION"
exit 1
fi
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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,7 +1,9 @@
diff --git a/app.config.js b/app.config.js
index 246d8abd3..ed8f7b2b2 100644
--- a/app.config.js
+++ b/app.config.js
@@ -23,10 +23,7 @@
const IS_DEV = !IS_TESTFLIGHT && !IS_PRODUCTION
@@ -18,10 +18,7 @@ module.exports = function (_config) {
const IS_DEV = !IS_TESTFLIGHT || !IS_PRODUCTION
const ASSOCIATED_DOMAINS = [
- 'applinks:bsky.app',
@@ -12,7 +14,7 @@
// When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
...(IS_DEV || IS_TESTFLIGHT ? [] : []),
]
@@ -38,27 +35,25 @@
@@ -33,27 +30,25 @@ module.exports = function (_config) {
return {
expo: {
version: VERSION,
@@ -27,7 +29,7 @@
policy: 'appVersion',
},
- icon: './assets/app-icons/ios_icon_default_next.png',
+ icon: './assets/app-icon.png',
+ icon: './assets/logo.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
@@ -43,25 +45,25 @@
- 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/app-icon.png',
+ icon: './assets/logo.png',
infoPlist: {
UIBackgroundModes: ['remote-notification'],
NSCameraUsageDescription:
@@ -118,7 +113,7 @@
@@ -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',
// 'com.apple.developer.device-information.user-assigned-device-name': true,
},
privacyManifests: {
@@ -181,14 +176,14 @@
NSPrivacyCollectedDataTypes: [
@@ -175,14 +169,14 @@ module.exports = function (_config) {
barStyle: 'light-content',
},
android: {
- icon: './assets/app-icons/android_icon_default_next.png',
+ icon: './assets/app-icon.png',
+ icon: './assets/logo.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-monochrome.png',
@@ -73,16 +75,38 @@
intentFilters: [
{
action: 'VIEW',
@@ -196,7 +191,7 @@
@@ -190,7 +184,7 @@ module.exports = function (_config) {
data: [
{
scheme: 'https',
- host: 'bsky.app',
+ host: 'syu.is',
},
...(IS_DEV
? [
@@ -280,7 +275,6 @@
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,
},
],
@@ -90,7 +114,7 @@
'./plugins/withGradleJVMHeapSizeIncrease.js',
'./plugins/withAndroidManifestLargeHeapPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js',
@@ -288,8 +282,6 @@
@@ -272,8 +265,6 @@ module.exports = function (_config) {
'./plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidDayNightThemePlugin.js',
'./plugins/withAndroidNoJitpackPlugin.js',
@@ -99,7 +123,16 @@
[
'expo-font',
{
@@ -417,30 +409,7 @@
@@ -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: {

View File

@@ -38,7 +38,7 @@ index 231447b4f..a44b3da05 100644
-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 = '/support/license'
+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'
@@ -81,7 +81,8 @@ index 231447b4f..a44b3da05 100644
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app'
export const VIDEO_FEED_URI =
'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/thevids'
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/thevids'
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app'
export const STAGING_VIDEO_FEED_URI =
'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids'
export const VIDEO_FEED_URIS = [VIDEO_FEED_URI, STAGING_VIDEO_FEED_URI]
@@ -102,8 +103,8 @@ index 231447b4f..a44b3da05 100644
export const webLinks = {
- tos: `https://bsky.social/about/support/tos`,
- privacy: `https://bsky.social/about/support/privacy-policy`,
+ tos: `/support/tos`,
+ privacy: `/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`,
}
@@ -146,7 +147,7 @@ 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}) {
@@ -201,14 +201,7 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
// for the ones we know need it
// -prf
export const KNOWN_AUTHED_ONLY_FEEDS = [
@@ -158,8 +159,9 @@ index de1e92533..3d1566800 100644
- 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky
- 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz
- 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why
+ 'at://did:plc:6qyecktefllvenje24fcxnie/app.bsky.feed.generator/app', // app feed, by syu.is
]
type GetPopularFeedsOptions = {limit?: number; enabled?: boolean}
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 0cf6ab546..399e592bc 100644
@@ -214,4 +216,3 @@ index 620382175..928480da2 100644
style={[a.text_md]}>
Discover
</InlineLinkText>{' '}

View File

@@ -1,75 +1,72 @@
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 2c4d6fa41..b69e2b18d 100644
index fb3008627..539ebc055 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -95,7 +95,7 @@ if (isAndroid) {
@@ -92,7 +92,7 @@ if (isAndroid) {
* Begin geolocation ASAP
*/
Geo.resolve()
-prefetchAgeAssuranceConfig()
+// // // prefetchAgeAssuranceConfig()
prefetchLiveEvents()
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
diff --git a/src/routes.ts b/src/routes.ts
index f325539c7..3e2c7b3eb 100644
index 1ed913bb2..c80340edb 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -72,9 +72,11 @@ export const router = new Router<AllNavigatableRoutes>({
FindContactsSettings: '/settings/find-contacts',
@@ -71,8 +71,8 @@ export const router = new Router<AllNavigatableRoutes>({
MiscellaneousNotificationSettings: '/settings/notifications/miscellaneous',
// support
Support: '/support',
- PrivacyPolicy: '/support/privacy',
- TermsOfService: '/support/tos',
+ PrivacyPolicy: ['/support/privacy-policy', '/about/support/privacy-policy'],
+ TermsOfService: ['/support/tos', '/about/support/tos'],
+ PrivacyPolicy: 'https://syu.is/about/support/privacy-policy',
+ TermsOfService: 'https://syu.is/about/support/tos',
CommunityGuidelines: '/support/community-guidelines',
+ License: ['/support/license', '/about/support/license'],
+ AppInfo: '/support/app',
CopyrightPolicy: '/support/copyright',
// hashtags
Hashtag: '/hashtag/:tag',
diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts
index 5c8ce3b97..ee85beb08 100644
--- a/src/state/session/agent.ts
+++ b/src/state/session/agent.ts
@@ -47,7 +47,8 @@ export function createPublicAgent() {
configureModerationForGuest() // Side effect but only relevant for tests
const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE})
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent
}
@@ -88,7 +89,8 @@ export async function createAgentAndResume(
// after session is attached
const aa = prefetchAgeAssuranceData({agent})
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare({
resolvers: [gates, moderation, aa],
@@ -127,7 +129,8 @@ export async function createAgentAndLogin(
const moderation = configureModerationForAccount(agent, account)
const aa = prefetchAgeAssuranceData({agent})
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare({
resolvers: [gates, moderation, aa],
@@ -299,7 +302,8 @@ export async function createAgentAndCreateAccount(
logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`})
}
- agent.configureProxy(BLUESKY_PROXY_HEADER.get())
+ // Disable proxy for self-hosted environments
+ // agent.configureProxy(BLUESKY_PROXY_HEADER.get())
return agent.prepare({
resolvers: [gates, moderation, aa],

View File

@@ -1,47 +1,460 @@
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 6b8257b91..35202224b 100644
--- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx
@@ -79,7 +79,7 @@
@@ -80,7 +80,7 @@ export function AboutSettingsScreen({}: Props) {
<Layout.Content>
<SettingsList.Container>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/tos"
+ to="/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms of Service`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
@@ -87,7 +87,7 @@
@@ -88,7 +88,7 @@ export function AboutSettingsScreen({}: Props) {
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
- to="https://bsky.social/about/support/privacy-policy"
+ to="/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy"
label={_(msg`Privacy Policy`)}>
<SettingsList.ItemIcon icon={NewspaperIcon} />
<SettingsList.ItemText>
diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx
index dd319a4c6..0e80f956a 100644
--- a/src/screens/Takendown.tsx
+++ b/src/screens/Takendown.tsx
@@ -210,10 +210,10 @@
@@ -223,11 +223,11 @@ export function Takendown() {
<Trans>
Your account was found to be in violation of the{' '}
<SimpleInlineLinkText
<InlineLinkText
- label={_(msg`Bluesky Social Terms of Service`)}
- to="https://bsky.social/about/support/tos"
+ label={_(msg`syu.is Terms of Service`)}
+ to="/support/tos"
style={[a.text_md, a.leading_snug]}>
+ to="https://syu.is/about/support/tos"
style={[a.text_md, a.leading_normal]}
overridePresentation>
- Bluesky Social Terms of Service
+ syu.is Terms of Service
</SimpleInlineLinkText>
</InlineLinkText>
. You have been sent an email outlining the specific violation
and suspension period, if applicable. You can appeal this
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index e058e2883..0f583c915 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -1,23 +1,16 @@
import React from 'react'
import {ActivityIndicator, StyleSheet} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
-
import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
-import {
- type HomeTabNavigatorParams,
- type NativeStackScreenProps,
-} from '#/lib/routes/types'
+import {type HomeTabNavigatorParams, type NativeStackScreenProps} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig'
import {isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events'
-import {
- type SavedFeedSourceInfo,
- usePinnedFeedsInfos,
-} from '#/state/queries/feed'
+import {type SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
@@ -27,11 +20,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {FeedPage} from '#/view/com/feeds/FeedPage'
import {HomeHeader} from '#/view/com/home/HomeHeader'
-import {
- Pager,
- type PagerRef,
- type RenderTabBarFnProps,
-} from '#/view/com/pager/Pager'
+import {Pager, type PagerRef, type RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
@@ -39,97 +28,60 @@ import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
import * as Layout from '#/components/Layout'
import {useDemoMode} from '#/storage/hooks/demo-mode'
+const DEFAULT_PINNED_FEEDS = [{
+ feedDescriptor: 'following',
+ displayName: 'Following',
+ id: 'following',
+ type: 'feed',
+ savedFeed: undefined,
+ pinned: true,
+}]
+
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
export function HomeScreen(props: Props) {
const {setShowLoggedOut} = useLoggedOutViewControls()
const {data: preferences} = usePreferencesQuery()
const {currentAccount} = useSession()
- const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
- usePinnedFeedsInfos()
+ const {data: pinnedFeedInfos} = usePinnedFeedsInfos()
+
+ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any
+ const safePinnedFeedInfos = pinnedFeedInfos || DEFAULT_PINNED_FEEDS
React.useEffect(() => {
if (isWeb && !currentAccount) {
const getParams = new URLSearchParams(window.location.search)
const splash = getParams.get('splash')
- if (splash === 'true') {
- setShowLoggedOut(true)
- return
- }
+ if (splash === 'true') { setShowLoggedOut(true); return }
}
-
const params = props.route.params
- if (
- currentAccount &&
- props.route.name === 'Start' &&
- params?.name &&
- params?.rkey
- ) {
- props.navigation.navigate('StarterPack', {
- rkey: params.rkey,
- name: params.name,
- })
+ if (currentAccount && props.route.name === 'Start' && params?.name && params?.rkey) {
+ props.navigation.navigate('StarterPack', { rkey: params.rkey, name: params.name })
}
- }, [
- currentAccount,
- props.navigation,
- props.route.name,
- props.route.params,
- setShowLoggedOut,
- ])
+ }, [currentAccount, props.navigation, props.route.name, props.route.params, setShowLoggedOut])
- if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
- return (
- <Layout.Screen testID="HomeScreen">
- <HomeScreenReady
- {...props}
- preferences={preferences}
- pinnedFeedInfos={pinnedFeedInfos}
- />
- </Layout.Screen>
- )
- } else {
- return (
- <Layout.Screen>
- <Layout.Center style={styles.loading}>
- <ActivityIndicator size="large" />
- </Layout.Center>
- </Layout.Screen>
- )
- }
+ return (
+ <Layout.Screen testID="HomeScreen">
+ <HomeScreenReady {...props} preferences={safePreferences} pinnedFeedInfos={safePinnedFeedInfos as any} />
+ </Layout.Screen>
+ )
}
-function HomeScreenReady({
- preferences,
- pinnedFeedInfos,
-}: Props & {
- preferences: UsePreferencesQueryResponse
- pinnedFeedInfos: SavedFeedSourceInfo[]
-}) {
- const allFeeds = React.useMemo(
- () => pinnedFeedInfos.map(f => f.feedDescriptor),
- [pinnedFeedInfos],
- )
- const maybeRawSelectedFeed: FeedDescriptor | undefined =
- useSelectedFeed() ?? allFeeds[0]
+function HomeScreenReady({preferences, pinnedFeedInfos}: any) {
+ const allFeeds = React.useMemo(() => pinnedFeedInfos.map(f => f.feedDescriptor), [pinnedFeedInfos])
+ const maybeRawSelectedFeed = useSelectedFeed() ?? allFeeds[0]
const setSelectedFeed = useSetSelectedFeed()
const maybeFoundIndex = allFeeds.indexOf(maybeRawSelectedFeed)
const selectedIndex = Math.max(0, maybeFoundIndex)
- const maybeSelectedFeed: FeedDescriptor | undefined = allFeeds[selectedIndex]
+ const maybeSelectedFeed = allFeeds[selectedIndex]
const requestNotificationsPermission = useRequestNotificationsPermission()
-
+
useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
useOTAUpdates()
-
- React.useEffect(() => {
- requestNotificationsPermission('Home')
- }, [requestNotificationsPermission])
+ React.useEffect(() => { requestNotificationsPermission('Home') }, [requestNotificationsPermission])
const pagerRef = React.useRef<PagerRef>(null)
const lastPagerReportedIndexRef = React.useRef(selectedIndex)
React.useLayoutEffect(() => {
- // Since the pager is not a controlled component, adjust it imperatively
- // if the selected index gets out of sync with what it last reported.
- // This is supposed to only happen on the web when you use the right nav.
if (selectedIndex !== lastPagerReportedIndexRef.current) {
lastPagerReportedIndexRef.current = selectedIndex
pagerRef.current?.setPage(selectedIndex)
@@ -138,205 +90,43 @@ function HomeScreenReady({
const {hasSession} = useSession()
const setMinimalShellMode = useSetMinimalShellMode()
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
-
- useFocusEffect(
- useNonReactiveCallback(() => {
- if (maybeSelectedFeed) {
- logEvent('home:feedDisplayed', {
- index: selectedIndex,
- feedType: maybeSelectedFeed.split('|')[0],
- feedUrl: maybeSelectedFeed,
- reason: 'focus',
- })
- }
- }),
- )
-
- const onPageSelected = React.useCallback(
- (index: number) => {
- setMinimalShellMode(false)
- const maybeFeed = allFeeds[index]
-
- // Mutate the ref before setting state to avoid the imperative syncing effect
- // above from starting a loop on Android when swiping back and forth.
- lastPagerReportedIndexRef.current = index
- setSelectedFeed(maybeFeed)
-
- if (maybeFeed) {
- logEvent('home:feedDisplayed', {
- index,
- feedType: maybeFeed.split('|')[0],
- feedUrl: maybeFeed,
- })
- }
- },
- [setSelectedFeed, setMinimalShellMode, allFeeds],
- )
-
- const onPressSelected = React.useCallback(() => {
- emitSoftReset()
- }, [])
-
- const onPageScrollStateChanged = React.useCallback(
- (state: 'idle' | 'dragging' | 'settling') => {
- 'worklet'
- if (state === 'dragging') {
- setMinimalShellMode(false)
- }
- },
- [setMinimalShellMode],
- )
+ useFocusEffect(React.useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]))
+
+ const onPageSelected = React.useCallback((index) => {
+ setMinimalShellMode(false)
+ const maybeFeed = allFeeds[index]
+ lastPagerReportedIndexRef.current = index
+ setSelectedFeed(maybeFeed)
+ }, [setSelectedFeed, setMinimalShellMode, allFeeds])
+
+ const onPressSelected = React.useCallback(() => { emitSoftReset() }, [])
+ const onPageScrollStateChanged = React.useCallback((state) => {
+ 'worklet'
+ if (state === 'dragging') setMinimalShellMode(false)
+ }, [setMinimalShellMode])
const [demoMode] = useDemoMode()
-
- const renderTabBar = React.useCallback(
- (props: RenderTabBarFnProps) => {
- if (demoMode) {
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- // @ts-ignore
- feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
- />
- )
- }
- return (
- <HomeHeader
- key="FEEDS_TAB_BAR"
- {...props}
- testID="homeScreenFeedTabs"
- onPressSelected={onPressSelected}
- feeds={pinnedFeedInfos}
- />
- )
- },
- [onPressSelected, pinnedFeedInfos, demoMode],
- )
-
- const renderFollowingEmptyState = React.useCallback(() => {
- return <FollowingEmptyState />
- }, [])
-
- const renderCustomFeedEmptyState = React.useCallback(() => {
- return <CustomFeedEmptyState />
- }, [])
-
- const homeFeedParams = React.useMemo<FeedParams>(() => {
- return {
- mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
- mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
- ? preferences.savedFeeds
- .filter(f => f.type === 'feed' || f.type === 'list')
- .map(f => f.value)
- : [],
- }
- }, [preferences])
-
- if (demoMode) {
- return (
- <Pager
- ref={pagerRef}
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}
- initialPage={selectedIndex}>
- <FeedPage
- testID="demoFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed="demo"
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
- </Pager>
- )
- }
-
- return hasSession ? (
- <Pager
- key={allFeeds.join(',')}
- ref={pagerRef}
- testID="homeScreen"
- initialPage={selectedIndex}
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- {pinnedFeedInfos.length ? (
- pinnedFeedInfos.map((feedInfo, index) => {
+ const renderTabBar = React.useCallback((props) => {
+ return <HomeHeader key="FEEDS_TAB_BAR" {...props} testID="homeScreenFeedTabs" onPressSelected={onPressSelected} feeds={pinnedFeedInfos} />
+ }, [onPressSelected, pinnedFeedInfos])
+
+ const renderFollowingEmptyState = React.useCallback(() => <FollowingEmptyState />, [])
+ const renderCustomFeedEmptyState = React.useCallback(() => <CustomFeedEmptyState />, [])
+
+ const homeFeedParams = React.useMemo(() => ({
+ mergeFeedEnabled: false, mergeFeedSources: []
+ }), [preferences])
+
+ return (
+ <Pager ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}>
+ {pinnedFeedInfos.map((feedInfo, index) => {
const feed = feedInfo.feedDescriptor
if (feed === 'following') {
- return (
- <FeedPage
- key={feed}
- testID="followingFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- feedParams={homeFeedParams}
- renderEmptyState={renderFollowingEmptyState}
- renderEndOfFeed={FollowingEndOfFeed}
- feedInfo={feedInfo}
- />
- )
+ return <FeedPage key={feed} testID="followingFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} feedParams={homeFeedParams} renderEmptyState={renderFollowingEmptyState} renderEndOfFeed={FollowingEndOfFeed} feedInfo={feedInfo} />
}
- const savedFeedConfig = feedInfo.savedFeed
- return (
- <FeedPage
- key={feed}
- testID="customFeedPage"
- isPageFocused={maybeSelectedFeed === feed}
- isPageAdjacent={Math.abs(selectedIndex - index) === 1}
- feed={feed}
- renderEmptyState={renderCustomFeedEmptyState}
- savedFeedConfig={savedFeedConfig}
- feedInfo={feedInfo}
- />
- )
- })
- ) : (
- <NoFeedsPinned preferences={preferences} />
- )}
- </Pager>
- ) : (
- <Pager
- testID="homeScreen"
- onPageSelected={onPageSelected}
- onPageScrollStateChanged={onPageScrollStateChanged}
- renderTabBar={renderTabBar}>
- <FeedPage
- testID="customFeedPage"
- isPageFocused
- isPageAdjacent={false}
- feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
- renderEmptyState={renderCustomFeedEmptyState}
- feedInfo={pinnedFeedInfos[0]}
- />
+ return <FeedPage key={feed} testID="customFeedPage" isPageFocused={maybeSelectedFeed === feed} isPageAdjacent={Math.abs(selectedIndex - index) === 1} feed={feed} renderEmptyState={renderCustomFeedEmptyState} savedFeedConfig={feedInfo.savedFeed} feedInfo={feedInfo} />
+ })}
</Pager>
)
}
-
-const styles = StyleSheet.create({
- loading: {
- height: '100%',
- alignContent: 'center',
- justifyContent: 'center',
- paddingBottom: 100,
- },
-})
+const styles = StyleSheet.create({ loading: { height: '100%', alignContent: 'center', justifyContent: 'center', paddingBottom: 100 } })
diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx
index a89eaadc4..228af4966 100644
--- a/src/view/screens/PrivacyPolicy.tsx
+++ b/src/view/screens/PrivacyPolicy.tsx
@@ -1,52 +1,49 @@
@@ -1,52 +1,13 @@
import React from 'react'
-import {View} from 'react-native'
-import {msg} from '@lingui/core/macro'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
@@ -54,28 +467,25 @@
-import {TextLink} from '#/view/com/util/Link'
-import {Text} from '#/view/com/util/text/Text'
-import {ScrollView} from '#/view/com/util/Views'
+import {ScrollView} from 'react-native'
+import { WebView } from 'react-native-webview'
import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
-export const PrivacyPolicyScreen = (_props: Props) => {
- const pal = usePalette('default')
- const {_} = useLingui()
- const setMinimalShellMode = useSetMinimalShellMode()
+export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
+ const t = useTheme()
-
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
-
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+export function PrivacyPolicyScreen() {
+ useSetTitle('Privacy Policy')
return (
<Layout.Screen>
- <ViewHeader title={_(msg`Privacy Policy`)} />
@@ -93,50 +503,20 @@
- </Text>
- </View>
- <View style={s.footerSpacer} />
+ <ScrollView
+ style={[a.flex_1, {backgroundColor: t.palette.white}]}
+ contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
+ <Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>Privacy Policy</Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Data Collection</Text>
+ <Text style={[a.mb_md]}>
+ syu.is collects minimal data necessary to provide the service. This includes your account information, posts, and interactions on the AT Protocol network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Data Storage</Text>
+ <Text style={[a.mb_md]}>
+ Your data is stored on the AT Protocol network. Posts and profile information are public by default as part of the decentralized social network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Third Parties</Text>
+ <Text style={[a.mb_md]}>
+ We do not sell your personal information to third parties. Your data may be visible to other users and services on the AT Protocol network.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Contact</Text>
+ <Text style={[a.mb_md]}>
+ For privacy-related questions, please contact the administrator.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}>日本語</Text>
+ <Text style={[a.mb_md]}>
+ syu.isはサービス提供に必要な最小限のデータのみを収集します。投稿やプロフィール情報はAT Protocolネットワーク上で公開されます。個人情報を第三者に販売することはありません。
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2026
+ </Text>
</ScrollView>
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/privacy-policy' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}
diff --git a/src/view/screens/TermsOfService.tsx b/src/view/screens/TermsOfService.tsx
index d843c713c..c0b34c886 100644
--- a/src/view/screens/TermsOfService.tsx
+++ b/src/view/screens/TermsOfService.tsx
@@ -1,50 +1,49 @@
@@ -1,50 +1,13 @@
import React from 'react'
-import {View} from 'react-native'
-import {msg} from '@lingui/core/macro'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
-import {useFocusEffect} from '@react-navigation/native'
-
-import {usePalette} from '#/lib/hooks/usePalette'
@@ -149,28 +529,25 @@
-import {TextLink} from '#/view/com/util/Link'
-import {Text} from '#/view/com/util/text/Text'
-import {ScrollView} from '#/view/com/util/Views'
+import {ScrollView} from 'react-native'
+import { WebView } from 'react-native-webview'
import * as Layout from '#/components/Layout'
-import {ViewHeader} from '../com/util/ViewHeader'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
-
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'>
-export const TermsOfServiceScreen = (_props: Props) => {
- const pal = usePalette('default')
- const setMinimalShellMode = useSetMinimalShellMode()
- const {_} = useLingui()
+export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
+ const t = useTheme()
-
- useFocusEffect(
- React.useCallback(() => {
- setMinimalShellMode(false)
- }, [setMinimalShellMode]),
- )
-
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+export function TermsOfServiceScreen() {
+ useSetTitle('Terms of Service')
return (
<Layout.Screen>
- <ViewHeader title={_(msg`Terms of Service`)} />
@@ -186,39 +563,8 @@
- </Text>
- </View>
- <View style={s.footerSpacer} />
+ <ScrollView
+ style={[a.flex_1, {backgroundColor: t.palette.white}]}
+ contentContainerStyle={[a.p_lg, a.pt_5xl, a.pb_5xl]}>
+ <Text style={[a.text_2xl, a.font_bold, a.mb_lg]}>Terms of Service</Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Acceptance</Text>
+ <Text style={[a.mb_md]}>
+ By using syu.is, you agree to these terms. If you do not agree, please do not use the service.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Prohibited Content</Text>
+ <Text style={[a.mb_md]}>
+ Do not post illegal content, spam, or harass others. Do not impersonate others or spread misinformation.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Account Termination</Text>
+ <Text style={[a.mb_md]}>
+ The administrator reserves the right to suspend or terminate accounts that violate these terms.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>Disclaimer</Text>
+ <Text style={[a.mb_md]}>
+ This service is provided "as is" without warranty of any kind.
+ </Text>
+
+ <Text style={[a.text_lg, a.font_bold, a.mt_xl, a.mb_md]}>日本語</Text>
+ <Text style={[a.mb_md]}>
+ syu.isを利用することで、これらの利用規約に同意したものとみなします。違法なコンテンツの投稿、スパム、他者への嫌がらせは禁止です。管理者は規約違反のアカウントを停止する権利を有します。本サービスは現状のまま提供され、いかなる保証もありません。
+ </Text>
+
+ <Text style={[a.text_sm, a.mt_xl, {color: t.palette.contrast_500}]}>
+ Last updated: 2026
+ </Text>
</ScrollView>
- </ScrollView>
+ <WebView source={{ uri: 'https://syu.is/about/support/tos' }} style={{ flex: 1 }} />
</Layout.Screen>
)
}

View File

@@ -1,8 +1,311 @@
diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx
index 9915d0a2d..4ae51215d 100644
--- a/src/components/dialogs/BirthDateSettings.tsx
+++ b/src/components/dialogs/BirthDateSettings.tsx
@@ -163,7 +163,7 @@ function BirthdayInner({
<Trans>
You must be at least 13 years old to use Bluesky. Read our{' '}
<SimpleInlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms of Service`)}>
Terms of Service
</SimpleInlineLinkText>{' '}
diff --git a/src/components/dialogs/ServerInput.tsx b/src/components/dialogs/ServerInput.tsx
index d7c02bb9f..fda1dfe4a 100644
--- a/src/components/dialogs/ServerInput.tsx
+++ b/src/components/dialogs/ServerInput.tsx
@@ -165,7 +165,7 @@ function DialogInner({
<Trans>
Bluesky is an open network where you can choose your own
provider. If you're new here, we recommend sticking with the
- default Bluesky Social option.
+ default syu.is option.
</Trans>
</Admonition>
</View>
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index 42a5fe417..8e7963512 100644
index ed2a6cfb7..1dc7f9227 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -294,17 +294,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
@@ -1,60 +1,50 @@
-import React, {type ComponentProps, type JSX} from 'react'
-import {Linking, ScrollView, TouchableOpacity, View} from 'react-native'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {msg, Plural, plural, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {StackActions, useNavigation} from '@react-navigation/native'
-
-import {useActorStatus} from '#/lib/actor-status'
-import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
-import {type PressableScale} from '#/lib/custom-animations/PressableScale'
-import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
-import {getTabState, TabState} from '#/lib/routes/helpers'
-import {type NavigationProp} from '#/lib/routes/types'
-import {sanitizeHandle} from '#/lib/strings/handles'
-import {colors} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
-import {emitSoftReset} from '#/state/events'
-import {useKawaiiMode} from '#/state/preferences/kawaii'
-import {useUnreadNotifications} from '#/state/queries/notifications/unread'
-import {useProfileQuery} from '#/state/queries/profile'
-import {type SessionAccount, useSession} from '#/state/session'
-import {useSetDrawerOpen} from '#/state/shell'
-import {formatCount} from '#/view/com/util/numeric/format'
-import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {NavSignupCard} from '#/view/shell/NavSignupCard'
-import {atoms as a, tokens, useTheme, web} from '#/alf'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import {Divider} from '#/components/Divider'
+import React, { type ComponentProps, type JSX } from 'react'
+import { Linking, ScrollView, TouchableOpacity, View } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import { msg, Plural, plural, Trans } from '@lingui/macro'
+import { useLingui } from '@lingui/react'
+import { StackActions, useNavigation } from '@react-navigation/native'
+
+import { useActorStatus } from '#/lib/actor-status'
+import { FEEDBACK_FORM_URL, HELP_DESK_URL } from '#/lib/constants'
+import { type PressableScale } from '#/lib/custom-animations/PressableScale'
+import { useNavigationTabState } from '#/lib/hooks/useNavigationTabState'
+import { getTabState, TabState } from '#/lib/routes/helpers'
+import { type NavigationProp } from '#/lib/routes/types'
+import { sanitizeHandle } from '#/lib/strings/handles'
+import { colors } from '#/lib/styles'
+import { isWeb } from '#/platform/detection'
+import { emitSoftReset } from '#/state/events'
+import { useKawaiiMode } from '#/state/preferences/kawaii'
+import { useUnreadNotifications } from '#/state/queries/notifications/unread'
+import { useProfileQuery } from '#/state/queries/profile'
+import { type SessionAccount, useSession } from '#/state/session'
+import { useSetDrawerOpen } from '#/state/shell'
+import { formatCount } from '#/view/com/util/numeric/format'
+import { UserAvatar } from '#/view/com/util/UserAvatar'
+import { NavSignupCard } from '#/view/shell/NavSignupCard'
+import { atoms as a, tokens, useTheme, web } from '#/alf'
+import { Button } from '#/components/Button'
+import { Divider } from '#/components/Divider'
import {
Bell_Filled_Corner0_Rounded as BellFilled,
Bell_Stroke2_Corner0_Rounded as Bell,
} from '#/components/icons/Bell'
-import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
-import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
-import {
- Hashtag_Filled_Corner0_Rounded as HashtagFilled,
- Hashtag_Stroke2_Corner0_Rounded as Hashtag,
-} from '#/components/icons/Hashtag'
import {
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
HomeOpen_Stoke2_Corner0_Rounded as Home,
} from '#/components/icons/HomeOpen'
-import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
-import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
-import {
- Message_Stroke2_Corner0_Rounded as Message,
- Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
-} from '#/components/icons/Message'
-import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
+import { MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled } from '#/components/icons/MagnifyingGlass'
+import { MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass } from '#/components/icons/MagnifyingGlass2'
+import { SettingsGear2_Stroke2_Corner0_Rounded as Settings } from '#/components/icons/SettingsGear2'
import {
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
} from '#/components/icons/UserCircle'
-import {InlineLinkText} from '#/components/Link'
-import {Text} from '#/components/Typography'
-import {useSimpleVerificationState} from '#/components/verification'
-import {VerificationCheck} from '#/components/verification/VerificationCheck'
+import { InlineLinkText } from '#/components/Link'
+import { Text } from '#/components/Typography'
+import { useSimpleVerificationState } from '#/components/verification'
+import { VerificationCheck } from '#/components/verification/VerificationCheck'
const iconWidth = 26
@@ -65,11 +55,11 @@ let DrawerProfileCard = ({
account: SessionAccount
onPressProfile: () => void
}): React.ReactNode => {
- const {_, i18n} = useLingui()
+ const { _, i18n } = useLingui()
const t = useTheme()
- const {data: profile} = useProfileQuery({did: account.did})
- const verification = useSimpleVerificationState({profile})
- const {isActive: live} = useActorStatus(profile)
+ const { data: profile } = useProfileQuery({ did: account.did })
+ const verification = useSimpleVerificationState({ profile })
+ const { isActive: live } = useActorStatus(profile)
return (
<TouchableOpacity
@@ -81,7 +71,6 @@ let DrawerProfileCard = ({
<UserAvatar
size={52}
avatar={profile?.avatar}
- // See https://github.com/bluesky-social/social-app/pull/1801:
usePlainRNImage={true}
type={profile?.associated?.labeler ? 'labeler' : 'user'}
live={live}
@@ -140,9 +129,9 @@ let DrawerProfileCard = ({
)
}
DrawerProfileCard = React.memo(DrawerProfileCard)
-export {DrawerProfileCard}
+export { DrawerProfileCard }
-let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
+let DrawerContent = ({ }: React.PropsWithoutRef<{}>): React.ReactNode => {
const t = useTheme()
const insets = useSafeAreaInsets()
const setDrawerOpen = useSetDrawerOpen()
@@ -150,27 +139,20 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
const {
isAtHome,
isAtSearch,
- isAtFeeds,
- isAtBookmarks,
isAtNotifications,
isAtMyProfile,
- isAtMessages,
} = useNavigationTabState()
- const {hasSession, currentAccount} = useSession()
-
- // events
- // =
+ const { hasSession, currentAccount } = useSession()
const onPressTab = React.useCallback(
(tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
const state = navigation.getState()
setDrawerOpen(false)
if (isWeb) {
- // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
if (tab === 'MyProfile') {
- navigation.navigate('Profile', {name: currentAccount!.handle})
+ navigation.navigate('Profile', { name: currentAccount!.handle })
} else {
- // @ts-expect-error struggles with string unions, apparently
+ // @ts-expect-error struggles with string unions
navigation.navigate(tab)
}
} else {
@@ -178,21 +160,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
if (tabState === TabState.InsideAtRoot) {
emitSoftReset()
} else if (tabState === TabState.Inside) {
- // find the correct navigator in which to pop-to-top
- const target = state.routes.find(route => route.name === `${tab}Tab`)
- ?.state?.key
+ const target = state.routes.find(route => route.name === `${tab}Tab`)?.state?.key
if (target) {
- // if we found it, trigger pop-to-top
- navigation.dispatch({
- ...StackActions.popToTop(),
- target,
- })
+ navigation.dispatch({ ...StackActions.popToTop(), target })
} else {
- // fallback: reset navigation
- navigation.reset({
- index: 0,
- routes: [{name: `${tab}Tab`}],
- })
+ navigation.reset({ index: 0, routes: [{ name: `${tab}Tab` }] })
}
} else {
navigation.navigate(`${tab}Tab`)
@@ -203,76 +175,21 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
)
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
-
- const onPressSearch = React.useCallback(
- () => onPressTab('Search'),
- [onPressTab],
- )
-
- const onPressMessages = React.useCallback(
- () => onPressTab('Messages'),
- [onPressTab],
- )
-
- const onPressNotifications = React.useCallback(
- () => onPressTab('Notifications'),
- [onPressTab],
- )
-
- const onPressProfile = React.useCallback(() => {
- onPressTab('MyProfile')
- }, [onPressTab])
-
- const onPressMyFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- setDrawerOpen(false)
- }, [navigation, setDrawerOpen])
-
- const onPressLists = React.useCallback(() => {
- navigation.navigate('Lists')
- setDrawerOpen(false)
- }, [navigation, setDrawerOpen])
-
- const onPressBookmarks = React.useCallback(() => {
- navigation.navigate('Bookmarks')
- setDrawerOpen(false)
- }, [navigation, setDrawerOpen])
-
+ const onPressSearch = React.useCallback(() => onPressTab('Search'), [onPressTab])
+ const onPressNotifications = React.useCallback(() => onPressTab('Notifications'), [onPressTab])
+ const onPressProfile = React.useCallback(() => { onPressTab('MyProfile') }, [onPressTab])
const onPressSettings = React.useCallback(() => {
navigation.navigate('Settings')
setDrawerOpen(false)
}, [navigation, setDrawerOpen])
- const onPressFeedback = React.useCallback(() => {
- Linking.openURL(
- FEEDBACK_FORM_URL({
- email: currentAccount?.email,
- handle: currentAccount?.handle,
- }),
- )
- }, [currentAccount])
-
- const onPressHelp = React.useCallback(() => {
- Linking.openURL(HELP_DESK_URL)
- }, [])
-
- // rendering
- // =
-
return (
<View
testID="drawer"
style={[a.flex_1, a.border_r, t.atoms.bg, t.atoms.border_contrast_low]}>
<ScrollView
style={[a.flex_1]}
- contentContainerStyle={[
- {
- paddingTop: Math.max(
- insets.top + a.pt_xl.paddingTop,
- a.pt_xl.paddingTop,
- ),
- },
- ]}>
+ contentContainerStyle={[{ paddingTop: Math.max(insets.top + a.pt_xl.paddingTop, a.pt_xl.paddingTop) }]}>
<View style={[a.px_xl]}>
{hasSession && currentAccount ? (
<DrawerProfileCard
@@ -284,7 +201,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<NavSignupCard />
</View>
)}
-
<Divider style={[a.mt_xl, a.mb_sm]} />
</View>
@@ -292,17 +208,10 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<>
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
@@ -11,7 +314,7 @@ index 42a5fe417..8e7963512 100644
isActive={isAtNotifications}
onPress={onPressNotifications}
/>
<FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
- <ListsMenuItem onPress={onPressLists} />
- <BookmarksMenuItem
- isActive={isAtBookmarks}
@@ -20,10 +323,53 @@ index 42a5fe417..8e7963512 100644
<ProfileMenuItem
isActive={isAtMyProfile}
onPress={onPressProfile}
@@ -359,17 +353,7 @@ let DrawerFooter = ({
),
},
]}>
@@ -312,7 +221,6 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
) : (
<>
<HomeMenuItem isActive={isAtHome} onPress={onPressHome} />
- <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} />
<SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} />
</>
)}
@@ -322,69 +230,11 @@ let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
<ExtraLinks />
</View>
</ScrollView>
-
- <DrawerFooter
- onPressFeedback={onPressFeedback}
- onPressHelp={onPressHelp}
- />
</View>
)
}
DrawerContent = React.memo(DrawerContent)
-export {DrawerContent}
-
-let DrawerFooter = ({
- onPressFeedback,
- onPressHelp,
-}: {
- onPressFeedback: () => void
- onPressHelp: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const insets = useSafeAreaInsets()
- return (
- <View
- style={[
- a.flex_row,
- a.gap_sm,
- a.flex_wrap,
- a.pl_xl,
- a.pt_md,
- {
- paddingBottom: Math.max(
- insets.bottom + tokens.space.xs,
- tokens.space.xl,
- ),
- },
- ]}>
- <Button
- label={_(msg`Send feedback`)}
- size="small"
@@ -35,31 +381,337 @@ index 42a5fe417..8e7963512 100644
- <Trans>Feedback</Trans>
- </ButtonText>
- </Button>
+{/* Feedback button removed for syu.is */}
<Button
label={_(msg`Get help`)}
size="small"
@@ -697,15 +681,21 @@ function ExtraLinks() {
<InlineLinkText
style={[a.text_md]}
label={_(msg`Terms of Service`)}
- <Button
- label={_(msg`Get help`)}
- size="small"
- variant="outline"
- color="secondary"
- onPress={onPressHelp}
- style={{
- backgroundColor: 'transparent',
- }}>
- <ButtonText>
- <Trans>Help</Trans>
- </ButtonText>
- </Button>
- </View>
- )
-}
-DrawerFooter = React.memo(DrawerFooter)
+export { DrawerContent }
interface MenuItemProps extends ComponentProps<typeof PressableScale> {
icon: JSX.Element
@@ -400,7 +250,7 @@ let SearchMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -426,7 +276,7 @@ let HomeMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -445,32 +295,6 @@ let HomeMenuItem = ({
}
HomeMenuItem = React.memo(HomeMenuItem)
-let ChatMenuItem = ({
- isActive,
- onPress,
-}: {
- isActive: boolean
- onPress: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
- return (
- <MenuItem
- icon={
- isActive ? (
- <MessageFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Message style={[t.atoms.text]} width={iconWidth} />
- )
- }
- label={_(msg`Chat`)}
- bold={isActive}
- onPress={onPress}
- />
- )
-}
-ChatMenuItem = React.memo(ChatMenuItem)
-
let NotificationsMenuItem = ({
isActive,
onPress,
@@ -478,7 +302,7 @@ let NotificationsMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
const numUnreadNotifications = useUnreadNotifications()
return (
@@ -495,11 +319,11 @@ let NotificationsMenuItem = ({
numUnreadNotifications === ''
? ''
: _(
- msg`${plural(numUnreadNotifications ?? 0, {
- one: '# unread item',
- other: '# unread items',
- })}` || '',
- )
+ msg`${plural(numUnreadNotifications ?? 0, {
+ one: '# unread item',
+ other: '# unread items',
+ })}` || '',
+ )
}
count={numUnreadNotifications}
bold={isActive}
@@ -509,72 +333,6 @@ let NotificationsMenuItem = ({
}
NotificationsMenuItem = React.memo(NotificationsMenuItem)
-let FeedsMenuItem = ({
- isActive,
- onPress,
-}: {
- isActive: boolean
- onPress: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
- return (
- <MenuItem
- icon={
- isActive ? (
- <HashtagFilled width={iconWidth} style={[t.atoms.text]} />
- ) : (
- <Hashtag width={iconWidth} style={[t.atoms.text]} />
- )
- }
- label={_(msg`Feeds`)}
- bold={isActive}
- onPress={onPress}
- />
- )
-}
-FeedsMenuItem = React.memo(FeedsMenuItem)
-
-let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
-
- return (
- <MenuItem
- icon={<List style={[t.atoms.text]} width={iconWidth} />}
- label={_(msg`Lists`)}
- onPress={onPress}
- />
- )
-}
-ListsMenuItem = React.memo(ListsMenuItem)
-
-let BookmarksMenuItem = ({
- isActive,
- onPress,
-}: {
- isActive: boolean
- onPress: () => void
-}): React.ReactNode => {
- const {_} = useLingui()
- const t = useTheme()
-
- return (
- <MenuItem
- icon={
- isActive ? (
- <BookmarkFilled style={[t.atoms.text]} width={iconWidth} />
- ) : (
- <Bookmark style={[t.atoms.text]} width={iconWidth} />
- )
- }
- label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))}
- onPress={onPress}
- />
- )
-}
-BookmarksMenuItem = React.memo(BookmarksMenuItem)
-
let ProfileMenuItem = ({
isActive,
onPress,
@@ -582,7 +340,7 @@ let ProfileMenuItem = ({
isActive: boolean
onPress: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -600,8 +358,8 @@ let ProfileMenuItem = ({
}
ProfileMenuItem = React.memo(ProfileMenuItem)
-let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
- const {_} = useLingui()
+let SettingsMenuItem = ({ onPress }: { onPress: () => void }): React.ReactNode => {
+ const { _ } = useLingui()
const t = useTheme()
return (
<MenuItem
@@ -613,7 +371,7 @@ let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
}
SettingsMenuItem = React.memo(SettingsMenuItem)
-function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
+function MenuItem({ icon, label, count, bold, onPress }: MenuItemProps) {
const t = useTheme()
return (
<Button
@@ -621,7 +379,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
onPress={onPress}
accessibilityRole="tab"
label={label}>
- {({hovered, pressed}) => (
+ {({ hovered, pressed }) => (
<View
style={[
a.flex_1,
@@ -640,7 +398,7 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
a.absolute,
a.inset_0,
a.align_end,
- {top: -4, right: a.gap_sm.gap * -1},
+ { top: -4, right: a.gap_sm.gap * -1 },
]}>
<View
style={[
@@ -686,37 +444,26 @@ function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
}
function ExtraLinks() {
- const {_} = useLingui()
const t = useTheme()
- const kawaii = useKawaiiMode()
+ const navigation = useNavigation<NavigationProp>()
return (
<View style={[a.flex_col, a.gap_md, a.flex_wrap]}>
- <InlineLinkText
- style={[a.text_md]}
- label={_(msg`Terms of Service`)}
- to="https://bsky.social/about/support/tos">
+ to="/support/tos">
<Trans>Terms of Service</Trans>
</InlineLinkText>
<InlineLinkText
style={[a.text_md]}
- <Trans>Terms of Service</Trans>
- </InlineLinkText>
- <InlineLinkText
- style={[a.text_md]}
- to="https://bsky.social/about/support/privacy-policy"
+ to="/support/privacy-policy"
label={_(msg`Privacy Policy`)}>
<Trans>Privacy Policy</Trans>
</InlineLinkText>
+ <InlineLinkText
+ style={[a.text_md]}
+ to="/support/license"
+ label="License">
+ License
+ </InlineLinkText>
{kawaii && (
<Text style={t.atoms.text_contrast_medium}>
<Trans>
- label={_(msg`Privacy Policy`)}>
- <Trans>Privacy Policy</Trans>
- </InlineLinkText>
- {kawaii && (
- <Text style={t.atoms.text_contrast_medium}>
- <Trans>
- Logo by{' '}
- <InlineLinkText
- style={[a.text_md]}
- to="/profile/sawaratsuki.bsky.social"
- label="@sawaratsuki.bsky.social">
- @sawaratsuki.bsky.social
- </InlineLinkText>
- </Trans>
+ <TouchableOpacity onPress={() => Linking.openURL('https://syu.is/about/support/tos')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ Terms of Service
</Text>
- )}
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => Linking.openURL('https://syu.is/about/support/privacy-policy')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ Privacy Policy
+ </Text>
+ </TouchableOpacity>
+ <TouchableOpacity onPress={() => navigation.navigate('License')}>
+ <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+ License
+ </Text>
+ </TouchableOpacity>
</View>
)
}
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 779ebda68..bfd9b70fa 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -198,38 +198,6 @@ export function BottomBar({navigation}: BottomTabBarProps) {
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
/>
- <Btn
- testID="bottomBarMessagesBtn"
- icon={
- isAtMessages ? (
- <MessageFilled
- width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
- />
- ) : (
- <Message
- width={iconWidth - 1}
- style={[styles.ctrlIcon, pal.text, styles.feedsIcon]}
- />
- )
- }
- onPress={onPressMessages}
- notificationCount={numUnreadMessages.numUnread}
- hasNew={numUnreadMessages.hasNew}
- accessible={true}
- accessibilityRole="tab"
- accessibilityLabel={_(msg`Chat`)}
- accessibilityHint={
- numUnreadMessages.count > 0
- ? _(
- msg`${plural(numUnreadMessages.numUnread ?? 0, {
- one: '# unread item',
- other: '# unread items',
- })}` || '',
- )
- : ''
- }
- />
<Btn
testID="bottomBarNotificationsBtn"
icon={
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
index 1d097fc9a..1ad7026a2 100644
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -109,13 +109,13 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
</>
)}
<InlineLinkText
- to="https://bsky.social/about/support/privacy-policy"
+ to="https://syu.is/about/support/privacy-policy"
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
{' • '}
<InlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="https://syu.is/about/support/tos"
label={_(msg`Terms`)}>
{_(msg`Terms`)}
</InlineLinkText>

View File

@@ -20,13 +20,13 @@ index 8365057e8..59c8506a2 100644
terms: {
overridePresentation: false,
- to: `https://bsky.social/about/support/tos`,
+ to: `/support/tos`,
+ to: `https://syu.is/about/support/tos`,
label: _(msg`Terms of Service`),
},
privacy: {
overridePresentation: false,
- to: `https://bsky.social/about/support/privacy-policy`,
+ to: `/support/privacy-policy`,
+ to: `https://syu.is/about/support/privacy-policy`,
label: _(msg`Privacy Policy`),
},
copyright: {

View File

@@ -1,17 +1,16 @@
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index fde223bd0..0e57c27a6 100644
index fa33a9d56..13af087c2 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -66,6 +66,8 @@ import {NotFoundScreen} from '#/view/screens/NotFound'
@@ -62,6 +62,7 @@ import {NotFoundScreen} from '#/view/screens/NotFound'
import {NotificationsScreen} from '#/view/screens/Notifications'
import {PostThreadScreen} from '#/view/screens/PostThread'
import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy'
+import {LicenseScreen} from '#/view/screens/License'
+import {AppInfoScreen} from '#/view/screens/AppInfo'
import {ProfileScreen} from '#/view/screens/Profile'
import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy'
import {StorybookScreen} from '#/view/screens/Storybook'
@@ -337,6 +339,16 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
import {Storybook} from '#/view/screens/Storybook'
@@ -335,6 +336,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
getComponent={() => TermsOfServiceScreen}
options={{title: title(msg`Terms of Service`)}}
/>
@@ -19,25 +18,157 @@ index fde223bd0..0e57c27a6 100644
+ name="License"
+ getComponent={() => LicenseScreen}
+ options={{title: title(msg`License`)}}
+ />
+ <Stack.Screen
+ name="AppInfo"
+ getComponent={() => AppInfoScreen}
+ options={{title: title(msg`App Info`)}}
+ />
<Stack.Screen
name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen}
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 0a3a0d545..ec6c43ff5 100644
index c315a8341..9b2f50a83 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -39,6 +39,8 @@ export type CommonNavigatorParams = {
@@ -39,6 +39,7 @@ export type CommonNavigatorParams = {
Support: undefined
PrivacyPolicy: undefined
TermsOfService: undefined
+ License: undefined
+ AppInfo: undefined
CommunityGuidelines: undefined
CopyrightPolicy: undefined
LanguageSettings: undefined
diff --git a/src/view/screens/License.tsx b/src/view/screens/License.tsx
new file mode 100644
index 000000000..87f52a972
--- /dev/null
+++ b/src/view/screens/License.tsx
@@ -0,0 +1,132 @@
+import React from 'react'
+import { ScrollView, Text as RNText, StyleSheet } from 'react-native'
+import * as Layout from '#/components/Layout'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+
+export function LicenseScreen() {
+ useSetTitle('License')
+ const t = useTheme()
+
+ return (
+ <Layout.Screen>
+ <Layout.Header.Outer>
+ <Layout.Header.BackButton />
+ <Layout.Header.Content>
+ <Layout.Header.TitleText>License</Layout.Header.TitleText>
+ </Layout.Header.Content>
+ <Layout.Header.Slot />
+ </Layout.Header.Outer>
+ <Layout.Content>
+ <ScrollView
+ style={[a.flex_1]}
+ contentContainerStyle={[a.p_lg, a.pt_xl, a.pb_5xl]}>
+ <RNText style={styles.text}>
+ This application is based on Bluesky Social App.
+ </RNText>
+
+ <RNText style={styles.link}>
+ https://github.com/bluesky-social/social-app
+ </RNText>
+
+ <RNText style={styles.sectionTitle}>MIT License</RNText>
+
+ <RNText style={styles.mono}>
+ Copyright (c) 2022-2025 Bluesky PBC
+ </RNText>
+
+ <RNText style={styles.text}>
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ </RNText>
+
+ <RNText style={styles.text}>
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ </RNText>
+
+ <RNText style={styles.text}>
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ </RNText>
+
+ <RNText style={styles.sectionTitle2}>日本語訳(参考)</RNText>
+
+ <RNText style={styles.text}>
+ 本ソフトウェアおよび関連文書ファイル(以下「ソフトウェア」)のコピーを取得する
+ すべての人に対し、ソフトウェアを無制限に扱うことを無償で許可します。これには、
+ ソフトウェアのコピーを使用、複製、変更、結合、公開、配布、サブライセンス、
+ および/または販売する権利、ならびにソフトウェアを提供する相手にそうした行為を
+ 許可する権利が含まれますが、これらに限定されません。
+ </RNText>
+
+ <RNText style={styles.text}>
+ 上記の著作権表示および本許諾表示を、ソフトウェアのすべてのコピーまたは
+ 重要な部分に記載するものとします。
+ </RNText>
+
+ <RNText style={styles.text}>
+ ソフトウェアは「現状のまま」で提供され、明示黙示を問わず、商品性、特定目的への
+ 適合性、および権利非侵害についての保証を含む、いかなる種類の保証もなされません。
+ いかなる場合においても、作者または著作権者は、契約行為、不法行為、またはそれ以外で
+ あろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用または
+ その他の扱いによって生じる一切の請求、損害、その他の義務について責任を負わないものとします。
+ </RNText>
+
+ <RNText style={styles.footer}>
+ Original License: https://github.com/bluesky-social/social-app/blob/main/LICENSE
+ </RNText>
+ </ScrollView>
+ </Layout.Content>
+ </Layout.Screen>
+ )
+}
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginBottom: 16,
+ },
+ text: {
+ fontSize: 14,
+ marginBottom: 12,
+ lineHeight: 20,
+ },
+ link: {
+ fontSize: 14,
+ marginBottom: 12,
+ color: '#0066cc',
+ },
+ sectionTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginTop: 16,
+ marginBottom: 12,
+ },
+ sectionTitle2: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginTop: 24,
+ marginBottom: 12,
+ },
+ mono: {
+ fontSize: 14,
+ marginBottom: 12,
+ fontFamily: 'monospace',
+ },
+ footer: {
+ fontSize: 12,
+ marginTop: 24,
+ color: '#666666',
+ },
+})

View File

@@ -1,5 +1,63 @@
diff --git a/src/routes.ts b/src/routes.ts
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -74,6 +74,7 @@ export const router = new Router<AllNavigatableRoutes>({
PrivacyPolicy: 'https://syu.is/about/support/privacy-policy',
TermsOfService: 'https://syu.is/about/support/tos',
CommunityGuidelines: '/support/community-guidelines',
+ License: 'https://syu.is/about/support/license',
CopyrightPolicy: '/support/copyright',
// hashtags
Hashtag: '/hashtag/:tag',
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -1,4 +1,5 @@
import {View} from 'react-native'
+import {Pressable, Linking} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg, Trans} from '@lingui/macro'
@@ -40,16 +41,6 @@ export const SplashScreen = ({
<View style={[a.pb_sm, a.pt_5xl]}>
<Logotype width={161} fill={t.atoms.text.color} />
</View>
-
- <Text
- style={[
- a.text_md,
- a.font_semi_bold,
- t.atoms.text_contrast_medium,
- a.text_center,
- ]}>
- <Trans>What's up?</Trans>
- </Text>
</View>
<View
@@ -102,6 +93,21 @@ export const SplashScreen = ({
<AppLanguageDropdown />
</View>
</View>
+ <View style={[a.pb_sm, a.justify_center, a.align_center]}>
+ <Pressable onPress={() => Linking.openURL('https://syu.is/about/support/license')}>
+ <Text
+ style={[
+ a.text_xs,
+ t.atoms.text_contrast_low,
+ {textDecorationLine: 'underline'},
+ ]}>
+ License
+ </Text>
+ </Pressable>
+ </View>
+ <View style={[a.pb_xl, a.justify_center, a.align_center]}>
+ <Text style={[a.text_xs, t.atoms.text_contrast_low]}>© syui</Text>
+ </View>
<View style={{height: insets.bottom}} />
</ErrorBoundary>
</Animated.View>
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
index d9185d778..504f521a4 100644
--- a/src/view/com/auth/SplashScreen.web.tsx
+++ b/src/view/com/auth/SplashScreen.web.tsx
@@ -94,14 +94,6 @@ export const SplashScreen = ({

View File

@@ -1,37 +1,8 @@
diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
index 2fa5aa7de..3faf6a7b3 100644
index 1703036b5..42baa462c 100644
--- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx
@@ -203,26 +203,8 @@ export function SettingsScreen({}: Props) {
<Trans>Notifications</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
- <SettingsList.LinkItem
- to="/settings/content-and-media"
- label={_(msg`Content and media`)}>
- <SettingsList.ItemIcon icon={WindowIcon} />
- <SettingsList.ItemText>
- <Trans>Content and media</Trans>
- </SettingsList.ItemText>
- </SettingsList.LinkItem>
- {IS_NATIVE &&
- findContactsEnabled &&
- !ax.features.enabled(ax.features.ImportContactsSettingsDisable) && (
- <SettingsList.LinkItem
- to="/settings/find-contacts"
- label={_(msg`Find friends from contacts`)}>
- <SettingsList.ItemIcon icon={ContactsIcon} />
- <SettingsList.ItemText>
- <Trans>Find friends from contacts</Trans>
- </SettingsList.ItemText>
- </SettingsList.LinkItem>
- )}
+ {/* Content and media removed for syu.is */}
+ {/* Find friends from contacts removed for syu.is */}
<SettingsList.LinkItem
to="/settings/appearance"
label={_(msg`Appearance`)}>
@@ -247,16 +229,6 @@ export function SettingsScreen({}: Props) {
@@ -231,16 +231,6 @@ export function SettingsScreen({}: Props) {
<Trans>Languages</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>

View File

@@ -1,22 +1,17 @@
diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
index 9a0a9c9d5..5a6563e52 100644
--- a/src/ageAssurance/index.tsx
+++ b/src/ageAssurance/index.tsx
@@ -90,25 +90,16 @@ function InnerProvider({children}: {children: React.ReactNode}) {
@@ -88,19 +88,16 @@ function InnerProvider({children}: {children: React.ReactNode}) {
return (
<AgeAssuranceStateContext.Provider
value={useMemo(() => {
- const chatDisabled = state.access !== AgeAssuranceAccess.Full
- const isUnderAdultAge = data?.birthdate
- ? isUnderAge(data.birthdate, 18)
- const isUnderage = data?.birthdate
- ? isUserUnderAdultAge(data.birthdate)
- : true
- const isOverRegionMinAccessAge = data?.birthdate
- ? !isUnderAge(data.birthdate, config.minAccessAge)
- : false
- const isOverAppMinAccessAge = data?.birthdate
- ? !isUnderAge(data.birthdate, MIN_ACCESS_AGE)
- : false
- const adultContentDisabled =
- state.access !== AgeAssuranceAccess.Full || isUnderAdultAge
- state.access !== AgeAssuranceAccess.Full || isUnderage
return {
Access: AgeAssuranceAccess,
Status: AgeAssuranceStatus,
@@ -28,12 +23,8 @@ diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
flags: {
- adultContentDisabled,
- chatDisabled,
- isOverRegionMinAccessAge,
- isOverAppMinAccessAge,
+ adultContentDisabled: false,
+ chatDisabled: false,
+ isOverRegionMinAccessAge: true,
+ isOverAppMinAccessAge: true,
},
}
}, [state, data, config])}>
}, [state, data])}>

View File

@@ -1,6 +1,8 @@
diff --git a/src/view/com/posts/DiscoverFallbackHeader.tsx b/src/view/com/posts/DiscoverFallbackHeader.tsx
index e35a33aaf..a36f84ae0 100644
--- a/src/view/com/posts/DiscoverFallbackHeader.tsx
+++ b/src/view/com/posts/DiscoverFallbackHeader.tsx
@@ -7,37 +7,5 @@
@@ -7,37 +7,5 @@ import {TextLink} from '../util/Link'
import {Text} from '../util/text/Text'
export function DiscoverFallbackHeader() {
@@ -39,23 +41,25 @@
- )
+ return null
}
diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx
index 352cc1dc0..f477521af 100644
--- a/src/view/com/posts/FollowingEmptyState.tsx
+++ b/src/view/com/posts/FollowingEmptyState.tsx
@@ -1,38 +1,15 @@
@@ -1,37 +1,14 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
-import {
- FontAwesomeIcon,
- type FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/react/macro'
import {Trans} from '@lingui/macro'
-import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette'
import {MagnifyingGlassIcon} from '#/lib/icons'
-import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles'
-import {IS_WEB} from '#/env'
-import {isWeb} from '#/platform/detection'
-import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
@@ -63,9 +67,9 @@
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (IS_WEB) {
- if (isWeb) {
- navigation.navigate('Search', {})
- } else {
- navigation.navigate('SearchTab')
@@ -76,11 +80,10 @@
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
-
return (
<View style={styles.container}>
<View style={styles.inner}>
@@ -45,36 +22,6 @@
@@ -45,36 +22,6 @@ export function FollowingEmptyState() {
happening.
</Trans>
</Text>
@@ -117,7 +120,7 @@
</View>
</View>
)
@@ -98,13 +45,4 @@
@@ -98,13 +45,4 @@ const styles = StyleSheet.create({
marginLeft: 'auto',
marginRight: 'auto',
},
@@ -131,22 +134,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,37 +1,14 @@
@@ -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/react/macro'
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 {IS_WEB} from '#/env'
-import {isWeb} from '#/platform/detection'
-import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
@@ -154,9 +159,9 @@
const pal = usePalette('default')
- const palInverted = usePalette('inverted')
- const navigation = useNavigation<NavigationProp>()
-
- const onPressFindAccounts = React.useCallback(() => {
- if (IS_WEB) {
- if (isWeb) {
- navigation.navigate('Search', {})
- } else {
- navigation.navigate('SearchTab')
@@ -167,11 +172,10 @@
- const onPressDiscoverFeeds = React.useCallback(() => {
- navigation.navigate('Feeds')
- }, [navigation])
-
return (
<View
style={[
@@ -41,41 +18,8 @@
@@ -41,41 +18,8 @@ export function FollowingEndOfFeed() {
]}>
<View style={styles.inner}>
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
@@ -179,8 +183,7 @@
- You've reached the end of your feed! Find some more accounts to
- follow.
- </Trans>
+ <Trans>You've reached the end of your feed!</Trans>
</Text>
- </Text>
- <Button
- type="inverted"
- style={styles.emptyBtn}
@@ -197,7 +200,8 @@
-
- <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
- <Trans>You can also discover new Custom Feeds to follow.</Trans>
- </Text>
+ <Trans>You've reached the end of your feed!</Trans>
</Text>
- <Button
- type="inverted"
- style={[styles.emptyBtn, s.mt10]}
@@ -214,7 +218,7 @@
</View>
</View>
)
@@ -93,13 +37,4 @@
@@ -93,13 +37,4 @@ const styles = StyleSheet.create({
width: '100%',
maxWidth: 460,
},
@@ -228,9 +232,11 @@
- 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
@@ -765,7 +765,7 @@
@@ -766,7 +766,7 @@ let PostFeed = ({
} else if (row.type === 'feedShutdownMsg') {
return <FeedShutdownMsg feedUri={feedUriOrActorDid} />
} else if (row.type === 'interstitialFollows') {

View File

@@ -1,17 +1,558 @@
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index ec5261dee..c670cf75a 100644
--- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go
@@ -317,6 +317,14 @@ func serve(cctx *cli.Context) error {
@@ -302,6 +302,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)
+ e.GET("/support/privacy-policy", server.WebGeneric)
+ e.GET("/support/license", server.WebGeneric)
+ e.GET("/support/app", server.WebGeneric)
+ e.GET("/support/help", server.WebGeneric)
+ // /about/support/ paths (for web compatibility)
+ e.GET("/about/support/tos", server.WebGeneric)
+ e.GET("/about/support/privacy-policy", server.WebGeneric)
+ e.GET("/about/support/license", 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)
@@ -755,3 +761,33 @@ func (srv *Server) WebIpCC(c echo.Context) error {
}
return c.JSON(200, outResponse)
}
+
+// Handler for About TOS page (syu.is specific)
+func (srv *Server) WebAboutTOS(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-tos.html", data)
+}
+
+// Handler for About Privacy Policy page (syu.is specific)
+func (srv *Server) WebAboutPrivacy(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-privacy.html", data)
+}
+
+// Handler for About Help page (syu.is specific)
+func (srv *Server) WebAboutHelp(c echo.Context) error {
+ data := srv.NewTemplateContext()
+ return c.Render(http.StatusOK, "about-help.html", data)
+}
+
+// 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/bskyweb/templates/about-app.html b/bskyweb/templates/about-app.html
new file mode 100644
index 000000000..000000002
--- /dev/null
+++ b/bskyweb/templates/about-app.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>App Info - Aiat</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ .section { background: #1a1a1a; }
+ .info-item { background: #2a2a2a; }
+ }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; color: #0066cc; text-decoration: none; }
+ .back-link:hover { text-decoration: underline; }
+ .app-header { text-align: center; margin-bottom: 32px; }
+ .app-icon { width: 80px; height: 80px; border-radius: 18px; margin-bottom: 12px; }
+ .app-name { font-size: 24px; font-weight: bold; margin-bottom: 4px; }
+ .app-version { font-size: 14px; color: #666; }
+ .section { background: #f5f5f5; border-radius: 16px; padding: 20px; margin-bottom: 16px; }
+ .section-title { font-size: 13px; font-weight: 600; color: #999; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
+ .description { font-size: 15px; line-height: 22px; }
+ .info-grid { display: flex; flex-wrap: wrap; gap: 8px; }
+ .info-item { flex: 1; min-width: 45%; text-align: center; background: #e8e8e8; border-radius: 12px; padding: 12px; }
+ .info-label { font-size: 11px; color: #999; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
+ .info-value { font-size: 16px; font-weight: 600; }
+ .developer-name { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
+ .link-row { display: flex; align-items: center; padding: 12px 0; border-top: 1px solid rgba(0,0,0,0.1); }
+ .link-icon { font-size: 14px; font-weight: 600; color: #666; width: 70px; }
+ .link-value { flex: 1; font-size: 14px; color: #0084ff; text-decoration: none; }
+ .link-value:hover { text-decoration: underline; }
+ .link-arrow { font-size: 16px; color: #ccc; }
+ .bitcoin-row { display: flex; align-items: center; background: rgba(247, 147, 26, 0.08); border-radius: 12px; padding: 14px; gap: 10px; }
+ .bitcoin-label { font-size: 18px; font-weight: 600; color: #f7931a; }
+ .bitcoin-address { flex: 1; font-size: 11px; font-family: monospace; color: #666; word-break: break-all; }
+ .copy-btn { font-size: 12px; color: #999; cursor: pointer; min-width: 50px; text-align: right; }
+ .copy-btn:hover { color: #0084ff; }
+ .footer { text-align: center; margin-top: 32px; padding-top: 20px; border-top: 1px solid #ddd; }
+ .copyright { font-size: 12px; color: #999; }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ </div>
+
+ <div class="app-header">
+ <img src="{{ staticCDNHost }}/static/app.png" alt="Aiat" class="app-icon">
+ <div class="app-name">Aiat</div>
+ <div class="app-version">v1.111.0</div>
+ </div>
+
+ <div class="section">
+ <p class="description">Aiat is a social networking application based on AT Protocol. Connect with your community on syu.is.</p>
+ </div>
+
+ <div class="section">
+ <div class="section-title">App Information</div>
+ <div class="info-grid">
+ <div class="info-item">
+ <div class="info-label">Version</div>
+ <div class="info-value">1.111.0</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">Category</div>
+ <div class="info-value">Social</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">Supported OS</div>
+ <div class="info-value">iOS 26.0+</div>
+ </div>
+ <div class="info-item">
+ <div class="info-label">Price</div>
+ <div class="info-value">Free</div>
+ </div>
+ </div>
+ </div>
+
+ <div class="section">
+ <div class="section-title">Developer</div>
+ <div class="developer-name">syui</div>
+ <div class="link-row">
+ <span class="link-icon">GitHub</span>
+ <a href="https://github.com/syui" class="link-value" target="_blank">github.com/syui</a>
+ <span class="link-arrow">→</span>
+ </div>
+ <div class="link-row">
+ <span class="link-icon">ATProto</span>
+ <a href="https://syu.is/syui" class="link-value" target="_blank">syu.is/syui</a>
+ <span class="link-arrow">→</span>
+ </div>
+ </div>
+
+ <div class="section">
+ <div class="section-title">Bitcoin</div>
+ <div class="bitcoin-row">
+ <span class="bitcoin-label">₿</span>
+ <span class="bitcoin-address" id="btc-address">3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr</span>
+ <span class="copy-btn" onclick="copyBTC()">copy</span>
+ </div>
+ </div>
+
+ <div class="footer">
+ <p class="copyright">© syui</p>
+ </div>
+
+ <script>
+ function copyBTC() {
+ const addr = document.getElementById('btc-address').textContent;
+ navigator.clipboard.writeText(addr).then(() => {
+ const btn = document.querySelector('.copy-btn');
+ btn.textContent = 'copied!';
+ btn.style.color = '#4CAF50';
+ setTimeout(() => {
+ btn.textContent = 'copy';
+ btn.style.color = '';
+ }, 2000);
+ });
+ }
+ </script>
+</body>
+</html>
diff --git a/bskyweb/templates/about-license.html b/bskyweb/templates/about-license.html
new file mode 100644
index 000000000..000000003
--- /dev/null
+++ b/bskyweb/templates/about-license.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>License - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ pre { background: #f5f5f5; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
+ @media (prefers-color-scheme: dark) { pre { background: #1a1a1a; } }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>License</h1>
+ </div>
+
+ <h2>Aiat (iOS/Android App)</h2>
+ <p>This application is based on the Bluesky Social App, which is open source software.</p>
+
+ <h2>Open Source Licenses</h2>
+ <p>This app uses the following open source software:</p>
+
+ <h3>Bluesky Social App</h3>
+ <p>Licensed under the MIT License</p>
+ <p><a href="https://github.com/bluesky-social/social-app" target="_blank">https://github.com/bluesky-social/social-app</a></p>
+
+ <h3>AT Protocol</h3>
+ <p>Licensed under the MIT License / Apache 2.0</p>
+ <p><a href="https://github.com/bluesky-social/atproto" target="_blank">https://github.com/bluesky-social/atproto</a></p>
+
+ <h2>Third Party Libraries</h2>
+ <p>This application includes various third-party libraries, each with their own licenses. For a complete list, please see the application's source code repository.</p>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>
diff --git a/bskyweb/templates/about-help.html b/bskyweb/templates/about-help.html
new file mode 100644
index 000000000..d37db25c5
--- /dev/null
+++ b/bskyweb/templates/about-help.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Help - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ h3 { font-size: 16px; margin: 16px 0 8px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ .faq-item { margin-bottom: 24px; }
+ .contact-box { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
+ @media (prefers-color-scheme: dark) {
+ .contact-box { background: #1a1a1a; }
+ }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Help Center</h1>
+ </div>
+
+ <h2>About syu.is</h2>
+ <p>syu.is is a social networking service built on the AT Protocol (Authenticated Transfer Protocol). It allows users to share content, connect with others, and participate in a decentralized social network.</p>
+
+ <h2>Frequently Asked Questions</h2>
+
+ <div class="faq-item">
+ <h3>What is the AT Protocol?</h3>
+ <p>The AT Protocol is a decentralized social networking protocol that allows users to own their data and identity. It enables federation between different services while maintaining user control.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I create an account?</h3>
+ <p>You can create an account by downloading the app or visiting the website. You'll need to provide an email address and choose a username.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I reset my password?</h3>
+ <p>You can reset your password through the login screen by selecting "Forgot Password" and following the instructions sent to your email.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I delete my account?</h3>
+ <p>You can delete your account through Settings &gt; Account. Please note that account deletion is permanent and cannot be undone.</p>
+ </div>
+
+ <div class="faq-item">
+ <h3>How do I report abuse or inappropriate content?</h3>
+ <p>You can report content by using the report function available on each post. Our moderation team will review reports and take appropriate action.</p>
+ </div>
+
+ <h2>Contact</h2>
+ <div class="contact-box">
+ <p>For additional support or questions:</p>
+ <ul>
+ <li>GitHub: <a href="https://github.com/syui" target="_blank">github.com/syui</a></li>
+ </ul>
+ </div>
+
+ <h2>Related Links</h2>
+ <ul>
+ <li><a href="/about/support/tos">Terms of Service</a></li>
+ <li><a href="/about/support/privacy-policy">Privacy Policy</a></li>
+ <li><a href="/about/support/license">License</a></li>
+ <li><a href="/about/support/app">App Info</a></li>
+ <li><a href="https://atproto.com" target="_blank">AT Protocol Documentation</a></li>
+ </ul>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>
diff --git a/bskyweb/templates/about-privacy.html b/bskyweb/templates/about-privacy.html
new file mode 100644
index 000000000..14a1168ad
--- /dev/null
+++ b/bskyweb/templates/about-privacy.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Privacy Policy - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Privacy Policy</h1>
+ </div>
+
+ <h2>1. Introduction</h2>
+ <p>This Privacy Policy explains how syu.is collects, uses, and protects your personal information when you use our service.</p>
+
+ <h2>2. Information We Collect</h2>
+ <p>We collect the following types of information:</p>
+ <ul>
+ <li><strong>Account Information:</strong> Email address, username, and profile information you provide</li>
+ <li><strong>Content:</strong> Posts, messages, and other content you create on the platform</li>
+ <li><strong>Usage Data:</strong> Information about how you interact with our service</li>
+ <li><strong>Device Information:</strong> Browser type, operating system, and device identifiers</li>
+ </ul>
+
+ <h2>3. How We Use Your Information</h2>
+ <p>We use your information to:</p>
+ <ul>
+ <li>Provide and maintain our service</li>
+ <li>Improve and personalize your experience</li>
+ <li>Communicate with you about the service</li>
+ <li>Ensure security and prevent abuse</li>
+ </ul>
+
+ <h2>4. Data Sharing</h2>
+ <p>As part of the AT Protocol federation, your public content may be shared with other servers in the network. We do not sell your personal information to third parties.</p>
+
+ <h2>5. Data Security</h2>
+ <p>We implement appropriate security measures to protect your personal information. However, no method of transmission over the Internet is 100% secure.</p>
+
+ <h2>6. Your Rights</h2>
+ <p>You have the right to:</p>
+ <ul>
+ <li>Access your personal data</li>
+ <li>Request correction of your data</li>
+ <li>Request deletion of your account</li>
+ <li>Export your data</li>
+ </ul>
+
+ <h2>7. Cookies</h2>
+ <p>We use cookies and similar technologies to maintain your session and improve your experience.</p>
+
+ <h2>8. Changes to This Policy</h2>
+ <p>We may update this Privacy Policy from time to time. We will notify you of any significant changes.</p>
+
+ <h2>9. Contact</h2>
+ <p>For privacy-related questions, please visit our <a href="/about/support/help">Help page</a>.</p>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>
diff --git a/bskyweb/templates/about-tos.html b/bskyweb/templates/about-tos.html
new file mode 100644
index 000000000..db5d82f5c
--- /dev/null
+++ b/bskyweb/templates/about-tos.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <title>Terms of Service - syu.is</title>
+ <link rel="icon" type="image/png" href="{{ staticCDNHost }}/static/favicon.png">
+ <style>
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ color: #1a1a1a;
+ background: #fff;
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+ @media (prefers-color-scheme: dark) {
+ body { background: #000; color: #e0e0e0; }
+ a { color: #6bb3ff; }
+ h1, h2, h3 { color: #fff; }
+ }
+ h1 { font-size: 28px; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #ddd; }
+ h2 { font-size: 20px; margin: 24px 0 12px; }
+ p { margin-bottom: 16px; }
+ ul { margin: 0 0 16px 24px; }
+ li { margin-bottom: 8px; }
+ a { color: #0066cc; text-decoration: none; }
+ a:hover { text-decoration: underline; }
+ .header { margin-bottom: 32px; }
+ .back-link { display: inline-block; margin-bottom: 16px; font-size: 14px; }
+ .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 14px; color: #666; }
+ </style>
+</head>
+<body>
+ <div class="header">
+ <a href="/" class="back-link">&larr; Back to syu.is</a>
+ <h1>Terms of Service</h1>
+ </div>
+
+ <h2>1. Introduction</h2>
+ <p>Welcome to syu.is. By using our service, you agree to these terms. Please read them carefully.</p>
+
+ <h2>2. Service Description</h2>
+ <p>syu.is is a social networking service built on the AT Protocol. We provide a platform for users to share content and connect with others.</p>
+
+ <h2>3. User Responsibilities</h2>
+ <p>As a user of syu.is, you agree to:</p>
+ <ul>
+ <li>Provide accurate information when creating an account</li>
+ <li>Keep your account credentials secure</li>
+ <li>Not use the service for illegal activities</li>
+ <li>Respect other users and their content</li>
+ <li>Comply with applicable laws and regulations</li>
+ </ul>
+
+ <h2>4. Content Guidelines</h2>
+ <p>Users are responsible for the content they post. Prohibited content includes:</p>
+ <ul>
+ <li>Illegal content</li>
+ <li>Harassment or abuse</li>
+ <li>Spam or misleading information</li>
+ <li>Content that violates others' rights</li>
+ </ul>
+
+ <h2>5. Privacy</h2>
+ <p>Your privacy is important to us. Please review our <a href="/about/support/privacy-policy">Privacy Policy</a> to understand how we handle your data.</p>
+
+ <h2>6. Disclaimer</h2>
+ <p>The service is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the service.</p>
+
+ <h2>7. Changes to Terms</h2>
+ <p>We may update these terms from time to time. Continued use of the service after changes constitutes acceptance of the new terms.</p>
+
+ <h2>8. Contact</h2>
+ <p>For questions about these terms, please visit our <a href="/about/support/help">Help page</a>.</p>
+
+ <div class="footer">
+ <p>Last updated: 2025</p>
+ <p>&copy; syu.is</p>
+ </div>
+</body>
+</html>

View File

@@ -1,75 +1,70 @@
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -196,38 +196,40 @@
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
/>
- <Btn
- testID="bottomBarMessagesBtn"
- icon={
- isAtMessages ? (
- <MessageFilled
- width={iconWidth - 1}
- style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
- />
- ) : (
- <Message
- width={iconWidth - 1}
- style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
- />
- )
- }
- onPress={onPressMessages}
- notificationCount={numUnreadMessages.numUnread}
- hasNew={numUnreadMessages.hasNew}
- accessible={true}
- accessibilityRole="tab"
- accessibilityLabel={_(msg`Chat`)}
- accessibilityHint={
- numUnreadMessages.count > 0
- ? _(
- plural(numUnreadMessages.numUnread ?? 0, {
- one: '# unread item',
- other: '# unread items',
- }),
- )
- : ''
- }
- />
+ {currentAccount?.isSelfHosted && (
+ <Btn
+ testID="bottomBarMessagesBtn"
+ icon={
+ isAtMessages ? (
+ <MessageFilled
+ width={iconWidth - 1}
+ style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
+ />
+ ) : (
+ <Message
+ width={iconWidth - 1}
+ style={[styles.ctrlIcon, t.atoms.text, styles.feedsIcon]}
+ />
+ )
+ }
+ onPress={onPressMessages}
+ notificationCount={numUnreadMessages.numUnread}
+ hasNew={numUnreadMessages.hasNew}
+ accessible={true}
+ accessibilityRole="tab"
+ accessibilityLabel={_(msg`Chat`)}
+ accessibilityHint={
+ numUnreadMessages.count > 0
+ ? _(
+ plural(numUnreadMessages.numUnread ?? 0, {
+ one: '# unread item',
+ other: '# unread items',
+ }),
+ )
+ : ''
+ }
+ />
+ )}
<Btn
testID="bottomBarNotificationsBtn"
icon={
diff --git a/src/state/messages/events/index.tsx b/src/state/messages/events/index.tsx
index 2ff0784ae..dc314ecc5 100644
--- a/src/state/messages/events/index.tsx
+++ b/src/state/messages/events/index.tsx
@@ -10,13 +10,7 @@ const MessagesEventBusContext = React.createContext<MessagesEventBus | null>(
MessagesEventBusContext.displayName = 'MessagesEventBusContext'
export function useMessagesEventBus() {
- const ctx = React.useContext(MessagesEventBusContext)
- if (!ctx) {
- throw new Error(
- 'useMessagesEventBus must be used within a MessagesEventBusProvider',
- )
- }
- return ctx
+ return React.useContext(MessagesEventBusContext)
}
export function MessagesEventBusProvider({
@@ -24,18 +18,11 @@ export function MessagesEventBusProvider({
}: {
children: React.ReactNode
}) {
- const {currentAccount} = useSession()
-
- if (!currentAccount) {
- return (
- <MessagesEventBusContext.Provider value={null}>
- {children}
- </MessagesEventBusContext.Provider>
- )
- }
-
+ // DM functionality is disabled for syu.is
return (
- <MessagesEventBusProviderInner>{children}</MessagesEventBusProviderInner>
+ <MessagesEventBusContext.Provider value={null}>
+ {children}
+ </MessagesEventBusContext.Provider>
)
}
diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx
index c5457d1cb..5bc37bdce 100644
--- a/src/state/queries/messages/list-conversations.tsx
+++ b/src/state/queries/messages/list-conversations.tsx
@@ -74,17 +74,12 @@ export function useListConvos() {
const empty = {accepted: [], request: []}
export function ListConvosProvider({children}: {children: React.ReactNode}) {
- const {hasSession} = useSession()
-
- if (!hasSession) {
- return (
- <ListConvosContext.Provider value={empty}>
- {children}
- </ListConvosContext.Provider>
- )
- }
-
- return <ListConvosProviderInner>{children}</ListConvosProviderInner>
+ // DM functionality is disabled for syu.is - always return empty
+ return (
+ <ListConvosContext.Provider value={empty}>
+ {children}
+ </ListConvosContext.Provider>
+ )
}
export function ListConvosProviderInner({

View File

@@ -1,41 +1,15 @@
diff --git a/src/env/common.ts b/src/env/common.ts
--- a/src/env/common.ts
+++ b/src/env/common.ts
@@ -88,7 +88,7 @@
* Metrics API host
*/
export const METRICS_API_HOST: string =
- process.env.EXPO_PUBLIC_METRICS_API_HOST || 'https://events.bsky.app'
+ process.env.EXPO_PUBLIC_METRICS_API_HOST || ''
@@ -107,9 +107,8 @@ export const GCP_PROJECT_ID: number =
/**
* Growthbook API host
@@ -128,9 +128,7 @@
* 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 GEOLOCATION_DEV_URL = process.env.GEOLOCATION_DEV_URL
export const GEOLOCATION_PROD_URL = `https://ip.bsky.app`
-export const GEOLOCATION_URL = IS_DEV
- ? (GEOLOCATION_DEV_URL ?? GEOLOCATION_PROD_URL)
- : GEOLOCATION_PROD_URL
+export const GEOLOCATION_URL = null
/**
* URLs for the live-event config web worker. Can be a
@@ -138,9 +136,7 @@
*/
export const LIVE_EVENTS_DEV_URL = process.env.LIVE_EVENTS_DEV_URL
export const LIVE_EVENTS_PROD_URL = `https://live-events.workers.bsky.app`
-export const LIVE_EVENTS_URL = IS_DEV
- ? (LIVE_EVENTS_DEV_URL ?? LIVE_EVENTS_PROD_URL)
- : LIVE_EVENTS_PROD_URL
+export const LIVE_EVENTS_URL = null
/**
* URLs for the app-config web worker. Can be a
@@ -148,6 +144,4 @@
*/
export const APP_CONFIG_DEV_URL = process.env.APP_CONFIG_DEV_URL
export const APP_CONFIG_PROD_URL = `https://app-config.workers.bsky.app`
-export const APP_CONFIG_URL = IS_DEV
- ? (APP_CONFIG_DEV_URL ?? APP_CONFIG_PROD_URL)
- : APP_CONFIG_PROD_URL
+export const APP_CONFIG_URL = null
export const BAPP_CONFIG_DEV_URL = process.env.BAPP_CONFIG_DEV_URL
export const BAPP_CONFIG_PROD_URL = `https://ip.bsky.app`
-export const BAPP_CONFIG_URL = IS_DEV
- ? (BAPP_CONFIG_DEV_URL ?? BAPP_CONFIG_PROD_URL)
- : BAPP_CONFIG_PROD_URL
+export const BAPP_CONFIG_URL = null

View File

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

View File

@@ -1,39 +1,23 @@
diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx
--- a/src/screens/Signup/StepInfo/index.tsx
+++ b/src/screens/Signup/StepInfo/index.tsx
@@ -8,46 +8,18 @@
@@ -7,11 +7,9 @@
import {isEmailMaybeInvalid} from '#/lib/strings/email'
import {logger} from '#/logger'
import {useSignupContext} from '#/screens/Signup/state'
-import {Policies} from '#/screens/Signup/StepInfo/Policies'
-import {is13, is18, useSignupContext} from '#/screens/Signup/state'
+import {useSignupContext} from '#/screens/Signup/state'
import {Policies} from '#/screens/Signup/StepInfo/Policies'
import {atoms as a, native} from '#/alf'
-import * as Admonition from '#/components/Admonition'
-import * as Dialog from '#/components/Dialog'
-import {DeviceLocationRequestDialog} from '#/components/dialogs/DeviceLocationRequestDialog'
-import * as DateField from '#/components/forms/DateField'
-import {type DateFieldRef} from '#/components/forms/DateField/types'
import {FormError} from '#/components/forms/FormError'
import {HostingProvider} from '#/components/forms/HostingProvider'
import * as TextField from '#/components/forms/TextField'
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
-import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
@@ -22,16 +20,6 @@
import {usePreemptivelyCompleteActivePolicyUpdate} from '#/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate'
-import * as Toast from '#/components/Toast'
-import {
- isUnderAge,
- MIN_ACCESS_AGE,
- useAgeAssuranceRegionConfigWithFallback,
-} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics'
-import {IS_NATIVE} from '#/env'
-import {
- useDeviceGeolocationApi,
- useIsDeviceGeolocationGranted,
-} from '#/geolocation'
import {BackNextButtons} from '../BackNextButtons'
-function sanitizeDate(date: Date): Date {
- if (!date || date.toString() === 'Invalid Date') {
- logger.error(`Create account: handled invalid date for birthDate`, {
@@ -47,41 +31,26 @@
export function StepInfo({
onPressBack,
isServerError,
@@ -72,22 +44,7 @@
@@ -55,7 +43,6 @@
const emailInputRef = useRef<TextInput>(null)
const passwordInputRef = useRef<TextInput>(null)
- const birthdateInputRef = useRef<DateFieldRef>(null)
- const aaRegionConfig = useAgeAssuranceRegionConfigWithFallback()
- const {setDeviceGeolocation} = useDeviceGeolocationApi()
- const locationControl = Dialog.useDialogControl()
- const isOverRegionMinAccessAge = state.dateOfBirth
- ? !isUnderAge(state.dateOfBirth.toISOString(), aaRegionConfig.minAccessAge)
- : true
- const isOverAppMinAccessAge = state.dateOfBirth
- ? !isUnderAge(state.dateOfBirth.toISOString(), MIN_ACCESS_AGE)
- : true
- const isOverMinAdultAge = state.dateOfBirth
- ? !isUnderAge(state.dateOfBirth.toISOString(), 18)
- : true
- const isDeviceGeolocationGranted = useIsDeviceGeolocationGranted()
-
const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false)
const tldtsRef = React.useRef<typeof tldts>(undefined)
@@ -107,10 +64,6 @@
@@ -76,10 +63,6 @@
const emailChanged = prevEmailValueRef.current !== email
const password = passwordValueRef.current
- if (!isOverRegionMinAccessAge) {
- if (!is13(state.dateOfBirth)) {
- return
- }
-
if (state.serviceDescription?.inviteCodeRequired && !inviteCode) {
return dispatch({
type: 'setError',
@@ -273,107 +226,16 @@
@@ -246,44 +229,21 @@
secureTextEntry
autoComplete="new-password"
autoCapitalize="none"
@@ -94,7 +63,7 @@
passwordRules="minlength: 8;"
/>
</TextField.Root>
- </View>
</View>
- <View>
- <DateField.LabelText>
- <Trans>Your birth date</Trans>
@@ -113,80 +82,19 @@
- accessibilityHint={_(msg`Select your date of birth`)}
- maximumDate={new Date()}
- />
</View>
-
- <View style={[a.gap_sm]}>
- <Policies serviceDescription={state.serviceDescription} />
-
- {!isOverRegionMinAccessAge || !isOverAppMinAccessAge ? (
- <Admonition.Outer type="error">
- <Admonition.Row>
- <Admonition.Icon />
- <Admonition.Content>
- <Admonition.Text>
- {!isOverAppMinAccessAge ? (
- <Plural
- value={MIN_ACCESS_AGE}
- other="You must be # years of age or older to create an account."
- />
- ) : (
- <Plural
- value={aaRegionConfig.minAccessAge}
- other="You must be # years of age or older to create an account in your region."
- />
- )}
- </Admonition.Text>
- {IS_NATIVE &&
- !isDeviceGeolocationGranted &&
- isOverAppMinAccessAge && (
- <Admonition.Text>
- <Trans>
- Have we got your location wrong?{' '}
- <SimpleInlineLinkText
- label={_(
- msg`Tap here to confirm your location with GPS.`,
- )}
- {...createStaticClick(() => {
- locationControl.open()
- })}>
- Tap here to confirm your location with GPS.
- </SimpleInlineLinkText>
- </Trans>
- </Admonition.Text>
- )}
- </Admonition.Content>
- </Admonition.Row>
- </Admonition.Outer>
- ) : !isOverMinAdultAge ? (
- <Admonition.Admonition type="warning">
- <Trans>
- If you are not yet an adult according to the laws of your
- country, your parent or legal guardian must read these Terms
- on your behalf.
- </Trans>
- </Admonition.Admonition>
- ) : undefined}
- </View>
-
- {IS_NATIVE && (
- <DeviceLocationRequestDialog
- control={locationControl}
- onLocationAcquired={props => {
- props.closeDialog(() => {
- // set this after close!
- setDeviceGeolocation(props.geolocation)
- Toast.show(_(msg`Your location has been updated.`), {
- type: 'success',
- })
- })
- }}
- />
- )}
<Policies
serviceDescription={state.serviceDescription}
- needsGuardian={!is18(state.dateOfBirth)}
- under13={!is13(state.dateOfBirth)}
+ needsGuardian={false}
+ under13={false}
/>
</>
) : undefined}
</View>
<BackNextButtons
- hideNext={!isOverRegionMinAccessAge}
- hideNext={!is13(state.dateOfBirth)}
+ hideNext={false}
showRetry={isServerError}
isLoading={state.isLoading}

View File

@@ -0,0 +1,361 @@
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -63,6 +63,7 @@ import {NotificationsScreen} from '#/view/screens/Notifications'
import {PostThreadScreen} from '#/view/screens/PostThread'
import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy'
import {LicenseScreen} from '#/view/screens/License'
+import {AppInfoScreen} from '#/view/screens/AppInfo'
import {ProfileScreen} from '#/view/screens/Profile'
import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy'
import {Storybook} from '#/view/screens/Storybook'
@@ -341,6 +342,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
getComponent={() => LicenseScreen}
options={{title: title(msg`License`)}}
/>
+ <Stack.Screen
+ name="AppInfo"
+ getComponent={() => AppInfoScreen}
+ options={{title: title(msg`App Info`)}}
+ />
<Stack.Screen
name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen}
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -40,6 +40,7 @@ export type CommonNavigatorParams = {
PrivacyPolicy: undefined
TermsOfService: undefined
License: undefined
+ AppInfo: undefined
CommunityGuidelines: undefined
CopyrightPolicy: undefined
LanguageSettings: undefined
diff --git a/src/routes.ts b/src/routes.ts
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -75,6 +75,7 @@ export const router = new Router<AllNavigatableRoutes>({
TermsOfService: 'https://syu.is/about/support/tos',
CommunityGuidelines: '/support/community-guidelines',
License: 'https://syu.is/about/support/license',
+ AppInfo: 'https://syu.is/about/support/app',
CopyrightPolicy: '/support/copyright',
// hashtags
Hashtag: '/hashtag/:tag',
diff --git a/src/view/screens/AppInfo.tsx b/src/view/screens/AppInfo.tsx
new file mode 100644
index 000000000..000000001
--- /dev/null
+++ b/src/view/screens/AppInfo.tsx
@@ -0,0 +1,310 @@
+import React, {useState} from 'react'
+import {
+ View,
+ ScrollView,
+ StyleSheet,
+ Pressable,
+ Image,
+} from 'react-native'
+import * as Clipboard from 'expo-clipboard'
+import * as WebBrowser from 'expo-web-browser'
+import {Trans} from '@lingui/macro'
+
+import * as Layout from '#/components/Layout'
+import {Text} from '#/components/Typography'
+import {useSetTitle} from '#/lib/hooks/useSetTitle'
+import {atoms as a, useTheme} from '#/alf'
+
+const APP_VERSION = '1.111.0'
+const APP_NAME = 'Aiat'
+const BITCOIN_ADDRESS = '3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr'
+
+export function AppInfoScreen() {
+ useSetTitle('App Info')
+ const t = useTheme()
+ const [copied, setCopied] = useState(false)
+
+ const copyToClipboard = async (text: string) => {
+ try {
+ await Clipboard.setStringAsync(text)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch (e) {
+ console.log('Clipboard not available')
+ }
+ }
+
+ const openUrl = (url: string) => {
+ WebBrowser.openBrowserAsync(url)
+ }
+
+ return (
+ <Layout.Screen>
+ <Layout.Header.Outer>
+ <Layout.Header.BackButton />
+ <Layout.Header.Content>
+ <Layout.Header.TitleText>
+ <Trans>App Info</Trans>
+ </Layout.Header.TitleText>
+ </Layout.Header.Content>
+ <Layout.Header.Slot />
+ </Layout.Header.Outer>
+ <Layout.Content>
+ <ScrollView
+ style={[a.flex_1]}
+ contentContainerStyle={[a.p_lg, a.pt_xl, a.pb_5xl]}>
+ {/* App Header */}
+ <View style={styles.appHeader}>
+ <View style={[styles.appIconContainer, t.atoms.bg_contrast_25]}>
+ <Image
+ source={require('../../../assets/logo.png')}
+ style={styles.appIcon}
+ resizeMode="cover"
+ />
+ </View>
+ <Text style={[styles.appName, t.atoms.text]}>
+ {APP_NAME}
+ </Text>
+ <Text style={[styles.appVersion, t.atoms.text_contrast_medium]}>
+ v{APP_VERSION}
+ </Text>
+ </View>
+
+ {/* Description Section */}
+ <View style={[styles.section, t.atoms.bg_contrast_25]}>
+ <Text style={[styles.description, t.atoms.text]}>
+ {APP_NAME} is a social networking application based on AT Protocol.
+ Connect with your community on syu.is.
+ </Text>
+ </View>
+
+ {/* App Information Section */}
+ <View style={[styles.section, t.atoms.bg_contrast_25]}>
+ <Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
+ App Information
+ </Text>
+ <View style={styles.infoGrid}>
+ <View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
+ <Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
+ Version
+ </Text>
+ <Text style={[styles.infoValue, t.atoms.text]}>
+ {APP_VERSION}
+ </Text>
+ </View>
+ <View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
+ <Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
+ Category
+ </Text>
+ <Text style={[styles.infoValue, t.atoms.text]}>
+ Social
+ </Text>
+ </View>
+ <View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
+ <Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
+ Supported OS
+ </Text>
+ <Text style={[styles.infoValue, t.atoms.text]}>
+ iOS 26.0+
+ </Text>
+ </View>
+ <View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
+ <Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
+ Price
+ </Text>
+ <Text style={[styles.infoValue, t.atoms.text]}>
+ Free
+ </Text>
+ </View>
+ </View>
+ </View>
+
+ {/* Developer Section */}
+ <View style={[styles.section, t.atoms.bg_contrast_25]}>
+ <Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
+ Developer
+ </Text>
+ <View style={styles.developerCard}>
+ <Text style={[styles.developerName, t.atoms.text]}>syui</Text>
+ </View>
+
+ <Pressable
+ onPress={() => openUrl('https://github.com/syui')}
+ style={[styles.linkRow, t.atoms.border_contrast_low]}>
+ <Text style={[styles.linkIcon, t.atoms.text_contrast_medium]}>
+ GitHub
+ </Text>
+ <Text style={[styles.linkValue, {color: '#0084ff'}]}>
+ github.com/syui
+ </Text>
+ <Text style={[styles.linkArrow, t.atoms.text_contrast_low]}>→</Text>
+ </Pressable>
+
+ <Pressable
+ onPress={() => openUrl('https://syu.is/syui')}
+ style={[styles.linkRow, t.atoms.border_contrast_low]}>
+ <Text style={[styles.linkIcon, t.atoms.text_contrast_medium]}>
+ ATProto
+ </Text>
+ <Text style={[styles.linkValue, {color: '#0084ff'}]}>
+ syu.is/syui
+ </Text>
+ <Text style={[styles.linkArrow, t.atoms.text_contrast_low]}>→</Text>
+ </Pressable>
+ </View>
+
+ {/* Bitcoin Section */}
+ <View style={[styles.section, t.atoms.bg_contrast_25]}>
+ <Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
+ Bitcoin
+ </Text>
+ <Pressable
+ onPress={() => copyToClipboard(BITCOIN_ADDRESS)}
+ style={styles.bitcoinRow}>
+ <Text style={styles.bitcoinLabel}>₿</Text>
+ <Text style={[styles.bitcoinAddress, t.atoms.text_contrast_medium]}>
+ {BITCOIN_ADDRESS}
+ </Text>
+ <Text style={[styles.copyHint, copied && styles.copiedHint]}>
+ {copied ? 'copied!' : 'copy'}
+ </Text>
+ </Pressable>
+ </View>
+
+ {/* Copyright */}
+ <View style={styles.copyright}>
+ <Text style={[styles.copyrightText, t.atoms.text_contrast_low]}>
+ © syui
+ </Text>
+ </View>
+ </ScrollView>
+ </Layout.Content>
+ </Layout.Screen>
+ )
+}
+
+const styles = StyleSheet.create({
+ appHeader: {
+ alignItems: 'center',
+ marginBottom: 24,
+ },
+ appIconContainer: {
+ width: 80,
+ height: 80,
+ borderRadius: 18,
+ overflow: 'hidden',
+ marginBottom: 12,
+ },
+ appIcon: {
+ width: '100%',
+ height: '100%',
+ },
+ appName: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ marginBottom: 4,
+ },
+ appVersion: {
+ fontSize: 14,
+ },
+ section: {
+ marginBottom: 16,
+ borderRadius: 16,
+ padding: 16,
+ },
+ sectionTitle: {
+ fontSize: 13,
+ fontWeight: '600',
+ textTransform: 'uppercase',
+ letterSpacing: 0.5,
+ marginBottom: 12,
+ },
+ description: {
+ fontSize: 15,
+ lineHeight: 22,
+ },
+ infoGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 8,
+ },
+ infoItem: {
+ flex: 1,
+ minWidth: '45%',
+ alignItems: 'center',
+ borderRadius: 12,
+ padding: 12,
+ },
+ infoLabel: {
+ fontSize: 11,
+ textTransform: 'uppercase',
+ letterSpacing: 0.5,
+ marginBottom: 4,
+ },
+ infoValue: {
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ },
+ developerCard: {
+ marginBottom: 12,
+ },
+ developerName: {
+ fontSize: 18,
+ fontWeight: '600',
+ },
+ linkRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 12,
+ borderTopWidth: 1,
+ },
+ linkIcon: {
+ fontSize: 14,
+ fontWeight: '600',
+ width: 70,
+ },
+ linkValue: {
+ flex: 1,
+ fontSize: 14,
+ },
+ linkArrow: {
+ fontSize: 16,
+ },
+ bitcoinRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: 'rgba(247, 147, 26, 0.08)',
+ borderRadius: 12,
+ padding: 14,
+ gap: 10,
+ },
+ bitcoinLabel: {
+ fontSize: 18,
+ fontWeight: '600',
+ color: '#f7931a',
+ },
+ bitcoinAddress: {
+ flex: 1,
+ fontSize: 11,
+ fontFamily: 'monospace',
+ },
+ copyHint: {
+ fontSize: 12,
+ color: '#999999',
+ minWidth: 50,
+ textAlign: 'right',
+ },
+ copiedHint: {
+ color: '#4CAF50',
+ fontWeight: '600',
+ },
+ copyright: {
+ alignItems: 'center',
+ marginTop: 20,
+ marginBottom: 20,
+ },
+ copyrightText: {
+ fontSize: 12,
+ },
+})

View File

@@ -0,0 +1,51 @@
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -46,23 +46,6 @@ export const SplashScreen = ({
<View
testID="signinOrCreateAccount"
style={[a.px_xl, a.gap_md, a.pb_2xl]}>
- <Button
- testID="createAccountButton"
- onPress={() => {
- onPressCreateAccount()
- playHaptic('Light')
- }}
- label={_(msg`Create new account`)}
- accessibilityHint={_(
- msg`Opens flow to create a new Bluesky account`,
- )}
- size="large"
- variant="solid"
- color="primary">
- <ButtonText>
- <Trans>Create account</Trans>
- </ButtonText>
- </Button>
<Button
testID="signInButton"
onPress={() => {
diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
--- a/src/view/com/auth/SplashScreen.web.tsx
+++ b/src/view/com/auth/SplashScreen.web.tsx
@@ -97,20 +97,6 @@ export const SplashScreen = ({
<View
testID="signinOrCreateAccount"
style={[a.w_full, a.px_xl, a.gap_md, a.pb_2xl, {maxWidth: 320}]}>
- <Button
- testID="createAccountButton"
- onPress={onPressCreateAccount}
- label={_(msg`Create new account`)}
- accessibilityHint={_(
- msg`Opens flow to create a new Bluesky account`,
- )}
- size="large"
- variant="solid"
- color="primary">
- <ButtonText>
- <Trans>Create account</Trans>
- </ButtonText>
- </Button>
<Button
testID="signInButton"
onPress={onPressSignin}

View File

@@ -1,71 +0,0 @@
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index 18bb8c8f0..bab286d7a 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -5,6 +5,7 @@ import {
jsonStringToLex,
} from '@atproto/api'
+import {PUBLIC_APPVIEW} from '#/lib/constants'
import {
getAppLanguageAsContentLanguage,
getContentLanguages,
@@ -12,6 +13,17 @@ import {
import {type FeedAPI, type FeedAPIResponse} from './types'
import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils'
+// Check if the feed is hosted on syu.is network
+function isSyuIsFeed(feedUri: string): boolean {
+ return feedUri.includes('did:plc:6qyecktefllvenje24fcxnie') || feedUri.includes('syu.is')
+}
+
+// Check if the agent is connected to syu.is
+function isAgentOnSyuIs(agent: BskyAgent): boolean {
+ const serviceUrl = agent.service?.toString() || ''
+ return serviceUrl.includes('syu.is')
+}
+
export class CustomFeedAPI implements FeedAPI {
agent: BskyAgent
params: GetCustomFeed.QueryParams
@@ -54,8 +66,12 @@ export class CustomFeedAPI implements FeedAPI {
const agent = this.agent
const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed)
- const res = agent.did
- ? await this.agent.app.bsky.feed.getFeed(
+ // For syu.is feeds accessed from non-syu.is accounts, use PUBLIC_APPVIEW
+ const needsPublicAppView = isSyuIsFeed(this.params.feed) && !isAgentOnSyuIs(agent)
+
+ const res = !agent.did || needsPublicAppView
+ ? await loggedOutFetch({...this.params, cursor, limit})
+ : await this.agent.app.bsky.feed.getFeed(
{
...this.params,
cursor,
@@ -70,7 +86,6 @@ export class CustomFeedAPI implements FeedAPI {
},
},
)
- : await loggedOutFetch({...this.params, cursor, limit})
if (res.success) {
// NOTE
// some custom feeds fail to enforce the pagination limit
@@ -120,7 +135,7 @@ async function loggedOutFetch({
// manually construct fetch call so we can add the `lang` cache-busting param
let res = await fetch(
- `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
+ `${PUBLIC_APPVIEW}/xrpc/app.bsky.feed.getFeed?feed=${encodeURIComponent(feed)}${
cursor ? `&cursor=${cursor}` : ''
}&limit=${limit}&lang=${contentLangs}`,
{
@@ -140,7 +155,7 @@ async function loggedOutFetch({
// no data, try again with language headers removed
res = await fetch(
- `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
+ `${PUBLIC_APPVIEW}/xrpc/app.bsky.feed.getFeed?feed=${encodeURIComponent(feed)}${
cursor ? `&cursor=${cursor}` : ''
}&limit=${limit}`,
{method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}},

View File

@@ -1,23 +0,0 @@
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 123456789..987654321 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -218,13 +218,13 @@ function ProfileScreenLoaded({
const showPostsTab = true
const showRepliesTab = hasSession
const showMediaTab = !hasLabeler
- const showVideosTab = !hasLabeler
+ const showVideosTab = false
const showLikesTab = isMe
const feedGenCount = profile.associated?.feedgens || 0
- const showFeedsTab = isMe || feedGenCount > 0
+ const showFeedsTab = feedGenCount > 0
const starterPackCount = profile.associated?.starterPacks || 0
- const showStarterPacksTab = isMe || starterPackCount > 0
+ const showStarterPacksTab = false
// subtract starterpack count from list count, since starterpacks are a type of list
const listCount = (profile.associated?.lists || 0) - starterPackCount
- const showListsTab = hasSession && (isMe || listCount > 0)
+ const showListsTab = false
const sectionTitles = [

View File

@@ -1,28 +0,0 @@
diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx
--- a/src/view/com/home/HomeHeader.tsx
+++ b/src/view/com/home/HomeHeader.tsx
@@ -3,7 +3,6 @@ import {useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types'
import {type FeedSourceInfo} from '#/state/queries/feed'
-import {useSession} from '#/state/session'
import {type RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {TabBar} from '../pager/TabBar'
import {HomeHeaderLayout} from './HomeHeaderLayout'
@@ -16,17 +15,15 @@ export function HomeHeader(
) {
const {feeds, onSelect: onSelectProp} = props
- const {hasSession} = useSession()
const navigation = useNavigation<NavigationProp>()
const hasPinnedCustom = React.useMemo<boolean>(() => {
- if (!hasSession) return false
return feeds.some(tab => {
const isFollowing = tab.uri === 'following'
return !isFollowing
})
- }, [feeds, hasSession])
+ }, [feeds])
const items = React.useMemo(() => {
const pinnedNames = feeds.map(f => f.displayName)

View File

@@ -1,13 +0,0 @@
diff --git a/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx b/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx
index 63e11a7f4..70fa993cf 100644
--- a/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx
+++ b/src/components/dialogs/nuxs/FindContactsAnnouncement.tsx
@@ -25,7 +25,7 @@ export const enabled = createIsEnabledCheck(props => {
!IS_E2E &&
isNative &&
isExistingUserAsOf(
- '2025-12-16T00:00:00.000Z',
+ '2099-12-16T00:00:00.000Z',
props.currentProfile.createdAt,
) &&
isFindContactsFeatureEnabled(props.geolocation.countryCode)

View File

@@ -1,132 +0,0 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -1,5 +1,5 @@
import {memo, useMemo, useState} from 'react'
-import {View} from 'react-native'
+import {Image, Pressable, View} from 'react-native'
import {
type AppBskyActorDefs,
moderateProfile,
@@ -11,7 +11,10 @@
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
+import {useQuery} from '@tanstack/react-query'
+
import {useHaptics} from '#/lib/haptics'
+import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
@@ -47,6 +50,103 @@
import {ProfileHeaderShell} from './Shell'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
+const SERVICE_FAVICONS: Record<string, any> = {
+ 'syui.ai': require('../../../../assets/favicons/syui.ai.png'),
+ 'bsky.app': require('../../../../assets/favicons/bsky.app.png'),
+ 'atproto.com': require('../../../../assets/favicons/atproto.com.png'),
+}
+
+async function resolvePds(did: string): Promise<string> {
+ if (did.startsWith('did:web:')) {
+ const host = did.split(':').slice(2).join(':')
+ const res = await fetch(`https://${host}/.well-known/did.json`)
+ if (!res.ok) throw new Error('failed to resolve did:web')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (pds) return pds
+ return `https://${host}`
+ }
+ const res = await fetch(`https://plc.directory/${did}`)
+ if (!res.ok) throw new Error('failed to resolve DID')
+ const doc = await res.json()
+ const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
+ if (!pds) throw new Error('no PDS found')
+ return pds
+}
+
+function ProfileServiceLinks({
+ profile,
+}: {
+ profile: AppBskyActorDefs.ProfileViewDetailed
+}) {
+ const t = useTheme()
+ const openLink = useOpenLink()
+
+ const {data: services} = useQuery({
+ queryKey: ['profile-services', profile.did],
+ queryFn: async () => {
+ const pds = await resolvePds(profile.did)
+ const res = await fetch(
+ `${pds}/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(profile.did)}`,
+ )
+ if (!res.ok) throw new Error('failed')
+ const data = await res.json()
+ const collections: string[] = data.collections || []
+ const serviceSet = new Set<string>()
+ for (const nsid of collections) {
+ const parts = nsid.split('.')
+ if (parts.length >= 2) {
+ const domain = parts.slice(0, 2).reverse().join('.')
+ serviceSet.add(domain)
+ }
+ }
+ return Array.from(serviceSet)
+ },
+ })
+
+ if (!services || services.length === 0) return null
+
+ return (
+ <View style={[a.flex_row, a.flex_wrap, a.gap_sm, a.pt_xs]}>
+ {services.map(service => (
+ <Pressable
+ key={service}
+ onPress={() =>
+ openLink(
+ `https://at.syu.is/@${profile.handle}/at/service/${service}`,
+ )
+ }
+ style={[
+ a.flex_row,
+ a.align_center,
+ a.gap_xs,
+ a.rounded_full,
+ t.atoms.bg_contrast_50,
+ {paddingVertical: 6, paddingHorizontal: 10},
+ ]}>
+ <Image
+ source={
+ SERVICE_FAVICONS[service] || {
+ uri: `https://www.google.com/s2/favicons?domain=${service}&sz=32`,
+ }
+ }
+ style={{width: 14, height: 14, borderRadius: 3}}
+ accessibilityIgnoresInvertColors
+ />
+ <Text
+ style={[
+ a.text_xs,
+ a.font_medium,
+ t.atoms.text_contrast_medium,
+ ]}>
+ {service}
+ </Text>
+ </Pressable>
+ ))}
+ </View>
+ )
+}
+
interface Props {
profile: AppBskyActorDefs.ProfileViewDetailed
descriptionRT: RichTextAPI | null
@@ -152,6 +252,7 @@
{!isPlaceholderProfile && !isBlockedUser && (
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />
+ <ProfileServiceLinks profile={profile} />
{descriptionRT && !moderation.ui('profileView').blur ? (
<View pointerEvents="auto">
<RichText

View File

@@ -1,35 +0,0 @@
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -385,17 +385,19 @@
threadgateRecord={threadgateRecord}
/>
- <PostControls
- post={post}
- record={record}
- richText={richText}
- onPressReply={onPressReply}
- logContext="FeedItem"
- feedContext={feedContext}
- reqId={reqId}
- threadgateRecord={threadgateRecord}
- onShowLess={onShowLess}
- viaRepost={viaRepost}
- />
+ {false && (
+ <PostControls
+ post={post}
+ record={record}
+ richText={richText}
+ onPressReply={onPressReply}
+ logContext="FeedItem"
+ feedContext={feedContext}
+ reqId={reqId}
+ threadgateRecord={threadgateRecord}
+ onShowLess={onShowLess}
+ viaRepost={viaRepost}
+ />
+ )}
</View>
<DiscoverDebug feedContext={feedContext} />

View File

@@ -1,50 +0,0 @@
--- a/src/view/com/posts/PostFeed.tsx 2026-02-16 02:19:59
+++ b/src/view/com/posts/PostFeed.tsx 2026-02-16 02:20:13
@@ -519,16 +519,17 @@
key: 'liveEventFeedsAndTrendingBanner-' + sliceIndex,
})
// Show composer prompt for Discover and Following feeds
- if (
- hasSession &&
- (feedUriOrActorDid === DISCOVER_FEED_URI ||
- feed === 'following')
- ) {
- arr.push({
- type: 'composerPrompt',
- key: 'composerPrompt-' + sliceIndex,
- })
- }
+ // Disabled: hide composer prompt
+ // if (
+ // hasSession &&
+ // (feedUriOrActorDid === DISCOVER_FEED_URI ||
+ // feed === 'following')
+ // ) {
+ // arr.push({
+ // type: 'composerPrompt',
+ // key: 'composerPrompt-' + sliceIndex,
+ // })
+ // }
} else if (sliceIndex === 15) {
if (areVideoFeedsEnabled && !trendingVideoDisabled) {
arr.push({
@@ -545,12 +546,13 @@
} else if (feedKind === 'following') {
if (sliceIndex === 0) {
// Show composer prompt for Following feed
- if (hasSession) {
- arr.push({
- type: 'composerPrompt',
- key: 'composerPrompt-' + sliceIndex,
- })
- }
+ // Disabled: hide composer prompt
+ // if (hasSession) {
+ // arr.push({
+ // type: 'composerPrompt',
+ // key: 'composerPrompt-' + sliceIndex,
+ // })
+ // }
}
} else if (feedKind === 'profile') {
if (sliceIndex === 5) {

View File

@@ -1,14 +0,0 @@
--- a/src/view/com/auth/SplashScreen.tsx 2026-02-16 02:55:08
+++ b/src/view/com/auth/SplashScreen.tsx 2026-02-16 02:55:16
@@ -118,8 +118,9 @@
accessibilityHint={_(
msg`Opens flow to sign in to your existing Bluesky account`,
)}
- size="large">
- <ButtonText style={{color: 'white'}}>
+ size="large"
+ color="primary">
+ <ButtonText>
<Trans>Sign in</Trans>
</ButtonText>
</Button>

View File

@@ -1,18 +0,0 @@
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -48,6 +48,7 @@
import {ProfileHeaderHandle} from './Handle'
import {ProfileHeaderMetrics} from './Metrics'
import {ProfileHeaderShell} from './Shell'
+import {ProfileAtLinks} from './ProfileAtLinks'
import {ProfileHeaderSuggestedFollows} from './SuggestedFollows'
const SERVICE_FAVICONS: Record<string, any> = {
@@ -253,6 +254,7 @@
<View style={a.gap_md}>
<ProfileHeaderMetrics profile={profile} />
<ProfileServiceLinks profile={profile} />
+ <ProfileAtLinks profile={profile} />
{descriptionRT && !moderation.ui('profileView').blur ? (
<View pointerEvents="auto">
<RichText

View File

@@ -1,19 +0,0 @@
--- a/src/view/shell/desktop/RightNav.tsx
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -111,14 +111,14 @@
</>
)}
<InlineLinkText
- to="https://bsky.social/about/support/privacy-policy"
+ to="/support/privacy-policy"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
<Text style={[t.atoms.text_contrast_low]}>{' ∙ '}</Text>
<InlineLinkText
- to="https://bsky.social/about/support/tos"
+ to="/support/tos"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Terms`)}>
{_(msg`Terms`)}

View File

@@ -1,60 +0,0 @@
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -2,6 +2,7 @@
import {Image as RNImage, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {Image} from 'expo-image'
+import {useVideoPlayer, VideoView} from 'expo-video'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -15,10 +16,13 @@
import splashImagePointer from '../../../../assets/splash/illustration-mobile.png'
// @ts-ignore
import darkSplashImagePointer from '../../../../assets/splash/illustration-mobile-dark.png'
+// @ts-ignore
+import splashVideoPointer from '../../../../assets/splash/illustration-mobile.mp4'
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
const darkSplashImageUri = RNImage.resolveAssetSource(
darkSplashImagePointer,
).uri
+const splashVideoUri = RNImage.resolveAssetSource(splashVideoPointer).uri
export const SplashScreen = ({
onPressSignin,
@@ -53,13 +57,30 @@
}
}, [t, isDarkMode])
+ const player = useVideoPlayer(splashVideoUri, p => {
+ p.loop = true
+ p.muted = true
+ p.play()
+ })
+
return (
<>
- <Image
- accessibilityIgnoresInvertColors
- source={{uri: isDarkMode ? darkSplashImageUri : splashImageUri}}
- style={[a.absolute, a.inset_0]}
- />
+ {isDarkMode ? (
+ <Image
+ accessibilityIgnoresInvertColors
+ source={{uri: darkSplashImageUri}}
+ style={[a.absolute, a.inset_0]}
+ />
+ ) : (
+ <VideoView
+ player={player}
+ style={[a.absolute, a.inset_0]}
+ contentFit="cover"
+ nativeControls={false}
+ allowsFullscreen={false}
+ allowsPictureInPicture={false}
+ />
+ )}
<Animated.View
entering={FadeIn.duration(90)}

View File

@@ -1,40 +0,0 @@
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -717,16 +717,7 @@
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
- // Show discard prompt if there's content AND either:
- // - No draft is loaded (new composition)
- // - Draft is loaded but has been modified
- if (hasContent && (!composerState.draftId || composerState.isDirty)) {
- closeAllDialogs()
- Keyboard.dismiss()
- discardPromptControl.open()
- } else {
- onClose()
- }
+ onClose()
}, [
thread,
composerState.draftId,
@@ -1551,18 +1542,7 @@
</>
) : (
<>
- {!isReply && (
- <DraftsButton
- onSelectDraft={onSelectDraft}
- onSaveDraft={onSaveDraft}
- onDiscard={onDiscard}
- isEmpty={isEmpty}
- isDirty={isDirty}
- isEditingDraft={isEditingDraft}
- canSaveDraft={canSaveDraft}
- textLength={textLength}
- />
- )}
+ {/* DraftsButton removed */}
<Button
testID="composerPublishBtn"
label={

View File

@@ -1,55 +0,0 @@
diff --git a/src/features/liveEvents/context.tsx b/src/features/liveEvents/context.tsx
index 3de2534a8..e11955f57 100644
--- a/src/features/liveEvents/context.tsx
+++ b/src/features/liveEvents/context.tsx
@@ -24,6 +24,7 @@ export const DEFAULT_LIVE_EVENTS = {
async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
try {
+ if (!LIVE_EVENTS_URL) return null
const res = await fetch(`${LIVE_EVENTS_URL}/config`)
if (!res.ok) return null
const data = await res.json()
diff --git a/src/geolocation/const.ts b/src/geolocation/const.ts
index 653e829ba..4992cc870 100644
--- a/src/geolocation/const.ts
+++ b/src/geolocation/const.ts
@@ -1,7 +1,9 @@
import {GEOLOCATION_URL} from '#/env'
import {type Geolocation} from '#/geolocation/types'
-export const GEOLOCATION_SERVICE_URL = `${GEOLOCATION_URL}/geolocation`
+export const GEOLOCATION_SERVICE_URL = GEOLOCATION_URL
+ ? `${GEOLOCATION_URL}/geolocation`
+ : null
/**
* Default geolocation config.
diff --git a/src/geolocation/service.ts b/src/geolocation/service.ts
index 2d9285b67..c7c3d02c0 100644
--- a/src/geolocation/service.ts
+++ b/src/geolocation/service.ts
@@ -26,9 +26,10 @@ const onGeolocationServiceResponseUpdate = (
}
async function fetchGeolocationServiceData(
- url: string,
+ url: string | null,
): Promise<Geolocation | undefined> {
if (debug.enabled) return debug.resolve(debug.geolocation)
+ if (!url) throw new Error('geolocation service disabled')
const res = await fetch(url)
if (!res.ok) {
throw new Error(`fetchGeolocationServiceData failed ${res.status}`)
diff --git a/src/state/appConfig.tsx b/src/state/appConfig.tsx
index 67b0e553e..9eacf7ead 100644
--- a/src/state/appConfig.tsx
+++ b/src/state/appConfig.tsx
@@ -30,6 +30,7 @@ let fetchAppConfigPromise: Promise<AppConfigResponse> | undefined
async function fetchAppConfig(): Promise<AppConfigResponse | null> {
try {
+ if (!APP_CONFIG_URL) return null
if (!fetchAppConfigPromise) {
fetchAppConfigPromise = (async () => {
const r = await fetch(`${APP_CONFIG_URL}/config`)

View File

@@ -1,310 +0,0 @@
import React, {useState} from 'react'
import {
View,
ScrollView,
StyleSheet,
Pressable,
Image,
} from 'react-native'
import * as Clipboard from 'expo-clipboard'
import * as WebBrowser from 'expo-web-browser'
import {Trans} from '@lingui/macro'
import * as Layout from '#/components/Layout'
import {Text} from '#/components/Typography'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {atoms as a, useTheme} from '#/alf'
const APP_VERSION = '1.111.0'
const APP_NAME = 'Aiat'
const BITCOIN_ADDRESS = '3BqHXxraZyBapyNpJmniJDh9zqzuB8aoRr'
export function AppInfoScreen() {
useSetTitle('App Info')
const t = useTheme()
const [copied, setCopied] = useState(false)
const copyToClipboard = async (text: string) => {
try {
await Clipboard.setStringAsync(text)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (e) {
console.log('Clipboard not available')
}
}
const openUrl = (url: string) => {
WebBrowser.openBrowserAsync(url)
}
return (
<Layout.Screen>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>App Info</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<ScrollView
style={[a.flex_1]}
contentContainerStyle={[a.p_lg, a.pt_xl, a.pb_5xl]}>
{/* App Header */}
<View style={styles.appHeader}>
<View style={[styles.appIconContainer, t.atoms.bg_contrast_25]}>
<Image
source={require('../../../assets/logo.png')}
style={styles.appIcon}
resizeMode="cover"
/>
</View>
<Text style={[styles.appName, t.atoms.text]}>
{APP_NAME}
</Text>
<Text style={[styles.appVersion, t.atoms.text_contrast_medium]}>
v{APP_VERSION}
</Text>
</View>
{/* Description Section */}
<View style={[styles.section, t.atoms.bg_contrast_25]}>
<Text style={[styles.description, t.atoms.text]}>
{APP_NAME} is a social networking application based on AT Protocol.
Connect with your community on syu.is.
</Text>
</View>
{/* App Information Section */}
<View style={[styles.section, t.atoms.bg_contrast_25]}>
<Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
App Information
</Text>
<View style={styles.infoGrid}>
<View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
<Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
Version
</Text>
<Text style={[styles.infoValue, t.atoms.text]}>
{APP_VERSION}
</Text>
</View>
<View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
<Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
Category
</Text>
<Text style={[styles.infoValue, t.atoms.text]}>
Social
</Text>
</View>
<View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
<Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
Supported OS
</Text>
<Text style={[styles.infoValue, t.atoms.text]}>
iOS 26.0+
</Text>
</View>
<View style={[styles.infoItem, t.atoms.bg_contrast_50]}>
<Text style={[styles.infoLabel, t.atoms.text_contrast_medium]}>
Price
</Text>
<Text style={[styles.infoValue, t.atoms.text]}>
Free
</Text>
</View>
</View>
</View>
{/* Developer Section */}
<View style={[styles.section, t.atoms.bg_contrast_25]}>
<Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
Developer
</Text>
<View style={styles.developerCard}>
<Text style={[styles.developerName, t.atoms.text]}>syui</Text>
</View>
<Pressable
onPress={() => openUrl('https://git.syui.ai/syui')}
style={[styles.linkRow, t.atoms.border_contrast_low]}>
<Text style={[styles.linkIcon, t.atoms.text_contrast_medium]}>
Git
</Text>
<Text style={[styles.linkValue, {color: '#0084ff'}]}>
git.syui.ai/syui
</Text>
<Text style={[styles.linkArrow, t.atoms.text_contrast_low]}></Text>
</Pressable>
<Pressable
onPress={() => openUrl('https://syu.is/syui')}
style={[styles.linkRow, t.atoms.border_contrast_low]}>
<Text style={[styles.linkIcon, t.atoms.text_contrast_medium]}>
ATProto
</Text>
<Text style={[styles.linkValue, {color: '#0084ff'}]}>
syu.is/syui
</Text>
<Text style={[styles.linkArrow, t.atoms.text_contrast_low]}></Text>
</Pressable>
</View>
{/* Bitcoin Section */}
<View style={[styles.section, t.atoms.bg_contrast_25]}>
<Text style={[styles.sectionTitle, t.atoms.text_contrast_medium]}>
Bitcoin
</Text>
<Pressable
onPress={() => copyToClipboard(BITCOIN_ADDRESS)}
style={styles.bitcoinRow}>
<Text style={styles.bitcoinLabel}></Text>
<Text style={[styles.bitcoinAddress, t.atoms.text_contrast_medium]}>
{BITCOIN_ADDRESS}
</Text>
<Text style={[styles.copyHint, copied && styles.copiedHint]}>
{copied ? 'copied!' : 'copy'}
</Text>
</Pressable>
</View>
{/* Copyright */}
<View style={styles.copyright}>
<Text style={[styles.copyrightText, t.atoms.text_contrast_low]}>
© syui
</Text>
</View>
</ScrollView>
</Layout.Content>
</Layout.Screen>
)
}
const styles = StyleSheet.create({
appHeader: {
alignItems: 'center',
marginBottom: 24,
},
appIconContainer: {
width: 80,
height: 80,
borderRadius: 18,
overflow: 'hidden',
marginBottom: 12,
},
appIcon: {
width: '100%',
height: '100%',
},
appName: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 4,
},
appVersion: {
fontSize: 14,
},
section: {
marginBottom: 16,
borderRadius: 16,
padding: 16,
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: 0.5,
marginBottom: 12,
},
description: {
fontSize: 15,
lineHeight: 22,
},
infoGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
infoItem: {
flex: 1,
minWidth: '45%',
alignItems: 'center',
borderRadius: 12,
padding: 12,
},
infoLabel: {
fontSize: 11,
textTransform: 'uppercase',
letterSpacing: 0.5,
marginBottom: 4,
},
infoValue: {
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
developerCard: {
marginBottom: 12,
},
developerName: {
fontSize: 18,
fontWeight: '600',
},
linkRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
borderTopWidth: 1,
},
linkIcon: {
fontSize: 14,
fontWeight: '600',
width: 70,
},
linkValue: {
flex: 1,
fontSize: 14,
},
linkArrow: {
fontSize: 16,
},
bitcoinRow: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(247, 147, 26, 0.08)',
borderRadius: 12,
padding: 14,
gap: 10,
},
bitcoinLabel: {
fontSize: 18,
fontWeight: '600',
color: '#f7931a',
},
bitcoinAddress: {
flex: 1,
fontSize: 11,
fontFamily: 'monospace',
},
copyHint: {
fontSize: 12,
color: '#999999',
minWidth: 50,
textAlign: 'right',
},
copiedHint: {
color: '#4CAF50',
fontWeight: '600',
},
copyright: {
alignItems: 'center',
marginTop: 20,
marginBottom: 20,
},
copyrightText: {
fontSize: 12,
},
})

View File

@@ -27,7 +27,7 @@ export function LicenseScreen() {
<Text style={[a.text_lg, a.font_bold, a.mt_lg, a.mb_md]}>MIT License</Text>
<Text style={[a.mb_md, {fontFamily: 'monospace'}]}>
Copyright (c) 2022-2026 Bluesky PBC
Copyright (c) 2022-2025 Bluesky PBC
</Text>
<Text style={[a.mb_md]}>

View File

@@ -1,150 +0,0 @@
import React from 'react'
import {Pressable, View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {createSinglePathSVG} from '#/components/icons/TEMPLATE'
// --- SVG Icons (viewBox 0 0 640 640, Font Awesome) ---
const GithubIcon = createSinglePathSVG({
path: 'M237.9 461.4C237.9 463.4 235.6 465 232.7 465C229.4 465.3 227.1 463.7 227.1 461.4C227.1 459.4 229.4 457.8 232.3 457.8C235.3 457.5 237.9 459.1 237.9 461.4zM206.8 456.9C206.1 458.9 208.1 461.2 211.1 461.8C213.7 462.8 216.7 461.8 217.3 459.8C217.9 457.8 216 455.5 213 454.6C210.4 453.9 207.5 454.9 206.8 456.9zM251 455.2C248.1 455.9 246.1 457.8 246.4 460.1C246.7 462.1 249.3 463.4 252.3 462.7C255.2 462 257.2 460.1 256.9 458.1C256.6 456.2 253.9 454.9 251 455.2zM316.8 72C178.1 72 72 177.3 72 316C72 426.9 141.8 521.8 241.5 555.2C254.3 557.5 258.8 549.6 258.8 543.1C258.8 536.9 258.5 502.7 258.5 481.7C258.5 481.7 188.5 496.7 173.8 451.9C173.8 451.9 162.4 422.8 146 415.3C146 415.3 123.1 399.6 147.6 399.9C147.6 399.9 172.5 401.9 186.2 425.7C208.1 464.3 244.8 453.2 259.1 446.6C261.4 430.6 267.9 419.5 275.1 412.9C219.2 406.7 162.8 398.6 162.8 302.4C162.8 274.9 170.4 261.1 186.4 243.5C183.8 237 175.3 210.2 189 175.6C209.9 169.1 258 202.6 258 202.6C278 197 299.5 194.1 320.8 194.1C342.1 194.1 363.6 197 383.6 202.6C383.6 202.6 431.7 169 452.6 175.6C466.3 210.3 457.8 237 455.2 243.5C471.2 261.2 481 275 481 302.4C481 398.9 422.1 406.6 366.2 412.9C375.4 420.8 383.2 435.8 383.2 459.3C383.2 493 382.9 534.7 382.9 542.9C382.9 549.4 387.5 557.3 400.2 555C500.2 521.8 568 426.9 568 316C568 177.3 455.5 72 316.8 72zM169.2 416.9C167.9 417.9 168.2 420.2 169.9 422.1C171.5 423.7 173.8 424.4 175.1 423.1C176.4 422.1 176.1 419.8 174.4 417.9C172.8 416.3 170.5 415.6 169.2 416.9zM158.4 408.8C157.7 410.1 158.7 411.7 160.7 412.7C162.3 413.7 164.3 413.4 165 412C165.7 410.7 164.7 409.1 162.7 408.1C160.7 407.5 159.1 407.8 158.4 408.8zM190.8 444.4C189.2 445.7 189.8 448.7 192.1 450.6C194.4 452.9 197.3 453.2 198.6 451.6C199.9 450.3 199.3 447.3 197.3 445.4C195.1 443.1 192.1 442.8 190.8 444.4zM179.4 429.7C177.8 430.7 177.8 433.3 179.4 435.6C181 437.9 183.7 438.9 185 437.9C186.6 436.6 186.6 434 185 431.7C183.6 429.4 181 428.4 179.4 429.7z',
viewBox: '0 0 640 640',
})
const XIcon = createSinglePathSVG({
path: 'M453.2 112L523.8 112L369.6 288.2L551 528L409 528L297.7 382.6L170.5 528L99.8 528L264.7 339.5L90.8 112L236.4 112L336.9 244.9L453.2 112zM428.4 485.8L467.5 485.8L215.1 152L173.1 152L428.4 485.8z',
viewBox: '0 0 640 640',
})
const YoutubeIcon = createSinglePathSVG({
path: 'M581.7 188.1C575.5 164.4 556.9 145.8 533.4 139.5C490.9 128 320.1 128 320.1 128C320.1 128 149.3 128 106.7 139.5C83.2 145.8 64.7 164.4 58.4 188.1C47 231 47 320.4 47 320.4C47 320.4 47 409.8 58.4 452.7C64.7 476.3 83.2 494.2 106.7 500.5C149.3 512 320.1 512 320.1 512C320.1 512 490.9 512 533.5 500.5C557 494.2 575.5 476.3 581.8 452.7C593.2 409.8 593.2 320.4 593.2 320.4C593.2 320.4 593.2 231 581.8 188.1zM264.2 401.6L264.2 239.2L406.9 320.4L264.2 401.6z',
viewBox: '0 0 640 640',
})
// --- Types ---
interface LinkItem {
service: string
username: string
}
interface LinkCollection {
links: LinkItem[]
createdAt: string
updatedAt?: string
}
// --- Service Config ---
const SERVICE_CONFIG: Record<
string,
{
name: string
urlTemplate: string
icon: React.ComponentType<{size?: 'xs' | 'sm' | 'md' | 'lg'; fill?: string}>
}
> = {
github: {
name: 'GitHub',
urlTemplate: 'https://github.com/{username}',
icon: GithubIcon,
},
x: {
name: 'X',
urlTemplate: 'https://x.com/{username}',
icon: XIcon,
},
youtube: {
name: 'YouTube',
urlTemplate: 'https://youtube.com/@{username}',
icon: YoutubeIcon,
},
}
// --- PDS Resolution ---
async function resolvePds(did: string): Promise<string> {
if (did.startsWith('did:web:')) {
const host = did.split(':').slice(2).join(':')
const res = await fetch(`https://${host}/.well-known/did.json`)
if (!res.ok) throw new Error('failed to resolve did:web')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (pds) return pds
return `https://${host}`
}
const res = await fetch(`https://plc.directory/${did}`)
if (!res.ok) throw new Error('failed to resolve DID')
const doc = await res.json()
const pds = doc.service?.find((s: any) => s.id === '#atproto_pds')?.serviceEndpoint
if (!pds) throw new Error('no PDS found')
return pds
}
// --- Component ---
export function ProfileAtLinks({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const t = useTheme()
const openLink = useOpenLink()
const {data: linkData} = useQuery({
queryKey: ['at-links', profile.did],
queryFn: async () => {
const pds = await resolvePds(profile.did)
const res = await fetch(
`${pds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(profile.did)}&collection=ai.syui.at.link&rkey=self`,
)
if (!res.ok) throw new Error('not found')
const json = await res.json()
return json.value as LinkCollection
},
retry: false,
staleTime: 1000 * 60 * 5,
})
if (!linkData?.links?.length) return null
return (
<View style={[a.flex_row, a.flex_wrap, a.gap_sm, a.pt_xs]}>
{linkData.links.map(link => {
const config = SERVICE_CONFIG[link.service]
if (!config) return null
const url = config.urlTemplate.replace('{username}', link.username)
const Icon = config.icon
return (
<Pressable
key={link.service}
onPress={() => openLink(url)}
accessibilityRole="link"
accessibilityLabel={`${config.name}: ${link.username}`}
style={[
a.flex_row,
a.align_center,
a.gap_xs,
a.rounded_full,
t.atoms.bg_contrast_50,
{paddingVertical: 6, paddingHorizontal: 10},
]}>
<Icon size="xs" fill={t.atoms.text_contrast_medium.color} />
<Text
style={[
a.text_xs,
a.font_medium,
t.atoms.text_contrast_medium,
]}>
{link.username}
</Text>
</Pressable>
)
})}
</View>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,14 +5,6 @@ cd $d
source $d/.env
function sediment() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
#xcrun simctl uninstall booted $BUNDLE_ID
echo "Running iOS preview workflow..."
@@ -42,26 +34,6 @@ else
echo "⚠️ Warning: $ASSETS_DIR not found"
fi
# 1.8. Update package.json version (prevent App Store version conflict)
echo "1.8. Updating package.json version..."
if [ -n "$APP_VERSION" ]; then
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..."
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
@@ -70,66 +42,31 @@ 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..."
# 4. Signing (Manual Step)
echo "4. Opening Xcode for Signing..."
XCODE_PROJ="ios/${APP_NAME}.xcodeproj"
# Fallback search if variable name logic differs
if [ ! -d "$XCODE_PROJ" ]; then
XCODE_PROJ=$(find ios -name "*.xcodeproj" | head -n 1)
fi
PBXPROJ="$XCODE_PROJ/project.pbxproj"
# Set DEVELOPMENT_TEAM in pbxproj
if [ -n "$DEVELOPMENT_TEAM" ]; then
echo " Setting DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM"
# Add DEVELOPMENT_TEAM to all build configurations
sediment "s/PRODUCT_BUNDLE_IDENTIFIER = /DEVELOPMENT_TEAM = $DEVELOPMENT_TEAM; PRODUCT_BUNDLE_IDENTIFIER = /g" "$PBXPROJ"
# Also set where it might already exist but be empty
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>${APP_GROUP}</string>
</array>
</dict>
</plist>
EOF
# Add CODE_SIGN_ENTITLEMENTS to pbxproj if not present
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"
# echo "========================================================"
# echo " [ACTION REQUIRED] "
# echo " Xcode opened ($XCODE_PROJ)."
# echo " 1. Go to 'Signing & Capabilities' tab."
# echo " 2. Select your Team."
# echo " 3. Verify 'App Clip' target is gone."
# echo " 4. Ensure no red errors exist."
# echo " Press ENTER here once you are done to continue building."
# echo "========================================================"
# read
open "$XCODE_PROJ"
echo "========================================================"
echo " [ACTION REQUIRED] "
echo " Xcode opened ($XCODE_PROJ)."
echo " 1. Go to 'Signing & Capabilities' tab."
echo " 2. Select your Team."
echo " 3. Verify 'App Clip' target is gone."
echo " 4. Ensure no red errors exist."
echo " Press ENTER here once you are done to continue building."
echo "========================================================"
read
# 5. Run
echo "5. Building and Running..."

Some files were not shown because too many files have changed in this diff Show More