Merge remote-tracking branch 'origin/main' into app-2662-add-client-events-for-unrenderable-video-playback
# Conflicts: # src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: Compile translations
|
||||||
|
description: Compile i18n translations and fail on compilation errors.
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: 🔤 Compile translations
|
||||||
|
shell: bash
|
||||||
|
run: pnpm intl:build 2>&1 | tee i18n.log
|
||||||
|
|
||||||
|
- name: Check for i18n compilation errors
|
||||||
|
shell: bash
|
||||||
|
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation
|
||||||
|
errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
name: Local EAS Build
|
||||||
|
description: Build an Expo app locally with a selected EAS profile.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
description: EAS platform to build (ios or android)
|
||||||
|
required: true
|
||||||
|
profile:
|
||||||
|
description: EAS build profile
|
||||||
|
required: true
|
||||||
|
output:
|
||||||
|
description: Output path for the local build artifact
|
||||||
|
required: true
|
||||||
|
log-path:
|
||||||
|
description: Optional path to tee build output into
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
bump-build-number:
|
||||||
|
description: Run the build through use-build-number-with-bump
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
sentry-auth-token:
|
||||||
|
description: Optional Sentry authentication token
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
sentry-release:
|
||||||
|
description: Optional Sentry release
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
sentry-dist:
|
||||||
|
description: Optional Sentry distribution
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Build locally with EAS
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ inputs.platform }}
|
||||||
|
PROFILE: ${{ inputs.profile }}
|
||||||
|
OUTPUT: ${{ inputs.output }}
|
||||||
|
LOG_PATH: ${{ inputs.log-path }}
|
||||||
|
BUMP_BUILD_NUMBER: ${{ inputs.bump-build-number }}
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ inputs.sentry-auth-token }}
|
||||||
|
SENTRY_RELEASE: ${{ inputs.sentry-release }}
|
||||||
|
SENTRY_DIST: ${{ inputs.sentry-dist }}
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
build_command=(
|
||||||
|
pnpm eas build
|
||||||
|
--platform "$PLATFORM"
|
||||||
|
--profile "$PROFILE"
|
||||||
|
--local
|
||||||
|
--output "$OUTPUT"
|
||||||
|
--non-interactive
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -n "$LOG_PATH" ]; then
|
||||||
|
mkdir -p "$(dirname "$LOG_PATH")"
|
||||||
|
if [ "$BUMP_BUILD_NUMBER" = "true" ]; then
|
||||||
|
pnpm use-build-number-with-bump "${build_command[@]}" 2>&1 | tee "$LOG_PATH"
|
||||||
|
else
|
||||||
|
"${build_command[@]}" 2>&1 | tee "$LOG_PATH"
|
||||||
|
fi
|
||||||
|
elif [ "$BUMP_BUILD_NUMBER" = "true" ]; then
|
||||||
|
pnpm use-build-number-with-bump "${build_command[@]}"
|
||||||
|
else
|
||||||
|
"${build_command[@]}"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: Setup Expo Project
|
||||||
|
description: Install dependencies and set up the Expo/EAS CLI for a build. Does not check out the repo.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
expo-token:
|
||||||
|
description: Expo token (EXPO_TOKEN secret)
|
||||||
|
required: true
|
||||||
|
eas-version:
|
||||||
|
description: EAS CLI version to install
|
||||||
|
required: false
|
||||||
|
default: '19.0.5'
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Check for EXPO_TOKEN
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
EXPO_TOKEN: ${{ inputs.expo-token }}
|
||||||
|
run: >
|
||||||
|
if [ -z "$EXPO_TOKEN" ]; then
|
||||||
|
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
|
- name: 🔧 Setup Node
|
||||||
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: package.json
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: 🪛 Setup jq
|
||||||
|
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
|
||||||
|
|
||||||
|
- name: ⚙️ Install dependencies
|
||||||
|
shell: bash
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: 🔨 Setup Expo CLI
|
||||||
|
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
|
||||||
|
with:
|
||||||
|
eas-version: ${{ inputs.eas-version }}
|
||||||
|
packager: 'pnpm --allow-build=dtrace-provider'
|
||||||
|
token: ${{ inputs.expo-token }}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
name: Write Environment Variables
|
||||||
|
description: Write the .env file and google-services.json used by the build.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
env-token:
|
||||||
|
description: Base .env contents (ENV_TOKEN secret)
|
||||||
|
required: true
|
||||||
|
sentry-dsn:
|
||||||
|
description: Sentry DSN (SENTRY_DSN secret)
|
||||||
|
required: true
|
||||||
|
bitdrift-api-key:
|
||||||
|
description: Bitdrift API key (BITDRIFT_API_KEY secret)
|
||||||
|
required: true
|
||||||
|
gcp-project-id:
|
||||||
|
description: GCP project ID (EXPO_PUBLIC_GCP_PROJECT_ID secret)
|
||||||
|
required: true
|
||||||
|
google-services-token:
|
||||||
|
description: google-services.json contents (GOOGLE_SERVICES_TOKEN secret)
|
||||||
|
required: true
|
||||||
|
expo-public-env:
|
||||||
|
description: >
|
||||||
|
EXPO_PUBLIC_ENV value. Only set for OTA deploys where eas.json isn't used;
|
||||||
|
for regular builds this is normally handled in eas.json.
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
release-version:
|
||||||
|
description: Version from package.json
|
||||||
|
value: ${{ steps.env.outputs.release-version }}
|
||||||
|
bundle-identifier:
|
||||||
|
description: git SHA of HEAD
|
||||||
|
value: ${{ steps.env.outputs.bundle-identifier }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: ✏️ Write environment variables
|
||||||
|
id: env
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
ENV_TOKEN: ${{ inputs.env-token }}
|
||||||
|
SENTRY_DSN: ${{ inputs.sentry-dsn }}
|
||||||
|
BITDRIFT_API_KEY: ${{ inputs.bitdrift-api-key }}
|
||||||
|
GCP_PROJECT_ID: ${{ inputs.gcp-project-id }}
|
||||||
|
GOOGLE_SERVICES_TOKEN: ${{ inputs.google-services-token }}
|
||||||
|
EXPO_PUBLIC_ENV: ${{ inputs.expo-public-env }}
|
||||||
|
run: |
|
||||||
|
echo "$ENV_TOKEN" > .env
|
||||||
|
# EXPO_PUBLIC_ENV is normally handled in eas.json; only written here for OTA deploys.
|
||||||
|
if [ -n "$EXPO_PUBLIC_ENV" ]; then
|
||||||
|
echo "EXPO_PUBLIC_ENV=$EXPO_PUBLIC_ENV" >> .env
|
||||||
|
fi
|
||||||
|
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
||||||
|
echo "release-version=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
||||||
|
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
||||||
|
echo "bundle-identifier=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
||||||
|
echo "EXPO_PUBLIC_SENTRY_DSN=$SENTRY_DSN" >> .env
|
||||||
|
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=$BITDRIFT_API_KEY" >> .env
|
||||||
|
echo "EXPO_PUBLIC_GCP_PROJECT_ID=$GCP_PROJECT_ID" >> .env
|
||||||
|
echo "$GOOGLE_SERVICES_TOKEN" > google-services.json
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
platform="${1:?usage: cleanup-nightly-e2e.sh <ios|android> <device-id>}"
|
||||||
|
device_id="${2:-}"
|
||||||
|
artifact_dir="${GITHUB_WORKSPACE:-$PWD}/artifacts/$platform"
|
||||||
|
mkdir -p "$artifact_dir"
|
||||||
|
|
||||||
|
if [[ -f i18n.log ]]; then
|
||||||
|
cp i18n.log "$artifact_dir/i18n.log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
stop_process_tree() {
|
||||||
|
local pid="$1"
|
||||||
|
local child
|
||||||
|
while read -r child; do
|
||||||
|
[[ -n "$child" ]] && stop_process_tree "$child"
|
||||||
|
done < <(pgrep -P "$pid" 2>/dev/null || true)
|
||||||
|
kill -TERM "$pid" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_pid_file() {
|
||||||
|
[[ -f "$1" ]] || return 0
|
||||||
|
local pid
|
||||||
|
pid="$(cat "$1")"
|
||||||
|
stop_process_tree "$pid"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_pid_file "$artifact_dir/logcat.pid"
|
||||||
|
stop_pid_file "$artifact_dir/metro.pid"
|
||||||
|
stop_pid_file "$artifact_dir/mock-server.pid"
|
||||||
|
stop_pid_file "$artifact_dir/emulator.pid"
|
||||||
|
|
||||||
|
if [[ "$platform" == "ios" ]]; then
|
||||||
|
if [[ -f "$artifact_dir/redis-bin.txt" ]]; then
|
||||||
|
"$(cat "$artifact_dir/redis-bin.txt")/redis-cli" \
|
||||||
|
-h 127.0.0.1 -p 6380 shutdown nosave >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [[ -f "$artifact_dir/postgres-bin.txt" ]]; then
|
||||||
|
"$(cat "$artifact_dir/postgres-bin.txt")/pg_ctl" \
|
||||||
|
-D "${RUNNER_TEMP:-/tmp}/nightly-e2e-postgres" -m fast stop >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
[[ -n "$device_id" ]] && xcrun simctl shutdown "$device_id" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
docker compose -f dev-env/dev-infra/docker-compose.yaml logs --no-color \
|
||||||
|
>>"$artifact_dir/docker-services.log" 2>&1 || true
|
||||||
|
docker compose -f dev-env/dev-infra/docker-compose.yaml down --volumes --remove-orphans >/dev/null 2>&1 || true
|
||||||
|
[[ -n "$device_id" ]] && adb -s "$device_id" emu kill >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
Executable
+179
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
platform="${1:?usage: run-nightly-e2e.sh <ios|android> <device-id>}"
|
||||||
|
device_id="${2:?usage: run-nightly-e2e.sh <ios|android> <device-id>}"
|
||||||
|
|
||||||
|
if [[ "$platform" != "ios" && "$platform" != "android" ]]; then
|
||||||
|
echo "Unsupported platform: $platform" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
artifact_dir="${GITHUB_WORKSPACE:-$PWD}/artifacts/$platform"
|
||||||
|
maestro_dir="$artifact_dir/maestro"
|
||||||
|
mkdir -p "$maestro_dir"
|
||||||
|
|
||||||
|
phase() {
|
||||||
|
printf '%s\n' "$1" >"$artifact_dir/phase.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_port() {
|
||||||
|
local port="$1"
|
||||||
|
local label="$2"
|
||||||
|
local attempts="${3:-120}"
|
||||||
|
|
||||||
|
for ((i = 1; i <= attempts; i++)); do
|
||||||
|
if nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for $label on port $port" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2329 # Invoked through the cleanup trap call chain.
|
||||||
|
stop_process_tree() {
|
||||||
|
local pid="$1"
|
||||||
|
local child
|
||||||
|
while read -r child; do
|
||||||
|
[[ -n "$child" ]] && stop_process_tree "$child"
|
||||||
|
done < <(pgrep -P "$pid" 2>/dev/null || true)
|
||||||
|
kill -TERM "$pid" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2329 # Invoked by cleanup, which is registered as a trap.
|
||||||
|
stop_pid_file() {
|
||||||
|
local pid_file="$1"
|
||||||
|
[[ -f "$pid_file" ]] || return 0
|
||||||
|
|
||||||
|
local pid
|
||||||
|
pid="$(cat "$pid_file")"
|
||||||
|
[[ -n "$pid" ]] || return 0
|
||||||
|
|
||||||
|
# pnpm and Expo both spawn multiple generations of children.
|
||||||
|
stop_process_tree "$pid"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2329 # Invoked by the EXIT/INT/TERM trap below.
|
||||||
|
cleanup() {
|
||||||
|
set +e
|
||||||
|
stop_pid_file "$artifact_dir/logcat.pid"
|
||||||
|
stop_pid_file "$artifact_dir/metro.pid"
|
||||||
|
stop_pid_file "$artifact_dir/mock-server.pid"
|
||||||
|
|
||||||
|
if [[ "$platform" == "ios" ]]; then
|
||||||
|
if [[ -f "$artifact_dir/redis.pid" ]]; then
|
||||||
|
redis_bin="$(cat "$artifact_dir/redis-bin.txt")"
|
||||||
|
"$redis_bin/redis-cli" -h 127.0.0.1 -p 6380 shutdown nosave >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if [[ -f "$artifact_dir/postgres-bin.txt" && -d "${RUNNER_TEMP:-/tmp}/nightly-e2e-postgres" ]]; then
|
||||||
|
postgres_bin="$(cat "$artifact_dir/postgres-bin.txt")"
|
||||||
|
"$postgres_bin/pg_ctl" -D "${RUNNER_TEMP:-/tmp}/nightly-e2e-postgres" -m fast stop >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
docker compose -f dev-env/dev-infra/docker-compose.yaml logs --no-color \
|
||||||
|
>>"$artifact_dir/docker-services.log" 2>&1 || true
|
||||||
|
docker compose -f dev-env/dev-infra/docker-compose.yaml down --volumes --remove-orphans >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
if [[ "$platform" == "android" ]]; then
|
||||||
|
adb -s "$device_id" logcat -c
|
||||||
|
adb -s "$device_id" logcat -v threadtime >"$artifact_dir/logcat.log" 2>&1 &
|
||||||
|
printf '%s\n' "$!" >"$artifact_dir/logcat.pid"
|
||||||
|
fi
|
||||||
|
|
||||||
|
phase "Starting PostgreSQL, Redis, and mock server"
|
||||||
|
if [[ "$platform" == "ios" ]]; then
|
||||||
|
brew install postgresql@14 2>&1 | tee "$artifact_dir/native-dependencies.log"
|
||||||
|
|
||||||
|
postgres_bin="$(brew --prefix postgresql@14)/bin"
|
||||||
|
redis_version="7.4.7"
|
||||||
|
redis_archive="${RUNNER_TEMP:-/tmp}/redis-${redis_version}.tar.gz"
|
||||||
|
redis_source="${RUNNER_TEMP:-/tmp}/redis-${redis_version}"
|
||||||
|
curl -fsSL -o "$redis_archive" \
|
||||||
|
"https://download.redis.io/releases/redis-${redis_version}.tar.gz"
|
||||||
|
echo "c97e57b0df330a9e091cacff012bebe763c275398cf36ff44cdba876814b595b $redis_archive" \
|
||||||
|
| shasum -a 256 --check | tee -a "$artifact_dir/native-dependencies.log"
|
||||||
|
rm -rf "$redis_source"
|
||||||
|
tar -xzf "$redis_archive" -C "${RUNNER_TEMP:-/tmp}"
|
||||||
|
make -C "$redis_source" -j "$(sysctl -n hw.ncpu)" \
|
||||||
|
2>&1 | tee -a "$artifact_dir/native-dependencies.log"
|
||||||
|
redis_bin="$redis_source/src"
|
||||||
|
"$redis_bin/redis-server" --version | tee -a "$artifact_dir/native-dependencies.log"
|
||||||
|
printf '%s\n' "$redis_bin" >"$artifact_dir/redis-bin.txt"
|
||||||
|
|
||||||
|
postgres_data="${RUNNER_TEMP:-/tmp}/nightly-e2e-postgres"
|
||||||
|
rm -rf "$postgres_data"
|
||||||
|
"$postgres_bin/initdb" -D "$postgres_data" --auth=trust --username=pg --no-locale \
|
||||||
|
>"$artifact_dir/postgres-init.log" 2>&1
|
||||||
|
"$postgres_bin/pg_ctl" -D "$postgres_data" \
|
||||||
|
-o "-p 5433 -h 127.0.0.1" -l "$artifact_dir/postgres.log" start
|
||||||
|
printf '%s\n' "$postgres_bin" >"$artifact_dir/postgres-bin.txt"
|
||||||
|
|
||||||
|
"$redis_bin/redis-server" \
|
||||||
|
--bind 127.0.0.1 \
|
||||||
|
--port 6380 \
|
||||||
|
--save "" \
|
||||||
|
--appendonly no \
|
||||||
|
--daemonize yes \
|
||||||
|
--pidfile "$artifact_dir/redis.pid" \
|
||||||
|
--logfile "$artifact_dir/redis.log"
|
||||||
|
|
||||||
|
wait_for_port 5433 "PostgreSQL"
|
||||||
|
wait_for_port 6380 "Redis"
|
||||||
|
pnpm --dir dev-env start:external >"$artifact_dir/mock-server.log" 2>&1 &
|
||||||
|
else
|
||||||
|
pnpm --dir dev-env start >"$artifact_dir/mock-server.log" 2>&1 &
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$!" >"$artifact_dir/mock-server.pid"
|
||||||
|
wait_for_port 1986 "the E2E mock-server manager"
|
||||||
|
|
||||||
|
phase "Starting Metro"
|
||||||
|
EXPO_PUBLIC_ENV=e2e \
|
||||||
|
NODE_ENV=test \
|
||||||
|
RN_SRC_EXT=e2e.ts,e2e.tsx \
|
||||||
|
pnpm exec expo start --dev-client --clear --port 8081 \
|
||||||
|
>"$artifact_dir/metro.log" 2>&1 &
|
||||||
|
printf '%s\n' "$!" >"$artifact_dir/metro.pid"
|
||||||
|
wait_for_port 8081 "Metro"
|
||||||
|
|
||||||
|
# Pre-warm Metro bundle so the first Maestro flow doesn't hit a cold-start delay
|
||||||
|
phase "Pre-warming Metro bundle"
|
||||||
|
bundle_platform="$platform"
|
||||||
|
curl -s -o /dev/null "http://localhost:8081/index.bundle?platform=${bundle_platform}&dev=true&minify=false"
|
||||||
|
echo "Metro bundle pre-warmed for $bundle_platform"
|
||||||
|
|
||||||
|
if [[ "$platform" == "android" ]]; then
|
||||||
|
phase "Configuring Android localhost routing"
|
||||||
|
adb -s "$device_id" reverse tcp:3000 tcp:3000
|
||||||
|
adb -s "$device_id" reverse tcp:8081 tcp:8081
|
||||||
|
fi
|
||||||
|
|
||||||
|
phase "Running Maestro flows"
|
||||||
|
set +e
|
||||||
|
maestro test \
|
||||||
|
--udid "$device_id" \
|
||||||
|
--format JUNIT \
|
||||||
|
--output "$artifact_dir/report.xml" \
|
||||||
|
--config __e2e__/config.yml \
|
||||||
|
--debug-output "$maestro_dir" \
|
||||||
|
--test-output-dir "$maestro_dir" \
|
||||||
|
--flatten-debug-output \
|
||||||
|
__e2e__ \
|
||||||
|
2>&1 | tee "$artifact_dir/maestro-cli.log"
|
||||||
|
maestro_status=${PIPESTATUS[0]}
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ "$maestro_status" -eq 0 ]]; then
|
||||||
|
phase "Completed"
|
||||||
|
else
|
||||||
|
phase "Maestro flow failure"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$maestro_status"
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
const ENTITY_REPLACEMENTS = {
|
||||||
|
'&': '&',
|
||||||
|
''': "'",
|
||||||
|
'>': '>',
|
||||||
|
'<': '<',
|
||||||
|
'"': '"',
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeXml(value = '') {
|
||||||
|
return value
|
||||||
|
.replace(/&(amp|apos|gt|lt|quot);/g, entity => ENTITY_REPLACEMENTS[entity])
|
||||||
|
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
|
||||||
|
.replace(/&#x([\da-f]+);/gi, (_, code) =>
|
||||||
|
String.fromCodePoint(Number.parseInt(code, 16)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function attributes(source = '') {
|
||||||
|
const result = {}
|
||||||
|
for (const match of source.matchAll(/([\w:.-]+)\s*=\s*(["'])(.*?)\2/gs)) {
|
||||||
|
result[match[1]] = decodeXml(match[3])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function concise(value, limit = 300) {
|
||||||
|
const normalized = decodeXml(value)
|
||||||
|
.replace(/<[^>]+>/g, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
return normalized.length > limit
|
||||||
|
? `${normalized.slice(0, limit - 1)}…`
|
||||||
|
: normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseJUnit(xml) {
|
||||||
|
const failures = []
|
||||||
|
const testcasePattern = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/gi
|
||||||
|
|
||||||
|
for (const match of xml.matchAll(testcasePattern)) {
|
||||||
|
const testcase = attributes(match[1])
|
||||||
|
const body = match[2] || ''
|
||||||
|
const failure = body.match(/<(failure|error)\b([^>]*)>([\s\S]*?)<\/\1>/i)
|
||||||
|
const selfClosingFailure = body.match(/<(failure|error)\b([^>]*)\/>/i)
|
||||||
|
const failureMatch = failure || selfClosingFailure
|
||||||
|
if (!failureMatch) continue
|
||||||
|
|
||||||
|
const failureAttributes = attributes(failureMatch[2])
|
||||||
|
const name = testcase.name || testcase.classname || 'Unnamed Maestro flow'
|
||||||
|
const message = concise(
|
||||||
|
failureAttributes.message || (failure ? failureMatch[3] : '') || 'Failed',
|
||||||
|
)
|
||||||
|
failures.push({name, message})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
const suite = xml.match(/<testsuite\b([^>]*)>/i)
|
||||||
|
const suiteAttributes = attributes(suite?.[1])
|
||||||
|
if (
|
||||||
|
Number(suiteAttributes.failures || 0) +
|
||||||
|
Number(suiteAttributes.errors || 0) >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
failures.push({
|
||||||
|
name: suiteAttributes.name || 'Maestro test suite',
|
||||||
|
message: 'JUnit reported a failure without testcase details',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failures
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMaestroCli(log) {
|
||||||
|
const failures = []
|
||||||
|
const failurePattern = /^\[Failed\]\s+(.+?)\s+\([^)]*\)\s+\((.+)\)\s*$/gm
|
||||||
|
for (const match of log.matchAll(failurePattern)) {
|
||||||
|
failures.push({
|
||||||
|
name: concise(match[1], 120),
|
||||||
|
message: concise(match[2]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return failures
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(root) {
|
||||||
|
if (!root || !fs.existsSync(root)) return []
|
||||||
|
const entries = fs.readdirSync(root, {withFileTypes: true})
|
||||||
|
return entries.flatMap(entry => {
|
||||||
|
const candidate = path.join(root, entry.name)
|
||||||
|
return entry.isDirectory() ? walk(candidate) : [candidate]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPhase(root) {
|
||||||
|
const phaseFile = walk(root).find(file => path.basename(file) === 'phase.txt')
|
||||||
|
return phaseFile ? fs.readFileSync(phaseFile, 'utf8').trim() : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function platformResult({name, status, root, artifactUrl}) {
|
||||||
|
const files = walk(root)
|
||||||
|
const reports = files.filter(file => /(?:report|junit).*\.xml$/i.test(file))
|
||||||
|
const junitFailures = reports.flatMap(report =>
|
||||||
|
parseJUnit(fs.readFileSync(report, 'utf8')),
|
||||||
|
)
|
||||||
|
const maestroLogs = files.filter(
|
||||||
|
file => path.basename(file) === 'maestro-cli.log',
|
||||||
|
)
|
||||||
|
const cliFailures = maestroLogs.flatMap(log =>
|
||||||
|
parseMaestroCli(fs.readFileSync(log, 'utf8')),
|
||||||
|
)
|
||||||
|
// A cancelled or timed-out Maestro run may never flush JUnit. Its CLI log is
|
||||||
|
// streamed continuously, so use those failure lines when JUnit has no detail.
|
||||||
|
const failures = junitFailures.length > 0 ? junitFailures : cliFailures
|
||||||
|
// A skipped platform (e.g. iOS while temporarily disabled) is not a failure
|
||||||
|
// as long as it produced no flow failures.
|
||||||
|
const failed =
|
||||||
|
(status !== 'success' && status !== 'skipped') || failures.length > 0
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
status,
|
||||||
|
failed,
|
||||||
|
failures,
|
||||||
|
phase: readPhase(root),
|
||||||
|
hasJUnit: reports.length > 0,
|
||||||
|
artifactUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusEmoji(status) {
|
||||||
|
if (status === 'success') return ':white_check_mark:'
|
||||||
|
if (status === 'skipped') return ':fast_forward:'
|
||||||
|
return ':x:'
|
||||||
|
}
|
||||||
|
|
||||||
|
function slackEscape(value) {
|
||||||
|
return value
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
}
|
||||||
|
|
||||||
|
function platformBlock(platform) {
|
||||||
|
const lines = [
|
||||||
|
`${statusEmoji(platform.status)} *${platform.name}* — job status: \`${platform.status}\``,
|
||||||
|
]
|
||||||
|
if (platform.failures.length > 0) {
|
||||||
|
for (const failure of platform.failures.slice(0, 8)) {
|
||||||
|
lines.push(
|
||||||
|
`• *${slackEscape(failure.name)}:* ${slackEscape(failure.message)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (platform.failures.length > 8) {
|
||||||
|
lines.push(`• …and ${platform.failures.length - 8} more failed flows`)
|
||||||
|
}
|
||||||
|
} else if (platform.failed && !platform.hasJUnit) {
|
||||||
|
lines.push(
|
||||||
|
`• *Setup phase:* ${slackEscape(platform.phase || 'No phase metadata was captured')}`,
|
||||||
|
)
|
||||||
|
} else if (platform.failed) {
|
||||||
|
lines.push(
|
||||||
|
`• Job failed after JUnit was written; latest phase: ${slackEscape(platform.phase || 'unknown')}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (platform.artifactUrl) {
|
||||||
|
lines.push(
|
||||||
|
`• <${platform.artifactUrl}|Open ${platform.name} logs and artifacts>`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return lines.join('\n').slice(0, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function githubSummary({notify, platforms, shortSha, runUrl, commitUrl}) {
|
||||||
|
const lines = [
|
||||||
|
`# Nightly Maestro E2E ${notify ? 'failed' : 'passed'}`,
|
||||||
|
'',
|
||||||
|
`- Commit: [\`${shortSha}\`](${commitUrl})`,
|
||||||
|
`- Workflow run: [open run](${runUrl})`,
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const platform of platforms) {
|
||||||
|
const headerEmoji =
|
||||||
|
platform.status === 'success'
|
||||||
|
? '✅'
|
||||||
|
: platform.status === 'skipped'
|
||||||
|
? '⏭️'
|
||||||
|
: '❌'
|
||||||
|
lines.push(
|
||||||
|
`## ${headerEmoji} ${platform.name}`,
|
||||||
|
'',
|
||||||
|
`Job status: \`${platform.status}\``,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
if (platform.failures.length > 0) {
|
||||||
|
for (const failure of platform.failures.slice(0, 10)) {
|
||||||
|
lines.push(`- **${failure.name}:** ${failure.message}`)
|
||||||
|
}
|
||||||
|
if (platform.failures.length > 10) {
|
||||||
|
lines.push(`- …and ${platform.failures.length - 10} more failed flows`)
|
||||||
|
}
|
||||||
|
lines.push('')
|
||||||
|
} else if (platform.failed && !platform.hasJUnit) {
|
||||||
|
lines.push(
|
||||||
|
`- Setup phase: ${platform.phase || 'No phase metadata was captured'}`,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
} else if (platform.failed) {
|
||||||
|
lines.push(
|
||||||
|
`- The job failed after JUnit was written (latest phase: ${platform.phase || 'unknown'})`,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (platform.artifactUrl) {
|
||||||
|
lines.push(
|
||||||
|
`- [${platform.name} logs and artifacts](${platform.artifactUrl})`,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSummary({
|
||||||
|
iosStatus,
|
||||||
|
androidStatus,
|
||||||
|
iosRoot,
|
||||||
|
androidRoot,
|
||||||
|
artifactUrls = {},
|
||||||
|
sha,
|
||||||
|
runUrl,
|
||||||
|
commitUrl,
|
||||||
|
}) {
|
||||||
|
const platforms = [
|
||||||
|
platformResult({
|
||||||
|
name: 'iOS',
|
||||||
|
status: iosStatus,
|
||||||
|
root: iosRoot,
|
||||||
|
artifactUrl: artifactUrls.ios,
|
||||||
|
}),
|
||||||
|
platformResult({
|
||||||
|
name: 'Android',
|
||||||
|
status: androidStatus,
|
||||||
|
root: androidRoot,
|
||||||
|
artifactUrl: artifactUrls.android,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
const notify = platforms.some(platform => platform.failed)
|
||||||
|
const shortSha = sha.slice(0, 12)
|
||||||
|
const lines = [
|
||||||
|
':rotating_light: *Nightly Maestro E2E failed*',
|
||||||
|
`*Commit:* <${commitUrl}|\`${shortSha}\`>`,
|
||||||
|
`*Workflow run:* <${runUrl}|open run>`,
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const platform of platforms) {
|
||||||
|
lines.push(
|
||||||
|
`${statusEmoji(platform.status)} *${platform.name}* — job status: \`${platform.status}\``,
|
||||||
|
)
|
||||||
|
if (platform.failures.length > 0) {
|
||||||
|
for (const failure of platform.failures.slice(0, 10)) {
|
||||||
|
lines.push(
|
||||||
|
`• *${slackEscape(failure.name)}:* ${slackEscape(failure.message)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (platform.failures.length > 10) {
|
||||||
|
lines.push(`• …and ${platform.failures.length - 10} more failed flows`)
|
||||||
|
}
|
||||||
|
} else if (platform.failed && !platform.hasJUnit) {
|
||||||
|
lines.push(
|
||||||
|
`• Setup phase: ${platform.phase || 'No phase metadata was captured'}`,
|
||||||
|
)
|
||||||
|
} else if (platform.failed) {
|
||||||
|
lines.push(
|
||||||
|
`• The job failed after JUnit was written (latest phase: ${platform.phase || 'unknown'})`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (platform.artifactUrl) {
|
||||||
|
lines.push(
|
||||||
|
`• <${platform.artifactUrl}|${platform.name} logs and artifacts>`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
lines.push('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = lines.join('\n').trim()
|
||||||
|
const blocks = [
|
||||||
|
{
|
||||||
|
type: 'header',
|
||||||
|
text: {type: 'plain_text', text: 'Nightly Maestro E2E failed'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'section',
|
||||||
|
text: {
|
||||||
|
type: 'mrkdwn',
|
||||||
|
text: `*Commit:* <${commitUrl}|\`${shortSha}\`>\n*Workflow run:* <${runUrl}|open run>`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{type: 'divider'},
|
||||||
|
...platforms.flatMap((platform, index) => [
|
||||||
|
{type: 'section', text: {type: 'mrkdwn', text: platformBlock(platform)}},
|
||||||
|
...(index < platforms.length - 1 ? [{type: 'divider'}] : []),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
notify,
|
||||||
|
platforms,
|
||||||
|
githubSummary: githubSummary({
|
||||||
|
notify,
|
||||||
|
platforms,
|
||||||
|
shortSha,
|
||||||
|
runUrl,
|
||||||
|
commitUrl,
|
||||||
|
}),
|
||||||
|
payload: {text, blocks},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const result = {}
|
||||||
|
for (let i = 0; i < argv.length; i += 2) {
|
||||||
|
const key = argv[i]
|
||||||
|
if (!key?.startsWith('--') || argv[i + 1] === undefined) {
|
||||||
|
throw new Error(`Invalid argument: ${key || '<missing>'}`)
|
||||||
|
}
|
||||||
|
result[key.slice(2)] = argv[i + 1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] &&
|
||||||
|
path.resolve(process.argv[1]) === path.resolve(import.meta.filename)
|
||||||
|
) {
|
||||||
|
const args = parseArgs(process.argv.slice(2))
|
||||||
|
const artifactUrls = args['artifact-urls']
|
||||||
|
? JSON.parse(fs.readFileSync(args['artifact-urls'], 'utf8'))
|
||||||
|
: {}
|
||||||
|
const summary = buildSummary({
|
||||||
|
iosStatus: args['ios-status'],
|
||||||
|
androidStatus: args['android-status'],
|
||||||
|
iosRoot: args['ios-root'],
|
||||||
|
androidRoot: args['android-root'],
|
||||||
|
artifactUrls,
|
||||||
|
sha: args.sha,
|
||||||
|
runUrl: args['run-url'],
|
||||||
|
commitUrl: args['commit-url'],
|
||||||
|
})
|
||||||
|
process.stdout.write(`${JSON.stringify(summary)}\n`)
|
||||||
|
}
|
||||||
@@ -10,12 +10,25 @@ on:
|
|||||||
options:
|
options:
|
||||||
- testflight-android
|
- testflight-android
|
||||||
- production
|
- production
|
||||||
|
submit:
|
||||||
|
type: boolean
|
||||||
|
description: Submit the build to Google Play (disable to only produce the APK artifact)
|
||||||
|
default: true
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
profile:
|
profile:
|
||||||
type: string
|
type: string
|
||||||
description: Build profile to use
|
description: Build profile to use
|
||||||
required: true
|
required: true
|
||||||
|
submit:
|
||||||
|
type: boolean
|
||||||
|
description: Submit the build to Google Play (disable to only produce the APK artifact)
|
||||||
|
default: true
|
||||||
|
runner:
|
||||||
|
type: string
|
||||||
|
description: Runner for the build job (defaults to Linux-x64-32core)
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
outputs:
|
outputs:
|
||||||
package-version:
|
package-version:
|
||||||
description: Version from package.json
|
description: Version from package.json
|
||||||
@@ -56,93 +69,92 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository == 'bluesky-social/social-app'
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
name: Build and Submit Android
|
name: Build Android
|
||||||
runs-on: Linux-x64-32core
|
runs-on: ${{ inputs.runner || 'Linux-x64-32core' }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: android-build
|
group: android-build
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
outputs:
|
outputs:
|
||||||
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
||||||
version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
|
||||||
apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check for EXPO_TOKEN
|
|
||||||
run: >
|
|
||||||
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
|
|
||||||
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 5
|
fetch-depth: 5
|
||||||
|
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- name: 🔧 Setup Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
- name: 🔧 Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: 🪛 Setup jq
|
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||||
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
|
|
||||||
|
|
||||||
- name: ⚙️ Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: 🔨 Setup Expo CLI
|
|
||||||
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
|
|
||||||
with:
|
|
||||||
eas-version: '19.0.5'
|
|
||||||
packager: 'pnpm --allow-build=dtrace-provider'
|
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
|
||||||
|
|
||||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
|
|
||||||
- name: 🔤 Compile translations
|
- name: 🔤 Compile translations
|
||||||
run: pnpm intl:build 2>&1 | tee i18n.log
|
uses: ./.github/actions/compile-i18n
|
||||||
|
|
||||||
- name: Check for i18n compilation errors
|
|
||||||
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation
|
|
||||||
errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
|
|
||||||
|
|
||||||
# EXPO_PUBLIC_ENV is handled in eas.json
|
# EXPO_PUBLIC_ENV is handled in eas.json
|
||||||
- name: Env
|
- name: ✏️ Write environment variables
|
||||||
id: env
|
id: env
|
||||||
run: |
|
uses: ./.github/actions/write-env
|
||||||
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
|
with:
|
||||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
env-token: ${{ secrets.ENV_TOKEN }}
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
sentry-dsn: ${{ secrets.SENTRY_DSN }}
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
bitdrift-api-key: ${{ secrets.BITDRIFT_API_KEY }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
gcp-project-id: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
google-services-token: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
|
||||||
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
|
|
||||||
echo "$json" > google-services.json
|
|
||||||
|
|
||||||
- name: 🏗️ EAS Build
|
- name: 🏗️ EAS Build
|
||||||
env:
|
uses: ./.github/actions/eas-local-build
|
||||||
PROFILE: ${{ inputs.profile || 'testflight-android' }}
|
with:
|
||||||
run: >
|
platform: android
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
profile: ${{ inputs.profile || 'testflight-android' }}
|
||||||
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
|
output: build.aab
|
||||||
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
|
bump-build-number: "true"
|
||||||
pnpm use-build-number-with-bump
|
sentry-auth-token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
pnpm eas build -p android
|
sentry-release: ${{ steps.env.outputs.release-version }}
|
||||||
--profile $PROFILE
|
sentry-dist: ${{ steps.env.outputs.bundle-identifier }}
|
||||||
--local --output build.aab --non-interactive
|
|
||||||
|
|
||||||
- name: 📚 Get version from package.json
|
- name: 📚 Get version from package.json
|
||||||
id: get-build-info
|
id: get-build-info
|
||||||
run: bash scripts/setGitHubOutput.sh
|
run: bash scripts/setGitHubOutput.sh
|
||||||
|
|
||||||
|
# Hands the built bundle off to the submit / universalApk jobs. Retention is
|
||||||
|
# deliberately short (1 day) since it's only an intra-run handoff artifact.
|
||||||
|
- name: 🚀 Upload AAB artifact
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: android-aab-${{ github.run_id }}
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: build.aab
|
||||||
|
|
||||||
|
submit:
|
||||||
|
name: Submit to Google Play
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
# Submit unless explicitly disabled; on events where inputs is empty this still submits.
|
||||||
|
if: ${{ inputs.submit != false }}
|
||||||
|
steps:
|
||||||
|
# eas submit reads app config from the repo, so we need a checkout.
|
||||||
|
- name: ⬇️ Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
with:
|
||||||
|
fetch-depth: 5
|
||||||
|
|
||||||
|
- name: 🔧 Setup Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
|
with:
|
||||||
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: ⬇️ Download AAB artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: android-aab-${{ github.run_id }}
|
||||||
|
|
||||||
- name: 🚀 Submit to Google Play
|
- name: 🚀 Submit to Google Play
|
||||||
env:
|
env:
|
||||||
PROFILE: ${{ inputs.profile || 'testflight-android' }}
|
PROFILE: ${{ inputs.profile || 'testflight-android' }}
|
||||||
@@ -150,13 +162,49 @@ jobs:
|
|||||||
|
|
||||||
- name: 🔔 Notify Slack of Play Store Submission
|
- name: 🔔 Notify Slack of Play Store Submission
|
||||||
if: ${{ inputs.profile == 'production' }}
|
if: ${{ inputs.profile == 'production' }}
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
payload-templated: true
|
payload-templated: true
|
||||||
payload: |
|
payload: |
|
||||||
{"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"}
|
{"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"}
|
||||||
|
|
||||||
|
# Record the commit only after a successful submit, so a failed submit doesn't
|
||||||
|
# advance the "most recent testflight" marker.
|
||||||
|
- name: ⬇️ Restore Cache
|
||||||
|
id: get-base-commit
|
||||||
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
|
if: ${{ inputs.profile == 'testflight-android' }}
|
||||||
|
with:
|
||||||
|
path: most-recent-testflight-commit.txt
|
||||||
|
key: most-recent-testflight-commit
|
||||||
|
|
||||||
|
- name: ✏️ Write commit hash to cache
|
||||||
|
if: ${{ inputs.profile == 'testflight-android' }}
|
||||||
|
env:
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
||||||
|
|
||||||
|
# Runs in parallel with submit: the QA APK shouldn't be blocked by a Play submission failure.
|
||||||
|
universalApk:
|
||||||
|
name: Build universal APK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
outputs:
|
||||||
|
apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Download AAB artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: android-aab-${{ github.run_id }}
|
||||||
|
|
||||||
|
# bundletool needs a JRE. ubuntu-latest ships a default JDK, but pin it explicitly
|
||||||
|
# like the build job so the toolchain is deterministic.
|
||||||
|
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
- name: 🔧 Setup bundletool
|
- name: 🔧 Setup bundletool
|
||||||
uses: amyu/setup-bundletool@cc2e1857284660bd625e43f2c8a45626f034302f # v1.1
|
uses: amyu/setup-bundletool@cc2e1857284660bd625e43f2c8a45626f034302f # v1.1
|
||||||
@@ -164,19 +212,24 @@ jobs:
|
|||||||
version: "1.18.3"
|
version: "1.18.3"
|
||||||
|
|
||||||
- name: 🔑 Decode keystore
|
- name: 🔑 Decode keystore
|
||||||
run: echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode >
|
env:
|
||||||
keystore.jks
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > keystore.jks
|
||||||
|
|
||||||
- name: 📦 Build signed universal APK
|
- name: 📦 Build signed universal APK
|
||||||
|
env:
|
||||||
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
bundletool build-apks \
|
bundletool build-apks \
|
||||||
--bundle=build.aab \
|
--bundle=build.aab \
|
||||||
--output=universal.apks \
|
--output=universal.apks \
|
||||||
--mode=universal \
|
--mode=universal \
|
||||||
--ks=keystore.jks \
|
--ks=keystore.jks \
|
||||||
--ks-pass=pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }} \
|
--ks-pass=pass:"$ANDROID_KEYSTORE_PASSWORD" \
|
||||||
--ks-key-alias=${{ secrets.ANDROID_KEY_ALIAS }} \
|
--ks-key-alias="$ANDROID_KEY_ALIAS" \
|
||||||
--key-pass=pass:${{ secrets.ANDROID_KEY_PASSWORD }}
|
--key-pass=pass:"$ANDROID_KEY_PASSWORD"
|
||||||
|
|
||||||
- name: 📋 Rename to .zip for extraction
|
- name: 📋 Rename to .zip for extraction
|
||||||
run: mv universal.apks universal.zip
|
run: mv universal.apks universal.zip
|
||||||
@@ -198,27 +251,13 @@ jobs:
|
|||||||
path: build.apk
|
path: build.apk
|
||||||
|
|
||||||
- name: 🔔 Notify Slack of APK Artifact
|
- name: 🔔 Notify Slack of APK Artifact
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
payload-templated: true
|
payload-templated: true
|
||||||
payload: |
|
payload: |
|
||||||
{"text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"}
|
{"text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"}
|
||||||
|
|
||||||
- name: ⬇️ Restore Cache
|
|
||||||
id: get-base-commit
|
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
||||||
if: ${{ inputs.profile == 'testflight-android' }}
|
|
||||||
with:
|
|
||||||
path: most-recent-testflight-commit.txt
|
|
||||||
key: most-recent-testflight-commit
|
|
||||||
|
|
||||||
- name: ✏️ Write commit hash to cache
|
|
||||||
if: ${{ inputs.profile == 'testflight-android' }}
|
|
||||||
env:
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
|
||||||
|
|
||||||
# Releases are cut from tags named after the version (e.g. "1.124.0"), so when a production
|
# Releases are cut from tags named after the version (e.g. "1.124.0"), so when a production
|
||||||
# build is dispatched against such a tag we attach the APK to the matching release. This runs
|
# build is dispatched against such a tag we attach the APK to the matching release. This runs
|
||||||
@@ -226,7 +265,7 @@ jobs:
|
|||||||
attachToRelease:
|
attachToRelease:
|
||||||
name: Attach APK to GitHub Release
|
name: Attach APK to GitHub Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build, universalApk]
|
||||||
if: ${{ inputs.profile == 'production' && github.ref_type == 'tag' && github.repository == 'bluesky-social/social-app' }}
|
if: ${{ inputs.profile == 'production' && github.ref_type == 'tag' && github.repository == 'bluesky-social/social-app' }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -254,7 +293,7 @@ jobs:
|
|||||||
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
with:
|
with:
|
||||||
name: ${{ needs.build.outputs.apk-artifact-name }}
|
name: ${{ needs.universalApk.outputs.apk-artifact-name }}
|
||||||
|
|
||||||
- name: 🏷️ Rename APK for release
|
- name: 🏷️ Rename APK for release
|
||||||
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
||||||
@@ -265,6 +304,7 @@ jobs:
|
|||||||
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
TAG: ${{ github.ref_name }}
|
TAG: ${{ github.ref_name }}
|
||||||
APK: Bluesky-${{ needs.build.outputs.package-version }}.apk
|
APK: Bluesky-${{ needs.build.outputs.package-version }}.apk
|
||||||
run: |
|
run: |
|
||||||
@@ -274,7 +314,7 @@ jobs:
|
|||||||
|
|
||||||
- name: 🔔 Notify Slack of Release Attachment
|
- name: 🔔 Notify Slack of Release Attachment
|
||||||
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
if: ${{ steps.release-check.outputs.exists == 'true' }}
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
description: TestFlight group to assign the build to after submitting ("none" to skip)
|
description: TestFlight group to assign the build to after submitting ("none" to skip)
|
||||||
default: none
|
default: none
|
||||||
|
runner:
|
||||||
|
type: string
|
||||||
|
description: Runner for the build job (defaults to macos-26-xlarge)
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
outputs:
|
outputs:
|
||||||
package-version:
|
package-version:
|
||||||
description: Version from package.json
|
description: Version from package.json
|
||||||
@@ -66,8 +71,8 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository == 'bluesky-social/social-app'
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
name: Build and Submit iOS
|
name: Build iOS
|
||||||
runs-on: macos-26-xlarge
|
runs-on: ${{ inputs.runner || 'macos-26-xlarge' }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ios-build
|
group: ios-build
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
@@ -75,38 +80,15 @@ jobs:
|
|||||||
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
||||||
build-number: ${{ steps.ipa-build-number.outputs.build-number }}
|
build-number: ${{ steps.ipa-build-number.outputs.build-number }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check for EXPO_TOKEN
|
|
||||||
run: >
|
|
||||||
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
|
|
||||||
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 5
|
fetch-depth: 5
|
||||||
|
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- name: 🔧 Setup Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
- name: 🔧 Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: 🪛 Setup jq
|
|
||||||
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
|
|
||||||
|
|
||||||
- name: ⚙️ Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: 🔨 Setup Expo CLI
|
|
||||||
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
|
|
||||||
with:
|
|
||||||
eas-version: '19.0.5'
|
|
||||||
packager: 'pnpm --allow-build=dtrace-provider'
|
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
|
||||||
|
|
||||||
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
|
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
|
||||||
with:
|
with:
|
||||||
@@ -114,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
- name: ☕️ Assert Cocoapods version
|
- name: ☕️ Assert Cocoapods version
|
||||||
run: |
|
run: |
|
||||||
EXPECTED=1.16.2
|
EXPECTED=1.17.0
|
||||||
ACTUAL=$(pod --version)
|
ACTUAL=$(pod --version)
|
||||||
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
||||||
echo "Expected Cocoapods $EXPECTED but runner has $ACTUAL."
|
echo "Expected Cocoapods $EXPECTED but runner has $ACTUAL."
|
||||||
@@ -133,38 +115,29 @@ jobs:
|
|||||||
key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
|
||||||
- name: 🔤 Compile translations
|
- name: 🔤 Compile translations
|
||||||
run: pnpm intl:build 2>&1 | tee i18n.log
|
uses: ./.github/actions/compile-i18n
|
||||||
|
|
||||||
- name: Check for i18n compilation errors
|
|
||||||
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation
|
|
||||||
errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
|
|
||||||
|
|
||||||
# EXPO_PUBLIC_ENV is handled in eas.json
|
# EXPO_PUBLIC_ENV is handled in eas.json
|
||||||
- name: ✏️ Write environment variables
|
- name: ✏️ Write environment variables
|
||||||
id: env
|
id: env
|
||||||
run: |
|
uses: ./.github/actions/write-env
|
||||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
with:
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
env-token: ${{ secrets.ENV_TOKEN }}
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
sentry-dsn: ${{ secrets.SENTRY_DSN }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
bitdrift-api-key: ${{ secrets.BITDRIFT_API_KEY }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
gcp-project-id: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
google-services-token: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
|
||||||
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
|
|
||||||
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
|
|
||||||
|
|
||||||
- name: 🏗️ EAS Build
|
- name: 🏗️ EAS Build
|
||||||
env:
|
uses: ./.github/actions/eas-local-build
|
||||||
PROFILE: ${{ inputs.profile || 'testflight' }}
|
with:
|
||||||
run: >
|
platform: ios
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
profile: ${{ inputs.profile || 'testflight' }}
|
||||||
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
|
output: build.tar.gz
|
||||||
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
|
bump-build-number: "true"
|
||||||
pnpm use-build-number-with-bump
|
sentry-auth-token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
pnpm eas build -p ios
|
sentry-release: ${{ steps.env.outputs.release-version }}
|
||||||
--profile $PROFILE
|
sentry-dist: ${{ steps.env.outputs.bundle-identifier }}
|
||||||
--local --output build.tar.gz --non-interactive
|
|
||||||
|
|
||||||
- name: 📂 Extract build artifact
|
- name: 📂 Extract build artifact
|
||||||
run: |
|
run: |
|
||||||
@@ -201,16 +174,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: 🚀 Deploy
|
|
||||||
run: pnpm eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa"
|
|
||||||
|
|
||||||
- name: 🪲 Upload dSYM to Sentry
|
|
||||||
run: >
|
|
||||||
SENTRY_ORG=blueskyweb
|
|
||||||
SENTRY_PROJECT=app
|
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
pnpm sentry-cli debug-files upload "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources
|
|
||||||
|
|
||||||
- name: 📚 Get version from package.json
|
- name: 📚 Get version from package.json
|
||||||
id: get-build-info
|
id: get-build-info
|
||||||
run: bash scripts/setGitHubOutput.sh
|
run: bash scripts/setGitHubOutput.sh
|
||||||
@@ -220,6 +183,7 @@ jobs:
|
|||||||
# number that actually lands in App Store Connect. `eas build:version:get` reads the
|
# number that actually lands in App Store Connect. `eas build:version:get` reads the
|
||||||
# remote counter, which a --local build does not advance, so it can be off by one —
|
# remote counter, which a --local build does not advance, so it can be off by one —
|
||||||
# using it here would make distribute_only poll for a nonexistent build.
|
# using it here would make distribute_only poll for a nonexistent build.
|
||||||
|
# PlistBuddy is macOS-only, which is why this stays in the build job.
|
||||||
- name: 🔢 Read build number from IPA
|
- name: 🔢 Read build number from IPA
|
||||||
id: ipa-build-number
|
id: ipa-build-number
|
||||||
run: |
|
run: |
|
||||||
@@ -235,18 +199,98 @@ jobs:
|
|||||||
echo "IPA build number: $build_number"
|
echo "IPA build number: $build_number"
|
||||||
echo "build-number=$build_number" >> "$GITHUB_OUTPUT"
|
echo "build-number=$build_number" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Hand the IPA and dSYM off to the submit job. Retention is deliberately short since
|
||||||
|
# this artifact only exists to bridge the two jobs within a single run.
|
||||||
|
- name: 🚀 Upload build artifact
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: ios-build-${{ github.run_id }}
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: |
|
||||||
|
${{ env.BUILD_DIR }}/Bluesky.ipa
|
||||||
|
${{ env.BUILD_DIR }}/Bluesky.app.dSYM.zip
|
||||||
|
|
||||||
|
submit:
|
||||||
|
name: Submit iOS
|
||||||
|
# Submission and dSYM upload are I/O bound and don't need the xlarge builder.
|
||||||
|
runs-on: macos-26
|
||||||
|
needs: [build]
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
with:
|
||||||
|
# eas submit reads the app config from the repo
|
||||||
|
fetch-depth: 5
|
||||||
|
|
||||||
|
- name: 🔧 Setup Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
|
with:
|
||||||
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: ⬇️ Download build artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: ios-build-${{ github.run_id }}
|
||||||
|
path: ios-build
|
||||||
|
|
||||||
|
- name: 🚀 Deploy
|
||||||
|
run: pnpm eas submit -p ios --non-interactive --path ios-build/Bluesky.ipa
|
||||||
|
|
||||||
|
- name: 🪲 Upload dSYM to Sentry
|
||||||
|
env:
|
||||||
|
SENTRY_ORG: blueskyweb
|
||||||
|
SENTRY_PROJECT: app
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
run: pnpm sentry-cli debug-files upload ios-build/Bluesky.app.dSYM.zip --include-sources
|
||||||
|
|
||||||
|
- name: 🔔 Notify Slack of Production Build
|
||||||
|
if: ${{ inputs.profile == 'production' }}
|
||||||
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload-templated: true
|
||||||
|
payload: |
|
||||||
|
{"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.build-number }}```"}
|
||||||
|
|
||||||
|
# Record the commit only after a successful submit, so a failed submit doesn't advance
|
||||||
|
# the baseline used for the next testflight build's changelog.
|
||||||
|
- name: ⬇️ Restore Cache
|
||||||
|
id: get-base-commit
|
||||||
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
|
if: ${{ inputs.profile == 'testflight' }}
|
||||||
|
with:
|
||||||
|
path: most-recent-testflight-commit.txt
|
||||||
|
key: most-recent-testflight-commit
|
||||||
|
|
||||||
|
- name: ✏️ Write commit hash to cache
|
||||||
|
env:
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
if: ${{ inputs.profile == 'testflight' }}
|
||||||
|
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
||||||
|
|
||||||
|
distribute:
|
||||||
|
name: Assign build to TestFlight group
|
||||||
|
# fastlane and jq ship preinstalled on the macOS runner image, and this step mostly idles
|
||||||
|
# polling Apple processing, so it runs on a normal-size runner.
|
||||||
|
runs-on: macos-26
|
||||||
|
needs: [build, submit]
|
||||||
|
# testFlightGroup defaults to 'none' on both workflow_call and dispatch; guard against the
|
||||||
|
# empty string too, since `!= 'none'` alone would be true for ''.
|
||||||
|
if: ${{ inputs.testFlightGroup && inputs.testFlightGroup != 'none' }}
|
||||||
|
steps:
|
||||||
# eas submit only uploads to App Store Connect; it can't assign a build to a
|
# eas submit only uploads to App Store Connect; it can't assign a build to a
|
||||||
# TestFlight group. fastlane's distribute_only mode skips the upload and assigns the
|
# TestFlight group. fastlane's distribute_only mode skips the upload and assigns the
|
||||||
# already-submitted build to the group, polling until Apple finishes processing it.
|
# already-submitted build to the group, polling until Apple finishes processing it.
|
||||||
- name: 🧪 Assign build to TestFlight group
|
- name: 🧪 Assign build to TestFlight group
|
||||||
if: ${{ inputs.testFlightGroup != 'none' }}
|
|
||||||
env:
|
env:
|
||||||
TESTFLIGHT_GROUP: ${{ inputs.testFlightGroup }}
|
TESTFLIGHT_GROUP: ${{ inputs.testFlightGroup }}
|
||||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
|
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
|
||||||
APP_VERSION: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}
|
APP_VERSION: ${{ needs.build.outputs.package-version }}
|
||||||
BUILD_NUMBER: ${{ steps.ipa-build-number.outputs.build-number }}
|
BUILD_NUMBER: ${{ needs.build.outputs.build-number }}
|
||||||
run: |
|
run: |
|
||||||
# Ensure the API key material is removed even if fastlane exits non-zero
|
# Ensure the API key material is removed even if fastlane exits non-zero
|
||||||
# (the step runs under `bash -e`, which would otherwise abort before cleanup).
|
# (the step runs under `bash -e`, which would otherwise abort before cleanup).
|
||||||
@@ -271,27 +315,3 @@ jobs:
|
|||||||
build_number:"$BUILD_NUMBER" \
|
build_number:"$BUILD_NUMBER" \
|
||||||
groups:"$TESTFLIGHT_GROUP" \
|
groups:"$TESTFLIGHT_GROUP" \
|
||||||
notify_external_testers:true
|
notify_external_testers:true
|
||||||
|
|
||||||
- name: 🔔 Notify Slack of Production Build
|
|
||||||
if: ${{ inputs.profile == 'production' }}
|
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
|
||||||
webhook-type: incoming-webhook
|
|
||||||
payload-templated: true
|
|
||||||
payload: |
|
|
||||||
{"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.ipa-build-number.outputs.build-number }}```"}
|
|
||||||
|
|
||||||
- name: ⬇️ Restore Cache
|
|
||||||
id: get-base-commit
|
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
||||||
if: ${{ inputs.profile == 'testflight' }}
|
|
||||||
with:
|
|
||||||
path: most-recent-testflight-commit.txt
|
|
||||||
key: most-recent-testflight-commit
|
|
||||||
|
|
||||||
- name: ✏️ Write commit hash to cache
|
|
||||||
env:
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
if: ${{ inputs.profile == 'testflight' }}
|
|
||||||
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: 🔧 Setup Node
|
- name: 🔧 Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -99,11 +99,7 @@ jobs:
|
|||||||
previous-commit-tag: ${{ inputs.runtimeVersion }}
|
previous-commit-tag: ${{ inputs.runtimeVersion }}
|
||||||
|
|
||||||
- name: 🔤 Compile translations
|
- name: 🔤 Compile translations
|
||||||
run: pnpm intl:build 2>&1 | tee i18n.log
|
uses: ./.github/actions/compile-i18n
|
||||||
|
|
||||||
- name: Check for i18n compilation errors
|
|
||||||
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation
|
|
||||||
errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
|
|
||||||
|
|
||||||
- name: Lint check
|
- name: Lint check
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
@@ -128,35 +124,26 @@ jobs:
|
|||||||
!steps.version.outputs.version-changed }}
|
!steps.version.outputs.version-changed }}
|
||||||
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
|
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
|
||||||
|
|
||||||
# eas.json not used here, set EXPO_PUBLIC_ENV
|
# eas.json not used here, so EXPO_PUBLIC_ENV must be written explicitly
|
||||||
- name: Env
|
- name: ✏️ Write environment variables
|
||||||
env:
|
|
||||||
CHANNEL: ${{ inputs.channel || 'testflight' }}
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
id: env
|
id: env
|
||||||
if: ${{ !steps.fingerprint.outputs.includes-changes &&
|
if: ${{ !steps.fingerprint.outputs.includes-changes && !steps.version.outputs.version-changed }}
|
||||||
!steps.version.outputs.version-changed }}
|
uses: ./.github/actions/write-env
|
||||||
run: |
|
with:
|
||||||
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
|
env-token: ${{ secrets.ENV_TOKEN }}
|
||||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
sentry-dsn: ${{ secrets.SENTRY_DSN }}
|
||||||
echo "EXPO_PUBLIC_ENV=$CHANNEL" >> .env
|
bitdrift-api-key: ${{ secrets.BITDRIFT_API_KEY }}
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
gcp-project-id: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
google-services-token: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
expo-public-env: ${{ inputs.channel || 'testflight' }}
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
|
||||||
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
|
|
||||||
echo "$json" > google-services.json
|
|
||||||
|
|
||||||
- name: 🏗️ Create Bundle
|
- name: 🏗️ Create Bundle
|
||||||
if: ${{ !steps.fingerprint.outputs.includes-changes &&
|
if: ${{ !steps.fingerprint.outputs.includes-changes &&
|
||||||
!steps.version.outputs.version-changed }}
|
!steps.version.outputs.version-changed }}
|
||||||
run: >
|
run: >
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
|
SENTRY_RELEASE=${{ steps.env.outputs.release-version }}
|
||||||
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
|
SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }}
|
||||||
pnpm export
|
pnpm export
|
||||||
|
|
||||||
- name: 📦 Package Bundle and 🚀 Deploy
|
- name: 📦 Package Bundle and 🚀 Deploy
|
||||||
@@ -182,305 +169,64 @@ jobs:
|
|||||||
!steps.version.outputs.version-changed }}
|
!steps.version.outputs.version-changed }}
|
||||||
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
||||||
|
|
||||||
# GitHub actions are horrible so let's just copy paste this in
|
|
||||||
buildIfNecessaryIOS:
|
buildIfNecessaryIOS:
|
||||||
name: Build and Submit iOS
|
name: Build and Submit iOS
|
||||||
runs-on: macos-26
|
|
||||||
concurrency:
|
|
||||||
group: ios-build
|
|
||||||
cancel-in-progress: false
|
|
||||||
needs: [bundleDeploy]
|
needs: [bundleDeploy]
|
||||||
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
||||||
# available here
|
# available here
|
||||||
if: ${{ inputs.channel != 'production' &&
|
if: ${{ inputs.channel != 'production' &&
|
||||||
needs.bundleDeploy.outputs.changes-detected && github.repository ==
|
needs.bundleDeploy.outputs.changes-detected && github.repository ==
|
||||||
'bluesky-social/social-app' }}
|
'bluesky-social/social-app' }}
|
||||||
steps:
|
uses: ./.github/workflows/build-submit-ios.yml
|
||||||
- name: Check for EXPO_TOKEN
|
with:
|
||||||
run: >
|
profile: testflight
|
||||||
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
|
testFlightGroup: none
|
||||||
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
|
# OTA rebuilds don't need the xlarge builder used for releases
|
||||||
exit 1
|
runner: macos-26
|
||||||
fi
|
# Pass only the secrets the reusable workflow declares, rather than `secrets: inherit`,
|
||||||
|
# so this workflow never hands the reusable workflow the entire repo secret store.
|
||||||
- name: ⬇️ Checkout
|
secrets:
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||||
with:
|
ENV_TOKEN: ${{ secrets.ENV_TOKEN }}
|
||||||
fetch-depth: 5
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
BITDRIFT_API_KEY: ${{ secrets.BITDRIFT_API_KEY }}
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
EXPO_PUBLIC_GCP_PROJECT_ID: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
|
||||||
|
GOOGLE_SERVICES_TOKEN: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
|
||||||
- name: 🔧 Setup Node
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
with:
|
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
node-version-file: package.json
|
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
|
||||||
cache: pnpm
|
SLACK_CLIENT_ALERT_WEBHOOK: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
|
|
||||||
- name: 🔨 Setup EAS
|
|
||||||
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
|
|
||||||
with:
|
|
||||||
eas-version: '19.0.5'
|
|
||||||
packager: 'pnpm --allow-build=dtrace-provider'
|
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
|
||||||
|
|
||||||
- name: ⚙️ Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
|
|
||||||
with:
|
|
||||||
xcode-version: "26.4"
|
|
||||||
|
|
||||||
- name: ☕️ Assert Cocoapods version
|
|
||||||
run: |
|
|
||||||
EXPECTED=1.16.2
|
|
||||||
ACTUAL=$(pod --version)
|
|
||||||
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
|
||||||
echo "Expected Cocoapods $EXPECTED but runner has $ACTUAL."
|
|
||||||
echo "The version ships preinstalled with the macOS runner image: https://github.com/actions/runner-images/blob/main/images/macos/macos-26-Readme.md"
|
|
||||||
echo "If the runner image changed, update EXPECTED here or reinstall the pinned version."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: 💾 Cache Pods
|
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
||||||
id: pods-cache
|
|
||||||
with:
|
|
||||||
path: ./ios/Pods
|
|
||||||
# We'll use the pnpm-lock.yaml for our hash since we don't yet have a Podfile.lock. Pod versions will not
|
|
||||||
# change unless the pnpm version changes as well.
|
|
||||||
key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }}
|
|
||||||
|
|
||||||
- name: 🔤 Compile translations
|
|
||||||
run: pnpm intl:build
|
|
||||||
|
|
||||||
# EXPO_PUBLIC_ENV is handled in eas.json
|
|
||||||
- name: Env
|
|
||||||
id: env
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
|
||||||
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
|
|
||||||
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
|
|
||||||
|
|
||||||
- name: 🏗️ EAS Build
|
|
||||||
run: >
|
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
|
|
||||||
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
|
|
||||||
pnpm use-build-number-with-bump
|
|
||||||
pnpm eas build -p ios
|
|
||||||
--profile testflight
|
|
||||||
--local --output build.tar.gz --non-interactive
|
|
||||||
|
|
||||||
- name: 📂 Extract build artifact
|
|
||||||
run: |
|
|
||||||
if [ -f "build.tar.gz" ]; then
|
|
||||||
echo "Extracting build.tar.gz..."
|
|
||||||
rm -rf ios-build
|
|
||||||
mkdir -p ios-build
|
|
||||||
tar -xzf build.tar.gz -C ios-build
|
|
||||||
echo "Extraction completed successfully"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Top-level extracted files:"
|
|
||||||
find ios-build -maxdepth 3 -print
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Searching for IPA..."
|
|
||||||
IPA_PATH="$(find ios-build -type f -name '*.ipa' -print -quit)"
|
|
||||||
if [ -z "$IPA_PATH" ]; then
|
|
||||||
echo "ERROR: No .ipa found anywhere under ios-build."
|
|
||||||
echo "Archive contents:"
|
|
||||||
tar -tzf build.tar.gz | sed -n '1,200p'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BUILD_DIR="$(dirname "$IPA_PATH")"
|
|
||||||
echo "Found IPA at: $IPA_PATH"
|
|
||||||
echo "Build dir: $BUILD_DIR"
|
|
||||||
echo ""
|
|
||||||
echo "Build dir contents:"
|
|
||||||
ls -la "$BUILD_DIR"
|
|
||||||
echo "BUILD_DIR=$BUILD_DIR" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "Archive file not found!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: 🚀 Deploy
|
|
||||||
run: pnpm eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa"
|
|
||||||
|
|
||||||
- name: 🪲 Upload dSYM to Sentry
|
|
||||||
run: >
|
|
||||||
SENTRY_ORG=blueskyweb
|
|
||||||
SENTRY_PROJECT=app
|
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
pnpm sentry-cli debug-files upload "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources
|
|
||||||
|
|
||||||
- name: ⬇️ Restore Cache
|
|
||||||
id: get-base-commit
|
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
||||||
if: ${{ inputs.channel == 'testflight' }}
|
|
||||||
with:
|
|
||||||
path: most-recent-testflight-commit.txt
|
|
||||||
key: most-recent-testflight-commit
|
|
||||||
|
|
||||||
- name: ✏️ Write commit hash to cache
|
|
||||||
if: ${{ inputs.channel == 'testflight' }}
|
|
||||||
env:
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
|
||||||
|
|
||||||
buildIfNecessaryAndroid:
|
buildIfNecessaryAndroid:
|
||||||
name: Build and Submit Android
|
name: Build and Submit Android
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: android-build
|
|
||||||
cancel-in-progress: false
|
|
||||||
needs: [bundleDeploy]
|
needs: [bundleDeploy]
|
||||||
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
||||||
# available here
|
# available here
|
||||||
if: ${{ inputs.channel != 'production' &&
|
if: ${{ inputs.channel != 'production' &&
|
||||||
needs.bundleDeploy.outputs.changes-detected && github.repository ==
|
needs.bundleDeploy.outputs.changes-detected && github.repository ==
|
||||||
'bluesky-social/social-app'}}
|
'bluesky-social/social-app' }}
|
||||||
|
# build-submit-android.yml contains an attachToRelease job that requests contents: write.
|
||||||
steps:
|
# That job is skipped here (it needs a production tag build), but GitHub statically
|
||||||
- name: Check for EXPO_TOKEN
|
# validates the reusable-workflow permission ceiling, so the caller must grant it.
|
||||||
run: >
|
permissions:
|
||||||
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
|
contents: write
|
||||||
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
|
uses: ./.github/workflows/build-submit-android.yml
|
||||||
exit 1
|
with:
|
||||||
fi
|
profile: testflight-android
|
||||||
|
runner: ubuntu-latest
|
||||||
- name: ⬇️ Checkout
|
# Pass only the secrets the reusable workflow declares, rather than `secrets: inherit`,
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
# so this workflow never hands the reusable workflow the entire repo secret store.
|
||||||
with:
|
secrets:
|
||||||
fetch-depth: 5
|
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
ENV_TOKEN: ${{ secrets.ENV_TOKEN }}
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
BITDRIFT_API_KEY: ${{ secrets.BITDRIFT_API_KEY }}
|
||||||
- name: 🔧 Setup Node
|
EXPO_PUBLIC_GCP_PROJECT_ID: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
GOOGLE_SERVICES_TOKEN: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
|
||||||
with:
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
node-version-file: package.json
|
SLACK_CLIENT_ALERT_WEBHOOK: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
||||||
cache: pnpm
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
- name: 🔨 Setup EAS
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
with:
|
|
||||||
eas-version: '19.0.5'
|
|
||||||
packager: 'pnpm --allow-build=dtrace-provider'
|
|
||||||
token: ${{ secrets.EXPO_TOKEN }}
|
|
||||||
|
|
||||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
|
||||||
with:
|
|
||||||
distribution: "temurin"
|
|
||||||
java-version: "17"
|
|
||||||
|
|
||||||
- name: ⚙️ Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: 🔤 Compile translations
|
|
||||||
run: pnpm intl:build
|
|
||||||
|
|
||||||
# EXPO_PUBLIC_ENV is handled in eas.json
|
|
||||||
- name: Env
|
|
||||||
id: env
|
|
||||||
run: |
|
|
||||||
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
|
|
||||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
|
|
||||||
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
|
|
||||||
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
|
|
||||||
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
|
|
||||||
echo "$json" > google-services.json
|
|
||||||
|
|
||||||
- name: 🏗️ EAS Build
|
|
||||||
run: >
|
|
||||||
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
|
|
||||||
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
|
|
||||||
pnpm use-build-number-with-bump
|
|
||||||
pnpm eas build -p android
|
|
||||||
--profile testflight-android
|
|
||||||
--local --output build.aab --non-interactive
|
|
||||||
|
|
||||||
- name: 📚 Get version from package.json
|
|
||||||
id: get-build-info
|
|
||||||
run: bash scripts/setGitHubOutput.sh
|
|
||||||
|
|
||||||
- name: 🚀 Submit to Google Play
|
|
||||||
run: pnpm eas submit -p android --profile testflight-android --non-interactive --path
|
|
||||||
build.aab
|
|
||||||
|
|
||||||
- name: 🔧 Setup bundletool
|
|
||||||
uses: amyu/setup-bundletool@cc2e1857284660bd625e43f2c8a45626f034302f # v1.1
|
|
||||||
with:
|
|
||||||
version: "1.18.3"
|
|
||||||
|
|
||||||
- name: 🔑 Decode keystore
|
|
||||||
run: echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode >
|
|
||||||
keystore.jks
|
|
||||||
|
|
||||||
- name: 📦 Build signed universal APK
|
|
||||||
run: |
|
|
||||||
bundletool build-apks \
|
|
||||||
--bundle=build.aab \
|
|
||||||
--output=universal.apks \
|
|
||||||
--mode=universal \
|
|
||||||
--ks=keystore.jks \
|
|
||||||
--ks-pass=pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }} \
|
|
||||||
--ks-key-alias=${{ secrets.ANDROID_KEY_ALIAS }} \
|
|
||||||
--key-pass=pass:${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
||||||
|
|
||||||
- name: 📋 Rename to .zip for extraction
|
|
||||||
run: mv universal.apks universal.zip
|
|
||||||
|
|
||||||
- name: 📦 Extract universal APK
|
|
||||||
run: unzip -p universal.zip universal.apk > build.apk
|
|
||||||
|
|
||||||
- name: ⏰ Get a timestamp
|
|
||||||
id: timestamp
|
|
||||||
run: echo "time=$(date -u +'%m-%d-%H-%M-%S')" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: 🚀 Upload Artifact
|
|
||||||
id: upload-artifact
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
||||||
with:
|
|
||||||
retention-days: 30
|
|
||||||
compression-level: 0
|
|
||||||
name: build-${{ steps.timestamp.outputs.time }}.apk
|
|
||||||
path: build.apk
|
|
||||||
|
|
||||||
- name: 🔔 Notify Slack
|
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
|
|
||||||
webhook-type: incoming-webhook
|
|
||||||
payload-templated: true
|
|
||||||
payload: |
|
|
||||||
{"text": "Android build is ready for testing. Download the artifact here: ${{ steps.upload-artifact.outputs.artifact-url }}"}
|
|
||||||
|
|
||||||
- name: ⬇️ Restore Cache
|
|
||||||
id: get-base-commit
|
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
||||||
if: ${{ inputs.channel != 'testflight' && inputs.channel != 'production' }}
|
|
||||||
with:
|
|
||||||
path: most-recent-testflight-commit.txt
|
|
||||||
key: most-recent-testflight-commit
|
|
||||||
|
|
||||||
- name: ✏️ Write commit hash to cache
|
|
||||||
env:
|
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
|
||||||
if: ${{ inputs.channel != 'testflight' && inputs.channel != 'production' }}
|
|
||||||
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
|
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Configure AWS credentials (OIDC)
|
- name: Configure AWS credentials (OIDC)
|
||||||
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
|
||||||
with:
|
with:
|
||||||
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
|
|
||||||
- name: Claude
|
- name: Claude
|
||||||
uses: anthropics/claude-code-action@f87768c6d25f92ae6efa7175e223ef77d4cbf97f # v1.0.166
|
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
|
||||||
with:
|
with:
|
||||||
use_bedrock: 'true'
|
use_bedrock: 'true'
|
||||||
additional_permissions: |
|
additional_permissions: |
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Configure AWS credentials (OIDC)
|
- name: Configure AWS credentials (OIDC)
|
||||||
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
|
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
|
||||||
with:
|
with:
|
||||||
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
|
||||||
aws-region: us-east-2
|
aws-region: us-east-2
|
||||||
|
|
||||||
- name: Claude review
|
- name: Claude review
|
||||||
uses: anthropics/claude-code-action@f87768c6d25f92ae6efa7175e223ef77d4cbf97f # v1.0.166
|
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
|
||||||
with:
|
with:
|
||||||
use_bedrock: 'true'
|
use_bedrock: 'true'
|
||||||
additional_permissions: |
|
additional_permissions: |
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job: [lint, prettier, typecheck]
|
job:
|
||||||
|
[lint, prettier, 'typecheck:ios', 'typecheck:android', 'typecheck:web']
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
@@ -52,7 +53,7 @@ jobs:
|
|||||||
exit $rc
|
exit $rc
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
- name: Install node
|
- name: Install node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -90,7 +91,7 @@ jobs:
|
|||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
- name: Install node
|
- name: Install node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ jobs:
|
|||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: 🔔 Notify Slack
|
- name: 🔔 Notify Slack
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
@@ -174,7 +174,7 @@ jobs:
|
|||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: 🔔 Notify Slack
|
- name: 🔔 Notify Slack
|
||||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
|
||||||
webhook-type: incoming-webhook
|
webhook-type: incoming-webhook
|
||||||
|
|||||||
@@ -0,0 +1,424 @@
|
|||||||
|
---
|
||||||
|
name: Nightly Maestro E2E
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 4 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: nightly-maestro-e2e-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
CI: "1"
|
||||||
|
MAESTRO_VERSION: "2.6.1"
|
||||||
|
MAESTRO_DRIVER_STARTUP_TIMEOUT: "180000"
|
||||||
|
MAESTRO_CLI_NO_ANALYTICS: "1"
|
||||||
|
MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED: "true"
|
||||||
|
MAESTRO_DISABLE_UPDATE_CHECK: "1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ios:
|
||||||
|
name: iOS Maestro E2E
|
||||||
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
|
runs-on: macos-26-xlarge
|
||||||
|
timeout-minutes: 120
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Select Xcode 26.4
|
||||||
|
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
|
||||||
|
with:
|
||||||
|
xcode-version: "26.4"
|
||||||
|
|
||||||
|
- name: Prepare E2E configuration
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts/ios
|
||||||
|
echo "Installing dependencies" > artifacts/ios/phase.txt
|
||||||
|
cp .env.example .env.test
|
||||||
|
cp google-services.json.example google-services.json
|
||||||
|
|
||||||
|
- name: Set up Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
|
with:
|
||||||
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Java 17
|
||||||
|
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
|
- name: Install dev-env dependencies
|
||||||
|
run: pnpm --dir dev-env install --frozen-lockfile 2>&1 | tee artifacts/ios/dependencies.log
|
||||||
|
|
||||||
|
- name: Compile translations
|
||||||
|
uses: ./.github/actions/compile-i18n
|
||||||
|
|
||||||
|
- name: Install Maestro 2.6.1
|
||||||
|
run: |
|
||||||
|
echo "Installing Maestro" > artifacts/ios/phase.txt
|
||||||
|
curl -fsSL -o "$RUNNER_TEMP/maestro.zip" \
|
||||||
|
"https://github.com/mobile-dev-inc/Maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip"
|
||||||
|
echo "3440825f514f537c6a96bcf5de995780c2a4a7f83a43208fdc95d4f1fecfad3b $RUNNER_TEMP/maestro.zip" \
|
||||||
|
| shasum -a 256 --check
|
||||||
|
unzip -q "$RUNNER_TEMP/maestro.zip" -d "$RUNNER_TEMP"
|
||||||
|
echo "$RUNNER_TEMP/maestro/bin" >> "$GITHUB_PATH"
|
||||||
|
"$RUNNER_TEMP/maestro/bin/maestro" --version | tee artifacts/ios/maestro-version.log
|
||||||
|
test "$("$RUNNER_TEMP/maestro/bin/maestro" --version)" = "$MAESTRO_VERSION"
|
||||||
|
|
||||||
|
- name: Boot one iOS simulator
|
||||||
|
run: |
|
||||||
|
echo "Booting iOS simulator" > artifacts/ios/phase.txt
|
||||||
|
device_name="iPhone 17"
|
||||||
|
runtime_name="iOS 26.5"
|
||||||
|
|
||||||
|
runtime_id=$(xcrun simctl list runtimes available --json | jq -r \
|
||||||
|
--arg name "$runtime_name" \
|
||||||
|
'[.runtimes[] | select(.name == $name and .isAvailable != false)] | first | .identifier // empty')
|
||||||
|
if [ -z "$runtime_id" ]; then
|
||||||
|
echo "The $runtime_name simulator runtime is not installed. Available iOS runtimes:" >&2
|
||||||
|
xcrun simctl list runtimes available --json | jq -r \
|
||||||
|
'.runtimes[] | select(.name | startswith("iOS")) | "- \(.name)"' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
device_type_id=$(xcrun simctl list devicetypes --json | jq -r \
|
||||||
|
--arg name "$device_name" \
|
||||||
|
'[.devicetypes[] | select(.name == $name)] | first | .identifier // empty')
|
||||||
|
if [ -z "$device_type_id" ]; then
|
||||||
|
echo "The $device_name simulator device type is not installed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
udid=$(xcrun simctl list devices available --json | jq -r \
|
||||||
|
--arg runtime "$runtime_id" \
|
||||||
|
--arg name "$device_name" \
|
||||||
|
'[.devices[$runtime][]? | select(.name == $name)] | first | .udid // empty')
|
||||||
|
if [ -z "$udid" ]; then
|
||||||
|
udid=$(xcrun simctl create "$device_name" "$device_type_id" "$runtime_id")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "IOS_UDID=$udid" >> "$GITHUB_ENV"
|
||||||
|
xcrun simctl shutdown all || true
|
||||||
|
xcrun simctl boot "$udid"
|
||||||
|
xcrun simctl bootstatus "$udid" -b
|
||||||
|
echo "Using $device_name on $runtime_name ($udid)"
|
||||||
|
|
||||||
|
- name: Mark iOS development client build phase
|
||||||
|
run: echo "Building the iOS development client" > artifacts/ios/phase.txt
|
||||||
|
|
||||||
|
- name: Build iOS development client
|
||||||
|
uses: ./.github/actions/eas-local-build
|
||||||
|
with:
|
||||||
|
platform: ios
|
||||||
|
profile: e2e
|
||||||
|
output: ${{ runner.temp }}/nightly-e2e-ios.tar.gz
|
||||||
|
log-path: artifacts/ios/build.log
|
||||||
|
|
||||||
|
- name: Install iOS development client
|
||||||
|
run: |
|
||||||
|
build_contents="$RUNNER_TEMP/nightly-e2e-ios-build"
|
||||||
|
mkdir -p "$build_contents"
|
||||||
|
tar -xzf "$RUNNER_TEMP/nightly-e2e-ios.tar.gz" -C "$build_contents"
|
||||||
|
app_path=$(find "$build_contents" -type d -name '*.app' -print -quit)
|
||||||
|
if [ -z "$app_path" ]; then
|
||||||
|
echo "The local EAS build did not contain an iOS simulator app" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
xcrun simctl install "$IOS_UDID" "$app_path" 2>&1 | tee -a artifacts/ios/build.log
|
||||||
|
|
||||||
|
- name: Run iOS Maestro suite
|
||||||
|
run: .github/scripts/run-nightly-e2e.sh ios "$IOS_UDID"
|
||||||
|
|
||||||
|
- name: Clean up iOS services and simulator
|
||||||
|
if: always()
|
||||||
|
run: .github/scripts/cleanup-nightly-e2e.sh ios "${IOS_UDID:-}"
|
||||||
|
|
||||||
|
- name: Upload iOS E2E artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: nightly-e2e-ios-${{ github.run_id }}
|
||||||
|
path: artifacts/ios
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
android:
|
||||||
|
name: Android Maestro E2E
|
||||||
|
if: github.repository == 'bluesky-social/social-app'
|
||||||
|
# Linux-x64-32core is a repository-managed runner label.
|
||||||
|
runs-on: Linux-x64-32core
|
||||||
|
timeout-minutes: 120
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Prepare E2E configuration
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts/android
|
||||||
|
echo "Installing dependencies" > artifacts/android/phase.txt
|
||||||
|
cp .env.example .env.test
|
||||||
|
cp google-services.json.example google-services.json
|
||||||
|
|
||||||
|
- name: Set up Expo project
|
||||||
|
uses: ./.github/actions/setup-expo-project
|
||||||
|
with:
|
||||||
|
expo-token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Java 17
|
||||||
|
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "17"
|
||||||
|
|
||||||
|
- name: Install dev-env dependencies
|
||||||
|
run: pnpm --dir dev-env install --frozen-lockfile 2>&1 | tee artifacts/android/dependencies.log
|
||||||
|
|
||||||
|
- name: Compile translations
|
||||||
|
uses: ./.github/actions/compile-i18n
|
||||||
|
|
||||||
|
- name: Install Maestro 2.6.1
|
||||||
|
run: |
|
||||||
|
echo "Installing Maestro" > artifacts/android/phase.txt
|
||||||
|
curl -fsSL -o "$RUNNER_TEMP/maestro.zip" \
|
||||||
|
"https://github.com/mobile-dev-inc/Maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip"
|
||||||
|
echo "3440825f514f537c6a96bcf5de995780c2a4a7f83a43208fdc95d4f1fecfad3b $RUNNER_TEMP/maestro.zip" \
|
||||||
|
| shasum -a 256 --check
|
||||||
|
unzip -q "$RUNNER_TEMP/maestro.zip" -d "$RUNNER_TEMP"
|
||||||
|
echo "$RUNNER_TEMP/maestro/bin" >> "$GITHUB_PATH"
|
||||||
|
"$RUNNER_TEMP/maestro/bin/maestro" --version | tee artifacts/android/maestro-version.log
|
||||||
|
test "$("$RUNNER_TEMP/maestro/bin/maestro" --version)" = "$MAESTRO_VERSION"
|
||||||
|
|
||||||
|
- name: Install and boot one Android emulator
|
||||||
|
run: |
|
||||||
|
echo "Booting Android emulator" > artifacts/android/phase.txt
|
||||||
|
android_sdk="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}"
|
||||||
|
sdkmanager_bin="$android_sdk/cmdline-tools/latest/bin/sdkmanager"
|
||||||
|
avdmanager_bin="$android_sdk/cmdline-tools/latest/bin/avdmanager"
|
||||||
|
# API 35 emulator images have known stability problems in headless CI
|
||||||
|
# (see flutter/flutter#153445); the qemu process died deterministically
|
||||||
|
# on the first native stack-screen push with the API 35 image.
|
||||||
|
system_image="system-images;android-34;google_apis;x86_64"
|
||||||
|
|
||||||
|
if [ ! -x "$sdkmanager_bin" ] || [ ! -x "$avdmanager_bin" ]; then
|
||||||
|
echo "Android command-line tools were not found under $android_sdk" >&2
|
||||||
|
find "$android_sdk/cmdline-tools" -maxdepth 3 -type f \( \
|
||||||
|
-name sdkmanager -o -name avdmanager \
|
||||||
|
\) -print >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ANDROID_HOME="$android_sdk"
|
||||||
|
export ANDROID_SDK_ROOT="$android_sdk"
|
||||||
|
export PATH="$android_sdk/platform-tools:$android_sdk/emulator:$PATH"
|
||||||
|
echo "ANDROID_HOME=$android_sdk" >> "$GITHUB_ENV"
|
||||||
|
echo "ANDROID_SDK_ROOT=$android_sdk" >> "$GITHUB_ENV"
|
||||||
|
echo "$android_sdk/platform-tools" >> "$GITHUB_PATH"
|
||||||
|
echo "$android_sdk/emulator" >> "$GITHUB_PATH"
|
||||||
|
echo "Using Android SDK at $android_sdk"
|
||||||
|
|
||||||
|
yes | "$sdkmanager_bin" --sdk_root="$android_sdk" --licenses >/dev/null || true
|
||||||
|
"$sdkmanager_bin" --sdk_root="$android_sdk" \
|
||||||
|
"platform-tools" "emulator" "$system_image"
|
||||||
|
|
||||||
|
export ANDROID_AVD_HOME="$RUNNER_TEMP/.android/avd"
|
||||||
|
mkdir -p "$ANDROID_AVD_HOME"
|
||||||
|
echo "ANDROID_AVD_HOME=$ANDROID_AVD_HOME" >> "$GITHUB_ENV"
|
||||||
|
echo no | "$avdmanager_bin" create avd \
|
||||||
|
--force \
|
||||||
|
--name nightly-e2e \
|
||||||
|
--package "$system_image" \
|
||||||
|
--device pixel_6
|
||||||
|
|
||||||
|
# Reduce resolution to lighten the SwiftShader software-rendering
|
||||||
|
# workload, and raise RAM/cores/heap so the debug RN app has headroom.
|
||||||
|
# The default 2560MB RAM led to silent qemu crashes mid-flow.
|
||||||
|
printf 'hw.lcd.width=720\nhw.lcd.height=1600\nhw.lcd.density=280\nhw.ramSize=6144\nhw.cpu.ncore=4\nvm.heapSize=512\n' \
|
||||||
|
>> "$ANDROID_AVD_HOME/nightly-e2e.avd/config.ini"
|
||||||
|
|
||||||
|
if [ -e /dev/kvm ] && [ ! -w /dev/kvm ]; then
|
||||||
|
sudo chmod 666 /dev/kvm
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Disable the emulator's Vulkan feature so graphics goes through the
|
||||||
|
# plain GLES SwiftShader translator. gfxstream Vulkan via SwiftShader
|
||||||
|
# Subzero crashed qemu silently at a deterministic rendering step;
|
||||||
|
# GLES-only is sufficient since the guest renders with skiagl.
|
||||||
|
#
|
||||||
|
# Run the launch in a background subshell so the emulator's exit
|
||||||
|
# status is recorded when it dies (it is otherwise backgrounded and
|
||||||
|
# its death is invisible). Write the emulator's real PID - not the
|
||||||
|
# subshell's - to emulator.pid, since cleanup-nightly-e2e.sh kills the
|
||||||
|
# PID from that file directly; killing the subshell would not kill the
|
||||||
|
# emulator child.
|
||||||
|
(
|
||||||
|
# wait returns the emulator's non-zero status on crash; set -e would
|
||||||
|
# abort the subshell before the status is logged.
|
||||||
|
set +e
|
||||||
|
"$android_sdk/emulator/emulator" @nightly-e2e \
|
||||||
|
-port 5554 \
|
||||||
|
-no-window \
|
||||||
|
-gpu swiftshader_indirect \
|
||||||
|
-feature -Vulkan \
|
||||||
|
-no-snapshot \
|
||||||
|
-noaudio \
|
||||||
|
-no-boot-anim \
|
||||||
|
-camera-back none \
|
||||||
|
> artifacts/android/emulator.log 2>&1 &
|
||||||
|
emulator_pid=$!
|
||||||
|
echo "$emulator_pid" > artifacts/android/emulator.pid
|
||||||
|
wait "$emulator_pid"
|
||||||
|
echo "Emulator exited with status $?" >> artifacts/android/emulator.log
|
||||||
|
) &
|
||||||
|
|
||||||
|
adb -s emulator-5554 wait-for-device
|
||||||
|
booted=false
|
||||||
|
for _ in $(seq 1 120); do
|
||||||
|
if [ "$(adb -s emulator-5554 shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; then
|
||||||
|
booted=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
if [ "$booted" != "true" ]; then
|
||||||
|
echo "Android emulator did not finish booting" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
adb -s emulator-5554 shell settings put global window_animation_scale 0
|
||||||
|
adb -s emulator-5554 shell settings put global transition_animation_scale 0
|
||||||
|
adb -s emulator-5554 shell settings put global animator_duration_scale 0
|
||||||
|
|
||||||
|
- name: Mark Android development client build phase
|
||||||
|
run: echo "Building the Android development client" > artifacts/android/phase.txt
|
||||||
|
|
||||||
|
- name: Build Android development client
|
||||||
|
uses: ./.github/actions/eas-local-build
|
||||||
|
with:
|
||||||
|
platform: android
|
||||||
|
profile: e2e
|
||||||
|
output: ${{ runner.temp }}/nightly-e2e-android.apk
|
||||||
|
log-path: artifacts/android/build.log
|
||||||
|
|
||||||
|
- name: Install Android development client
|
||||||
|
run: |
|
||||||
|
adb -s emulator-5554 install -r "$RUNNER_TEMP/nightly-e2e-android.apk" \
|
||||||
|
2>&1 | tee -a artifacts/android/build.log
|
||||||
|
|
||||||
|
- name: Run Android Maestro suite
|
||||||
|
run: .github/scripts/run-nightly-e2e.sh android emulator-5554
|
||||||
|
|
||||||
|
- name: Capture emulator crash diagnostics
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "=== Emulator process status ==="
|
||||||
|
pgrep -fa "emulator.*nightly-e2e" || echo "Emulator process not found"
|
||||||
|
echo "=== Emulator exit status ==="
|
||||||
|
grep "Emulator exited" artifacts/android/emulator.log || echo "No emulator exit status recorded"
|
||||||
|
echo "=== OOM killer check (kernel) ==="
|
||||||
|
oom_lines=$(sudo dmesg 2>/dev/null | grep -iE "oom|killed process|out of memory" | tail -20)
|
||||||
|
echo "${oom_lines:-No kernel OOM evidence found (or dmesg unavailable)}"
|
||||||
|
echo "=== systemd-oomd check ==="
|
||||||
|
oomd_lines=$(journalctl -u systemd-oomd --no-pager 2>/dev/null | tail -20)
|
||||||
|
echo "${oomd_lines:-No systemd-oomd journal entries (or journalctl unavailable)}"
|
||||||
|
echo "=== journal kernel tail ==="
|
||||||
|
journalctl -k --no-pager 2>/dev/null | tail -30 || echo "journalctl -k unavailable"
|
||||||
|
echo "=== Emulator crash database ==="
|
||||||
|
ls -la /tmp/android-runner/emu-crash-*.db 2>/dev/null || echo "No crash database found"
|
||||||
|
} > artifacts/android/emulator-diagnostics.log 2>&1
|
||||||
|
|
||||||
|
- name: Clean up Android services and emulator
|
||||||
|
if: always()
|
||||||
|
run: .github/scripts/cleanup-nightly-e2e.sh android emulator-5554
|
||||||
|
|
||||||
|
- name: Upload Android E2E artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: nightly-e2e-android-${{ github.run_id }}
|
||||||
|
path: artifacts/android
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
report:
|
||||||
|
name: Report E2E failures
|
||||||
|
needs: [ios, android]
|
||||||
|
if: ${{ always() && github.repository == 'bluesky-social/social-app' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Download iOS artifacts
|
||||||
|
continue-on-error: true
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: nightly-e2e-ios-${{ github.run_id }}
|
||||||
|
path: downloaded-artifacts/ios
|
||||||
|
|
||||||
|
- name: Download Android artifacts
|
||||||
|
continue-on-error: true
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: nightly-e2e-android-${{ github.run_id }}
|
||||||
|
path: downloaded-artifacts/android
|
||||||
|
|
||||||
|
- name: Resolve artifact links
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}#artifacts"
|
||||||
|
jq -n --arg run "$run_url" '{ios: $run, android: $run}' > artifact-links.json
|
||||||
|
if gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
|
||||||
|
> artifact-response.json; then
|
||||||
|
jq --arg base "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts" \
|
||||||
|
--arg run "$run_url" \
|
||||||
|
'{
|
||||||
|
ios: (.artifacts | map(select(.name | startswith("nightly-e2e-ios-"))) | first | if . then ($base + "/" + (.id | tostring)) else $run end),
|
||||||
|
android: (.artifacts | map(select(.name | startswith("nightly-e2e-android-"))) | first | if . then ($base + "/" + (.id | tostring)) else $run end)
|
||||||
|
}' artifact-response.json > artifact-links.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Summarize platform results
|
||||||
|
id: summary
|
||||||
|
env:
|
||||||
|
IOS_STATUS: ${{ needs.ios.result }}
|
||||||
|
ANDROID_STATUS: ${{ needs.android.result }}
|
||||||
|
run: |
|
||||||
|
node .github/scripts/summarize-maestro.mjs \
|
||||||
|
--ios-status "$IOS_STATUS" \
|
||||||
|
--android-status "$ANDROID_STATUS" \
|
||||||
|
--ios-root downloaded-artifacts/ios \
|
||||||
|
--android-root downloaded-artifacts/android \
|
||||||
|
--artifact-urls artifact-links.json \
|
||||||
|
--sha "$GITHUB_SHA" \
|
||||||
|
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||||
|
--commit-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}" \
|
||||||
|
> e2e-summary.json
|
||||||
|
echo "notify=$(jq -r .notify e2e-summary.json)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "payload=$(jq -c .payload e2e-summary.json)" >> "$GITHUB_OUTPUT"
|
||||||
|
jq -r .githubSummary e2e-summary.json >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- name: Notify Slack of E2E failures
|
||||||
|
if: steps.summary.outputs.notify == 'true'
|
||||||
|
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.E2E_FAILURES_SLACK_WEBHOOK }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: ${{ steps.summary.outputs.payload }}
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
|
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
|
||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
- name: Install node
|
- name: Install node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: 🔧 Setup Node
|
- name: 🔧 Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|||||||
@@ -19,8 +19,47 @@ concurrency:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Populate this from main so every PR can restore the same trusted baseline.
|
||||||
|
webpack-analyzer-base:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
|
- name: 🔧 Setup Node
|
||||||
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
|
with:
|
||||||
|
node-version-file: package.json
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: ⬇️ Get base stats from cache
|
||||||
|
id: get-base-stats
|
||||||
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
|
with:
|
||||||
|
path: stats.json
|
||||||
|
key: stats-base-main-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: 🔦 Generate stats file for base commit
|
||||||
|
if: ${{ !steps.get-base-stats.outputs.cache-hit }}
|
||||||
|
run: |
|
||||||
|
pnpm install
|
||||||
|
pnpm intl:build
|
||||||
|
pnpm generate-webpack-stats-file
|
||||||
|
|
||||||
|
- name: ⬆️ Save base stats to cache
|
||||||
|
if: ${{ !steps.get-base-stats.outputs.cache-hit }}
|
||||||
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
|
with:
|
||||||
|
path: stats.json
|
||||||
|
key: stats-base-main-${{ github.sha }}
|
||||||
|
|
||||||
webpack-analyzer:
|
webpack-analyzer:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}}
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request'}}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -34,7 +73,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: 🔧 Setup Node
|
- name: 🔧 Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -71,15 +110,18 @@ jobs:
|
|||||||
|
|
||||||
- name: ⬇️ Get base stats from cache
|
- name: ⬇️ Get base stats from cache
|
||||||
id: get-base-stats
|
id: get-base-stats
|
||||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
# Restore-only prevents PR-scoped fallback builds from creating caches.
|
||||||
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: stats-base.json
|
path: stats.json
|
||||||
key: stats-base-${{ steps.base-commit.outputs.base-commit }}
|
key: stats-base-main-${{ steps.base-commit.outputs.base-commit }}
|
||||||
|
|
||||||
- name: Restore to base commit
|
- name: Restore to base commit
|
||||||
if: ${{ !steps.get-base-stats.outputs.cache-hit }}
|
if: ${{ !steps.get-base-stats.outputs.cache-hit }}
|
||||||
|
env:
|
||||||
|
BASE_COMMIT: ${{ steps.base-commit.outputs.base-commit }}
|
||||||
run: |
|
run: |
|
||||||
git reset HEAD~
|
git reset "$BASE_COMMIT"
|
||||||
git restore .
|
git restore .
|
||||||
|
|
||||||
- name: 🔦 Generate stats file from base commit
|
- name: 🔦 Generate stats file from base commit
|
||||||
@@ -88,13 +130,12 @@ jobs:
|
|||||||
pnpm install
|
pnpm install
|
||||||
pnpm intl:build
|
pnpm intl:build
|
||||||
pnpm generate-webpack-stats-file
|
pnpm generate-webpack-stats-file
|
||||||
mv stats.json stats-base.json
|
|
||||||
|
|
||||||
- name: % Get diff
|
- name: % Get diff
|
||||||
id: get-diff
|
id: get-diff
|
||||||
uses: NejcZdovc/bundle-size-diff@5321de41d2d62a7b0f4d6e60f59d1280a0034160 # v1.1.0
|
uses: NejcZdovc/bundle-size-diff@5321de41d2d62a7b0f4d6e60f59d1280a0034160 # v1.1.0
|
||||||
with:
|
with:
|
||||||
base_path: "stats-base.json"
|
base_path: "stats.json"
|
||||||
pr_path: "../stats-new.json"
|
pr_path: "../stats-new.json"
|
||||||
excluded_assets: "(.+).chunk.js|(.+).js.map|(.+).json|(.+).png|(.+).svg|(.+).webp|(.+).jpg|(.+).ico"
|
excluded_assets: "(.+).chunk.js|(.+).js.map|(.+).json|(.+).png|(.+).svg|(.+).webp|(.+).jpg|(.+).ico"
|
||||||
|
|
||||||
@@ -127,7 +168,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: 🔧 Setup Node
|
- name: 🔧 Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: Install node
|
- name: Install node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: package.json
|
node-version-file: package.json
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "e2eRefreshHome"
|
id: "e2eRefreshHome"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "replyBtn"
|
id: "replyBtn"
|
||||||
|
# Wait for the composer to fully open before typing. Tapping replyBtn right
|
||||||
|
# after the previous publish can race the closing composer on Android.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply text only"
|
- inputText: "Reply text only"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
@@ -51,6 +57,11 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "composeFAB"
|
id: "composeFAB"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "replyBtn"
|
id: "replyBtn"
|
||||||
|
# Wait for the composer to fully open before typing.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply with an image"
|
- inputText: "Reply with an image"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "openMediaBtn"
|
id: "openMediaBtn"
|
||||||
@@ -63,6 +74,11 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "composeFAB"
|
id: "composeFAB"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "replyBtn"
|
id: "replyBtn"
|
||||||
|
# Wait for the composer to fully open before typing.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply with a https://example.com link card"
|
- inputText: "Reply with a https://example.com link card"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
|
|||||||
@@ -29,51 +29,88 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "homeScreenFeedTabs-selector-1"
|
id: "homeScreenFeedTabs-selector-1"
|
||||||
text: "alice-favs"
|
text: "alice-favs"
|
||||||
|
|
||||||
# Set alice-favs first
|
# Reordering feeds is driven by a drag on the feed-drag-handle. Maestro cannot
|
||||||
- tapOn: "Open drawer menu"
|
# activate the RNGH Pan gesture from a synthetic swipe on Android (proven
|
||||||
- tapOn:
|
# twice - coordinate swipes never register the pan), so the reorder
|
||||||
id: "menuItemButton-Feeds"
|
# verification below runs on iOS only. If Android drag coverage is needed,
|
||||||
- tapOn:
|
# revisit with the SavedFeedsA11y move buttons rather than a swipe.
|
||||||
id: "editFeedsBtn"
|
- runFlow:
|
||||||
- swipe:
|
when:
|
||||||
label: "Drag feed down"
|
platform: iOS
|
||||||
from:
|
commands:
|
||||||
id: "feed-drag-handle"
|
# Set alice-favs first
|
||||||
direction: "DOWN"
|
- tapOn: "Open drawer menu"
|
||||||
duration: 1000
|
- tapOn:
|
||||||
- tapOn:
|
id: "menuItemButton-Feeds"
|
||||||
label: "Save button"
|
- tapOn:
|
||||||
id: "saveChangesBtn"
|
id: "editFeedsBtn"
|
||||||
- tapOn: "Go back"
|
- swipe:
|
||||||
- assertVisible:
|
label: "Drag feed down"
|
||||||
id: "homeScreenFeedTabs-selector-0"
|
from:
|
||||||
text: "alice-favs"
|
id: "feed-drag-handle"
|
||||||
- assertVisible:
|
direction: "DOWN"
|
||||||
id: "homeScreenFeedTabs-selector-1"
|
duration: 1000
|
||||||
text: "Following"
|
- assertVisible:
|
||||||
|
id: "saveChangesBtn"
|
||||||
|
enabled: true
|
||||||
|
- tapOn:
|
||||||
|
label: "Save button"
|
||||||
|
id: "saveChangesBtn"
|
||||||
|
- tapOn: "Go back"
|
||||||
|
- assertVisible:
|
||||||
|
id: "homeScreenFeedTabs-selector-0"
|
||||||
|
text: "alice-favs"
|
||||||
|
- assertVisible:
|
||||||
|
id: "homeScreenFeedTabs-selector-1"
|
||||||
|
text: "Following"
|
||||||
|
|
||||||
# Set following first
|
# Set following first
|
||||||
- tapOn: "Open drawer menu"
|
- tapOn: "Open drawer menu"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "menuItemButton-Feeds"
|
id: "menuItemButton-Feeds"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "editFeedsBtn"
|
id: "editFeedsBtn"
|
||||||
- swipe:
|
- swipe:
|
||||||
label: "Drag feed down"
|
label: "Drag feed down"
|
||||||
from:
|
from:
|
||||||
id: "feed-drag-handle"
|
id: "feed-drag-handle"
|
||||||
direction: "DOWN"
|
direction: "DOWN"
|
||||||
duration: 1000
|
duration: 1000
|
||||||
- tapOn:
|
- assertVisible:
|
||||||
label: "Save button"
|
id: "saveChangesBtn"
|
||||||
id: "saveChangesBtn"
|
enabled: true
|
||||||
- tapOn: "Go back"
|
- tapOn:
|
||||||
- assertVisible:
|
label: "Save button"
|
||||||
id: "homeScreenFeedTabs-selector-0"
|
id: "saveChangesBtn"
|
||||||
text: "Following"
|
- tapOn: "Go back"
|
||||||
- assertVisible:
|
- assertVisible:
|
||||||
id: "homeScreenFeedTabs-selector-1"
|
id: "homeScreenFeedTabs-selector-0"
|
||||||
text: "alice-favs"
|
text: "Following"
|
||||||
|
- assertVisible:
|
||||||
|
id: "homeScreenFeedTabs-selector-1"
|
||||||
|
text: "alice-favs"
|
||||||
|
|
||||||
|
# On Android, the reorder path above is skipped. Smoke-test that the feeds
|
||||||
|
# edit screen opens and the pinned feeds render, then return to a valid state.
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- tapOn: "Open drawer menu"
|
||||||
|
- tapOn:
|
||||||
|
id: "menuItemButton-Feeds"
|
||||||
|
- tapOn:
|
||||||
|
id: "editFeedsBtn"
|
||||||
|
- assertVisible: "Following"
|
||||||
|
- assertVisible: "alice-favs"
|
||||||
|
# Two back presses to reach Home: the first pops the saved-feeds editor
|
||||||
|
# back to the Feeds screen, the second pops Feeds back to Home. On iOS the
|
||||||
|
# equivalent path saves changes first (saveChangesBtn calls
|
||||||
|
# navigation.goBack), so a single "Go back" there already lands on Home.
|
||||||
|
# This Android smoke branch never saves, so it needs the extra pop to
|
||||||
|
# leave the screen state on Home, which the shared steps below expect.
|
||||||
|
- tapOn: "Go back"
|
||||||
|
- tapOn: "Go back"
|
||||||
|
|
||||||
# Remove following
|
# Remove following
|
||||||
- tapOn: "Open drawer menu"
|
- tapOn: "Open drawer menu"
|
||||||
|
|||||||
@@ -15,6 +15,24 @@ appId: xyz.blueskyweb.app
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
id: "e2eStartOnboarding"
|
id: "e2eStartOnboarding"
|
||||||
- tapOn: "Open avatar creator"
|
- tapOn: "Open avatar creator"
|
||||||
|
# The avatar-creator bottom sheet (Dialog.Inner, non-scrollable) opens only
|
||||||
|
# half-expanded on the short E2E emulator (720x1600), a ~220px sliver with the
|
||||||
|
# emoji grid below the fold. It is NOT a scroll view, so scrollUntilVisible's
|
||||||
|
# swipe grabs the sheet's own drag gesture and flings it closed. Instead, drag
|
||||||
|
# the sheet upward to expand it to full height, which brings the picker into
|
||||||
|
# view. iOS opens the sheet fully already, so this is Android-only.
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- swipe:
|
||||||
|
label: "Drag the bottom sheet up to expand it"
|
||||||
|
start: "50%, 90%"
|
||||||
|
end: "50%, 20%"
|
||||||
|
duration: 600
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible: "Select an emoji"
|
||||||
|
timeout: 10000
|
||||||
- tapOn: "Select the zap emoji as your avatar"
|
- tapOn: "Select the zap emoji as your avatar"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
label: "Tap on yellow"
|
label: "Tap on yellow"
|
||||||
@@ -22,6 +40,20 @@ appId: xyz.blueskyweb.app
|
|||||||
- tapOn: "Done"
|
- tapOn: "Done"
|
||||||
- waitForAnimationToEnd
|
- waitForAnimationToEnd
|
||||||
- tapOn: "Select an avatar"
|
- tapOn: "Select an avatar"
|
||||||
|
# Reopening the creator sheet lands on the same half-expanded sliver on
|
||||||
|
# Android, so expand it again before reaching for the emoji grid. No-op on iOS.
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
- swipe:
|
||||||
|
label: "Drag the bottom sheet up to expand it"
|
||||||
|
start: "50%, 90%"
|
||||||
|
end: "50%, 20%"
|
||||||
|
duration: 600
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible: "Select an emoji"
|
||||||
|
timeout: 10000
|
||||||
- tapOn: "Select the atom emoji as your avatar"
|
- tapOn: "Select the atom emoji as your avatar"
|
||||||
- tapOn: "Done"
|
- tapOn: "Done"
|
||||||
- waitForAnimationToEnd
|
- waitForAnimationToEnd
|
||||||
|
|||||||
@@ -16,13 +16,28 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "e2eStartOnboarding"
|
id: "e2eStartOnboarding"
|
||||||
- tapOn: "Select an avatar"
|
- tapOn: "Select an avatar"
|
||||||
- waitForAnimationToEnd
|
- waitForAnimationToEnd
|
||||||
- assertVisible: "Photos"
|
- runFlow:
|
||||||
- assertVisible: "Collections"
|
when:
|
||||||
- tapOn:
|
platform: iOS
|
||||||
point: "50%,22%"
|
commands:
|
||||||
- waitForAnimationToEnd
|
- assertVisible: "Photos"
|
||||||
- tapOn: "Done"
|
- assertVisible: "Collections"
|
||||||
- waitForAnimationToEnd
|
- tapOn:
|
||||||
|
point: "50%,22%"
|
||||||
|
- waitForAnimationToEnd
|
||||||
|
- tapOn: "Done"
|
||||||
|
- waitForAnimationToEnd
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
platform: Android
|
||||||
|
commands:
|
||||||
|
# The system photo picker opened here shows MediaStore photos, which
|
||||||
|
# the e2e run doesn't seed (media is seeded into app-scoped storage for
|
||||||
|
# the composer's mocked picker instead). With no photo to pick, dismiss
|
||||||
|
# the picker and continue - onContinue falls back to the generated
|
||||||
|
# placeholder avatar, and nothing later in the flow depends on the image.
|
||||||
|
- back
|
||||||
|
- waitForAnimationToEnd
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "onboardingContinue"
|
id: "onboardingContinue"
|
||||||
- assertVisible: "What are your interests?"
|
- assertVisible: "What are your interests?"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "editProfileSaveBtn"
|
id: "editProfileSaveBtn"
|
||||||
- assertNotVisible:
|
- assertNotVisible:
|
||||||
id: "editProfileModal"
|
id: "editProfileModal"
|
||||||
- assertVisible: "Alicia"
|
- assertVisible: ".*Alicia.*"
|
||||||
- assertVisible: "One cool hacker"
|
- assertVisible: "One cool hacker"
|
||||||
|
|
||||||
# Remove display name and description via the edit profile modal
|
# Remove display name and description via the edit profile modal
|
||||||
@@ -64,7 +64,10 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "editProfileSaveBtn"
|
id: "editProfileSaveBtn"
|
||||||
- assertNotVisible:
|
- assertNotVisible:
|
||||||
id: "editProfileModal"
|
id: "editProfileModal"
|
||||||
- assertVisible: "alice.test"
|
# The display-name node renders the handle as a Text with a nested badge View
|
||||||
|
# once the display name is cleared, so the a11y text is not the bare handle
|
||||||
|
# string on Android. Match it as a substring instead.
|
||||||
|
- assertVisible: ".*alice\\.test.*"
|
||||||
- assertNotVisible: "One cool hacker"
|
- assertNotVisible: "One cool hacker"
|
||||||
|
|
||||||
# Set avi and banner via the edit profile modal
|
# Set avi and banner via the edit profile modal
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: "Send report to Dev-env Moderation"
|
text: "Send report to Dev-env Moderation"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: "Send report to Dev-env Moderation"
|
text: "Send report to Dev-env Moderation"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -39,5 +39,7 @@ appId: xyz.blueskyweb.app
|
|||||||
text: Your report will be sent to Dev-env Moderation.*
|
text: Your report will be sent to Dev-env Moderation.*
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -29,5 +29,7 @@ appId: xyz.blueskyweb.app
|
|||||||
- hideKeyboard
|
- hideKeyboard
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "report:submit"
|
id: "report:submit"
|
||||||
- assertNotVisible:
|
- extendedWaitUntil:
|
||||||
id: "report:dialog"
|
notVisible:
|
||||||
|
id: "report:dialog"
|
||||||
|
timeout: 20000
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ appId: xyz.blueskyweb.app
|
|||||||
- inputText: "Test thread"
|
- inputText: "Test thread"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
|
# Wait for the composer to close and the home feed to settle before signing
|
||||||
|
# out. Without a settle guard the next action can race the closing composer.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composeFAB"
|
||||||
|
timeout: 10000
|
||||||
|
|
||||||
# Login, reply to the thread, and log out
|
# Login, reply to the thread, and log out
|
||||||
- tapOn:
|
- tapOn:
|
||||||
@@ -31,9 +37,19 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "viewHeaderHomeFeedPrefsBtn"
|
id: "viewHeaderHomeFeedPrefsBtn"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "replyBtn"
|
id: "replyBtn"
|
||||||
|
# Wait for the composer to fully open before typing.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply 1"
|
- inputText: "Reply 1"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
|
# Wait for the composer to close before signing out.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composeFAB"
|
||||||
|
timeout: 10000
|
||||||
|
|
||||||
# Login, confirm notification exists, mute thread, and log out
|
# Login, confirm notification exists, mute thread, and log out
|
||||||
- tapOn:
|
- tapOn:
|
||||||
@@ -45,10 +61,8 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "viewHeaderHomeFeedPrefsBtn"
|
id: "viewHeaderHomeFeedPrefsBtn"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "bottomBarNotificationsBtn"
|
id: "bottomBarNotificationsBtn"
|
||||||
- assertVisible:
|
- assertVisible: ".*Reply 1.*"
|
||||||
id: "feedItem-by-bob.test"
|
- tapOn: ".*Reply 1.*"
|
||||||
- tapOn:
|
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "postDropdownBtn"
|
id: "postDropdownBtn"
|
||||||
childOf:
|
childOf:
|
||||||
@@ -67,16 +81,78 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "bottomBarProfileBtn"
|
id: "bottomBarProfileBtn"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "profilePager-selector-1"
|
id: "profilePager-selector-1"
|
||||||
- tapOn:
|
# Both replies target the thread root ("Test thread" by alice), which sits at
|
||||||
id: "replyBtn"
|
# the top of bob's Replies tab. That tab renders each post in the thread with
|
||||||
|
# its own replyBtn, so scope the tap to the root post's card
|
||||||
|
# (feedItem-by-alice.test) rather than relying on which replyBtn Maestro picks
|
||||||
|
# first. This keeps both reply taps deterministic regardless of list order or
|
||||||
|
# how many posts have rendered.
|
||||||
|
#
|
||||||
|
# Even with the close-gating below, the replyBtn tap can land on a recycled list
|
||||||
|
# row while the author feed re-renders after a publish, and be swallowed so the
|
||||||
|
# composer never opens. Wrapping the tap + open-wait in retry makes opening the
|
||||||
|
# composer idempotent: a swallowed tap just re-taps until the publish button
|
||||||
|
# appears. A first-try success does not retry.
|
||||||
|
- retry:
|
||||||
|
maxRetries: 3
|
||||||
|
commands:
|
||||||
|
- tapOn:
|
||||||
|
id: "replyBtn"
|
||||||
|
childOf:
|
||||||
|
id: "feedItem-by-alice.test"
|
||||||
|
# Wait for the composer to fully open before typing.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply 2"
|
- inputText: "Reply 2"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
- tapOn:
|
# Wait for the composer to actually close before opening it again. replyBtn
|
||||||
id: "replyBtn"
|
# stays in the accessibility tree behind the open composer sheet, so waiting on
|
||||||
|
# its visibility returns immediately and does not gate on the close animation or
|
||||||
|
# the author-feed re-render that follows a post - the next replyBtn tap then
|
||||||
|
# fires mid-transition and is swallowed, so the composer never opens. Gate on
|
||||||
|
# the publish button disappearing (the composer is gone), then confirm the
|
||||||
|
# reply button underneath is back and let animations settle.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
notVisible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 15000
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "replyBtn"
|
||||||
|
timeout: 10000
|
||||||
|
- waitForAnimationToEnd
|
||||||
|
# As with Reply 2: even after gating on the composer close, this tap can hit a
|
||||||
|
# recycled row during the post-publish feed re-render and be swallowed, so wrap
|
||||||
|
# the open in retry to make it idempotent.
|
||||||
|
- retry:
|
||||||
|
maxRetries: 3
|
||||||
|
commands:
|
||||||
|
- tapOn:
|
||||||
|
id: "replyBtn"
|
||||||
|
childOf:
|
||||||
|
id: "feedItem-by-alice.test"
|
||||||
|
# Wait for the composer to fully open before typing.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 10000
|
||||||
- inputText: "Reply 3"
|
- inputText: "Reply 3"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "composerPublishBtn"
|
id: "composerPublishBtn"
|
||||||
|
# Wait for the composer to actually close before signing out. As above,
|
||||||
|
# replyBtn stays visible behind the sheet, so gate on the publish button
|
||||||
|
# disappearing first, then confirm the reply button underneath has returned.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
notVisible:
|
||||||
|
id: "composerPublishBtn"
|
||||||
|
timeout: 15000
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "replyBtn"
|
||||||
|
timeout: 10000
|
||||||
|
|
||||||
# Login, confirm notifications dont exist, unmute the thread, ~~confirm notifications exist~~
|
# Login, confirm notifications dont exist, unmute the thread, ~~confirm notifications exist~~
|
||||||
# Mute thread behaviour no longer change old notifications after muting/unmuting a thread -sfn
|
# Mute thread behaviour no longer change old notifications after muting/unmuting a thread -sfn
|
||||||
@@ -92,10 +168,7 @@ appId: xyz.blueskyweb.app
|
|||||||
- assertVisible: ".*Reply 1.*"
|
- assertVisible: ".*Reply 1.*"
|
||||||
- assertNotVisible: ".*Reply 2.*"
|
- assertNotVisible: ".*Reply 2.*"
|
||||||
- assertNotVisible: ".*Reply 3.*"
|
- assertNotVisible: ".*Reply 3.*"
|
||||||
- assertVisible:
|
- tapOn: ".*Reply 1.*"
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
|
||||||
id: "feedItem-by-bob.test"
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "postDropdownBtn"
|
id: "postDropdownBtn"
|
||||||
childOf:
|
childOf:
|
||||||
|
|||||||
+17
-13
@@ -9,23 +9,27 @@ appId: xyz.blueskyweb.app
|
|||||||
when:
|
when:
|
||||||
platform: iOS
|
platform: iOS
|
||||||
commands:
|
commands:
|
||||||
- openLink: "exp+bluesky://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081"
|
- extendedWaitUntil:
|
||||||
- runFlow:
|
visible: "http://localhost:8081"
|
||||||
when:
|
timeout: 60000
|
||||||
visible: 'Open in "Bluesky"'
|
- tapOn: "http://localhost:8081"
|
||||||
commands:
|
|
||||||
- tapOn: Open
|
|
||||||
- runFlow:
|
- runFlow:
|
||||||
when:
|
when:
|
||||||
platform: Android
|
platform: Android
|
||||||
commands:
|
commands:
|
||||||
- tapOn: 'http://localhost:8081'
|
- extendedWaitUntil:
|
||||||
- runFlow:
|
visible: "http://10.0.2.2:8081"
|
||||||
label: "Dismiss Expo dev menu"
|
timeout: 60000
|
||||||
when:
|
- tapOn: "http://10.0.2.2:8081"
|
||||||
visible: "Continue"
|
- extendedWaitUntil:
|
||||||
commands:
|
visible: "Continue"
|
||||||
- back
|
timeout: 180000
|
||||||
|
- tapOn: "Continue"
|
||||||
|
- back
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: e2eProxyHeaderInput
|
||||||
|
timeout: 180000
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: e2eProxyHeaderInput
|
id: e2eProxyHeaderInput
|
||||||
- inputText: ${output.result}
|
- inputText: ${output.result}
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ func serve(cctx *cli.Context) error {
|
|||||||
e.GET("/settings/interests", server.WebGenericNoindex)
|
e.GET("/settings/interests", server.WebGenericNoindex)
|
||||||
e.GET("/settings/about", server.WebGenericNoindex)
|
e.GET("/settings/about", server.WebGenericNoindex)
|
||||||
e.GET("/settings/notifications", server.WebGenericNoindex)
|
e.GET("/settings/notifications", server.WebGenericNoindex)
|
||||||
|
e.GET("/settings/notifications/activity", server.WebGenericNoindex)
|
||||||
e.GET("/sys/debug", server.WebGenericNoindex)
|
e.GET("/sys/debug", server.WebGenericNoindex)
|
||||||
e.GET("/sys/debug-mod", server.WebGenericNoindex)
|
e.GET("/sys/debug-mod", server.WebGenericNoindex)
|
||||||
e.GET("/sys/log", server.WebGenericNoindex)
|
e.GET("/sys/log", server.WebGenericNoindex)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh node ./mock-server.ts"
|
"start": "NODE_ENV=development ./dev-infra/with-test-redis-and-db.sh node ./mock-server.ts",
|
||||||
|
"start:external": "NODE_ENV=development PGPORT=5433 PGHOST=localhost PGUSER=pg PGPASSWORD=password PGDATABASE=postgres DB_POSTGRES_URL=postgresql://pg:password@127.0.0.1:5433/postgres REDIS_HOST=127.0.0.1:6380 node ./mock-server.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.20.22",
|
"@atproto/api": "^0.20.22",
|
||||||
|
|||||||
@@ -29,6 +29,49 @@ adb reverse tcp:3000 tcp:3000
|
|||||||
- In a second tab, run `pnpm e2e:build`
|
- In a second tab, run `pnpm e2e:build`
|
||||||
- In a third tab, run `pnpm e2e:run __e2e__`
|
- In a third tab, run `pnpm e2e:run __e2e__`
|
||||||
|
|
||||||
|
## Nightly Maestro CI
|
||||||
|
|
||||||
|
The `Nightly Maestro E2E` GitHub Actions workflow runs every day at 04:00 UTC
|
||||||
|
and can also be started from the Actions tab with **Run workflow**. It runs iOS
|
||||||
|
and Android concurrently, but each platform runs all of `__e2e__/config.yml`
|
||||||
|
sequentially on one explicitly selected simulator or emulator. The flows share a
|
||||||
|
stateful mock-server manager, so the suite must not be sharded.
|
||||||
|
|
||||||
|
The jobs run Maestro CLI 2.6.1 locally on GitHub Actions; Maestro Cloud is not
|
||||||
|
used. iOS runs on `macos-26-xlarge` with Xcode 26.4. Android runs on
|
||||||
|
`Linux-x64-32core`. Both use Java 17 and the Node and pnpm versions declared in
|
||||||
|
`package.json`. The iOS job selects an iPhone 17 simulator running iOS 26.5;
|
||||||
|
Android directly provisions and boots a Pixel 6 AVD with the API 35 Google APIs
|
||||||
|
x86_64 image using the Android SDK command-line tools.
|
||||||
|
Both development clients use the `e2e` EAS profile and the same reusable local
|
||||||
|
EAS build action as the release build workflows; the resulting simulator app
|
||||||
|
and APK are installed directly on the selected devices.
|
||||||
|
|
||||||
|
The mock-server manager listens on host port 1986 and creates test services on
|
||||||
|
port 3000. Metro listens on 8081. Android reverses ports 3000 and 8081 into the
|
||||||
|
emulator; port 1986 remains host-side because Maestro JavaScript calls it from
|
||||||
|
the runner. Android uses the existing Docker Compose PostgreSQL 14 and Redis 7
|
||||||
|
services on ports 5433 and 6380. GitHub-hosted macOS cannot run nested Docker
|
||||||
|
virtualization, so iOS provisions ephemeral native PostgreSQL 14.x and Redis
|
||||||
|
7.4.7 on those same ports and starts `pnpm --dir dev-env start:external`.
|
||||||
|
|
||||||
|
Each platform uploads a `nightly-e2e-<platform>-<run-id>` artifact for 14 days.
|
||||||
|
It contains JUnit at `report.xml`, Maestro screenshots, videos, command metadata
|
||||||
|
and `maestro.log` under `maestro/`, plus Metro, native build, mock-server, service,
|
||||||
|
dependency, and translation logs. The workflow always uploads what was captured,
|
||||||
|
including when setup or the native build fails before Maestro starts.
|
||||||
|
|
||||||
|
Add the repository secret `E2E_FAILURES_SLACK_WEBHOOK` before enabling the
|
||||||
|
schedule. The aggregation job runs even when either platform fails and posts one
|
||||||
|
detailed Slack notification containing both job statuses, failed flow details or
|
||||||
|
the failed setup phase, the commit and workflow links, and links to both artifact
|
||||||
|
sets. Successful runs do not post to Slack.
|
||||||
|
|
||||||
|
Before relying on the schedule, manually dispatch the workflow and verify both
|
||||||
|
platforms against live Metro and `dev-env`, Android localhost routing, artifact
|
||||||
|
uploads on success and failure, one Slack message for a forced failure, and no
|
||||||
|
Slack message for an all-green run.
|
||||||
|
|
||||||
## Using Flashlight for Performance Testing
|
## Using Flashlight for Performance Testing
|
||||||
1. Make sure Maestro is installed (optional: only for automated testing) by following the instructions above
|
1. Make sure Maestro is installed (optional: only for automated testing) by following the instructions above
|
||||||
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
|
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
|
||||||
|
|||||||
@@ -21,6 +21,17 @@
|
|||||||
"EXPO_PUBLIC_ENV": "production"
|
"EXPO_PUBLIC_ENV": "production"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"e2e": {
|
||||||
|
"extends": "development",
|
||||||
|
"android": {
|
||||||
|
"buildType": "apk"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"EXPO_PUBLIC_ENV": "e2e",
|
||||||
|
"NODE_ENV": "test",
|
||||||
|
"RN_SRC_EXT": "e2e.ts,e2e.tsx"
|
||||||
|
}
|
||||||
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"extends": "base",
|
"extends": "base",
|
||||||
"distribution": "internal",
|
"distribution": "internal",
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ function BottomSheetNativeComponentInner({
|
|||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const cornerRadius = rest.cornerRadius ?? 0
|
const cornerRadius = rest.cornerRadius ?? 0
|
||||||
const {height: screenHeight} = useWindowDimensions()
|
const {height: screenHeight} = useWindowDimensions()
|
||||||
|
const isHeightConstrained = maxHeight != null || rest.fullHeight === true
|
||||||
|
|
||||||
// sigh... on older Android versions, screenHeight does not include safe area insets
|
// sigh... on older Android versions, screenHeight does not include safe area insets
|
||||||
// on newer Androids + iOS, it does. we need to find the inner bit + the bottom inset
|
// on newer Androids + iOS, it does. we need to find the inner bit + the bottom inset
|
||||||
@@ -182,7 +183,7 @@ function BottomSheetNativeComponentInner({
|
|||||||
]}>
|
]}>
|
||||||
<View
|
<View
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
style={maxHeight == null ? undefined : {flex: 1}}>
|
style={isHeightConstrained ? {flex: 1} : undefined}>
|
||||||
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
|
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
|
import {Component} from 'react'
|
||||||
|
|
||||||
import {type BottomSheetViewProps} from './BottomSheet.types'
|
import {type BottomSheetViewProps} from './BottomSheet.types'
|
||||||
|
|
||||||
export function BottomSheetNativeComponent(_: BottomSheetViewProps) {
|
export class BottomSheetNativeComponent extends Component<BottomSheetViewProps> {
|
||||||
throw new Error('BottomSheetNativeComponent is not available on web')
|
/*
|
||||||
|
* Native sheets do not exist on web; there is nothing to dismiss.
|
||||||
|
*/
|
||||||
|
static dismissAll = async () => {}
|
||||||
|
|
||||||
|
render(): never {
|
||||||
|
throw new Error('BottomSheetNativeComponent is not available on web')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfo(): ReferrerInfo | null {
|
/*
|
||||||
|
* Promise-returning for parity with Android, whose native referrer API only
|
||||||
|
* exposes a promise.
|
||||||
|
*/
|
||||||
|
export function getReferrerInfo(): Promise<ReferrerInfo | null> {
|
||||||
|
return Promise.resolve(getReferrerInfoSync())
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReferrerInfoSync(): ReferrerInfo | null {
|
||||||
const referrer = SharedPrefs.getString('referrer')
|
const referrer = SharedPrefs.getString('referrer')
|
||||||
if (referrer) {
|
if (referrer) {
|
||||||
SharedPrefs.removeValue('referrer')
|
SharedPrefs.removeValue('referrer')
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfo(): ReferrerInfo | null {
|
export function getReferrerInfo(): Promise<ReferrerInfo | null> {
|
||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfo(): ReferrerInfo | null {
|
/*
|
||||||
|
* Promise-returning for parity with Android, whose native referrer API only
|
||||||
|
* exposes a promise.
|
||||||
|
*/
|
||||||
|
export function getReferrerInfo(): Promise<ReferrerInfo | null> {
|
||||||
|
return Promise.resolve(getReferrerInfoSync())
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReferrerInfoSync(): ReferrerInfo | null {
|
||||||
if (
|
if (
|
||||||
Platform.OS === 'web' &&
|
Platform.OS === 'web' &&
|
||||||
// for ssr
|
// for ssr
|
||||||
|
|||||||
@@ -1617,14 +1617,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/view/com/composer/text-input/TextInput.tsx": {
|
|
||||||
"typescript/no-floating-promises": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"typescript/no-misused-promises": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/view/com/composer/text-input/TextInput.web.tsx": {
|
"src/view/com/composer/text-input/TextInput.web.tsx": {
|
||||||
"typescript/no-misused-promises": {
|
"typescript/no-misused-promises": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|||||||
+9
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky.app",
|
"name": "bsky.app",
|
||||||
"version": "1.128.0",
|
"version": "1.129.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.18.0"
|
"node": ">=24.18.0"
|
||||||
@@ -62,7 +62,10 @@
|
|||||||
"lint": "oxlint --quiet src modules",
|
"lint": "oxlint --quiet src modules",
|
||||||
"lint-native": "swiftlint ./modules && ktlint ./modules",
|
"lint-native": "swiftlint ./modules && ktlint ./modules",
|
||||||
"lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules",
|
"lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules",
|
||||||
"typecheck": "tsgo --project ./tsconfig.check.json",
|
"typecheck": "pnpm run typecheck:ios && pnpm run typecheck:android && pnpm run typecheck:web",
|
||||||
|
"typecheck:ios": "tsc --project ./tsconfig.check.ios.json",
|
||||||
|
"typecheck:android": "tsc --project ./tsconfig.check.android.json",
|
||||||
|
"typecheck:web": "tsc --project ./tsconfig.check.web.json",
|
||||||
"e2e:mock-server": "cd dev-env && pnpm start",
|
"e2e:mock-server": "cd dev-env && pnpm start",
|
||||||
"e2e:build": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
|
"e2e:build": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
|
||||||
"e2e:build-android": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:android",
|
"e2e:build-android": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:android",
|
||||||
@@ -98,7 +101,7 @@
|
|||||||
"@atproto/syntax": "0.7.2",
|
"@atproto/syntax": "0.7.2",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@bsky.app/alf": "^0.1.14",
|
"@bsky.app/alf": "^0.1.15",
|
||||||
"@bsky.app/expo-dynamic-app-icon": "^1.8.5",
|
"@bsky.app/expo-dynamic-app-icon": "^1.8.5",
|
||||||
"@bsky.app/expo-guess-language": "^0.2.8",
|
"@bsky.app/expo-guess-language": "^0.2.8",
|
||||||
"@bsky.app/expo-image-crop-tool": "^0.5.1",
|
"@bsky.app/expo-image-crop-tool": "^0.5.1",
|
||||||
@@ -158,6 +161,7 @@
|
|||||||
"expo": "54.0.34",
|
"expo": "54.0.34",
|
||||||
"expo-age-range": "0.2.18",
|
"expo-age-range": "0.2.18",
|
||||||
"expo-application": "~7.0.8",
|
"expo-application": "~7.0.8",
|
||||||
|
"expo-asset": "~12.0.13",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
"expo-build-properties": "~1.0.10",
|
"expo-build-properties": "~1.0.10",
|
||||||
"expo-camera": "~17.0.10",
|
"expo-camera": "~17.0.10",
|
||||||
@@ -270,7 +274,7 @@
|
|||||||
"@types/psl": "1.1.1",
|
"@types/psl": "1.1.1",
|
||||||
"@types/react": "^19.1.17",
|
"@types/react": "^19.1.17",
|
||||||
"@types/react-dom": "^19.1.11",
|
"@types/react-dom": "^19.1.11",
|
||||||
"@typescript/native-preview": "^7.0.0-dev.20260428.1",
|
"@typescript/native": "npm:typescript@^7.0.2",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"babel-plugin-module-resolver": "^5.0.2",
|
"babel-plugin-module-resolver": "^5.0.2",
|
||||||
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
||||||
@@ -295,7 +299,7 @@
|
|||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"svgo": "^4.0.2",
|
"svgo": "^4.0.2",
|
||||||
"ts-plugin-sort-import-suggestions": "^1.0.4",
|
"ts-plugin-sort-import-suggestions": "^1.0.4",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
||||||
"webpack-bundle-analyzer": "^4.10.1"
|
"webpack-bundle-analyzer": "^4.10.1"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
|||||||
@@ -55,3 +55,15 @@ index 06829bd00dbded262e1871aaf6db3ae0cfa9d1b1..1b158185a6d7e907aa18ddc806902fcc
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diff --git a/lib/typescript/ReactNativeSVG.web.d.ts b/lib/typescript/ReactNativeSVG.web.d.ts
|
||||||
|
deleted file mode 100644
|
||||||
|
index e2001975fa97398fb159d2f85f45d067200f575b..0000000000000000000000000000000000000000
|
||||||
|
diff --git a/lib/typescript/ReactNativeSVG.web.d.ts.map b/lib/typescript/ReactNativeSVG.web.d.ts.map
|
||||||
|
deleted file mode 100644
|
||||||
|
index c81af4416af5ee3eaca77b51bfc2952b30d49c8a..0000000000000000000000000000000000000000
|
||||||
|
diff --git a/lib/typescript/elements.web.d.ts b/lib/typescript/elements.web.d.ts
|
||||||
|
deleted file mode 100644
|
||||||
|
index cf4083fbf749b2b002fb649d80b33ac74763847b..0000000000000000000000000000000000000000
|
||||||
|
diff --git a/lib/typescript/elements.web.d.ts.map b/lib/typescript/elements.web.d.ts.map
|
||||||
|
deleted file mode 100644
|
||||||
|
index 29602aa47ad903bc7aaa111462a61dd862c4476d..0000000000000000000000000000000000000000
|
||||||
|
|||||||
Generated
+219
-78
@@ -231,7 +231,7 @@ patchedDependencies:
|
|||||||
react-native-keyboard-controller@1.21.8: ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271
|
react-native-keyboard-controller@1.21.8: ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271
|
||||||
react-native-pager-view@6.8.0: 0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108
|
react-native-pager-view@6.8.0: 0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108
|
||||||
react-native-reanimated@3.19.1: 97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6
|
react-native-reanimated@3.19.1: 97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6
|
||||||
react-native-svg@15.12.1: 31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9
|
react-native-svg@15.12.1: ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310
|
||||||
react-native-uitextview@1.4.0: 62de26caadfc39d1bdff204cdc03a705c0cd343999825e06d8c0c120de06cc99
|
react-native-uitextview@1.4.0: 62de26caadfc39d1bdff204cdc03a705c0cd343999825e06d8c0c120de06cc99
|
||||||
react-native-view-shot@4.0.3: a2457dc30a82cc8c21f371a6f860d9396d450a2a1b4265b1a4ee13bdb24d3268
|
react-native-view-shot@4.0.3: a2457dc30a82cc8c21f371a6f860d9396d450a2a1b4265b1a4ee13bdb24d3268
|
||||||
react-native@0.81.5: 2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194
|
react-native@0.81.5: 2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194
|
||||||
@@ -257,8 +257,8 @@ importers:
|
|||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.4
|
version: 6.0.4
|
||||||
'@bsky.app/alf':
|
'@bsky.app/alf':
|
||||||
specifier: ^0.1.14
|
specifier: ^0.1.15
|
||||||
version: 0.1.14(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 0.1.15(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
'@bsky.app/expo-dynamic-app-icon':
|
'@bsky.app/expo-dynamic-app-icon':
|
||||||
specifier: ^1.8.5
|
specifier: ^1.8.5
|
||||||
version: 1.8.5(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 1.8.5(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -330,10 +330,10 @@ importers:
|
|||||||
version: 9.2.7
|
version: 9.2.7
|
||||||
'@lingui/core':
|
'@lingui/core':
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))
|
version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))
|
||||||
'@lingui/react':
|
'@lingui/react':
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))(react@19.1.0)
|
version: 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))(react@19.1.0)
|
||||||
'@react-native-async-storage/async-storage':
|
'@react-native-async-storage/async-storage':
|
||||||
specifier: 2.2.0
|
specifier: 2.2.0
|
||||||
version: 2.2.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))
|
version: 2.2.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))
|
||||||
@@ -436,6 +436,9 @@ importers:
|
|||||||
expo-application:
|
expo-application:
|
||||||
specifier: ~7.0.8
|
specifier: ~7.0.8
|
||||||
version: 7.0.8(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
|
version: 7.0.8(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
|
||||||
|
expo-asset:
|
||||||
|
specifier: ~12.0.13
|
||||||
|
version: 12.0.13(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
expo-blur:
|
expo-blur:
|
||||||
specifier: ~15.0.8
|
specifier: ~15.0.8
|
||||||
version: 15.0.8(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 15.0.8(expo@54.0.34(@babel/core@7.29.0)(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -642,10 +645,10 @@ importers:
|
|||||||
version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-progress:
|
react-native-progress:
|
||||||
specifier: bluesky-social/react-native-progress
|
specifier: bluesky-social/react-native-progress
|
||||||
version: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
|
version: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
|
||||||
react-native-qrcode-styled:
|
react-native-qrcode-styled:
|
||||||
specifier: ^0.3.3
|
specifier: ^0.3.3
|
||||||
version: 0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 0.3.3(react-native-svg@15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-reanimated:
|
react-native-reanimated:
|
||||||
specifier: 3.19.1
|
specifier: 3.19.1
|
||||||
version: 3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -657,7 +660,7 @@ importers:
|
|||||||
version: 4.24.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 4.24.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-svg:
|
react-native-svg:
|
||||||
specifier: 15.12.1
|
specifier: 15.12.1
|
||||||
version: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-uitextview:
|
react-native-uitextview:
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0(patch_hash=62de26caadfc39d1bdff204cdc03a705c0cd343999825e06d8c0c120de06cc99)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 1.4.0(patch_hash=62de26caadfc39d1bdff204cdc03a705c0cd343999825e06d8c0c120de06cc99)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -696,7 +699,7 @@ importers:
|
|||||||
version: 2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
sonner-native:
|
sonner-native:
|
||||||
specifier: 0.21.0
|
specifier: 0.21.0
|
||||||
version: 0.21.0(patch_hash=4bcd0da0fec32e943460e6e788a9e4adf601b507d2dfd0c8246a6c8c74d8f638)(4e42d15c94c2c56af69d343a007eb893)
|
version: 0.21.0(patch_hash=4bcd0da0fec32e943460e6e788a9e4adf601b507d2dfd0c8246a6c8c74d8f638)(0ed429160839218d22ac63d892397351)
|
||||||
tippy.js:
|
tippy.js:
|
||||||
specifier: ^6.3.7
|
specifier: ^6.3.7
|
||||||
version: 6.3.7
|
version: 6.3.7
|
||||||
@@ -727,10 +730,10 @@ importers:
|
|||||||
version: 4.14.2
|
version: 4.14.2
|
||||||
'@lingui/babel-plugin-lingui-macro':
|
'@lingui/babel-plugin-lingui-macro':
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.5(typescript@6.0.3)
|
version: 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/cli':
|
'@lingui/cli':
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.5(typescript@6.0.3)
|
version: 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@pmmmwh/react-refresh-webpack-plugin':
|
'@pmmmwh/react-refresh-webpack-plugin':
|
||||||
specifier: ^0.5.15
|
specifier: ^0.5.15
|
||||||
version: 0.5.17(react-refresh@0.14.2)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.106.2(postcss@8.5.14)))(webpack@5.106.2(postcss@8.5.14))
|
version: 0.5.17(react-refresh@0.14.2)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.106.2(postcss@8.5.14)))(webpack@5.106.2(postcss@8.5.14))
|
||||||
@@ -767,9 +770,9 @@ importers:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^19.1.11
|
specifier: ^19.1.11
|
||||||
version: 19.1.11(@types/react@19.1.17)
|
version: 19.1.11(@types/react@19.1.17)
|
||||||
'@typescript/native-preview':
|
'@typescript/native':
|
||||||
specifier: ^7.0.0-dev.20260428.1
|
specifier: npm:typescript@^7.0.2
|
||||||
version: 7.0.0-dev.20260512.1
|
version: typescript@7.0.2
|
||||||
babel-jest:
|
babel-jest:
|
||||||
specifier: ^29.7.0
|
specifier: ^29.7.0
|
||||||
version: 29.7.0(@babel/core@7.29.0)
|
version: 29.7.0(@babel/core@7.29.0)
|
||||||
@@ -846,8 +849,8 @@ importers:
|
|||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4
|
version: 1.0.4
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^6.0.2
|
specifier: npm:@typescript/typescript6@^6.0.2
|
||||||
version: 6.0.3
|
version: '@typescript/typescript6@6.0.2'
|
||||||
webpack-bundle-analyzer:
|
webpack-bundle-analyzer:
|
||||||
specifier: ^4.10.1
|
specifier: ^4.10.1
|
||||||
version: 4.10.2
|
version: 4.10.2
|
||||||
@@ -1603,8 +1606,8 @@ packages:
|
|||||||
'@braintree/sanitize-url@6.0.4':
|
'@braintree/sanitize-url@6.0.4':
|
||||||
resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
|
resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
|
||||||
|
|
||||||
'@bsky.app/alf@0.1.14':
|
'@bsky.app/alf@0.1.15':
|
||||||
resolution: {integrity: sha512-c/KK6avyjEnYzhXDsN0rQaTFSbi8BPg4sJCW8aUk8AtKsO34GTaY6FU1adQ2i8xVfe5UIRu9FJjnBoGpuXj21A==}
|
resolution: {integrity: sha512-e6blt+oZ2klv+Cp7u11FeZckoILdXOtUZ11ATERgjZgb4zX3y7rt4e2fI03JkJO31wTvXHG8A1LnM96Az+ndJg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
@@ -3715,51 +3718,128 @@ packages:
|
|||||||
'@types/yargs@17.0.35':
|
'@types/yargs@17.0.35':
|
||||||
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
|
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
|
||||||
|
|
||||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-aix-ppc64@7.0.2':
|
||||||
resolution: {integrity: sha512-l9AJi/TIVMPx5R1c7fxZCSA7eUaHeA0C9Mxdxx/oQJo1K/GtbI3mzYe/SiKNltko1KSdKUmWVhPwxTOS289REg==}
|
resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@typescript/typescript-darwin-arm64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-darwin-x64@7.0.2':
|
||||||
resolution: {integrity: sha512-oABZLQrfB8JN2Ct2CiLK5PyE28Em3sIJlZsAMD45/A2ymtIaa5826dwv8vapE5Wjp54ao0LXxCSuKFm1A8zzCQ==}
|
resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-freebsd-arm64@7.0.2':
|
||||||
resolution: {integrity: sha512-xvbwzpTe+5N6bnBI/t9n4zsGzXxz3V6rVbvDUoJmRLfav5fz+ck0QDkGQGUPrQEEIp0KEzQvx7c+AEZnzdvTQA==}
|
resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-freebsd-x64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-arm64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260512.1':
|
'@typescript/typescript-linux-arm@7.0.2':
|
||||||
resolution: {integrity: sha512-0Hs1Gqa/t9cthoPdqHud1pFGUr9DgJivBTjwquTUh8jt/6PI2bQxoMNZLiN/bhqeDFDTzdxoMBfCaytsTMcXqw==}
|
resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-linux-loong64@7.0.2':
|
||||||
resolution: {integrity: sha512-qr5h6FPo74bN/U+EwRuayBhUbxaji8xzFbIbhMOA2oYSc/qozp5ia2g1+9xGw67MXxPPw/IPT+UGvrNK7K1NeQ==}
|
resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-mips64el@7.0.2':
|
||||||
|
resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-ppc64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-riscv64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-s390x@7.0.2':
|
||||||
|
resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-x64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-netbsd-arm64@7.0.2':
|
||||||
resolution: {integrity: sha512-meNWxhNEfaqos2U0JXvfxWvy4JWrKE9fZepCndDZi+t04X+AIiLYp5s6crWnKP67nzVAzMNgTAc8mu8CnGM+/A==}
|
resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-netbsd-x64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-openbsd-arm64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-openbsd-x64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@typescript/typescript-sunos-x64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@typescript/typescript-win32-arm64@7.0.2':
|
||||||
|
resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-win32-x64@7.0.2':
|
||||||
resolution: {integrity: sha512-Hp6vBnxJSKEEAVWgIoWMmfqkZXCdkhm6XTivrwgRzBwWfiTVe2ZyZ7byWegIKeNnBbffg/K2KvoM8JAHl059GQ==}
|
resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@typescript/native-preview@7.0.0-dev.20260512.1':
|
'@typescript/typescript6@6.0.2':
|
||||||
resolution: {integrity: sha512-KIzYPGuxZnyiiYkYrozDT94Af2nwbdLXoY1cgGY66RRa9HSEw13RH9WHg8wA8fZhT4wYzF5uF7WY3hz0QhaxGg==}
|
resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==}
|
||||||
engines: {node: '>=16.20.0'}
|
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.1':
|
'@ungap/structured-clone@1.3.1':
|
||||||
@@ -8655,6 +8735,11 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
typescript@7.0.2:
|
||||||
|
resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ua-parser-js@0.7.41:
|
ua-parser-js@0.7.41:
|
||||||
resolution: {integrity: sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==}
|
resolution: {integrity: sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -10069,7 +10154,7 @@ snapshots:
|
|||||||
|
|
||||||
'@braintree/sanitize-url@6.0.4': {}
|
'@braintree/sanitize-url@6.0.4': {}
|
||||||
|
|
||||||
'@bsky.app/alf@0.1.14(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
'@bsky.app/alf@0.1.15(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||||
@@ -10902,19 +10987,19 @@ snapshots:
|
|||||||
|
|
||||||
'@lingui/babel-plugin-extract-messages@5.9.5': {}
|
'@lingui/babel-plugin-extract-messages@5.9.5': {}
|
||||||
|
|
||||||
'@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3)':
|
'@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
'@lingui/conf': 5.9.5(typescript@6.0.3)
|
'@lingui/conf': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))
|
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))
|
||||||
'@lingui/message-utils': 5.9.5
|
'@lingui/message-utils': 5.9.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@lingui/cli@5.9.5(typescript@6.0.3)':
|
'@lingui/cli@5.9.5(@typescript/typescript6@6.0.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/generator': 7.29.1
|
'@babel/generator': 7.29.1
|
||||||
@@ -10922,10 +11007,10 @@ snapshots:
|
|||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
'@lingui/babel-plugin-extract-messages': 5.9.5
|
'@lingui/babel-plugin-extract-messages': 5.9.5
|
||||||
'@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@6.0.3)
|
'@lingui/babel-plugin-lingui-macro': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/conf': 5.9.5(typescript@6.0.3)
|
'@lingui/conf': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))
|
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))
|
||||||
'@lingui/format-po': 5.9.5(typescript@6.0.3)
|
'@lingui/format-po': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/message-utils': 5.9.5
|
'@lingui/message-utils': 5.9.5
|
||||||
chokidar: 3.5.1
|
chokidar: 3.5.1
|
||||||
cli-table: 0.3.11
|
cli-table: 0.3.11
|
||||||
@@ -10948,26 +11033,26 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@lingui/conf@5.9.5(typescript@6.0.3)':
|
'@lingui/conf@5.9.5(@typescript/typescript6@6.0.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
cosmiconfig: 8.3.6(typescript@6.0.3)
|
cosmiconfig: 8.3.6(@typescript/typescript6@6.0.2)
|
||||||
jest-validate: 29.7.0
|
jest-validate: 29.7.0
|
||||||
jiti: 2.7.0
|
jiti: 2.7.0
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@lingui/core@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))':
|
'@lingui/core@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
'@lingui/message-utils': 5.9.5
|
'@lingui/message-utils': 5.9.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@6.0.3)
|
'@lingui/babel-plugin-lingui-macro': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
|
|
||||||
'@lingui/format-po@5.9.5(typescript@6.0.3)':
|
'@lingui/format-po@5.9.5(@typescript/typescript6@6.0.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lingui/conf': 5.9.5(typescript@6.0.3)
|
'@lingui/conf': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
'@lingui/message-utils': 5.9.5
|
'@lingui/message-utils': 5.9.5
|
||||||
date-fns: 3.6.0
|
date-fns: 3.6.0
|
||||||
pofile: 1.1.4
|
pofile: 1.1.4
|
||||||
@@ -10979,13 +11064,13 @@ snapshots:
|
|||||||
'@messageformat/parser': 5.1.1
|
'@messageformat/parser': 5.1.1
|
||||||
js-sha256: 0.10.1
|
js-sha256: 0.10.1
|
||||||
|
|
||||||
'@lingui/react@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))(react@19.1.0)':
|
'@lingui/react@5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(typescript@6.0.3))
|
'@lingui/core': 5.9.5(@lingui/babel-plugin-lingui-macro@5.9.5(@typescript/typescript6@6.0.2))
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@lingui/babel-plugin-lingui-macro': 5.9.5(typescript@6.0.3)
|
'@lingui/babel-plugin-lingui-macro': 5.9.5(@typescript/typescript6@6.0.2)
|
||||||
|
|
||||||
'@messageformat/parser@5.1.1':
|
'@messageformat/parser@5.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12563,36 +12648,69 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/yargs-parser': 21.0.3
|
'@types/yargs-parser': 21.0.3
|
||||||
|
|
||||||
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-aix-ppc64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-darwin-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-darwin-arm64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-linux-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-darwin-x64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-linux-arm@7.0.0-dev.20260512.1':
|
'@typescript/typescript-freebsd-arm64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-linux-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-freebsd-x64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-win32-arm64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-linux-arm64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview-win32-x64@7.0.0-dev.20260512.1':
|
'@typescript/typescript-linux-arm@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript/native-preview@7.0.0-dev.20260512.1':
|
'@typescript/typescript-linux-loong64@7.0.2':
|
||||||
optionalDependencies:
|
optional: true
|
||||||
'@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260512.1
|
|
||||||
'@typescript/native-preview-darwin-x64': 7.0.0-dev.20260512.1
|
'@typescript/typescript-linux-mips64el@7.0.2':
|
||||||
'@typescript/native-preview-linux-arm': 7.0.0-dev.20260512.1
|
optional: true
|
||||||
'@typescript/native-preview-linux-arm64': 7.0.0-dev.20260512.1
|
|
||||||
'@typescript/native-preview-linux-x64': 7.0.0-dev.20260512.1
|
'@typescript/typescript-linux-ppc64@7.0.2':
|
||||||
'@typescript/native-preview-win32-arm64': 7.0.0-dev.20260512.1
|
optional: true
|
||||||
'@typescript/native-preview-win32-x64': 7.0.0-dev.20260512.1
|
|
||||||
|
'@typescript/typescript-linux-riscv64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-s390x@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-linux-x64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-netbsd-arm64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-netbsd-x64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-openbsd-arm64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-openbsd-x64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-sunos-x64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-win32-arm64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript-win32-x64@7.0.2':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/typescript6@6.0.2':
|
||||||
|
dependencies:
|
||||||
|
'@typescript/old': typescript@6.0.3
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.1': {}
|
'@ungap/structured-clone@1.3.1': {}
|
||||||
|
|
||||||
@@ -13434,14 +13552,14 @@ snapshots:
|
|||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
cosmiconfig@8.3.6(typescript@6.0.3):
|
cosmiconfig@8.3.6(@typescript/typescript6@6.0.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
import-fresh: 3.3.1
|
import-fresh: 3.3.1
|
||||||
js-yaml: 4.1.1
|
js-yaml: 4.1.1
|
||||||
parse-json: 5.2.0
|
parse-json: 5.2.0
|
||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: '@typescript/typescript6@6.0.2'
|
||||||
|
|
||||||
create-jest@29.7.0(@types/node@24.12.4):
|
create-jest@29.7.0(@types/node@24.12.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17082,18 +17200,18 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)):
|
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-svg: 15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
|
||||||
react-native-qrcode-styled@0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-qrcode-styled@0.3.3(react-native-svg@15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
qrcode: 1.5.4
|
qrcode: 1.5.4
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-fast-compare: 3.2.2
|
react-fast-compare: 3.2.2
|
||||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||||
react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-svg: 15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
|
||||||
react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-reanimated@3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17127,7 +17245,7 @@ snapshots:
|
|||||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||||
warn-once: 0.1.1
|
warn-once: 0.1.1
|
||||||
|
|
||||||
react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-svg@15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
css-select: 5.2.2
|
css-select: 5.2.2
|
||||||
css-tree: 1.1.3
|
css-tree: 1.1.3
|
||||||
@@ -17677,7 +17795,7 @@ snapshots:
|
|||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
websocket-driver: 0.7.4
|
websocket-driver: 0.7.4
|
||||||
|
|
||||||
sonner-native@0.21.0(patch_hash=4bcd0da0fec32e943460e6e788a9e4adf601b507d2dfd0c8246a6c8c74d8f638)(4e42d15c94c2c56af69d343a007eb893):
|
sonner-native@0.21.0(patch_hash=4bcd0da0fec32e943460e6e788a9e4adf601b507d2dfd0c8246a6c8c74d8f638)(0ed429160839218d22ac63d892397351):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||||
@@ -17685,7 +17803,7 @@ snapshots:
|
|||||||
react-native-reanimated: 3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-reanimated: 3.19.1(patch_hash=97a1967f37a4213fbab59e1fd59a1bf859b5efc79af5e1b528a47fe488e6c5d6)(@babel/core@7.29.0)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-safe-area-context: 5.6.2(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-safe-area-context: 5.6.2(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-screens: 4.24.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-screens: 4.24.0(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
react-native-svg: 15.12.1(patch_hash=ae309e9542214f5ae54935e253dedd236765feedac7be343f81d532d7fe09310)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
|
||||||
sonner@2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
sonner@2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -18110,6 +18228,29 @@ snapshots:
|
|||||||
|
|
||||||
typescript@6.0.3: {}
|
typescript@6.0.3: {}
|
||||||
|
|
||||||
|
typescript@7.0.2:
|
||||||
|
optionalDependencies:
|
||||||
|
'@typescript/typescript-aix-ppc64': 7.0.2
|
||||||
|
'@typescript/typescript-darwin-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-darwin-x64': 7.0.2
|
||||||
|
'@typescript/typescript-freebsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-freebsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-arm': 7.0.2
|
||||||
|
'@typescript/typescript-linux-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-loong64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-mips64el': 7.0.2
|
||||||
|
'@typescript/typescript-linux-ppc64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-riscv64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-s390x': 7.0.2
|
||||||
|
'@typescript/typescript-linux-x64': 7.0.2
|
||||||
|
'@typescript/typescript-netbsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-netbsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-openbsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-openbsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-sunos-x64': 7.0.2
|
||||||
|
'@typescript/typescript-win32-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-win32-x64': 7.0.2
|
||||||
|
|
||||||
ua-parser-js@0.7.41: {}
|
ua-parser-js@0.7.41: {}
|
||||||
|
|
||||||
ua-parser-js@1.0.41: {}
|
ua-parser-js@1.0.41: {}
|
||||||
|
|||||||
+18
-8
@@ -117,6 +117,7 @@ import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings'
|
|||||||
import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
|
import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
|
||||||
import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings'
|
import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings'
|
||||||
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
|
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
|
||||||
|
import {ActivityNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ActivityNotificationSettings'
|
||||||
import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
|
import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
|
||||||
import {SettingsScreen} from '#/screens/Settings/Settings'
|
import {SettingsScreen} from '#/screens/Settings/Settings'
|
||||||
import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
|
import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
|
||||||
@@ -451,6 +452,14 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
|
|||||||
getComponent={() => NotificationSettingsScreen}
|
getComponent={() => NotificationSettingsScreen}
|
||||||
options={{title: title(msg`Notification settings`), requireAuth: true}}
|
options={{title: title(msg`Notification settings`), requireAuth: true}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="ActivityNotificationSettings"
|
||||||
|
getComponent={() => ActivityNotificationSettingsScreen}
|
||||||
|
options={{
|
||||||
|
title: title(msg`Activity notifications`),
|
||||||
|
requireAuth: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="ContentAndMediaSettings"
|
name="ContentAndMediaSettings"
|
||||||
getComponent={() => ContentAndMediaSettingsScreen}
|
getComponent={() => ContentAndMediaSettingsScreen}
|
||||||
@@ -1003,14 +1012,15 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (IS_WEB) {
|
if (IS_WEB) {
|
||||||
const referrerInfo = Referrer.getReferrerInfo()
|
void Referrer.getReferrerInfo().then(referrerInfo => {
|
||||||
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
||||||
ax.metric('deepLink:referrerReceived', {
|
ax.metric('deepLink:referrerReceived', {
|
||||||
to: window.location.href,
|
to: window.location.href,
|
||||||
referrer: referrerInfo?.referrer,
|
referrer: referrerInfo?.referrer,
|
||||||
hostname: referrerInfo?.hostname,
|
hostname: referrerInfo?.hostname,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// temp, just testing
|
// temp, just testing
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export function Composer({
|
|||||||
ref={IS_WEB ? sift.refs.setAnchor : undefined}
|
ref={IS_WEB ? sift.refs.setAnchor : undefined}
|
||||||
style={
|
style={
|
||||||
node.type === 'facet' && {
|
node.type === 'facet' && {
|
||||||
color: t.palette.primary_500,
|
color: t.atoms.text_link.color,
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
{node.raw}
|
{node.raw}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import {type StyleProp, type ViewStyle} from 'react-native'
|
|||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
|
import {type TriggerProps as MenuTriggerProps} from '#/components/Menu/types'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {type AuxiliaryViewProps} from './types'
|
import {type AuxiliaryViewProps, type TriggerProps} from './types'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ContainerItem,
|
ContainerItem,
|
||||||
@@ -16,7 +17,6 @@ export {
|
|||||||
ItemText,
|
ItemText,
|
||||||
LabelText,
|
LabelText,
|
||||||
Root,
|
Root,
|
||||||
Trigger,
|
|
||||||
useMenuContext as useContextMenuContext,
|
useMenuContext as useContextMenuContext,
|
||||||
useMenuControl as useContextMenuControl,
|
useMenuControl as useContextMenuControl,
|
||||||
} from '#/components/Menu'
|
} from '#/components/Menu'
|
||||||
@@ -30,14 +30,37 @@ export function AuxiliaryView({}: AuxiliaryViewProps) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On web the context menu is just a Menu; contentLabel, onTap, style, and
|
||||||
|
* swipeGesture only apply to the native press-and-hold presentation.
|
||||||
|
*/
|
||||||
|
export function Trigger({children, label, hint, role}: TriggerProps) {
|
||||||
|
return (
|
||||||
|
<Menu.Trigger label={label} hint={hint} role={role}>
|
||||||
|
{/*
|
||||||
|
* Menu supplies the same web-arm child props shape as ContextMenu's
|
||||||
|
* TriggerChildProps; only the native arms of the two unions differ,
|
||||||
|
* and those never occur here.
|
||||||
|
*/}
|
||||||
|
{children as unknown as MenuTriggerProps['children']}
|
||||||
|
</Menu.Trigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
children,
|
||||||
label,
|
label,
|
||||||
|
align: _align,
|
||||||
style,
|
style,
|
||||||
onCloseAutoFocus,
|
onCloseAutoFocus,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
label?: string
|
label?: string
|
||||||
|
/**
|
||||||
|
* Native positions the menu against the message bubble explicitly; the web
|
||||||
|
* dropdown is anchored by radix, so this is accepted only for parity.
|
||||||
|
*/
|
||||||
|
align?: 'left' | 'right'
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
onCloseAutoFocus?: (event: Event) => void
|
onCloseAutoFocus?: (event: Event) => void
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -157,7 +157,8 @@ export function Outer({
|
|||||||
[open, close],
|
[open, close],
|
||||||
)
|
)
|
||||||
|
|
||||||
const isHeightConstrained = nativeOptions?.maxHeight != null
|
const isHeightConstrained =
|
||||||
|
nativeOptions?.maxHeight != null || nativeOptions?.fullHeight === true
|
||||||
|
|
||||||
const context = useMemo(
|
const context = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
type GestureResponderEvent,
|
type GestureResponderEvent,
|
||||||
type LayoutChangeEvent,
|
type LayoutChangeEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
|
type ScrollView,
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
View,
|
View,
|
||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
@@ -24,6 +25,7 @@ import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useA11y} from '#/state/a11y'
|
import {useA11y} from '#/state/a11y'
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
|
import {type ListMethods} from '#/view/com/util/List'
|
||||||
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {Context} from '#/components/Dialog/context'
|
import {Context} from '#/components/Dialog/context'
|
||||||
@@ -224,11 +226,21 @@ export function Inner({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrollableInner = Inner
|
/*
|
||||||
|
* There is no inner ScrollView on web - the ref is accepted for parity with
|
||||||
|
* the native variant and never attached, so native-only scrolling code in
|
||||||
|
* shared callers stays a no-op here.
|
||||||
|
*/
|
||||||
|
export function ScrollableInner({
|
||||||
|
ref: _ref,
|
||||||
|
...props
|
||||||
|
}: DialogInnerProps & {ref?: React.Ref<ScrollView>}) {
|
||||||
|
return <Inner {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
export const InnerFlatList = forwardRef<
|
export const InnerFlatList = forwardRef<
|
||||||
FlatList,
|
ListMethods,
|
||||||
FlatListProps<any> & {label: string} & {
|
FlatListProps<any> & {label?: string} & {
|
||||||
webInnerStyle?: StyleProp<ViewStyle>
|
webInnerStyle?: StyleProp<ViewStyle>
|
||||||
webInnerContentContainerStyle?: StyleProp<ViewStyle>
|
webInnerContentContainerStyle?: StyleProp<ViewStyle>
|
||||||
footer?: React.ReactNode
|
footer?: React.ReactNode
|
||||||
@@ -247,7 +259,11 @@ export const InnerFlatList = forwardRef<
|
|||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
return (
|
return (
|
||||||
<Inner
|
<Inner
|
||||||
label={label}
|
/*
|
||||||
|
* Most shared callers cannot pass a label since the native variant has
|
||||||
|
* no such prop; aria-label is simply absent for them, as before.
|
||||||
|
*/
|
||||||
|
label={label as string}
|
||||||
style={[
|
style={[
|
||||||
a.overflow_hidden,
|
a.overflow_hidden,
|
||||||
a.px_0,
|
a.px_0,
|
||||||
@@ -256,7 +272,13 @@ export const InnerFlatList = forwardRef<
|
|||||||
]}
|
]}
|
||||||
contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}>
|
contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}>
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={ref}
|
/*
|
||||||
|
* The FlatList instance satisfies the (web) ListMethods interface
|
||||||
|
* shared callers hold their refs as, except scrollToTop, which no
|
||||||
|
* platform-agnostic caller can use since the native ListMethods
|
||||||
|
* (FlatList) lacks it too.
|
||||||
|
*/
|
||||||
|
ref={ref as React.Ref<FlatList>}
|
||||||
style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, style]}
|
style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, style]}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -321,7 +343,11 @@ export function Close() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Handle() {
|
/*
|
||||||
|
* The drag handle only exists on the native bottom sheet; props are accepted
|
||||||
|
* for parity with the native variant.
|
||||||
|
*/
|
||||||
|
export function Handle(_props: {difference?: boolean; fill?: string}) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import type Animated from 'react-native-reanimated'
|
||||||
|
import {type AnimatedRef, type SharedValue} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import {DotGrid2x3_Stroke2_Corner0_Rounded as GripIcon} from '#/components/icons/DotGrid'
|
import {DotGrid2x3_Stroke2_Corner0_Rounded as GripIcon} from '#/components/icons/DotGrid'
|
||||||
@@ -18,6 +20,10 @@ interface SortableListProps<T> {
|
|||||||
onDragEnd?: () => void
|
onDragEnd?: () => void
|
||||||
/** Fixed row height used for position math. */
|
/** Fixed row height used for position math. */
|
||||||
itemHeight: number
|
itemHeight: number
|
||||||
|
/** Ref to the parent Animated.ScrollView for auto-scroll. Ignored on web. */
|
||||||
|
scrollRef?: AnimatedRef<Animated.ScrollView>
|
||||||
|
/** Scroll offset shared value from useScrollViewOffset. Ignored on web. */
|
||||||
|
scrollOffset?: SharedValue<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortableList<T>({
|
export function SortableList<T>({
|
||||||
|
|||||||
@@ -481,11 +481,11 @@ export function ProfileGrid({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
{color: t.palette.primary_500},
|
t.atoms.text_link,
|
||||||
hovered &&
|
hovered &&
|
||||||
web({
|
web({
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: 'underline',
|
||||||
textDecorationColor: t.palette.primary_500,
|
textDecorationColor: t.atoms.text_link.color,
|
||||||
}),
|
}),
|
||||||
]}>
|
]}>
|
||||||
<Trans>See more</Trans>
|
<Trans>See more</Trans>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {msg} from '@lingui/core/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {HITSLOP_30} from '#/lib/constants'
|
|
||||||
import {type NavigationProp} from '#/lib/routes/types'
|
import {type NavigationProp} from '#/lib/routes/types'
|
||||||
import {useSetDrawerOpen} from '#/state/shell'
|
import {useSetDrawerOpen} from '#/state/shell'
|
||||||
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
|
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
|
||||||
@@ -33,6 +32,8 @@ import {Text} from '#/components/Typography'
|
|||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_IOS} from '#/env'
|
import {IS_IOS} from '#/env'
|
||||||
|
|
||||||
|
const HEADER_BUTTON_HITSLOP = {top: 30, bottom: 10, left: 30, right: 30}
|
||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
children,
|
||||||
noBottomBorder,
|
noBottomBorder,
|
||||||
@@ -136,7 +137,7 @@ export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
shape="round"
|
shape="round"
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
hitSlop={HITSLOP_30}
|
hitSlop={HEADER_BUTTON_HITSLOP}
|
||||||
style={[
|
style={[
|
||||||
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
|
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
|
||||||
a.bg_transparent,
|
a.bg_transparent,
|
||||||
@@ -170,7 +171,7 @@ export function MenuButton() {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
shape="square"
|
shape="square"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
hitSlop={HITSLOP_30}
|
hitSlop={HEADER_BUTTON_HITSLOP}
|
||||||
style={[
|
style={[
|
||||||
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
|
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
|
||||||
a.bg_transparent,
|
a.bg_transparent,
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export function ImageMenu({onPressShare, onPressSave}: Props) {
|
|||||||
visible={isMounted}
|
visible={isMounted}
|
||||||
animationType="none"
|
animationType="none"
|
||||||
onRequestClose={close}
|
onRequestClose={close}
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
statusBarTranslucent>
|
statusBarTranslucent>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ function LinkPeek({
|
|||||||
// dialog can show.
|
// dialog can show.
|
||||||
useInAppBrowser: useInAppBrowserPref === true,
|
useInAppBrowser: useInAppBrowserPref === true,
|
||||||
browserToolbarColor: t.atoms.bg.backgroundColor,
|
browserToolbarColor: t.atoms.bg.backgroundColor,
|
||||||
browserControlsColor: t.palette.primary_500,
|
browserControlsColor: t.atoms.text_link.color,
|
||||||
}}
|
}}
|
||||||
borderRadius={borderRadius}
|
borderRadius={borderRadius}
|
||||||
// Fires only when not morphing natively (in-app browser off/unset).
|
// Fires only when not morphing natively (in-app browser off/unset).
|
||||||
@@ -487,14 +487,14 @@ export function InlineLinkText({
|
|||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[
|
style={[
|
||||||
{color: t.palette.primary_500},
|
t.atoms.text_link,
|
||||||
interacted &&
|
interacted &&
|
||||||
!disableUnderline && {
|
!disableUnderline && {
|
||||||
...web({
|
...web({
|
||||||
outline: 0,
|
outline: 0,
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: 'underline',
|
||||||
textDecorationColor:
|
textDecorationColor:
|
||||||
flattenedStyle.color ?? t.palette.primary_500,
|
flattenedStyle.color ?? t.atoms.text_link.color,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
flattenedStyle,
|
flattenedStyle,
|
||||||
@@ -591,14 +591,14 @@ export function SimpleInlineLinkText({
|
|||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[
|
style={[
|
||||||
{color: t.palette.primary_500},
|
t.atoms.text_link,
|
||||||
interacted &&
|
interacted &&
|
||||||
!disableUnderline && {
|
!disableUnderline && {
|
||||||
...web({
|
...web({
|
||||||
outline: 0,
|
outline: 0,
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: 'underline',
|
||||||
textDecorationColor:
|
textDecorationColor:
|
||||||
flattenedStyle.color ?? t.palette.primary_500,
|
flattenedStyle.color ?? t.atoms.text_link.color,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
flattenedStyle,
|
flattenedStyle,
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import {
|
|||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export {type DialogControlProps as MenuControlProps} from '#/components/Dialog'
|
||||||
|
|
||||||
export {useMenuContext}
|
export {useMenuContext}
|
||||||
|
|
||||||
export function useMenuControl(): Dialog.DialogControlProps {
|
export function useMenuControl(): Dialog.DialogControlProps {
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export function ImageEmbed({
|
|||||||
onPress(0, [singleContainerRef.current], [singleDimsRef.current])
|
onPress(0, [singleContainerRef.current], [singleDimsRef.current])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(rest.viewContext, 'rest.viewContext')
|
||||||
return (
|
return (
|
||||||
<View style={[a.mt_sm, rest.style]}>
|
<View style={[a.mt_sm, rest.style]}>
|
||||||
<ImageContextMenu
|
<ImageContextMenu
|
||||||
@@ -127,9 +128,7 @@ export function ImageEmbed({
|
|||||||
crop={
|
crop={
|
||||||
rest.viewContext === PostEmbedViewContext.ThreadHighlighted
|
rest.viewContext === PostEmbedViewContext.ThreadHighlighted
|
||||||
? 'none'
|
? 'none'
|
||||||
: rest.isWithinQuote
|
: 'constrained'
|
||||||
? 'square'
|
|
||||||
: 'constrained'
|
|
||||||
}
|
}
|
||||||
image={image}
|
image={image}
|
||||||
onContainerRef={ref => {
|
onContainerRef={ref => {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
export function VideoEmbedInnerWeb() {
|
import {type VideoEmbedInnerWebProps} from './VideoEmbedInnerWeb.shared'
|
||||||
|
|
||||||
|
export {
|
||||||
|
HLSUnsupportedError,
|
||||||
|
VideoNotFoundError,
|
||||||
|
} from './VideoEmbedInnerWeb.shared'
|
||||||
|
|
||||||
|
export function VideoEmbedInnerWeb(_props: VideoEmbedInnerWebProps): never {
|
||||||
throw new Error('VideoEmbedInnerWeb may not be used on native.')
|
throw new Error('VideoEmbedInnerWeb may not be used on native.')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {type AppBskyEmbedVideo} from '@atproto/api'
|
||||||
|
|
||||||
|
export type VideoEmbedInnerWebProps = {
|
||||||
|
embed: AppBskyEmbedVideo.View
|
||||||
|
active: boolean
|
||||||
|
setActive: () => void
|
||||||
|
onScreen: boolean
|
||||||
|
lastKnownTime: React.RefObject<number | undefined>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HLSUnsupportedError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super('HLS is not supported')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VideoNotFoundError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super('Video not found')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import {useCallback, useEffect, useId, useRef, useState} from 'react'
|
import {useCallback, useEffect, useId, useRef, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type AppBskyEmbedVideo} from '@atproto/api'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import type * as HlsTypes from 'hls.js'
|
import type * as HlsTypes from 'hls.js'
|
||||||
@@ -10,21 +9,25 @@ import {atoms as a} from '#/alf'
|
|||||||
import {AltBadgeWithDialog} from '#/components/AltBadgeWithDialog'
|
import {AltBadgeWithDialog} from '#/components/AltBadgeWithDialog'
|
||||||
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
||||||
import * as BandwidthEstimate from './bandwidth-estimate'
|
import * as BandwidthEstimate from './bandwidth-estimate'
|
||||||
|
import {
|
||||||
|
HLSUnsupportedError,
|
||||||
|
type VideoEmbedInnerWebProps,
|
||||||
|
VideoNotFoundError,
|
||||||
|
} from './VideoEmbedInnerWeb.shared'
|
||||||
import {Controls} from './web-controls/VideoControls'
|
import {Controls} from './web-controls/VideoControls'
|
||||||
|
|
||||||
|
export {
|
||||||
|
HLSUnsupportedError,
|
||||||
|
VideoNotFoundError,
|
||||||
|
} from './VideoEmbedInnerWeb.shared'
|
||||||
|
|
||||||
export function VideoEmbedInnerWeb({
|
export function VideoEmbedInnerWeb({
|
||||||
embed,
|
embed,
|
||||||
active,
|
active,
|
||||||
setActive,
|
setActive,
|
||||||
onScreen,
|
onScreen,
|
||||||
lastKnownTime,
|
lastKnownTime,
|
||||||
}: {
|
}: VideoEmbedInnerWebProps) {
|
||||||
embed: AppBskyEmbedVideo.View
|
|
||||||
active: boolean
|
|
||||||
setActive: () => void
|
|
||||||
onScreen: boolean
|
|
||||||
lastKnownTime: React.RefObject<number | undefined>
|
|
||||||
}) {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const [focused, setFocused] = useState(false)
|
const [focused, setFocused] = useState(false)
|
||||||
@@ -104,12 +107,6 @@ export function VideoEmbedInnerWeb({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HLSUnsupportedError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('HLS is not supported')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bluesky serves HLS as MPEG-TS with H.264 + AAC. `Hls.isSupported()` is loose
|
// Bluesky serves HLS as MPEG-TS with H.264 + AAC. `Hls.isSupported()` is loose
|
||||||
// (true if MSE supports *any* of {H.264, AV1, VP9} OR *any* of {AAC, FLAC}),
|
// (true if MSE supports *any* of {H.264, AV1, VP9} OR *any* of {AAC, FLAC}),
|
||||||
// so on Linux boxes missing H.264 (e.g. no ubuntu-restricted-extras, sandboxed
|
// so on Linux boxes missing H.264 (e.g. no ubuntu-restricted-extras, sandboxed
|
||||||
@@ -138,12 +135,6 @@ function canPlayBskyVideoCodecs(): boolean {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VideoNotFoundError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('Video not found')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fatal hls.js playback error. `detail` is the hls.js error details code
|
* Fatal hls.js playback error. `detail` is the hls.js error details code
|
||||||
* (e.g. bufferAppendError), which buckets failures more usefully than the
|
* (e.g. bufferAppendError), which buckets failures more usefully than the
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
export function Controls() {
|
import {type ControlsProps} from './VideoControls.shared'
|
||||||
|
|
||||||
|
export function Controls(_props: ControlsProps): never {
|
||||||
throw new Error('VideoWebControls may not be used on native.')
|
throw new Error('VideoWebControls may not be used on native.')
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import type Hls from 'hls.js'
|
||||||
|
|
||||||
|
export type ControlsProps = {
|
||||||
|
videoRef: React.RefObject<HTMLVideoElement | null>
|
||||||
|
hlsRef: React.RefObject<Hls | undefined | null>
|
||||||
|
active: boolean
|
||||||
|
setActive: () => void
|
||||||
|
focused: boolean
|
||||||
|
setFocused: (focused: boolean) => void
|
||||||
|
onScreen: boolean
|
||||||
|
fullscreenRef: React.RefObject<HTMLDivElement | null>
|
||||||
|
hlsLoading: boolean
|
||||||
|
hasSubtitleTrack: boolean
|
||||||
|
isGif: boolean
|
||||||
|
altText?: string
|
||||||
|
updateCuePositions: (controlsVisible?: boolean) => void
|
||||||
|
}
|
||||||
+2
-16
@@ -3,7 +3,6 @@ import {Pressable, View} from 'react-native'
|
|||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
import type Hls from 'hls.js'
|
|
||||||
|
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {
|
import {
|
||||||
@@ -33,6 +32,7 @@ import {TimeIndicator} from '../TimeIndicator'
|
|||||||
import {ControlButton} from './ControlButton'
|
import {ControlButton} from './ControlButton'
|
||||||
import {Scrubber} from './Scrubber'
|
import {Scrubber} from './Scrubber'
|
||||||
import {formatTime, useVideoElement} from './utils'
|
import {formatTime, useVideoElement} from './utils'
|
||||||
|
import {type ControlsProps} from './VideoControls.shared'
|
||||||
import {VolumeControl} from './VolumeControl'
|
import {VolumeControl} from './VolumeControl'
|
||||||
|
|
||||||
export function Controls({
|
export function Controls({
|
||||||
@@ -49,21 +49,7 @@ export function Controls({
|
|||||||
isGif,
|
isGif,
|
||||||
altText,
|
altText,
|
||||||
updateCuePositions,
|
updateCuePositions,
|
||||||
}: {
|
}: ControlsProps) {
|
||||||
videoRef: React.RefObject<HTMLVideoElement | null>
|
|
||||||
hlsRef: React.RefObject<Hls | undefined | null>
|
|
||||||
active: boolean
|
|
||||||
setActive: () => void
|
|
||||||
focused: boolean
|
|
||||||
setFocused: (focused: boolean) => void
|
|
||||||
onScreen: boolean
|
|
||||||
fullscreenRef: React.RefObject<HTMLDivElement | null>
|
|
||||||
hlsLoading: boolean
|
|
||||||
hasSubtitleTrack: boolean
|
|
||||||
isGif: boolean
|
|
||||||
altText?: string
|
|
||||||
updateCuePositions: (controlsVisible?: boolean) => void
|
|
||||||
}) {
|
|
||||||
const {
|
const {
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {type AppBskyFeedDefs, type ModerationDecision} from '@atproto/api'
|
|||||||
export enum PostEmbedViewContext {
|
export enum PostEmbedViewContext {
|
||||||
ThreadHighlighted = 'ThreadHighlighted',
|
ThreadHighlighted = 'ThreadHighlighted',
|
||||||
Feed = 'Feed',
|
Feed = 'Feed',
|
||||||
FeedEmbedRecordWithMedia = 'FeedEmbedRecordWithMedia',
|
|
||||||
ChatMessage = 'ChatMessage',
|
ChatMessage = 'ChatMessage',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import {useCallback, useMemo} from 'react'
|
import {useCallback, useMemo} from 'react'
|
||||||
import {LayoutAnimation, type TextStyle} from 'react-native'
|
import {LayoutAnimation, type TextStyle} from 'react-native'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
import {HITSLOP_10} from '#/lib/constants'
|
||||||
import {atoms as a, flatten, type TextStyleProp, useTheme} from '#/alf'
|
import {atoms as a, flatten, type TextStyleProp, useTheme} from '#/alf'
|
||||||
@@ -14,7 +12,7 @@ export function ShowMoreTextButton({
|
|||||||
style,
|
style,
|
||||||
}: TextStyleProp & {onPress: () => void}) {
|
}: TextStyleProp & {onPress: () => void}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
@@ -30,7 +28,7 @@ export function ShowMoreTextButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Expand post text`)}
|
label={l`Expand post text`}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={[
|
style={[
|
||||||
a.self_start,
|
a.self_start,
|
||||||
@@ -43,13 +41,13 @@ export function ShowMoreTextButton({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
textStyle,
|
textStyle,
|
||||||
|
t.atoms.text_link,
|
||||||
{
|
{
|
||||||
color: t.palette.primary_500,
|
|
||||||
opacity: pressed ? 0.6 : 1,
|
opacity: pressed ? 0.6 : 1,
|
||||||
textDecorationLine: hovered ? 'underline' : undefined,
|
textDecorationLine: hovered ? 'underline' : undefined,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<Trans>Show More</Trans>
|
<Trans>Show more</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -150,10 +150,10 @@ function TranslationLink({
|
|||||||
label={l`Translate`}
|
label={l`Translate`}
|
||||||
hoverStyle={[
|
hoverStyle={[
|
||||||
native({opacity: 0.5}),
|
native({opacity: 0.5}),
|
||||||
web([a.underline, {textDecorationColor: t.palette.primary_500}]),
|
web([a.underline, {textDecorationColor: t.atoms.text_link.color}]),
|
||||||
]}
|
]}
|
||||||
hitSlop={HITSLOP_30}>
|
hitSlop={HITSLOP_30}>
|
||||||
<Text style={[a.text_sm, {color: t.palette.primary_500}]}>
|
<Text style={[a.text_sm, t.atoms.text_link]}>
|
||||||
<Trans>Translate</Trans>
|
<Trans>Translate</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -229,7 +229,7 @@ function TranslationError({
|
|||||||
label={l`Try Google Translate`}
|
label={l`Try Google Translate`}
|
||||||
hoverStyle={[
|
hoverStyle={[
|
||||||
native({opacity: 0.5}),
|
native({opacity: 0.5}),
|
||||||
web([a.underline, {textDecorationColor: t.palette.primary_500}]),
|
web([a.underline, {textDecorationColor: t.atoms.text_link.color}]),
|
||||||
]}
|
]}
|
||||||
hitSlop={HITSLOP_30}>
|
hitSlop={HITSLOP_30}>
|
||||||
<Text
|
<Text
|
||||||
@@ -237,7 +237,7 @@ function TranslationError({
|
|||||||
a.text_xs,
|
a.text_xs,
|
||||||
a.font_medium,
|
a.font_medium,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
{color: t.palette.primary_500},
|
t.atoms.text_link,
|
||||||
]}>
|
]}>
|
||||||
<Trans>Try Google Translate</Trans>
|
<Trans>Try Google Translate</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
|||||||
import {Default as ProfileCard} from '#/components/ProfileCard'
|
import {Default as ProfileCard} from '#/components/ProfileCard'
|
||||||
import {IS_NATIVE, IS_WEB} from '#/env'
|
import {IS_NATIVE, IS_WEB} from '#/env'
|
||||||
|
|
||||||
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) {
|
function keyExtractor(item: AppBskyActorDefs.ProfileView, index: number) {
|
||||||
return `${item.did}-${index}`
|
return `${item.did}-${index}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
|
|||||||
const renderItem = ({
|
const renderItem = ({
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
}: ListRenderItemInfo<AppBskyActorDefs.ProfileViewBasic>) => {
|
}: ListRenderItemInfo<AppBskyActorDefs.ProfileView>) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {useCallback, useEffect, useImperativeHandle, useState} from 'react'
|
import {useCallback, useEffect, useImperativeHandle, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
findNodeHandle,
|
|
||||||
type ListRenderItemInfo,
|
type ListRenderItemInfo,
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
type EmptyStateButtonProps,
|
type EmptyStateButtonProps,
|
||||||
} from '#/view/com/util/EmptyState'
|
} from '#/view/com/util/EmptyState'
|
||||||
import {List, type ListRef} from '#/view/com/util/List'
|
import {List, type ListRef} from '#/view/com/util/List'
|
||||||
|
import {findListNativeTag} from '#/view/com/util/listNativeTag'
|
||||||
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {atoms as a, ios, useTheme} from '#/alf'
|
import {atoms as a, ios, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
@@ -57,7 +57,7 @@ interface ProfileFeedgensProps {
|
|||||||
emptyStateIcon?: React.ComponentType<any> | React.ReactElement
|
emptyStateIcon?: React.ComponentType<any> | React.ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: AppBskyGraphDefs.StarterPackView) {
|
function keyExtractor(item: AppBskyGraphDefs.StarterPackViewBasic) {
|
||||||
return item.uri
|
return item.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,13 +138,16 @@ export function ProfileStarterPacks({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (IS_IOS && enabled && scrollElRef.current) {
|
if (IS_IOS && enabled && scrollElRef.current) {
|
||||||
const nativeTag = findNodeHandle(scrollElRef.current)
|
const nativeTag = findListNativeTag(scrollElRef.current)
|
||||||
setScrollViewTag(nativeTag)
|
setScrollViewTag(nativeTag)
|
||||||
}
|
}
|
||||||
}, [enabled, scrollElRef, setScrollViewTag])
|
}, [enabled, scrollElRef, setScrollViewTag])
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({item, index}: ListRenderItemInfo<AppBskyGraphDefs.StarterPackView>) => {
|
({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
}: ListRenderItemInfo<AppBskyGraphDefs.StarterPackViewBasic>) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
|||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {atoms as a, flatten, useBreakpoints, web} from '#/alf'
|
import {atoms as a, flatten, useBreakpoints, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import {type WelcomeModalControl} from '#/components/hooks/useWelcomeModal.shared'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
@@ -17,11 +18,7 @@ import {useAnalytics} from '#/analytics'
|
|||||||
const welcomeModalBg = require('../../assets/images/welcome-modal-bg.jpg')
|
const welcomeModalBg = require('../../assets/images/welcome-modal-bg.jpg')
|
||||||
|
|
||||||
interface WelcomeModalProps {
|
interface WelcomeModalProps {
|
||||||
control: {
|
control: WelcomeModalControl
|
||||||
isOpen: boolean
|
|
||||||
open: () => void
|
|
||||||
close: () => void
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WelcomeModal({control}: WelcomeModalProps) {
|
export function WelcomeModal({control}: WelcomeModalProps) {
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
export function FindContactsFlow() {
|
import {type Action, type State} from './state'
|
||||||
|
|
||||||
|
export function FindContactsFlow(_props: {
|
||||||
|
state: State
|
||||||
|
dispatch: React.ActionDispatch<[Action]>
|
||||||
|
onBack?: () => void
|
||||||
|
onCancel: () => void
|
||||||
|
context: 'Onboarding' | 'Standalone'
|
||||||
|
}): never {
|
||||||
throw new Error('FindContactsFlow is not available on web')
|
throw new Error('FindContactsFlow is not available on web')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export function useWelcomeModal() {
|
import {type WelcomeModalControl} from './useWelcomeModal.shared'
|
||||||
|
|
||||||
|
export function useWelcomeModal(): WelcomeModalControl {
|
||||||
throw new Error('useWelcomeModal is web only')
|
throw new Error('useWelcomeModal is web only')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export type WelcomeModalControl = {
|
||||||
|
isOpen: boolean
|
||||||
|
open: () => void
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ import {useEffect, useState} from 'react'
|
|||||||
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
import {type WelcomeModalControl} from './useWelcomeModal.shared'
|
||||||
|
|
||||||
export function useWelcomeModal() {
|
export function useWelcomeModal(): WelcomeModalControl {
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {type AppBskyEmbedImages} from '@atproto/api'
|
|||||||
|
|
||||||
import {atoms as a, useBreakpoints} from '#/alf'
|
import {atoms as a, useBreakpoints} from '#/alf'
|
||||||
import {type Dimensions} from '#/components/Lightbox/types'
|
import {type Dimensions} from '#/components/Lightbox/types'
|
||||||
import {PostEmbedViewContext} from '#/components/Post/Embed/types'
|
import {type PostEmbedViewContext} from '#/components/Post/Embed/types'
|
||||||
import {GalleryItem} from './ImageLayoutGridItem'
|
import {GalleryItem} from './ImageLayoutGridItem'
|
||||||
|
|
||||||
interface ImageLayoutGridProps {
|
interface ImageLayoutGridProps {
|
||||||
@@ -28,9 +28,7 @@ export function ImageLayoutGrid({
|
|||||||
...props
|
...props
|
||||||
}: ImageLayoutGridProps) {
|
}: ImageLayoutGridProps) {
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const isWithinQuote =
|
const isWithinQuote = isWithinQuoteProp
|
||||||
isWithinQuoteProp ??
|
|
||||||
props.viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
|
|
||||||
const gap = isWithinQuote ? (gtMobile ? a.gap_xs : a.gap_2xs) : a.gap_xs
|
const gap = isWithinQuote ? (gtMobile ? a.gap_xs : a.gap_2xs) : a.gap_xs
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import {
|
|||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {type ModerationUI} from '@atproto/api'
|
import {type ModerationUI} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ADULT_CONTENT_LABELS,
|
ADULT_CONTENT_LABELS,
|
||||||
@@ -78,7 +76,7 @@ function ContentHiderActive({
|
|||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const [override, setOverride] = useState(false)
|
const [override, setOverride] = useState(false)
|
||||||
const control = useModerationDetailsDialogControl()
|
const control = useModerationDetailsDialogControl()
|
||||||
@@ -97,7 +95,7 @@ function ContentHiderActive({
|
|||||||
(blur.type === 'label' && blur.source.type !== 'user')
|
(blur.type === 'label' && blur.source.type !== 'user')
|
||||||
) {
|
) {
|
||||||
if (desc.isSubjectAccount) {
|
if (desc.isSubjectAccount) {
|
||||||
return _(msg`${desc.name} (Account)`)
|
return l`${desc.name} (Account)`
|
||||||
} else {
|
} else {
|
||||||
return desc.name
|
return desc.name
|
||||||
}
|
}
|
||||||
@@ -128,7 +126,7 @@ function ContentHiderActive({
|
|||||||
|
|
||||||
const def = cause.labelDef || getDefinition(labelDefs, cause.label)
|
const def = cause.labelDef || getDefinition(labelDefs, cause.label)
|
||||||
if (def.identifier === 'porn' || def.identifier === 'sexual') {
|
if (def.identifier === 'porn' || def.identifier === 'sexual') {
|
||||||
return _(msg`Adult Content`)
|
return l`Adult Content`
|
||||||
}
|
}
|
||||||
return getLabelStrings(i18n.locale, globalLabelStrings, def).name
|
return getLabelStrings(i18n.locale, globalLabelStrings, def).name
|
||||||
})
|
})
|
||||||
@@ -138,7 +136,7 @@ function ContentHiderActive({
|
|||||||
}
|
}
|
||||||
return [...new Set(selfBlurNames)].join(', ')
|
return [...new Set(selfBlurNames)].join(', ')
|
||||||
}, [
|
}, [
|
||||||
_,
|
l,
|
||||||
modui.blurs,
|
modui.blurs,
|
||||||
blur,
|
blur,
|
||||||
desc.name,
|
desc.name,
|
||||||
@@ -151,7 +149,6 @@ function ContentHiderActive({
|
|||||||
return (
|
return (
|
||||||
<View testID={testID} style={[a.overflow_hidden, style]}>
|
<View testID={testID} style={[a.overflow_hidden, style]}>
|
||||||
<ModerationDetailsDialog control={control} modcause={blur} />
|
<ModerationDetailsDialog control={control} modcause={blur} />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onPress={e => {
|
onPress={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -166,10 +163,10 @@ function ContentHiderActive({
|
|||||||
label={desc.name}
|
label={desc.name}
|
||||||
accessibilityHint={
|
accessibilityHint={
|
||||||
modui.noOverride
|
modui.noOverride
|
||||||
? _(msg`Learn more about the moderation applied to this content`)
|
? l`Learn more about the moderation applied to this content`
|
||||||
: override
|
: override
|
||||||
? _(msg`Hides the content`)
|
? l`Hides the content`
|
||||||
: _(msg`Shows the content`)
|
: l`Shows the content`
|
||||||
}>
|
}>
|
||||||
{state => (
|
{state => (
|
||||||
<View
|
<View
|
||||||
@@ -223,7 +220,6 @@ function ContentHiderActive({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{desc.source && blur.type === 'label' && !override && (
|
{desc.source && blur.type === 'label' && !override && (
|
||||||
<Button
|
<Button
|
||||||
onPress={e => {
|
onPress={e => {
|
||||||
@@ -231,9 +227,7 @@ function ContentHiderActive({
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
control.open()
|
control.open()
|
||||||
}}
|
}}
|
||||||
label={_(
|
label={l`Learn more about the moderation applied to this content`}
|
||||||
msg`Learn more about the moderation applied to this content`,
|
|
||||||
)}
|
|
||||||
style={[a.pt_sm]}>
|
style={[a.pt_sm]}>
|
||||||
{state => (
|
{state => (
|
||||||
<Text
|
<Text
|
||||||
@@ -252,7 +246,7 @@ function ContentHiderActive({
|
|||||||
)}{' '}
|
)}{' '}
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
{color: t.palette.primary_500},
|
t.atoms.text_link,
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
state.hovered && [web({textDecoration: 'underline'})],
|
state.hovered && [web({textDecoration: 'underline'})],
|
||||||
]}>
|
]}>
|
||||||
@@ -262,7 +256,6 @@ function ContentHiderActive({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{override && <View style={childContainerStyle}>{children}</View>}
|
{override && <View style={childContainerStyle}>{children}</View>}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,7 +54,15 @@ export function PostAlerts({
|
|||||||
const isOwnPost = !!post && post.author.did === currentAccount?.did
|
const isOwnPost = !!post && post.author.did === currentAccount?.did
|
||||||
const allLabels: ComAtprotoLabelDefs.Label[] =
|
const allLabels: ComAtprotoLabelDefs.Label[] =
|
||||||
isOwnPost && view === 'expanded'
|
isOwnPost && view === 'expanded'
|
||||||
? [...(post.labels ?? []), ...(post.author.labels ?? [])]
|
? [
|
||||||
|
...(post.labels ?? []),
|
||||||
|
/*
|
||||||
|
* Account labels appear on Profile. We don't show them here unless the
|
||||||
|
* user's mod settings are configured such that the labels land in the
|
||||||
|
* modui handling.
|
||||||
|
*/
|
||||||
|
// ...(post.author.labels ?? [])
|
||||||
|
]
|
||||||
: []
|
: []
|
||||||
/*
|
/*
|
||||||
* Labels that the moderation system already surfaces in this context -
|
* Labels that the moderation system already surfaces in this context -
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import {
|
|||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {type ModerationUI} from '@atproto/api'
|
import {type ModerationUI} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
@@ -38,7 +36,7 @@ export function ScreenHider({
|
|||||||
containerStyle?: StyleProp<ViewStyle>
|
containerStyle?: StyleProp<ViewStyle>
|
||||||
}>) {
|
}>) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const [override, setOverride] = useState(false)
|
const [override, setOverride] = useState(false)
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
@@ -131,15 +129,13 @@ export function ScreenHider({
|
|||||||
control.open()
|
control.open()
|
||||||
}}
|
}}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={_(msg`Learn more about this warning`)}
|
accessibilityLabel={l`Learn more about this warning`}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_lg,
|
a.text_lg,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
{
|
t.atoms.text_link,
|
||||||
color: t.palette.primary_500,
|
|
||||||
},
|
|
||||||
web({
|
web({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}),
|
}),
|
||||||
@@ -158,7 +154,7 @@ export function ScreenHider({
|
|||||||
color="primary"
|
color="primary"
|
||||||
size="large"
|
size="large"
|
||||||
style={[a.rounded_full]}
|
style={[a.rounded_full]}
|
||||||
label={_(msg`Go back`)}
|
label={l`Go back`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (navigation.canGoBack()) {
|
if (navigation.canGoBack()) {
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
@@ -176,7 +172,7 @@ export function ScreenHider({
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large"
|
||||||
style={[a.rounded_full]}
|
style={[a.rounded_full]}
|
||||||
label={_(msg`Show anyway`)}
|
label={l`Show anyway`}
|
||||||
onPress={() => setOverride(v => !v)}>
|
onPress={() => setOverride(v => !v)}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Show anyway</Trans>
|
<Trans>Show anyway</Trans>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import {type ColorValue} from 'react-native'
|
||||||
|
|
||||||
|
export interface GestureAction {
|
||||||
|
color: ColorValue
|
||||||
|
action: () => void
|
||||||
|
threshold: number
|
||||||
|
icon: React.ElementType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GestureActions {
|
||||||
|
leftFirst?: GestureAction
|
||||||
|
leftSecond?: GestureAction
|
||||||
|
rightFirst?: GestureAction
|
||||||
|
rightSecond?: GestureAction
|
||||||
|
}
|
||||||
@@ -16,20 +16,7 @@ import Animated, {
|
|||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
|
import {type GestureActions} from './GestureActionView.shared'
|
||||||
interface GestureAction {
|
|
||||||
color: ColorValue
|
|
||||||
action: () => void
|
|
||||||
threshold: number
|
|
||||||
icon: React.ElementType
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GestureActions {
|
|
||||||
leftFirst?: GestureAction
|
|
||||||
leftSecond?: GestureAction
|
|
||||||
rightFirst?: GestureAction
|
|
||||||
rightSecond?: GestureAction
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_WIDTH = Dimensions.get('screen').width
|
const MAX_WIDTH = Dimensions.get('screen').width
|
||||||
const ICON_SIZE = 32
|
const ICON_SIZE = 32
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
export function GestureActionView({children}: {children: React.ReactNode}) {
|
import {type GestureActions} from './GestureActionView.shared'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Swipe gestures only exist on native; children render as-is on web.
|
||||||
|
*/
|
||||||
|
export function GestureActionView({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
actions: GestureActions
|
||||||
|
}) {
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function useIntentHandler() {
|
|||||||
await WebBrowser.dismissBrowser().catch(() => {})
|
await WebBrowser.dismissBrowser().catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const referrerInfo = Referrer.getReferrerInfo()
|
const referrerInfo = await Referrer.getReferrerInfo()
|
||||||
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
||||||
ax.metric('deepLink:referrerReceived', {
|
ax.metric('deepLink:referrerReceived', {
|
||||||
to: url,
|
to: url,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export function useNavigationTabState() {
|
|||||||
return {
|
return {
|
||||||
isAtHome: currentRoute === 'Home',
|
isAtHome: currentRoute === 'Home',
|
||||||
isAtSearch: currentRoute === 'Search',
|
isAtSearch: currentRoute === 'Search',
|
||||||
|
isAtFeeds: currentRoute === 'Feeds',
|
||||||
|
isAtBookmarks: currentRoute === 'Bookmarks',
|
||||||
isAtNotifications: currentRoute === 'Notifications',
|
isAtNotifications: currentRoute === 'Notifications',
|
||||||
isAtMyProfile: currentRoute === 'MyProfile',
|
isAtMyProfile: currentRoute === 'MyProfile',
|
||||||
isAtMessages: currentRoute === 'Messages',
|
isAtMessages: currentRoute === 'Messages',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export function useOTAUpdates() {}
|
export function useOTAUpdates() {}
|
||||||
export function useApplyPullRequestOTAUpdate() {
|
export function useApplyPullRequestOTAUpdate() {
|
||||||
return {
|
return {
|
||||||
tryApplyUpdate: () => {},
|
tryApplyUpdate: async (_channel: string) => {},
|
||||||
revertToEmbedded: () => {},
|
revertToEmbedded: () => {},
|
||||||
isCurrentlyRunningPullRequestDeployment: false,
|
isCurrentlyRunningPullRequestDeployment: false,
|
||||||
currentChannel: 'web-build',
|
currentChannel: 'web-build',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export function Provider({children}: {children: React.ReactNode}) {
|
|||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
|
|
||||||
const noop = () => {}
|
const noop = (_scope: string) => {}
|
||||||
|
|
||||||
export function useHotkeysContext() {
|
export function useHotkeysContext() {
|
||||||
return useMemo(
|
return useMemo(
|
||||||
|
|||||||
@@ -168,10 +168,14 @@ function createResizedImage(
|
|||||||
|
|
||||||
export async function saveBytesToDisk(
|
export async function saveBytesToDisk(
|
||||||
filename: string,
|
filename: string,
|
||||||
bytes: Uint8Array<ArrayBuffer>,
|
bytes: Uint8Array,
|
||||||
type: string,
|
type: string,
|
||||||
) {
|
) {
|
||||||
const blob = new Blob([bytes], {type})
|
/*
|
||||||
|
* Bytes handed to us are never SharedArrayBuffer-backed, but the broader
|
||||||
|
* Uint8Array parameter type matches the native variant.
|
||||||
|
*/
|
||||||
|
const blob = new Blob([bytes as Uint8Array<ArrayBuffer>], {type})
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
downloadUrl(url, filename)
|
downloadUrl(url, filename)
|
||||||
// Firefox requires a small delay
|
// Firefox requires a small delay
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {Asset} from 'expo-asset'
|
||||||
import {
|
import {
|
||||||
documentDirectory,
|
documentDirectory,
|
||||||
getInfoAsync,
|
getInfoAsync,
|
||||||
@@ -9,10 +10,15 @@ import ExpoImageCropTool, {
|
|||||||
} from '@bsky.app/expo-image-crop-tool'
|
} from '@bsky.app/expo-image-crop-tool'
|
||||||
|
|
||||||
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants'
|
||||||
|
import {IS_ANDROID} from '#/env'
|
||||||
import {compressIfNeeded} from './manip'
|
import {compressIfNeeded} from './manip'
|
||||||
import {type PickerImage} from './picker.shared'
|
import {type PickerImage} from './picker.shared'
|
||||||
|
|
||||||
async function getFile() {
|
async function getFile() {
|
||||||
|
if (IS_ANDROID) {
|
||||||
|
return await getAndroidFile()
|
||||||
|
}
|
||||||
|
|
||||||
const imagesDir = documentDirectory!
|
const imagesDir = documentDirectory!
|
||||||
.split('/')
|
.split('/')
|
||||||
.slice(0, -6)
|
.slice(0, -6)
|
||||||
@@ -41,6 +47,44 @@ async function getFile() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Android emulator can't reach the iOS simulator's sample photo library,
|
||||||
|
* so we load a jpg bundled with the app instead. It is bundled via require()
|
||||||
|
* (resolved by Metro), so it survives `pm clear`, which Maestro's clearState
|
||||||
|
* runs at the start of every flow. An adb-seeded file in app-scoped external
|
||||||
|
* storage does not survive: pm clear wipes that directory each flow, so the
|
||||||
|
* seeded file is gone before the picker mock ever reads it.
|
||||||
|
*/
|
||||||
|
async function getAndroidFile() {
|
||||||
|
const asset = Asset.fromModule(
|
||||||
|
require('../../../assets/images/welcome-modal-bg.jpg'),
|
||||||
|
)
|
||||||
|
await asset.downloadAsync()
|
||||||
|
|
||||||
|
const path = asset.localUri!
|
||||||
|
const fileInfo = await getInfoAsync(path)
|
||||||
|
|
||||||
|
if (!fileInfo.exists) {
|
||||||
|
throw new Error('Failed to get file info')
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dimensions of the bundled asset (assets/images/welcome-modal-bg.jpg). Only
|
||||||
|
* used for downstream aspect-ratio display; the actual bytes are read from
|
||||||
|
* disk by compressIfNeeded.
|
||||||
|
*/
|
||||||
|
return await compressIfNeeded(
|
||||||
|
{
|
||||||
|
path,
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
size: fileInfo.size,
|
||||||
|
width: 1432,
|
||||||
|
height: 1025,
|
||||||
|
},
|
||||||
|
IMAGE_SIZE_CONFIG_2K_1MB,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function openPicker(): Promise<PickerImage[]> {
|
export async function openPicker(): Promise<PickerImage[]> {
|
||||||
return [await getFile()]
|
return [await getFile()]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import {type ImagePickerOptions} from 'expo-image-picker'
|
||||||
import {type OpenCropperOptions} from '@bsky.app/expo-image-crop-tool'
|
import {type OpenCropperOptions} from '@bsky.app/expo-image-crop-tool'
|
||||||
|
|
||||||
import {type PickerImage} from './picker.shared'
|
import {type PickerImage} from './picker.shared'
|
||||||
import {type CameraOpts} from './types'
|
|
||||||
|
|
||||||
export {openPicker, openUnifiedPicker} from './picker.shared'
|
export {openPicker, openUnifiedPicker} from './picker.shared'
|
||||||
|
|
||||||
export async function openCamera(_opts: CameraOpts): Promise<PickerImage> {
|
export async function openCamera(
|
||||||
|
_opts: ImagePickerOptions,
|
||||||
|
): Promise<PickerImage> {
|
||||||
throw new Error('openCamera is not supported on web')
|
throw new Error('openCamera is not supported on web')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,3 @@ export interface PickerOpts {
|
|||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
maxFiles?: number
|
maxFiles?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CameraOpts {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
freeStyleCropEnabled?: boolean
|
|
||||||
cropperCircleOverlay?: boolean
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export type CommonNavigatorParams = {
|
|||||||
ActivityPrivacySettings: undefined
|
ActivityPrivacySettings: undefined
|
||||||
ContentAndMediaSettings: undefined
|
ContentAndMediaSettings: undefined
|
||||||
NotificationSettings: undefined
|
NotificationSettings: undefined
|
||||||
|
ActivityNotificationSettings: undefined
|
||||||
InterestsSettings: undefined
|
InterestsSettings: undefined
|
||||||
AboutSettings: undefined
|
AboutSettings: undefined
|
||||||
AppIconSettings: undefined
|
AppIconSettings: undefined
|
||||||
|
|||||||
+149
-153
@@ -98,12 +98,12 @@ msgid "{0, plural, one {# hour} other {# hours}}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: labels.length
|
#. placeholder {0}: labels.length
|
||||||
#: src/components/moderation/PostAlerts.tsx:149
|
#: src/components/moderation/PostAlerts.tsx:157
|
||||||
msgid "{0, plural, one {# label applied to your post} other {# labels applied to your post}}"
|
msgid "{0, plural, one {# label applied to your post} other {# labels applied to your post}}"
|
||||||
msgstr "{0, plural, one {# label applied to your post} other {# labels applied to your post}}"
|
msgstr "{0, plural, one {# label applied to your post} other {# labels applied to your post}}"
|
||||||
|
|
||||||
#. placeholder {0}: labels.length
|
#. placeholder {0}: labels.length
|
||||||
#: src/components/moderation/PostAlerts.tsx:156
|
#: src/components/moderation/PostAlerts.tsx:164
|
||||||
msgid "{0, plural, one {# label applied} other {# labels applied}}"
|
msgid "{0, plural, one {# label applied} other {# labels applied}}"
|
||||||
msgstr "{0, plural, one {# label applied} other {# labels applied}}"
|
msgstr "{0, plural, one {# label applied} other {# labels applied}}"
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ msgid "{0} · {1}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: desc.name
|
#. placeholder {0}: desc.name
|
||||||
#: src/components/moderation/ContentHider.tsx:100
|
#: src/components/moderation/ContentHider.tsx:98
|
||||||
msgid "{0} (Account)"
|
msgid "{0} (Account)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ msgstr ""
|
|||||||
#. Number of images beyond the first 3
|
#. Number of images beyond the first 3
|
||||||
#. placeholder {0}: labels.length
|
#. placeholder {0}: labels.length
|
||||||
#. placeholder {0}: totalNumber - 3
|
#. placeholder {0}: totalNumber - 3
|
||||||
#: src/components/moderation/PostAlerts.tsx:155
|
#: src/components/moderation/PostAlerts.tsx:163
|
||||||
#: src/view/com/composer/ComposerReplyTo.tsx:278
|
#: src/view/com/composer/ComposerReplyTo.tsx:278
|
||||||
msgid "+{0}"
|
msgid "+{0}"
|
||||||
msgstr "+{0}"
|
msgstr "+{0}"
|
||||||
@@ -966,7 +966,7 @@ msgstr ""
|
|||||||
msgid "7 days"
|
msgid "7 days"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:17
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:27
|
||||||
msgid "A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens"
|
msgid "A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1333,7 +1333,7 @@ msgstr ""
|
|||||||
msgid "Add to lists"
|
msgid "Add to lists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/PostControls/BookmarkButton.tsx:144
|
#: src/components/PostControls/BookmarkButton.tsx:121
|
||||||
msgid "Add to saved posts"
|
msgid "Add to saved posts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1395,7 +1395,7 @@ msgstr ""
|
|||||||
msgid "Adult content"
|
msgid "Adult content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:131
|
#: src/components/moderation/ContentHider.tsx:129
|
||||||
#: src/lib/moderation/useGlobalLabelStrings.ts:34
|
#: src/lib/moderation/useGlobalLabelStrings.ts:34
|
||||||
#: src/lib/moderation/useModerationCauseDescription.ts:149
|
#: src/lib/moderation/useModerationCauseDescription.ts:149
|
||||||
#: src/view/com/composer/labels/LabelsBtn.tsx:123
|
#: src/view/com/composer/labels/LabelsBtn.tsx:123
|
||||||
@@ -1555,7 +1555,7 @@ msgstr ""
|
|||||||
msgid "Already have a code?"
|
msgid "Already have a code?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:192
|
#: src/components/WelcomeModal.tsx:189
|
||||||
msgid "Already have an account?"
|
msgid "Already have an account?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1609,7 +1609,7 @@ msgstr ""
|
|||||||
msgid "An error has occurred"
|
msgid "An error has occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:448
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:434
|
||||||
msgid "An error occurred"
|
msgid "An error occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1626,7 +1626,7 @@ msgstr ""
|
|||||||
msgid "An error occurred while fetching the feed."
|
msgid "An error occurred while fetching the feed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:374
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:377
|
||||||
msgid "An error occurred while generating your starter pack. Want to try again?"
|
msgid "An error occurred while generating your starter pack. Want to try again?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1679,7 +1679,7 @@ msgstr ""
|
|||||||
msgid "An illustration depicting user avatars flowing from a contact book into the Bluesky app"
|
msgid "An illustration depicting user avatars flowing from a contact book into the Bluesky app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:35
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:45
|
||||||
msgid "An illustration of several Bluesky posts alongside repost, like, and comment icons"
|
msgid "An illustration of several Bluesky posts alongside repost, like, and comment icons"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2114,8 +2114,8 @@ msgid "Before creating a post or replying, you must first verify your email."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/dialogs/StarterPackDialog.tsx:72
|
#: src/components/dialogs/StarterPackDialog.tsx:72
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:264
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:267
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:274
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:277
|
||||||
#: src/view/screens/Profile.tsx:350
|
#: src/view/screens/Profile.tsx:350
|
||||||
msgid "Before creating a starter pack, you must first verify your email."
|
msgid "Before creating a starter pack, you must first verify your email."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2130,10 +2130,10 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/dms/dialogs/NewChatDialog.tsx:155
|
#: src/components/dms/dialogs/NewChatDialog.tsx:155
|
||||||
#: src/components/dms/MessageProfileButton.tsx:60
|
#: src/components/dms/MessageProfileButton.tsx:60
|
||||||
#: src/screens/Messages/ChatList.tsx:169
|
#: src/screens/Messages/ChatList.tsx:168
|
||||||
#: src/screens/Messages/ChatList.tsx:187
|
#: src/screens/Messages/ChatList.tsx:186
|
||||||
#: src/screens/Messages/ChatList.tsx:288
|
#: src/screens/Messages/ChatList.tsx:287
|
||||||
#: src/screens/Messages/ChatList.tsx:544
|
#: src/screens/Messages/ChatList.tsx:543
|
||||||
#: src/screens/Messages/Conversation.tsx:203
|
#: src/screens/Messages/Conversation.tsx:203
|
||||||
#: src/screens/Messages/ConversationSettings/MemberMenu.tsx:99
|
#: src/screens/Messages/ConversationSettings/MemberMenu.tsx:99
|
||||||
msgid "Before you can message another user, you must first verify your email."
|
msgid "Before you can message another user, you must first verify your email."
|
||||||
@@ -2277,7 +2277,7 @@ msgstr ""
|
|||||||
msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours."
|
msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:203
|
#: src/screens/Profile/Sections/Labels.tsx:204
|
||||||
msgid "Blocking does not prevent this labeler from placing labels on your account."
|
msgid "Blocking does not prevent this labeler from placing labels on your account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2353,7 +2353,7 @@ msgstr ""
|
|||||||
msgid "Bluesky stores your contacts as encoded data. Removing your contacts will immediately delete this data."
|
msgid "Bluesky stores your contacts as encoded data. Removing your contacts will immediately delete this data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:340
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:343
|
||||||
msgid "Bluesky will choose a set of recommended accounts from people in your network."
|
msgid "Bluesky will choose a set of recommended accounts from people in your network."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2503,7 +2503,7 @@ msgstr ""
|
|||||||
msgid "By you"
|
msgid "By you"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/photos/OpenCameraBtn.tsx:69
|
#: src/view/com/composer/photos/OpenCameraBtn.tsx:65
|
||||||
msgid "Camera"
|
msgid "Camera"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2580,7 +2580,7 @@ msgstr ""
|
|||||||
msgid "Cancel reply"
|
msgid "Cancel reply"
|
||||||
msgstr "Cancel reply"
|
msgstr "Cancel reply"
|
||||||
|
|
||||||
#: src/screens/Search/Shell.tsx:595
|
#: src/screens/Search/Shell.tsx:584
|
||||||
msgid "Cancel search"
|
msgid "Cancel search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2738,9 +2738,9 @@ msgstr ""
|
|||||||
msgid "Chat not found."
|
msgid "Chat not found."
|
||||||
msgstr "Chat not found."
|
msgstr "Chat not found."
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:570
|
#: src/screens/Messages/ChatList.tsx:569
|
||||||
#: src/screens/Messages/ChatList.tsx:605
|
#: src/screens/Messages/ChatList.tsx:604
|
||||||
#: src/screens/Messages/ChatList.tsx:652
|
#: src/screens/Messages/ChatList.tsx:651
|
||||||
msgid "Chat options"
|
msgid "Chat options"
|
||||||
msgstr "Chat options"
|
msgstr "Chat options"
|
||||||
|
|
||||||
@@ -2763,10 +2763,10 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/dms/ConvoMenu.tsx:88
|
#: src/components/dms/ConvoMenu.tsx:88
|
||||||
#: src/Navigation.tsx:515
|
#: src/Navigation.tsx:515
|
||||||
#: src/screens/Messages/ChatList.tsx:98
|
#: src/screens/Messages/ChatList.tsx:97
|
||||||
#: src/screens/Messages/ChatList.tsx:102
|
#: src/screens/Messages/ChatList.tsx:101
|
||||||
#: src/screens/Messages/ChatList.tsx:672
|
#: src/screens/Messages/ChatList.tsx:671
|
||||||
#: src/screens/Messages/ChatList.tsx:682
|
#: src/screens/Messages/ChatList.tsx:681
|
||||||
#: src/screens/Messages/Settings.tsx:48
|
#: src/screens/Messages/Settings.tsx:48
|
||||||
msgid "Chat settings"
|
msgid "Chat settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2793,9 +2793,9 @@ msgctxt "toast"
|
|||||||
msgid "Chat unmuted"
|
msgid "Chat unmuted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:92
|
#: src/screens/Messages/ChatList.tsx:91
|
||||||
#: src/screens/Messages/ChatList.tsx:556
|
#: src/screens/Messages/ChatList.tsx:555
|
||||||
#: src/screens/Messages/ChatList.tsx:596
|
#: src/screens/Messages/ChatList.tsx:595
|
||||||
msgid "Chats"
|
msgid "Chats"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2832,7 +2832,7 @@ msgstr ""
|
|||||||
msgid "Choose Feeds"
|
msgid "Choose Feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:349
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:352
|
||||||
msgid "Choose for me"
|
msgid "Choose for me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3022,8 +3022,8 @@ msgstr ""
|
|||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Dialog/index.web.tsx:127
|
#: src/components/Dialog/index.web.tsx:129
|
||||||
#: src/components/Dialog/index.web.tsx:317
|
#: src/components/Dialog/index.web.tsx:339
|
||||||
msgid "Close active dialog"
|
msgid "Close active dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3085,7 +3085,7 @@ msgstr "Close the incoming requests banner"
|
|||||||
msgid "Close this dialog"
|
msgid "Close this dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:217
|
#: src/components/WelcomeModal.tsx:214
|
||||||
msgid "Close welcome modal"
|
msgid "Close welcome modal"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3278,7 +3278,7 @@ msgid "Content promoting or depicting self-harm"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ModerationDetailsDialog.tsx:70
|
#: src/components/moderation/ModerationDetailsDialog.tsx:70
|
||||||
#: src/components/moderation/ScreenHider.tsx:100
|
#: src/components/moderation/ScreenHider.tsx:98
|
||||||
#: src/lib/moderation/useGlobalLabelStrings.ts:22
|
#: src/lib/moderation/useGlobalLabelStrings.ts:22
|
||||||
#: src/lib/moderation/useModerationCauseDescription.ts:46
|
#: src/lib/moderation/useModerationCauseDescription.ts:46
|
||||||
msgid "Content Warning"
|
msgid "Content Warning"
|
||||||
@@ -3288,7 +3288,7 @@ msgstr ""
|
|||||||
msgid "Content warnings"
|
msgid "Content warnings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Menu/index.web.tsx:91
|
#: src/components/Menu/index.web.tsx:93
|
||||||
msgid "Context menu backdrop, click to close the menu."
|
msgid "Context menu backdrop, click to close the menu."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3363,7 +3363,7 @@ msgstr "Conversation not found."
|
|||||||
msgid "Copied build version to clipboard"
|
msgid "Copied build version to clipboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Search/Shell.tsx:515
|
#: src/screens/Search/Shell.tsx:504
|
||||||
msgid "Copied link to clipboard"
|
msgid "Copied link to clipboard"
|
||||||
msgstr "Copied link to clipboard"
|
msgstr "Copied link to clipboard"
|
||||||
|
|
||||||
@@ -3602,7 +3602,7 @@ msgstr "cows pigs"
|
|||||||
#: src/components/dialogs/StarterPackDialog.tsx:113
|
#: src/components/dialogs/StarterPackDialog.tsx:113
|
||||||
#: src/components/dialogs/StarterPackDialog.tsx:210
|
#: src/components/dialogs/StarterPackDialog.tsx:210
|
||||||
#: src/components/dms/InitiateChatFlow.tsx:502
|
#: src/components/dms/InitiateChatFlow.tsx:502
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:329
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:332
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3619,8 +3619,8 @@ msgstr ""
|
|||||||
msgid "Create a QR code for a starter pack"
|
msgid "Create a QR code for a starter pack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:207
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:210
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:316
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:319
|
||||||
#: src/Navigation.tsx:551
|
#: src/Navigation.tsx:551
|
||||||
msgid "Create a starter pack"
|
msgid "Create a starter pack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3630,12 +3630,12 @@ msgstr ""
|
|||||||
msgid "Create a Starter Pack"
|
msgid "Create a Starter Pack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:303
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:306
|
||||||
msgid "Create a starter pack for me"
|
msgid "Create a starter pack for me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:158
|
#: src/components/WelcomeModal.tsx:155
|
||||||
#: src/components/WelcomeModal.tsx:166
|
#: src/components/WelcomeModal.tsx:163
|
||||||
#: src/screens/Messages/JoinRequest.tsx:310
|
#: src/screens/Messages/JoinRequest.tsx:310
|
||||||
#: src/screens/Signup/index.tsx:141
|
#: src/screens/Signup/index.tsx:141
|
||||||
#: src/view/com/auth/SplashScreen.tsx:106
|
#: src/view/com/auth/SplashScreen.tsx:106
|
||||||
@@ -3665,7 +3665,7 @@ msgstr ""
|
|||||||
msgid "Create an avatar instead"
|
msgid "Create an avatar instead"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:214
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:217
|
||||||
msgid "Create another"
|
msgid "Create another"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3997,7 +3997,7 @@ msgstr "Disable"
|
|||||||
msgid "Disable 2FA"
|
msgid "Disable 2FA"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:414
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:400
|
||||||
msgid "Disable captions"
|
msgid "Disable captions"
|
||||||
msgstr "Disable captions"
|
msgstr "Disable captions"
|
||||||
|
|
||||||
@@ -4089,7 +4089,7 @@ msgstr ""
|
|||||||
msgid "Discover New Feeds"
|
msgid "Discover New Feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Dialog/index.tsx:413
|
#: src/components/Dialog/index.tsx:414
|
||||||
#: src/features/inviteFriends/components/FollowersPromoBanner.tsx:83
|
#: src/features/inviteFriends/components/FollowersPromoBanner.tsx:83
|
||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4137,7 +4137,7 @@ msgstr ""
|
|||||||
msgid "Display name"
|
msgid "Display name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:23
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:33
|
||||||
msgid "Ditch the trolls and clickbait. Find real people and conversations that matter to you."
|
msgid "Ditch the trolls and clickbait. Find real people and conversations that matter to you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4215,7 +4215,7 @@ msgstr ""
|
|||||||
msgid "Double tap or long press the message to add a reaction"
|
msgid "Double tap or long press the message to add a reaction"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Dialog/index.tsx:414
|
#: src/components/Dialog/index.tsx:415
|
||||||
msgid "Double tap to close the dialog"
|
msgid "Double tap to close the dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4504,7 +4504,7 @@ msgstr ""
|
|||||||
msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website."
|
msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx:61
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx:64
|
||||||
msgid "Embedded video player"
|
msgid "Embedded video player"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4528,7 +4528,7 @@ msgstr ""
|
|||||||
msgid "Enable beta features"
|
msgid "Enable beta features"
|
||||||
msgstr "Enable beta features"
|
msgstr "Enable beta features"
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:415
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:401
|
||||||
msgid "Enable captions"
|
msgid "Enable captions"
|
||||||
msgstr "Enable captions"
|
msgstr "Enable captions"
|
||||||
|
|
||||||
@@ -4576,7 +4576,7 @@ msgstr ""
|
|||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/Sections/Feed.tsx:131
|
#: src/screens/Profile/Sections/Feed.tsx:132
|
||||||
msgid "End of feed"
|
msgid "End of feed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4603,7 +4603,7 @@ msgstr ""
|
|||||||
msgid "Enter code"
|
msgid "Enter code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:433
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:419
|
||||||
msgid "Enter fullscreen"
|
msgid "Enter fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4733,7 +4733,7 @@ msgstr ""
|
|||||||
msgid "Excludes users you follow"
|
msgid "Excludes users you follow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:432
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:418
|
||||||
msgid "Exit fullscreen"
|
msgid "Exit fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4750,7 +4750,7 @@ msgstr ""
|
|||||||
msgid "Expand or collapse the full post you are replying to"
|
msgid "Expand or collapse the full post you are replying to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/ShowMoreTextButton.tsx:33
|
#: src/components/Post/ShowMoreTextButton.tsx:31
|
||||||
msgid "Expand post text"
|
msgid "Expand post text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4798,14 +4798,14 @@ msgid "Explicit sexual images."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Navigation.tsx:760
|
#: src/Navigation.tsx:760
|
||||||
#: src/screens/Search/Shell.tsx:552
|
#: src/screens/Search/Shell.tsx:541
|
||||||
#: src/view/shell/desktop/LeftNav.tsx:677
|
#: src/view/shell/desktop/LeftNav.tsx:677
|
||||||
#: src/view/shell/Drawer.tsx:473
|
#: src/view/shell/Drawer.tsx:473
|
||||||
msgid "Explore"
|
msgid "Explore"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:171
|
#: src/components/WelcomeModal.tsx:168
|
||||||
#: src/components/WelcomeModal.tsx:180
|
#: src/components/WelcomeModal.tsx:177
|
||||||
msgid "Explore the app"
|
msgid "Explore the app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4881,7 +4881,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/Lightbox/Lightbox.web.tsx:302
|
#: src/components/Lightbox/Lightbox.web.tsx:302
|
||||||
#: src/features/inviteFriends/InviteFriendsDialogInner.tsx:130
|
#: src/features/inviteFriends/InviteFriendsDialogInner.tsx:130
|
||||||
#: src/screens/Search/Shell.tsx:516
|
#: src/screens/Search/Shell.tsx:505
|
||||||
msgid "Failed to copy link"
|
msgid "Failed to copy link"
|
||||||
msgstr "Failed to copy link"
|
msgstr "Failed to copy link"
|
||||||
|
|
||||||
@@ -4969,7 +4969,7 @@ msgctxt "toast"
|
|||||||
msgid "Failed to leave group chat"
|
msgid "Failed to leave group chat"
|
||||||
msgstr "Failed to leave group chat"
|
msgstr "Failed to leave group chat"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:421
|
#: src/screens/Messages/ChatList.tsx:420
|
||||||
#: src/screens/Messages/Inbox.tsx:202
|
#: src/screens/Messages/Inbox.tsx:202
|
||||||
msgid "Failed to load conversations"
|
msgid "Failed to load conversations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5022,12 +5022,12 @@ msgstr ""
|
|||||||
msgid "Failed to lock group chat"
|
msgid "Failed to lock group chat"
|
||||||
msgstr "Failed to lock group chat"
|
msgstr "Failed to lock group chat"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:637
|
#: src/screens/Messages/ChatList.tsx:636
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:447
|
#: src/view/shell/bottom-bar/BottomBar.tsx:447
|
||||||
msgid "Failed to mark all chats as read"
|
msgid "Failed to mark all chats as read"
|
||||||
msgstr "Failed to mark all chats as read"
|
msgstr "Failed to mark all chats as read"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:646
|
#: src/screens/Messages/ChatList.tsx:645
|
||||||
#: src/screens/Messages/Inbox.tsx:304
|
#: src/screens/Messages/Inbox.tsx:304
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:456
|
#: src/view/shell/bottom-bar/BottomBar.tsx:456
|
||||||
msgid "Failed to mark all requests as read"
|
msgid "Failed to mark all requests as read"
|
||||||
@@ -5369,7 +5369,7 @@ msgstr ""
|
|||||||
msgid "Find people you know"
|
msgid "Find people you know"
|
||||||
msgstr "Find people you know"
|
msgstr "Find people you know"
|
||||||
|
|
||||||
#: src/screens/Search/Shell.tsx:765
|
#: src/screens/Search/Shell.tsx:753
|
||||||
msgid "Find posts, users, and feeds on Bluesky"
|
msgid "Find posts, users, and feeds on Bluesky"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5382,7 +5382,7 @@ msgstr ""
|
|||||||
msgid "Find your friends on Bluesky by verifying your phone number and matching with your contacts. We protect your information and you control what happens next. <0>Learn more</0>"
|
msgid "Find your friends on Bluesky by verifying your phone number and matching with your contacts. We protect your information and you control what happens next. <0>Learn more</0>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:21
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:31
|
||||||
msgid "Find your people"
|
msgid "Find your people"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5623,7 +5623,7 @@ msgstr ""
|
|||||||
msgid "Forever"
|
msgid "Forever"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:30
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:40
|
||||||
msgid "Forget the noise"
|
msgid "Forget the noise"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5642,7 +5642,7 @@ msgstr ""
|
|||||||
msgid "Four message bubbles representing a group chat. First message: \"Did you hear the news? Bluesky has group chats now!\" Second message: \"omg, no way\" Third message: \"Wow, 50 people in one chat!\" Fourth message: \"You can send invite links too!\""
|
msgid "Four message bubbles representing a group chat. First message: \"Did you hear the news? Bluesky has group chats now!\" Second message: \"omg, no way\" Third message: \"Wow, 50 people in one chat!\" Fourth message: \"You can send invite links too!\""
|
||||||
msgstr "Four message bubbles representing a group chat. First message: \"Did you hear the news? Bluesky has group chats now!\" Second message: \"omg, no way\" Third message: \"Wow, 50 people in one chat!\" Fourth message: \"You can send invite links too!\""
|
msgstr "Four message bubbles representing a group chat. First message: \"Did you hear the news? Bluesky has group chats now!\" Second message: \"omg, no way\" Third message: \"Wow, 50 people in one chat!\" Fourth message: \"You can send invite links too!\""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:12
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:22
|
||||||
msgid "Free your feed"
|
msgid "Free your feed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5669,7 +5669,7 @@ msgstr ""
|
|||||||
msgid "From these people"
|
msgid "From these people"
|
||||||
msgstr "From these people"
|
msgstr "From these people"
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:337
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:340
|
||||||
msgid "Generate a starter pack"
|
msgid "Generate a starter pack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5808,9 +5808,9 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/dialogs/LinkWarning.tsx:127
|
#: src/components/dialogs/LinkWarning.tsx:127
|
||||||
#: src/components/dialogs/LinkWarning.tsx:133
|
#: src/components/dialogs/LinkWarning.tsx:133
|
||||||
#: src/components/Layout/Header/index.tsx:133
|
#: src/components/Layout/Header/index.tsx:134
|
||||||
#: src/components/moderation/ScreenHider.tsx:161
|
#: src/components/moderation/ScreenHider.tsx:157
|
||||||
#: src/components/moderation/ScreenHider.tsx:170
|
#: src/components/moderation/ScreenHider.tsx:166
|
||||||
#: src/screens/Login/components/AuthLayout/Header/index.tsx:74
|
#: src/screens/Login/components/AuthLayout/Header/index.tsx:74
|
||||||
#: src/screens/Login/components/ConfirmHostingProviderDialog.tsx:160
|
#: src/screens/Login/components/ConfirmHostingProviderDialog.tsx:160
|
||||||
#: src/screens/Login/components/ConfirmHostingProviderDialog.tsx:163
|
#: src/screens/Login/components/ConfirmHostingProviderDialog.tsx:163
|
||||||
@@ -6145,7 +6145,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/interstitials/Trending.tsx:133
|
#: src/components/interstitials/Trending.tsx:133
|
||||||
#: src/components/interstitials/TrendingVideos.tsx:139
|
#: src/components/interstitials/TrendingVideos.tsx:139
|
||||||
#: src/components/moderation/ContentHider.tsx:220
|
#: src/components/moderation/ContentHider.tsx:217
|
||||||
#: src/components/moderation/LabelPreference.tsx:141
|
#: src/components/moderation/LabelPreference.tsx:141
|
||||||
#: src/components/moderation/PostHider.tsx:140
|
#: src/components/moderation/PostHider.tsx:140
|
||||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:820
|
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:820
|
||||||
@@ -6235,7 +6235,7 @@ msgstr ""
|
|||||||
msgid "Hide verification badges"
|
msgid "Hide verification badges"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:171
|
#: src/components/moderation/ContentHider.tsx:168
|
||||||
#: src/components/moderation/PostHider.tsx:94
|
#: src/components/moderation/PostHider.tsx:94
|
||||||
msgid "Hides the content"
|
msgid "Hides the content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -6543,7 +6543,7 @@ msgstr "In-app, push, everyone"
|
|||||||
msgid "In-app, push, people you follow"
|
msgid "In-app, push, people you follow"
|
||||||
msgstr "In-app, push, people you follow"
|
msgstr "In-app, push, people you follow"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:438
|
#: src/screens/Messages/ChatList.tsx:437
|
||||||
msgid "Inbox empty"
|
msgid "Inbox empty"
|
||||||
msgstr "Inbox empty"
|
msgstr "Inbox empty"
|
||||||
|
|
||||||
@@ -6826,11 +6826,11 @@ msgid "KWS website"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. placeholder {0}: sanitizeDisplayName(desc.source!)
|
#. placeholder {0}: sanitizeDisplayName(desc.source!)
|
||||||
#: src/components/moderation/ContentHider.tsx:251
|
#: src/components/moderation/ContentHider.tsx:245
|
||||||
msgid "Labeled by {0}."
|
msgid "Labeled by {0}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:249
|
#: src/components/moderation/ContentHider.tsx:243
|
||||||
msgid "Labeled by the author."
|
msgid "Labeled by the author."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6847,7 +6847,7 @@ msgstr ""
|
|||||||
msgid "Labels applied to this post"
|
msgid "Labels applied to this post"
|
||||||
msgstr "Labels applied to this post"
|
msgstr "Labels applied to this post"
|
||||||
|
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:194
|
#: src/screens/Profile/Sections/Labels.tsx:195
|
||||||
msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network."
|
msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6899,7 +6899,7 @@ msgctxt "english-only-resource"
|
|||||||
msgid "Learn more"
|
msgid "Learn more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ScreenHider.tsx:147
|
#: src/components/moderation/ScreenHider.tsx:143
|
||||||
msgid "Learn More"
|
msgid "Learn More"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6932,8 +6932,8 @@ msgstr ""
|
|||||||
msgid "Learn more about self hosting your PDS."
|
msgid "Learn more about self hosting your PDS."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:169
|
#: src/components/moderation/ContentHider.tsx:166
|
||||||
#: src/components/moderation/ContentHider.tsx:235
|
#: src/components/moderation/ContentHider.tsx:230
|
||||||
msgid "Learn more about the moderation applied to this content"
|
msgid "Learn more about the moderation applied to this content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6946,7 +6946,7 @@ msgid "Learn more about these changes and how to share your thoughts with us by
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/PostHider.tsx:116
|
#: src/components/moderation/PostHider.tsx:116
|
||||||
#: src/components/moderation/ScreenHider.tsx:134
|
#: src/components/moderation/ScreenHider.tsx:132
|
||||||
msgid "Learn more about this warning"
|
msgid "Learn more about this warning"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -6970,7 +6970,7 @@ msgid "Learn more in your <0>account settings.</0>"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/dialogs/ServerInput.tsx:222
|
#: src/components/dialogs/ServerInput.tsx:222
|
||||||
#: src/components/moderation/ContentHider.tsx:259
|
#: src/components/moderation/ContentHider.tsx:253
|
||||||
#: src/screens/Login/components/HostingProviderDialog.tsx:243
|
#: src/screens/Login/components/HostingProviderDialog.tsx:243
|
||||||
msgid "Learn more."
|
msgid "Learn more."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -7037,7 +7037,7 @@ msgstr "Left group chat."
|
|||||||
msgid "left to go."
|
msgid "left to go."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:354
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:357
|
||||||
msgid "Let me choose"
|
msgid "Let me choose"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -7288,7 +7288,7 @@ msgid "Load new notifications"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/ProfileFeed/index.tsx:206
|
#: src/screens/Profile/ProfileFeed/index.tsx:206
|
||||||
#: src/screens/Profile/Sections/Feed.tsx:117
|
#: src/screens/Profile/Sections/Feed.tsx:118
|
||||||
#: src/screens/ProfileList/FeedSection.tsx:113
|
#: src/screens/ProfileList/FeedSection.tsx:113
|
||||||
#: src/view/com/feeds/FeedPage.tsx:168
|
#: src/view/com/feeds/FeedPage.tsx:168
|
||||||
msgid "Load new posts"
|
msgid "Load new posts"
|
||||||
@@ -7382,7 +7382,7 @@ msgstr "Love GIFs"
|
|||||||
msgid "Make adjustments to email settings for your account"
|
msgid "Make adjustments to email settings for your account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:311
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:314
|
||||||
msgid "Make one for me"
|
msgid "Make one for me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -7413,15 +7413,15 @@ msgstr "Manual"
|
|||||||
msgid "Mark all as read"
|
msgid "Mark all as read"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:656
|
#: src/screens/Messages/ChatList.tsx:655
|
||||||
#: src/screens/Messages/ChatList.tsx:660
|
#: src/screens/Messages/ChatList.tsx:659
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:465
|
#: src/view/shell/bottom-bar/BottomBar.tsx:465
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:469
|
#: src/view/shell/bottom-bar/BottomBar.tsx:469
|
||||||
msgid "Mark all chats as read"
|
msgid "Mark all chats as read"
|
||||||
msgstr "Mark all chats as read"
|
msgstr "Mark all chats as read"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:664
|
#: src/screens/Messages/ChatList.tsx:663
|
||||||
#: src/screens/Messages/ChatList.tsx:668
|
#: src/screens/Messages/ChatList.tsx:667
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:473
|
#: src/view/shell/bottom-bar/BottomBar.tsx:473
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:477
|
#: src/view/shell/bottom-bar/BottomBar.tsx:477
|
||||||
msgid "Mark all requests as read"
|
msgid "Mark all requests as read"
|
||||||
@@ -7436,12 +7436,12 @@ msgstr ""
|
|||||||
msgid "Marked all as read"
|
msgid "Marked all as read"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:634
|
#: src/screens/Messages/ChatList.tsx:633
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:444
|
#: src/view/shell/bottom-bar/BottomBar.tsx:444
|
||||||
msgid "Marked all chats as read"
|
msgid "Marked all chats as read"
|
||||||
msgstr "Marked all chats as read"
|
msgstr "Marked all chats as read"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:643
|
#: src/screens/Messages/ChatList.tsx:642
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:453
|
#: src/view/shell/bottom-bar/BottomBar.tsx:453
|
||||||
msgid "Marked all requests as read"
|
msgid "Marked all requests as read"
|
||||||
msgstr "Marked all requests as read"
|
msgstr "Marked all requests as read"
|
||||||
@@ -7888,11 +7888,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/components/dms/dialogs/NewChatDialog.tsx:169
|
#: src/components/dms/dialogs/NewChatDialog.tsx:169
|
||||||
#: src/components/dms/dialogs/NewChatDialog.tsx:184
|
#: src/components/dms/dialogs/NewChatDialog.tsx:184
|
||||||
|
#: src/screens/Messages/ChatList.tsx:231
|
||||||
#: src/screens/Messages/ChatList.tsx:232
|
#: src/screens/Messages/ChatList.tsx:232
|
||||||
#: src/screens/Messages/ChatList.tsx:233
|
#: src/screens/Messages/ChatList.tsx:455
|
||||||
#: src/screens/Messages/ChatList.tsx:456
|
#: src/screens/Messages/ChatList.tsx:456
|
||||||
#: src/screens/Messages/ChatList.tsx:457
|
#: src/screens/Messages/ChatList.tsx:580
|
||||||
#: src/screens/Messages/ChatList.tsx:581
|
|
||||||
msgid "New chat"
|
msgid "New chat"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8052,7 +8052,7 @@ msgstr ""
|
|||||||
msgid "Night"
|
msgid "Night"
|
||||||
msgstr "Night"
|
msgstr "Night"
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:32
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:42
|
||||||
msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention."
|
msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8138,7 +8138,7 @@ msgstr ""
|
|||||||
msgid "No messages yet"
|
msgid "No messages yet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:14
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:24
|
||||||
msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you."
|
msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8179,12 +8179,12 @@ msgstr "No one can send messages"
|
|||||||
msgid "No posts here"
|
msgid "No posts here"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Profile/Sections/Feed.tsx:81
|
#: src/screens/Profile/Sections/Feed.tsx:82
|
||||||
#: src/view/screens/Profile.tsx:430
|
#: src/view/screens/Profile.tsx:430
|
||||||
msgid "No posts yet"
|
msgid "No posts yet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostQuotes.tsx:107
|
#: src/view/com/post-thread/PostQuotes.tsx:114
|
||||||
msgid "No quotes yet"
|
msgid "No quotes yet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8273,7 +8273,7 @@ msgstr ""
|
|||||||
msgid "Nobody has liked this yet. Maybe you should be the first!"
|
msgid "Nobody has liked this yet. Maybe you should be the first!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostQuotes.tsx:109
|
#: src/view/com/post-thread/PostQuotes.tsx:116
|
||||||
msgid "Nobody has quoted this yet. Maybe you should be the first!"
|
msgid "Nobody has quoted this yet. Maybe you should be the first!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8514,7 +8514,7 @@ msgstr "Only posts with videos"
|
|||||||
msgid "Only replies"
|
msgid "Only replies"
|
||||||
msgstr "Only replies"
|
msgstr "Only replies"
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:41
|
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:39
|
||||||
msgid "Only WebVTT (.vtt) files are supported"
|
msgid "Only WebVTT (.vtt) files are supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8527,8 +8527,8 @@ msgid "Oops, this profile doesn't exist. Try scanning another one."
|
|||||||
msgstr "Oops, this profile doesn't exist. Try scanning another one."
|
msgstr "Oops, this profile doesn't exist. Try scanning another one."
|
||||||
|
|
||||||
#: src/components/Lists.tsx:184
|
#: src/components/Lists.tsx:184
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:363
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:366
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:372
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:375
|
||||||
#: src/screens/Settings/AppPasswords.tsx:59
|
#: src/screens/Settings/AppPasswords.tsx:59
|
||||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:108
|
#: src/screens/Settings/components/ChangeHandleDialog.tsx:108
|
||||||
#: src/view/screens/Profile.tsx:132
|
#: src/view/screens/Profile.tsx:132
|
||||||
@@ -8565,7 +8565,7 @@ msgstr ""
|
|||||||
msgid "Open draft"
|
msgid "Open draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Layout/Header/index.tsx:167
|
#: src/components/Layout/Header/index.tsx:168
|
||||||
msgid "Open drawer menu"
|
msgid "Open drawer menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8694,7 +8694,7 @@ msgstr ""
|
|||||||
msgid "Opens alt text dialog"
|
msgid "Opens alt text dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/photos/OpenCameraBtn.tsx:70
|
#: src/view/com/composer/photos/OpenCameraBtn.tsx:66
|
||||||
msgid "Opens camera on device"
|
msgid "Opens camera on device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8912,7 +8912,7 @@ msgid "Password updated!"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:395
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:381
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -8920,7 +8920,7 @@ msgstr ""
|
|||||||
msgid "Pause GIF"
|
msgid "Pause GIF"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:346
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:332
|
||||||
msgid "Pause video"
|
msgid "Pause video"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9058,7 +9058,7 @@ msgid "Pinned to your feeds"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:168
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:396
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:382
|
||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9072,7 +9072,7 @@ msgid "Play GIF"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/index.tsx:169
|
#: src/components/Post/Embed/VideoEmbed/index.tsx:169
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:347
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:333
|
||||||
msgid "Play video"
|
msgid "Play video"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9364,10 +9364,6 @@ msgctxt "toast"
|
|||||||
msgid "Post pinned"
|
msgid "Post pinned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/PostControls/BookmarkButton.tsx:67
|
|
||||||
msgid "Post saved"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/state/queries/pinned-post.ts:61
|
#: src/state/queries/pinned-post.ts:61
|
||||||
msgctxt "toast"
|
msgctxt "toast"
|
||||||
msgid "Post unpinned"
|
msgid "Post unpinned"
|
||||||
@@ -9682,11 +9678,11 @@ msgstr ""
|
|||||||
msgid "Read the Bluesky Terms of Service"
|
msgid "Read the Bluesky Terms of Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:149
|
#: src/components/WelcomeModal.tsx:146
|
||||||
msgid "Real conversations."
|
msgid "Real conversations."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:147
|
#: src/components/WelcomeModal.tsx:144
|
||||||
msgid "Real people."
|
msgid "Real people."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -9743,7 +9739,7 @@ msgstr ""
|
|||||||
msgid "Reject join request"
|
msgid "Reject join request"
|
||||||
msgstr "Reject join request"
|
msgstr "Reject join request"
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:425
|
#: src/screens/Messages/ChatList.tsx:424
|
||||||
#: src/screens/Messages/Inbox.tsx:206
|
#: src/screens/Messages/Inbox.tsx:206
|
||||||
msgid "Reload conversations"
|
msgid "Reload conversations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9857,7 +9853,7 @@ msgstr ""
|
|||||||
msgid "Remove from saved feeds"
|
msgid "Remove from saved feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/PostControls/BookmarkButton.tsx:143
|
#: src/components/PostControls/BookmarkButton.tsx:120
|
||||||
#: src/screens/Bookmarks.tsx:252
|
#: src/screens/Bookmarks.tsx:252
|
||||||
msgid "Remove from saved posts"
|
msgid "Remove from saved posts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9938,7 +9934,7 @@ msgstr ""
|
|||||||
msgid "Removed from saved feeds"
|
msgid "Removed from saved feeds"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/PostControls/BookmarkButton.tsx:107
|
#: src/components/PostControls/BookmarkButton.tsx:88
|
||||||
#: src/screens/Bookmarks.tsx:211
|
#: src/screens/Bookmarks.tsx:211
|
||||||
msgid "Removed from saved posts"
|
msgid "Removed from saved posts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10353,10 +10349,10 @@ msgstr ""
|
|||||||
#: src/components/moderation/ReportDialog/index.tsx:297
|
#: src/components/moderation/ReportDialog/index.tsx:297
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:56
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:56
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:59
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:59
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:377
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:380
|
||||||
#: src/screens/Login/LoginForm.tsx:531
|
#: src/screens/Login/LoginForm.tsx:531
|
||||||
#: src/screens/Login/LoginForm.tsx:537
|
#: src/screens/Login/LoginForm.tsx:537
|
||||||
#: src/screens/Messages/ChatList.tsx:430
|
#: src/screens/Messages/ChatList.tsx:429
|
||||||
#: src/screens/Messages/components/MessageListError.tsx:24
|
#: src/screens/Messages/components/MessageListError.tsx:24
|
||||||
#: src/screens/Messages/Inbox.tsx:211
|
#: src/screens/Messages/Inbox.tsx:211
|
||||||
#: src/screens/Messages/JoinRequests.tsx:361
|
#: src/screens/Messages/JoinRequests.tsx:361
|
||||||
@@ -10515,8 +10511,8 @@ msgstr ""
|
|||||||
msgid "Say hello!"
|
msgid "Say hello!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Messages/ChatList.tsx:223
|
#: src/screens/Messages/ChatList.tsx:222
|
||||||
#: src/screens/Messages/ChatList.tsx:447
|
#: src/screens/Messages/ChatList.tsx:446
|
||||||
msgid "Say hi to someone"
|
msgid "Say hi to someone"
|
||||||
msgstr "Say hi to someone"
|
msgstr "Say hi to someone"
|
||||||
|
|
||||||
@@ -10559,9 +10555,9 @@ msgstr ""
|
|||||||
#: src/components/forms/SearchInput.tsx:53
|
#: src/components/forms/SearchInput.tsx:53
|
||||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:218
|
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:218
|
||||||
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:224
|
#: src/screens/Search/components/AdvancedSearchDialog/index.tsx:224
|
||||||
#: src/screens/Search/Shell.tsx:552
|
#: src/screens/Search/Shell.tsx:541
|
||||||
#: src/screens/Search/Shell.tsx:616
|
#: src/screens/Search/Shell.tsx:604
|
||||||
#: src/screens/Search/Shell.tsx:753
|
#: src/screens/Search/Shell.tsx:741
|
||||||
#: src/view/shell/bottom-bar/BottomBar.tsx:216
|
#: src/view/shell/bottom-bar/BottomBar.tsx:216
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -10775,8 +10771,8 @@ msgstr ""
|
|||||||
msgid "Select app language"
|
msgid "Select app language"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:60
|
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:58
|
||||||
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:67
|
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:65
|
||||||
msgid "Select caption file (.vtt)"
|
msgid "Select caption file (.vtt)"
|
||||||
msgstr "Select caption file (.vtt)"
|
msgstr "Select caption file (.vtt)"
|
||||||
|
|
||||||
@@ -11168,8 +11164,8 @@ msgstr ""
|
|||||||
msgid "Share this feed"
|
msgid "Share this feed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Search/Shell.tsx:568
|
#: src/screens/Search/Shell.tsx:557
|
||||||
#: src/screens/Search/Shell.tsx:638
|
#: src/screens/Search/Shell.tsx:626
|
||||||
msgid "Share this search"
|
msgid "Share this search"
|
||||||
msgstr "Share this search"
|
msgstr "Share this search"
|
||||||
|
|
||||||
@@ -11214,7 +11210,7 @@ msgstr "Sharing your age range reveals only the range associated with your Apple
|
|||||||
msgid "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
msgid "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||||
msgstr "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
msgstr "Sharing your age range reveals only the range associated with your Google Account – for example, that you’re at least 18. <0>Your exact age and birthday are never shared,</0> and this data never leaves this device. Therefore, it only enables access on this device. Alternatively, <1>you can use our trusted partner, KWS</1>, to complete your verification and enable access on all platforms."
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:220
|
#: src/components/moderation/ContentHider.tsx:217
|
||||||
#: src/components/moderation/LabelPreference.tsx:143
|
#: src/components/moderation/LabelPreference.tsx:143
|
||||||
#: src/components/moderation/PostHider.tsx:140
|
#: src/components/moderation/PostHider.tsx:140
|
||||||
msgid "Show"
|
msgid "Show"
|
||||||
@@ -11224,8 +11220,8 @@ msgstr ""
|
|||||||
msgid "Show alt text"
|
msgid "Show alt text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ScreenHider.tsx:179
|
#: src/components/moderation/ScreenHider.tsx:175
|
||||||
#: src/components/moderation/ScreenHider.tsx:182
|
#: src/components/moderation/ScreenHider.tsx:178
|
||||||
#: src/features/liveNow/components/LiveStatusDialog.tsx:318
|
#: src/features/liveNow/components/LiveStatusDialog.tsx:318
|
||||||
#: src/features/liveNow/components/LiveStatusDialog.tsx:322
|
#: src/features/liveNow/components/LiveStatusDialog.tsx:322
|
||||||
#: src/screens/List/ListHiddenScreen.tsx:194
|
#: src/screens/List/ListHiddenScreen.tsx:194
|
||||||
@@ -11269,9 +11265,9 @@ msgstr ""
|
|||||||
msgid "Show lists of users to select from"
|
msgid "Show lists of users to select from"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/ShowMoreTextButton.tsx:52
|
#: src/components/Post/ShowMoreTextButton.tsx:50
|
||||||
msgid "Show More"
|
msgid "Show more"
|
||||||
msgstr ""
|
msgstr "Show more"
|
||||||
|
|
||||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:594
|
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:594
|
||||||
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:596
|
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:596
|
||||||
@@ -11336,15 +11332,15 @@ msgstr ""
|
|||||||
msgid "Shows other accounts you can switch to"
|
msgid "Shows other accounts you can switch to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ContentHider.tsx:172
|
#: src/components/moderation/ContentHider.tsx:169
|
||||||
#: src/components/moderation/PostHider.tsx:94
|
#: src/components/moderation/PostHider.tsx:94
|
||||||
msgid "Shows the content"
|
msgid "Shows the content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/dialogs/Signin.tsx:98
|
#: src/components/dialogs/Signin.tsx:98
|
||||||
#: src/components/dialogs/Signin.tsx:100
|
#: src/components/dialogs/Signin.tsx:100
|
||||||
#: src/components/WelcomeModal.tsx:197
|
#: src/components/WelcomeModal.tsx:194
|
||||||
#: src/components/WelcomeModal.tsx:209
|
#: src/components/WelcomeModal.tsx:206
|
||||||
#: src/screens/Hashtag.tsx:231
|
#: src/screens/Hashtag.tsx:231
|
||||||
#: src/screens/Login/index.tsx:143
|
#: src/screens/Login/index.tsx:143
|
||||||
#: src/screens/Login/index.tsx:165
|
#: src/screens/Login/index.tsx:165
|
||||||
@@ -11431,7 +11427,7 @@ msgstr ""
|
|||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Sign up"
|
msgstr "Sign up"
|
||||||
|
|
||||||
#: src/components/moderation/ScreenHider.tsx:98
|
#: src/components/moderation/ScreenHider.tsx:96
|
||||||
#: src/lib/moderation/useGlobalLabelStrings.ts:28
|
#: src/lib/moderation/useGlobalLabelStrings.ts:28
|
||||||
msgid "Sign-in Required"
|
msgid "Sign-in Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -11494,7 +11490,7 @@ msgstr ""
|
|||||||
msgid "So many thoughts, you should post one"
|
msgid "So many thoughts, you should post one"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/WelcomeModal.tsx:151
|
#: src/components/WelcomeModal.tsx:148
|
||||||
msgid "Social media you control."
|
msgid "Social media you control."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -11588,7 +11584,7 @@ msgid "Something went wrong, please try again"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Moderation/index.tsx:112
|
#: src/screens/Moderation/index.tsx:112
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:184
|
#: src/screens/Profile/Sections/Labels.tsx:185
|
||||||
#: src/screens/Settings/BetaFeaturesSettings.tsx:80
|
#: src/screens/Settings/BetaFeaturesSettings.tsx:80
|
||||||
#: src/screens/Settings/components/BetaFeaturesFeedbackDialog.tsx:84
|
#: src/screens/Settings/components/BetaFeaturesFeedbackDialog.tsx:84
|
||||||
msgid "Something went wrong, please try again."
|
msgid "Something went wrong, please try again."
|
||||||
@@ -11715,7 +11711,7 @@ msgstr ""
|
|||||||
msgid "Starter Packs"
|
msgid "Starter Packs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:295
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:298
|
||||||
msgid "Starter packs let you easily share your favorite feeds and people with your friends."
|
msgid "Starter packs let you easily share your favorite feeds and people with your friends."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -11806,7 +11802,7 @@ msgid "Subscribe to {publicationTitle} on {0}"
|
|||||||
msgstr "Subscribe to {publicationTitle} on {0}"
|
msgstr "Subscribe to {publicationTitle} on {0}"
|
||||||
|
|
||||||
#. placeholder {0}: labelerInfo.creator.handle
|
#. placeholder {0}: labelerInfo.creator.handle
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:231
|
#: src/screens/Profile/Sections/Labels.tsx:232
|
||||||
msgid "Subscribe to @{0} to use these labels:"
|
msgid "Subscribe to @{0} to use these labels:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12108,7 +12104,7 @@ msgstr ""
|
|||||||
msgid "That username is already taken"
|
msgid "That username is already taken"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostQuotes.tsx:135
|
#: src/view/com/post-thread/PostQuotes.tsx:142
|
||||||
msgid "That's all, folks!"
|
msgid "That's all, folks!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12387,7 +12383,7 @@ msgstr "They own this chat"
|
|||||||
msgid "They won’t be able to rejoin unless you invite them again."
|
msgid "They won’t be able to rejoin unless you invite them again."
|
||||||
msgstr "They won’t be able to rejoin unless you invite them again."
|
msgstr "They won’t be able to rejoin unless you invite them again."
|
||||||
|
|
||||||
#: src/components/moderation/ScreenHider.tsx:118
|
#: src/components/moderation/ScreenHider.tsx:116
|
||||||
msgid "This {screenDescription} has been flagged:"
|
msgid "This {screenDescription} has been flagged:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12403,7 +12399,7 @@ msgstr "This account has been marked as automated by its owner."
|
|||||||
msgid "This account has one or more attempted verifications, but it is not currently verified."
|
msgid "This account has one or more attempted verifications, but it is not currently verified."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/moderation/ScreenHider.tsx:113
|
#: src/components/moderation/ScreenHider.tsx:111
|
||||||
msgid "This account has requested that users sign in to view their profile."
|
msgid "This account has requested that users sign in to view their profile."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12591,7 +12587,7 @@ msgstr ""
|
|||||||
msgid "This label was applied to the entire user account and will appear on all posts."
|
msgid "This label was applied to the entire user account and will appear on all posts."
|
||||||
msgstr "This label was applied to the entire user account and will appear on all posts."
|
msgstr "This label was applied to the entire user account and will appear on all posts."
|
||||||
|
|
||||||
#: src/screens/Profile/Sections/Labels.tsx:218
|
#: src/screens/Profile/Sections/Labels.tsx:219
|
||||||
msgid "This labeler hasn't declared what labels it publishes, and may not be active."
|
msgid "This labeler hasn't declared what labels it publishes, and may not be active."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -13187,7 +13183,7 @@ msgstr "Unmute this group chat"
|
|||||||
msgid "Unmute thread"
|
msgid "Unmute thread"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:344
|
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:330
|
||||||
msgid "Unmute video"
|
msgid "Unmute video"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -13253,7 +13249,7 @@ msgstr ""
|
|||||||
msgid "Unsubscribed from list"
|
msgid "Unsubscribed from list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/composer/text-input/TextInput.tsx:128
|
#: src/view/com/composer/text-input/TextInput.tsx:127
|
||||||
msgid "Unsupported clipboard content"
|
msgid "Unsupported clipboard content"
|
||||||
msgstr "Unsupported clipboard content"
|
msgstr "Unsupported clipboard content"
|
||||||
|
|
||||||
@@ -13725,7 +13721,7 @@ msgid "View details"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/ViewFullThread.tsx:31
|
#: src/view/com/posts/ViewFullThread.tsx:31
|
||||||
#: src/view/com/posts/ViewFullThread.tsx:64
|
#: src/view/com/posts/ViewFullThread.tsx:65
|
||||||
msgid "View full thread"
|
msgid "View full thread"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14156,7 +14152,7 @@ msgid "Who can verify?"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Home/NoFeedsPinned.tsx:80
|
#: src/screens/Home/NoFeedsPinned.tsx:80
|
||||||
#: src/screens/Messages/ChatList.tsx:410
|
#: src/screens/Messages/ChatList.tsx:409
|
||||||
#: src/screens/Messages/Inbox.tsx:191
|
#: src/screens/Messages/Inbox.tsx:191
|
||||||
msgid "Whoops!"
|
msgid "Whoops!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -14558,7 +14554,7 @@ msgstr ""
|
|||||||
msgid "You have unsaved changes. Would you like to save them before viewing your drafts?"
|
msgid "You have unsaved changes. Would you like to save them before viewing your drafts?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:292
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:295
|
||||||
msgid "You haven't created a starter pack yet!"
|
msgid "You haven't created a starter pack yet!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14617,7 +14613,7 @@ msgstr "You must be a chat owner to remove a member."
|
|||||||
msgid "You must be at least 13 years old to use Bluesky. Read our <0>Terms of Service</0> for more information."
|
msgid "You must be at least 13 years old to use Bluesky. Read our <0>Terms of Service</0> for more information."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/StarterPack/ProfileStarterPacks.tsx:365
|
#: src/components/StarterPack/ProfileStarterPacks.tsx:368
|
||||||
msgid "You must be following at least seven other people to generate a starter pack."
|
msgid "You must be following at least seven other people to generate a starter pack."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -14975,11 +14971,11 @@ msgid "Your preferred language"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx:102
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx:102
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.web.tsx:53
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.web.tsx:54
|
||||||
msgid "Your profile picture"
|
msgid "Your profile picture"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:26
|
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:36
|
||||||
msgid "Your profile picture surrounded by concentric circles of other users' profile pictures"
|
msgid "Your profile picture surrounded by concentric circles of other users' profile pictures"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Used ONLY by the Android typecheck pass (tsconfig.check.android.json) - it
|
||||||
|
* is excluded from the other passes and has no runtime effect.
|
||||||
|
*
|
||||||
|
* react-native-device-attest's Android variant implements only the Play
|
||||||
|
* Integrity statics, so the iOS-only getDeviceCheckToken (whose app call
|
||||||
|
* site is IS_IOS-guarded) fails to resolve under .android module suffixes.
|
||||||
|
* Pin the module to its platform-neutral base class, which declares the
|
||||||
|
* full API surface.
|
||||||
|
*/
|
||||||
|
declare module 'react-native-device-attest' {
|
||||||
|
export {DeviceAttestBase as default} from 'react-native-device-attest/build/DeviceAttestBase'
|
||||||
|
}
|
||||||
Vendored
+102
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Used ONLY by the web typecheck pass (tsconfig.check.web.json) - it is
|
||||||
|
* excluded from the other passes and has no runtime effect.
|
||||||
|
*
|
||||||
|
* Some packages publish .web.d.ts declarations that diverge from the API
|
||||||
|
* the app is written against (or, worse, raw TypeScript sources that break
|
||||||
|
* under `moduleSuffixes: [".web", ""]`), so this pins them to their native
|
||||||
|
* declarations. react-native-svg has the same problem, handled by deleting
|
||||||
|
* its divergent web declarations in patches/react-native-svg@15.12.1.patch
|
||||||
|
* so resolution falls through to the native ones.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Side-effect CSS imports in .web files are handled by the bundler.
|
||||||
|
*/
|
||||||
|
declare module '*.css'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* expo-file-system's declarations build File/Directory on top of
|
||||||
|
* `./ExpoFileSystem`, which remaps to a web shim whose classes are empty.
|
||||||
|
* The fully-typed base classes live in ExpoFileSystem.types (no .web
|
||||||
|
* sibling), so mirror the FileSystem.d.ts wrapper classes on top of those.
|
||||||
|
*/
|
||||||
|
declare module 'expo-file-system' {
|
||||||
|
import {
|
||||||
|
Directory as ExpoFileSystemDirectory,
|
||||||
|
File as ExpoFileSystemFile,
|
||||||
|
} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||||
|
export {
|
||||||
|
type DirectoryCreateOptions,
|
||||||
|
type DirectoryInfo,
|
||||||
|
type DownloadOptions,
|
||||||
|
EncodingType,
|
||||||
|
type FileCreateOptions,
|
||||||
|
FileHandle,
|
||||||
|
type FileInfo,
|
||||||
|
type FileWriteOptions,
|
||||||
|
type InfoOptions,
|
||||||
|
type PathInfo,
|
||||||
|
} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||||
|
import {type PathInfo as ExpoPathInfo} from 'expo-file-system/build/ExpoFileSystem.types'
|
||||||
|
import {PathUtilities} from 'expo-file-system/build/pathUtilities'
|
||||||
|
|
||||||
|
export class Paths extends PathUtilities {
|
||||||
|
static get cache(): Directory
|
||||||
|
static get bundle(): Directory
|
||||||
|
static get document(): Directory
|
||||||
|
static get appleSharedContainers(): Record<string, Directory>
|
||||||
|
static get totalDiskSpace(): number
|
||||||
|
static get availableDiskSpace(): number
|
||||||
|
static info(...uris: string[]): ExpoPathInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export class File extends ExpoFileSystemFile implements Blob {
|
||||||
|
constructor(...uris: (string | File | Directory)[])
|
||||||
|
get parentDirectory(): Directory
|
||||||
|
get extension(): string
|
||||||
|
get name(): string
|
||||||
|
readableStream(): ReadableStream<Uint8Array<ArrayBuffer>>
|
||||||
|
writableStream(): WritableStream<Uint8Array<ArrayBufferLike>>
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer>
|
||||||
|
stream(): ReadableStream<Uint8Array<ArrayBuffer>>
|
||||||
|
slice(start?: number, end?: number, contentType?: string): Blob
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Directory extends ExpoFileSystemDirectory {
|
||||||
|
constructor(...uris: (string | File | Directory)[])
|
||||||
|
get parentDirectory(): Directory
|
||||||
|
list(): (Directory | File)[]
|
||||||
|
get name(): string
|
||||||
|
createFile(name: string, mimeType: string | null): File
|
||||||
|
createDirectory(name: string): Directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* expo-image-manipulator's web declarations export the module class where
|
||||||
|
* the native ones export an instance, hiding instance methods such as
|
||||||
|
* `manipulate` from `typeof ImageManipulator`. Mirror the native surface.
|
||||||
|
*/
|
||||||
|
declare module 'expo-image-manipulator' {
|
||||||
|
import {type ImageManipulator as ImageManipulatorModule} from 'expo-image-manipulator/build/ImageManipulator.types'
|
||||||
|
export const ImageManipulator: ImageManipulatorModule
|
||||||
|
export {
|
||||||
|
manipulateAsync,
|
||||||
|
useImageManipulator,
|
||||||
|
} from 'expo-image-manipulator/build/ImageManipulator'
|
||||||
|
export {
|
||||||
|
type Action,
|
||||||
|
type ActionCrop,
|
||||||
|
type ActionExtent,
|
||||||
|
type ActionFlip,
|
||||||
|
type ActionResize,
|
||||||
|
type ActionRotate,
|
||||||
|
FlipType,
|
||||||
|
type ImageResult,
|
||||||
|
SaveFormat,
|
||||||
|
type SaveOptions,
|
||||||
|
} from 'expo-image-manipulator/build/ImageManipulator.types'
|
||||||
|
export {type ImageManipulatorContext} from 'expo-image-manipulator/build/ImageManipulatorContext'
|
||||||
|
export {type ImageRef} from 'expo-image-manipulator/build/ImageRef'
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ export const router = new Router<AllNavigatableRoutes>({
|
|||||||
AboutSettings: '/settings/about',
|
AboutSettings: '/settings/about',
|
||||||
AppIconSettings: '/settings/app-icon',
|
AppIconSettings: '/settings/app-icon',
|
||||||
NotificationSettings: '/settings/notifications',
|
NotificationSettings: '/settings/notifications',
|
||||||
|
ActivityNotificationSettings: '/settings/notifications/activity',
|
||||||
FindContactsSettings: '/settings/find-contacts',
|
FindContactsSettings: '/settings/find-contacts',
|
||||||
// support
|
// support
|
||||||
Support: '/support',
|
Support: '/support',
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useAnimatedRef} from 'react-native-reanimated'
|
|
||||||
import {type ChatBskyActorGetStatus, type ChatBskyConvoDefs} from '@atproto/api'
|
import {type ChatBskyActorGetStatus, type ChatBskyConvoDefs} from '@atproto/api'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {
|
import {
|
||||||
@@ -275,7 +274,7 @@ export function ChatList({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const aa = useAgeAssurance()
|
const aa = useAgeAssurance()
|
||||||
const scrollElRef: ListRef = useAnimatedRef()
|
const scrollElRef: ListRef = useRef(null)
|
||||||
const {isWithinSplitView} = useIsWithinSplitView()
|
const {isWithinSplitView} = useIsWithinSplitView()
|
||||||
|
|
||||||
const openChatControl = useCallback(() => {
|
const openChatControl = useCallback(() => {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import Animated, {
|
|||||||
runOnJS,
|
runOnJS,
|
||||||
type ScrollEvent,
|
type ScrollEvent,
|
||||||
type SharedValue,
|
type SharedValue,
|
||||||
useAnimatedRef,
|
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useDerivedValue,
|
useDerivedValue,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
@@ -154,7 +153,7 @@ export function MessagesList({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
const textInputId = 'chat-input-' + useId()
|
const textInputId = 'chat-input-' + useId()
|
||||||
const flatListRef = useAnimatedRef<ListMethods>()
|
const flatListRef = useRef<ListMethods | null>(null)
|
||||||
|
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
||||||
() => new Set(),
|
() => new Set(),
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
export function StepFindContacts() {
|
import {type Action, type State} from '#/components/contacts/state'
|
||||||
|
|
||||||
|
export function StepFindContacts(_props: {
|
||||||
|
flowState: State
|
||||||
|
flowDispatch: React.ActionDispatch<[Action]>
|
||||||
|
}): never {
|
||||||
throw new Error('StepFindContacts is not available on web')
|
throw new Error('StepFindContacts is not available on web')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export function StepFindContactsIntro() {
|
export function StepFindContactsIntro(): never {
|
||||||
throw new Error('StepFindContactsIntro is not available on web')
|
throw new Error('StepFindContactsIntro is not available on web')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
|
||||||
|
export type ValuePropositionPagerProps = {
|
||||||
|
step: 0 | 1 | 2
|
||||||
|
/**
|
||||||
|
* Only the native pager changes pages itself (by swiping); the web pager
|
||||||
|
* is driven entirely by the step prop.
|
||||||
|
*/
|
||||||
|
setStep: (step: 0 | 1 | 2) => void
|
||||||
|
avatarUri?: string
|
||||||
|
}
|
||||||
|
|
||||||
export function useValuePropText(step: 0 | 1 | 2) {
|
export function useValuePropText(step: 0 | 1 | 2) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {atoms as a, tokens, useTheme} from '#/alf'
|
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {PROP_1, PROP_2, PROP_3} from './images'
|
import {PROP_1, PROP_2, PROP_3} from './images'
|
||||||
import {Dot, useValuePropText} from './ValuePropositionPager.shared'
|
import {
|
||||||
|
Dot,
|
||||||
|
useValuePropText,
|
||||||
|
type ValuePropositionPagerProps,
|
||||||
|
} from './ValuePropositionPager.shared'
|
||||||
|
|
||||||
export function ValuePropositionPager({
|
export function ValuePropositionPager({
|
||||||
step,
|
step,
|
||||||
setStep,
|
setStep,
|
||||||
avatarUri,
|
avatarUri,
|
||||||
}: {
|
}: ValuePropositionPagerProps) {
|
||||||
step: 0 | 1 | 2
|
|
||||||
setStep: (step: 0 | 1 | 2) => void
|
|
||||||
avatarUri?: string
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const [activePage, setActivePage] = useState(step)
|
const [activePage, setActivePage] = useState(step)
|
||||||
const ref = useRef<PagerView>(null)
|
const ref = useRef<PagerView>(null)
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {PROP_1, PROP_2, PROP_3} from './images'
|
import {PROP_1, PROP_2, PROP_3} from './images'
|
||||||
import {Dot, useValuePropText} from './ValuePropositionPager.shared'
|
import {
|
||||||
|
Dot,
|
||||||
|
useValuePropText,
|
||||||
|
type ValuePropositionPagerProps,
|
||||||
|
} from './ValuePropositionPager.shared'
|
||||||
|
|
||||||
export function ValuePropositionPager({
|
export function ValuePropositionPager({
|
||||||
step,
|
step,
|
||||||
avatarUri,
|
avatarUri,
|
||||||
}: {
|
}: ValuePropositionPagerProps) {
|
||||||
step: 0 | 1 | 2
|
|
||||||
avatarUri?: string
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function renderItem({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
|
function keyExtractor(item: AppBskyActorDefs.ProfileView) {
|
||||||
return item.did
|
return item.did
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {useCallback, useEffect, useImperativeHandle, useState} from 'react'
|
import {useCallback, useEffect, useImperativeHandle, useState} from 'react'
|
||||||
import {findNodeHandle, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
type EmptyStateButtonProps,
|
type EmptyStateButtonProps,
|
||||||
} from '#/view/com/util/EmptyState'
|
} from '#/view/com/util/EmptyState'
|
||||||
import {type ListRef} from '#/view/com/util/List'
|
import {type ListRef} from '#/view/com/util/List'
|
||||||
|
import {findListNativeTag} from '#/view/com/util/listNativeTag'
|
||||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {atoms as a, ios, useTheme} from '#/alf'
|
import {atoms as a, ios, useTheme} from '#/alf'
|
||||||
import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
|
import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
|
||||||
@@ -87,7 +88,7 @@ export function ProfileFeedSection({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (IS_IOS && isFocused && scrollElRef.current) {
|
if (IS_IOS && isFocused && scrollElRef.current) {
|
||||||
const nativeTag = findNodeHandle(scrollElRef.current)
|
const nativeTag = findListNativeTag(scrollElRef.current)
|
||||||
setScrollViewTag(nativeTag)
|
setScrollViewTag(nativeTag)
|
||||||
}
|
}
|
||||||
}, [isFocused, scrollElRef, setScrollViewTag])
|
}, [isFocused, scrollElRef, setScrollViewTag])
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user