Merge branch 'bluesky-social:main' into main
commit
1f95628475
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
name: Build and Submit Android
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
type: choice
|
||||
description: Build profile to use
|
||||
options:
|
||||
- production
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Submit Android
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
|
||||
- name: 🔨 Setup EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
expo-version: latest
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: ⛏️ Setup EAS local builds
|
||||
run: yarn global add eas-cli-local-build-plugin
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: ⚙️ Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: ✏️ Write environment variables
|
||||
run: |
|
||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
||||
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
|
||||
|
||||
- name: 🏗️ EAS Build
|
||||
run: yarn use-build-number eas build -p android --profile production --local --output build.aab --non-interactive
|
||||
|
||||
- name: 🚀 Deploy
|
||||
run: eas submit -p android --non-interactive --path build.aab
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
name: Build and Submit iOS
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
type: choice
|
||||
description: Build profile to use
|
||||
options:
|
||||
- production
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Submit iOS
|
||||
runs-on: macos-14
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🔧 Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
|
||||
- name: 🔨 Setup EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
expo-version: latest
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: ⛏️ Setup EAS local builds
|
||||
run: yarn global add eas-cli-local-build-plugin
|
||||
|
||||
- name: ⚙️ Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: ☕️ Setup Cocoapods
|
||||
uses: maxim-lobanov/setup-cocoapods@v1
|
||||
with:
|
||||
version: 1.14.3
|
||||
|
||||
- name: 💾 Cache Pods
|
||||
uses: actions/cache@v3
|
||||
id: pods-cache
|
||||
with:
|
||||
path: ./ios/Pods
|
||||
# We'll use the yarn.lock for our hash since we don't yet have a Podfile.lock. Pod versions will not
|
||||
# change unless the yarn version changes as well.
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: ✏️ Write environment variables
|
||||
run: |
|
||||
echo "${{ secrets.ENV_TOKEN }}" > .env
|
||||
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
|
||||
|
||||
- name: 🏗️ EAS Build
|
||||
run: yarn use-build-number eas build -p ios --profile production --local --output build.ipa --non-interactive
|
||||
|
||||
- name: 🚀 Deploy
|
||||
run: eas submit -p ios --non-interactive --path build.ipa
|
|
@ -1,52 +0,0 @@
|
|||
name: Deploy Nightly Testflight Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy Nightly Testflight Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
|
||||
- name: Setup EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Bump build number
|
||||
run: yarn bump:ios
|
||||
|
||||
- name: EAS build and submit
|
||||
run: eas build -p ios --profile production --auto-submit --non-interactive
|
||||
|
||||
- name: Commit
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: Nightly iOS Build Bump
|
||||
branch: main
|
||||
commit_user_name: github-actions[bot]
|
||||
commit_user_email: github-actions[bot]@users.noreply.github.com
|
|
@ -11,24 +11,12 @@ const DARK_SPLASH_CONFIG = {
|
|||
resizeMode: 'cover',
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (config) {
|
||||
/**
|
||||
* App version number. Should be incremented as part of a release cycle.
|
||||
*/
|
||||
const VERSION = pkg.version
|
||||
|
||||
/**
|
||||
* iOS build number. Must be incremented for each TestFlight version.
|
||||
* WARNING: Always leave this variable on line 24! If it is moved, you need to update ./scripts/bumpIosBuildNumber.sh
|
||||
*/
|
||||
const IOS_BUILD_NUMBER = '7'
|
||||
|
||||
/**
|
||||
* Android build number. Must be incremented for each release.
|
||||
* WARNING: Always leave this variable on line 30! If it is moved, you need to update ./scripts/bumpAndroidBuildNumber.sh
|
||||
*/
|
||||
const ANDROID_VERSION_CODE = 62
|
||||
|
||||
/**
|
||||
* Uses built-in Expo env vars
|
||||
*
|
||||
|
@ -36,11 +24,10 @@ module.exports = function () {
|
|||
*/
|
||||
const PLATFORM = process.env.EAS_BUILD_PLATFORM
|
||||
|
||||
/**
|
||||
* Additional granularity for the `dist` field
|
||||
*/
|
||||
const DIST_BUILD_NUMBER =
|
||||
PLATFORM === 'android' ? ANDROID_VERSION_CODE : IOS_BUILD_NUMBER
|
||||
PLATFORM === 'android'
|
||||
? process.env.BSKY_ANDROID_VERSION_CODE
|
||||
: process.env.BSKY_IOS_BUILD_NUMBER
|
||||
|
||||
return {
|
||||
expo: {
|
||||
|
@ -57,7 +44,6 @@ module.exports = function () {
|
|||
userInterfaceStyle: 'automatic',
|
||||
splash: SPLASH_CONFIG,
|
||||
ios: {
|
||||
buildNumber: IOS_BUILD_NUMBER,
|
||||
supportsTablet: false,
|
||||
bundleIdentifier: 'xyz.blueskyweb.app',
|
||||
config: {
|
||||
|
@ -85,7 +71,6 @@ module.exports = function () {
|
|||
backgroundColor: '#ffffff',
|
||||
},
|
||||
android: {
|
||||
versionCode: ANDROID_VERSION_CODE,
|
||||
icon: './assets/icon.png',
|
||||
adaptiveIcon: {
|
||||
foregroundImage: './assets/icon-android-foreground.png',
|
||||
|
|
|
@ -191,7 +191,7 @@ func serve(cctx *cli.Context) error {
|
|||
e.GET("/settings", server.WebGeneric)
|
||||
e.GET("/settings/language", server.WebGeneric)
|
||||
e.GET("/settings/app-passwords", server.WebGeneric)
|
||||
e.GET("/settings/home-feed", server.WebGeneric)
|
||||
e.GET("/settings/following-feed", server.WebGeneric)
|
||||
e.GET("/settings/saved-feeds", server.WebGeneric)
|
||||
e.GET("/settings/threads", server.WebGeneric)
|
||||
e.GET("/settings/external-embeds", server.WebGeneric)
|
||||
|
|
19
eas.json
19
eas.json
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"cli": {
|
||||
"version": ">= 3.8.1",
|
||||
"promptToConfigurePushNotifications": false
|
||||
"promptToConfigurePushNotifications": false,
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"base": {
|
||||
|
@ -28,7 +29,21 @@
|
|||
"production": {
|
||||
"extends": "base",
|
||||
"ios": {
|
||||
"resourceClass": "large"
|
||||
"resourceClass": "large",
|
||||
"autoIncrement": true
|
||||
},
|
||||
"android": {
|
||||
"autoIncrement": true
|
||||
},
|
||||
"channel": "production"
|
||||
},
|
||||
"github": {
|
||||
"extends": "base",
|
||||
"ios": {
|
||||
"autoIncrement": true
|
||||
},
|
||||
"android": {
|
||||
"autoIncrement": true
|
||||
},
|
||||
"channel": "production"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
RCT_REMAP_SHADOW_PROPERTY(numberOfLines, numberOfLines, NSInteger)
|
||||
RCT_REMAP_SHADOW_PROPERTY(allowsFontScaling, allowsFontScaling, BOOL)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(numberOfLines, NSInteger)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onTextLayout, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(ellipsizeMode, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)
|
||||
|
|
|
@ -40,19 +40,19 @@ class RNUITextViewShadow: RCTShadowView {
|
|||
self.setAttributedText()
|
||||
}
|
||||
|
||||
// Tell yoga not to use flexbox
|
||||
// Returning true here will tell Yoga to not use flexbox and instead use our custom measure func.
|
||||
override func isYogaLeafNode() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// We only need to insert text children
|
||||
// We should only insert children that are UITextView shadows
|
||||
override func insertReactSubview(_ subview: RCTShadowView!, at atIndex: Int) {
|
||||
if subview.isKind(of: RNUITextViewChildShadow.self) {
|
||||
super.insertReactSubview(subview, at: atIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Whenever the subvies update, set the text
|
||||
// Every time the subviews change, we need to reformat and render the text.
|
||||
override func didUpdateReactSubviews() {
|
||||
self.setAttributedText()
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class RNUITextViewShadow: RCTShadowView {
|
|||
return
|
||||
}
|
||||
|
||||
// Update the text
|
||||
// Since we are inside the shadow view here, we have to find the real view and update the text.
|
||||
self.bridge.uiManager.addUIBlock { uiManager, viewRegistry in
|
||||
guard let textView = viewRegistry?[self.reactTag] as? RNUITextView else {
|
||||
return
|
||||
|
@ -100,18 +100,25 @@ class RNUITextViewShadow: RCTShadowView {
|
|||
// Create the attributed string with the generic attributes
|
||||
let string = NSMutableAttributedString(string: child.text, attributes: attributes)
|
||||
|
||||
// Set the paragraph style attributes if necessary
|
||||
// Set the paragraph style attributes if necessary. We can check this by seeing if the provided
|
||||
// line height is not 0.0.
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
if child.lineHeight != 0.0 {
|
||||
paragraphStyle.minimumLineHeight = child.lineHeight
|
||||
paragraphStyle.maximumLineHeight = child.lineHeight
|
||||
// Whenever we change the line height for the text, we are also removing the DynamicType
|
||||
// adjustment for line height. We need to get the multiplier and apply that to the
|
||||
// line height.
|
||||
let scaleMultiplier = scaledFontSize / child.fontSize
|
||||
paragraphStyle.minimumLineHeight = child.lineHeight * scaleMultiplier
|
||||
paragraphStyle.maximumLineHeight = child.lineHeight * scaleMultiplier
|
||||
|
||||
string.addAttribute(
|
||||
NSAttributedString.Key.paragraphStyle,
|
||||
value: paragraphStyle,
|
||||
range: NSMakeRange(0, string.length)
|
||||
)
|
||||
|
||||
// Store that height
|
||||
// To calcualte the size of the text without creating a new UILabel or UITextView, we have
|
||||
// to store this line height for later.
|
||||
self.lineHeight = child.lineHeight
|
||||
} else {
|
||||
self.lineHeight = font.lineHeight
|
||||
|
@ -124,24 +131,22 @@ class RNUITextViewShadow: RCTShadowView {
|
|||
self.dirtyLayout()
|
||||
}
|
||||
|
||||
// Create a YGSize based on the max width
|
||||
// To create the needed size we need to:
|
||||
// 1. Get the max size that we can use for the view
|
||||
// 2. Calculate the height of the text based on that max size
|
||||
// 3. Determine how many lines the text is, and limit that number if it exceeds the max
|
||||
// 4. Set the frame size and return the YGSize. YGSize requires Float values while CGSize needs CGFloat
|
||||
func getNeededSize(maxWidth: Float) -> YGSize {
|
||||
// Create the max size and figure out the size of the entire text
|
||||
let maxSize = CGSize(width: CGFloat(maxWidth), height: CGFloat(MAXFLOAT))
|
||||
let textSize = self.attributedText.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
// Figure out how many total lines there are
|
||||
let totalLines = Int(ceil(textSize.height / self.lineHeight))
|
||||
var totalLines = Int(ceil(textSize.height / self.lineHeight))
|
||||
|
||||
// Default to the text size
|
||||
var neededSize: CGSize = textSize.size
|
||||
|
||||
// If the total lines > max number, return size with the max
|
||||
if self.numberOfLines != 0, totalLines > self.numberOfLines {
|
||||
neededSize = CGSize(width: CGFloat(maxWidth), height: CGFloat(CGFloat(self.numberOfLines) * self.lineHeight))
|
||||
totalLines = self.numberOfLines
|
||||
}
|
||||
|
||||
self.frameSize = neededSize
|
||||
return YGSize(width: Float(neededSize.width), height: Float(neededSize.height))
|
||||
self.frameSize = CGSize(width: CGFloat(maxWidth), height: CGFloat(CGFloat(totalLines) * self.lineHeight))
|
||||
return YGSize(width: Float(self.frameSize.width), height: Float(self.frameSize.height))
|
||||
}
|
||||
}
|
||||
|
|
13
package.json
13
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.68.0",
|
||||
"version": "1.69.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
@ -12,8 +12,12 @@
|
|||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"use-build-number": "./scripts/useBuildNumberEnv.sh",
|
||||
"build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/",
|
||||
"build-all": "yarn intl:build && eas build --platform all",
|
||||
"build-all": "yarn intl:build && yarn use-build-number eas build --platform all",
|
||||
"build-ios": "yarn use-build-number eas build -p ios",
|
||||
"build-android": "yarn use-build-number eas build -p android",
|
||||
"build": "yarn use-build-number eas build",
|
||||
"start": "expo start --dev-client",
|
||||
"start:prod": "expo start --dev-client --no-dev --minify",
|
||||
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
|
||||
|
@ -36,10 +40,7 @@
|
|||
"intl:check": "yarn intl:extract && git diff-index -G'(^[^\\*# /])|(^#\\w)|(^\\s+[^\\*#/])' HEAD || (echo '\n⚠️ i18n detected un-extracted translations\n' && exit 1)",
|
||||
"intl:extract": "lingui extract",
|
||||
"intl:compile": "lingui compile",
|
||||
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
|
||||
"bump": "./scripts/bumpIosBuildNumber.sh && ./scripts/bumpAndroidBuildNumber.sh",
|
||||
"bump:ios": "./scripts/bumpIosBuildNumber.sh",
|
||||
"bump:android": "./scripts/bumpAndroidBuildNumber.sh"
|
||||
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.9.5",
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
diff --git a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
|
||||
index ef4f368..2b0da35 100644
|
||||
--- a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
|
||||
+++ b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
|
||||
@@ -273,8 +273,12 @@ function useLinking(ref, _ref) {
|
||||
});
|
||||
const currentIndex = history.index;
|
||||
try {
|
||||
- if (nextIndex !== -1 && nextIndex < currentIndex) {
|
||||
- // An existing entry for this path exists and it's less than current index, go back to that
|
||||
+ if (
|
||||
+ nextIndex !== -1 &&
|
||||
+ nextIndex < currentIndex &&
|
||||
+ // We should only go back if the entry exists and it's less than current index
|
||||
+ history.get(nextIndex - currentIndex)
|
||||
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
|
||||
await history.go(nextIndex - currentIndex);
|
||||
} else {
|
||||
// We couldn't find an existing entry to go back to, so we'll go back by the delta
|
||||
diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js
|
||||
index 62a3b43..11a5a28 100644
|
||||
--- a/node_modules/@react-navigation/native/lib/module/useLinking.js
|
||||
+++ b/node_modules/@react-navigation/native/lib/module/useLinking.js
|
||||
@@ -264,8 +264,12 @@ export default function useLinking(ref, _ref) {
|
||||
});
|
||||
const currentIndex = history.index;
|
||||
try {
|
||||
- if (nextIndex !== -1 && nextIndex < currentIndex) {
|
||||
- // An existing entry for this path exists and it's less than current index, go back to that
|
||||
+ if (
|
||||
+ nextIndex !== -1 &&
|
||||
+ nextIndex < currentIndex &&
|
||||
+ // We should only go back if the entry exists and it's less than current index
|
||||
+ history.get(nextIndex - currentIndex)
|
||||
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
|
||||
await history.go(nextIndex - currentIndex);
|
||||
} else {
|
||||
// We couldn't find an existing entry to go back to, so we'll go back by the delta
|
||||
diff --git a/node_modules/@react-navigation/native/src/useLinking.tsx b/node_modules/@react-navigation/native/src/useLinking.tsx
|
||||
index 3db40b7..9ba4ecd 100644
|
||||
--- a/node_modules/@react-navigation/native/src/useLinking.tsx
|
||||
+++ b/node_modules/@react-navigation/native/src/useLinking.tsx
|
||||
@@ -381,7 +381,12 @@ export default function useLinking(
|
||||
const currentIndex = history.index;
|
||||
|
||||
try {
|
||||
- if (nextIndex !== -1 && nextIndex < currentIndex) {
|
||||
+ if (
|
||||
+ nextIndex !== -1 &&
|
||||
+ nextIndex < currentIndex &&
|
||||
+ // We should only go back if the entry exists and it's less than current index
|
||||
+ history.get(nextIndex - currentIndex)
|
||||
+ ) {
|
||||
// An existing entry for this path exists and it's less than current index, go back to that
|
||||
await history.go(nextIndex - currentIndex);
|
||||
} else {
|
|
@ -0,0 +1,5 @@
|
|||
# React Navigation history bug patch
|
||||
|
||||
This patches react-navigation to fix the issues in https://github.com/bluesky-social/social-app/issues/710.
|
||||
|
||||
This is based on the PR found at https://github.com/react-navigation/react-navigation/pull/11833
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
# The number here should always be the line number the iOS build variable is on
|
||||
line=$(sed "30q;d" ./app.config.js)
|
||||
currentBuildNumber=$(echo "$line" | grep -oE '[0-9]+([.][0-9]+)?')
|
||||
newBuildNumber=$((currentBuildNumber+1))
|
||||
newBuildVariable="const ANDROID_VERSION_CODE = '$newBuildNumber'"
|
||||
sed -i.bak "30s/.*/ $newBuildVariable/" ./app.config.js
|
||||
rm -rf ./app.config.js.bak
|
||||
|
||||
echo "Android build number bumped to $newBuildNumber"
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
# The number here should always be the line number the iOS build variable is on
|
||||
line=$(sed "24q;d" ./app.config.js)
|
||||
currentBuildNumber=$(echo "$line" | grep -oE '[0-9]+([.][0-9]+)?')
|
||||
newBuildNumber=$((currentBuildNumber+1))
|
||||
newBuildVariable="const IOS_BUILD_NUMBER = '$newBuildNumber'"
|
||||
sed -i.bak "24s/.*/ $newBuildVariable/" ./app.config.js
|
||||
rm -rf ./app.config.js.bak
|
||||
|
||||
echo "iOS build number bumped to $newBuildNumber"
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
outputIos=$(eas build:version:get -p ios)
|
||||
outputAndroid=$(eas build:version:get -p android)
|
||||
currentIosVersion=${outputIos#*buildNumber - }
|
||||
currentAndroidVersion=${outputAndroid#*versionCode - }
|
||||
|
||||
BSKY_IOS_BUILD_NUMBER=$((currentIosVersion+1))
|
||||
BSKY_ANDROID_VERSION_CODE=$((currentAndroidVersion+1))
|
||||
|
||||
bash -c "BSKY_IOS_BUILD_NUMBER=$BSKY_IOS_BUILD_NUMBER BSKY_ANDROID_VERSION_CODE=$BSKY_ANDROID_VERSION_CODE $*"
|
||||
|
|
@ -71,7 +71,7 @@ import {AppPasswords} from 'view/screens/AppPasswords'
|
|||
import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts'
|
||||
import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts'
|
||||
import {SavedFeeds} from 'view/screens/SavedFeeds'
|
||||
import {PreferencesHomeFeed} from 'view/screens/PreferencesHomeFeed'
|
||||
import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed'
|
||||
import {PreferencesThreads} from 'view/screens/PreferencesThreads'
|
||||
import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds'
|
||||
import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth'
|
||||
|
@ -242,9 +242,12 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
|||
options={{title: title(msg`Edit My Feeds`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PreferencesHomeFeed"
|
||||
getComponent={() => PreferencesHomeFeed}
|
||||
options={{title: title(msg`Home Feed Preferences`), requireAuth: true}}
|
||||
name="PreferencesFollowingFeed"
|
||||
getComponent={() => PreferencesFollowingFeed}
|
||||
options={{
|
||||
title: title(msg`Following Feed Preferences`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PreferencesThreads"
|
||||
|
|
|
@ -73,19 +73,19 @@ export const darkPalette: Palette = {
|
|||
white: tokens.color.gray_0,
|
||||
black: tokens.color.trueBlack,
|
||||
|
||||
contrast_25: tokens.color.gray_1000,
|
||||
contrast_50: tokens.color.gray_975,
|
||||
contrast_100: tokens.color.gray_950,
|
||||
contrast_200: tokens.color.gray_900,
|
||||
contrast_300: tokens.color.gray_800,
|
||||
contrast_400: tokens.color.gray_700,
|
||||
contrast_500: tokens.color.gray_600,
|
||||
contrast_600: tokens.color.gray_500,
|
||||
contrast_700: tokens.color.gray_400,
|
||||
contrast_800: tokens.color.gray_300,
|
||||
contrast_900: tokens.color.gray_200,
|
||||
contrast_950: tokens.color.gray_100,
|
||||
contrast_975: tokens.color.gray_50,
|
||||
contrast_25: `hsl(211, 28%, 8%)`,
|
||||
contrast_50: `hsl(211, 28%, 11%)`,
|
||||
contrast_100: `hsl(211, 28%, 16%)`,
|
||||
contrast_200: `hsl(211, 28%, 24%)`,
|
||||
contrast_300: `hsl(211, 24%, 31%)`,
|
||||
contrast_400: `hsl(211, 24%, 38%)`,
|
||||
contrast_500: `hsl(211, 20%, 44%)`,
|
||||
contrast_600: `hsl(211, 20%, 55%)`,
|
||||
contrast_700: `hsl(211, 20%, 63%)`,
|
||||
contrast_800: `hsl(211, 20%, 71%)`,
|
||||
contrast_900: `hsl(211, 20%, 79%)`,
|
||||
contrast_950: `hsl(211, 20%, 87%)`,
|
||||
contrast_975: `hsl(211, 20%, 95%)`,
|
||||
|
||||
primary_25: tokens.color.blue_25,
|
||||
primary_50: tokens.color.blue_50,
|
||||
|
@ -132,21 +132,28 @@ export const darkPalette: Palette = {
|
|||
|
||||
export const dimPalette: Palette = {
|
||||
...darkPalette,
|
||||
black: tokens.color.gray_1000,
|
||||
black: `hsl(211, 28%, 12%)`,
|
||||
|
||||
contrast_25: tokens.color.gray_975,
|
||||
contrast_50: tokens.color.gray_950,
|
||||
contrast_100: tokens.color.gray_900,
|
||||
contrast_200: tokens.color.gray_800,
|
||||
contrast_300: tokens.color.gray_700,
|
||||
contrast_400: tokens.color.gray_600,
|
||||
contrast_500: tokens.color.gray_500,
|
||||
contrast_600: tokens.color.gray_400,
|
||||
contrast_700: tokens.color.gray_300,
|
||||
contrast_800: tokens.color.gray_200,
|
||||
contrast_900: tokens.color.gray_100,
|
||||
contrast_950: tokens.color.gray_50,
|
||||
contrast_975: tokens.color.gray_25,
|
||||
contrast_25: `hsl(211, 28%, 15%)`,
|
||||
contrast_50: `hsl(211, 28%, 18%)`,
|
||||
contrast_100: `hsl(211, 28%, 24%)`,
|
||||
contrast_200: `hsl(211, 28%, 27%)`,
|
||||
contrast_300: `hsl(211, 24%, 34%)`,
|
||||
contrast_400: `hsl(211, 24%, 41%)`,
|
||||
contrast_500: `hsl(211, 20%, 52%)`,
|
||||
contrast_600: `hsl(211, 20%, 55%)`,
|
||||
contrast_700: `hsl(211, 20%, 67%)`,
|
||||
contrast_800: `hsl(211, 20%, 71%)`,
|
||||
contrast_900: `hsl(211, 20%, 79%)`,
|
||||
contrast_950: `hsl(211, 20%, 87%)`,
|
||||
contrast_975: `hsl(211, 20%, 95%)`,
|
||||
|
||||
primary_600: `hsl(211, 95%, 39%)`,
|
||||
primary_700: `hsl(211, 90%, 30%)`,
|
||||
primary_800: `hsl(211, 90%, 23%)`,
|
||||
primary_900: `hsl(211, 80%, 16%)`,
|
||||
primary_950: `hsl(211, 80%, 13%)`,
|
||||
primary_975: `hsl(211, 80%, 10%)`,
|
||||
} as const
|
||||
|
||||
export const light = {
|
||||
|
@ -325,6 +332,7 @@ export const dark: Theme = {
|
|||
export const dim: Theme = {
|
||||
...dark,
|
||||
name: 'dim',
|
||||
palette: dimPalette,
|
||||
atoms: {
|
||||
...dark.atoms,
|
||||
text: {
|
||||
|
@ -393,5 +401,20 @@ export const dim: Theme = {
|
|||
border_contrast_high: {
|
||||
borderColor: dimPalette.contrast_300,
|
||||
},
|
||||
shadow_sm: {
|
||||
...atoms.shadow_sm,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(211, 28%, 3%)`,
|
||||
},
|
||||
shadow_md: {
|
||||
...atoms.shadow_md,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(211, 28%, 3%)`,
|
||||
},
|
||||
shadow_lg: {
|
||||
...atoms.shadow_lg,
|
||||
shadowOpacity: 0.7,
|
||||
shadowColor: `hsl(211, 28%, 3%)`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import {GestureResponderEvent, Linking} from 'react-native'
|
||||
import {GestureResponderEvent} from 'react-native'
|
||||
import {
|
||||
useLinkProps,
|
||||
useNavigation,
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
import {useModalControls} from '#/state/modals'
|
||||
import {router} from '#/routes'
|
||||
import {Text, TextProps} from '#/components/Typography'
|
||||
import {useOpenLink} from 'state/preferences/in-app-browser'
|
||||
|
||||
/**
|
||||
* Only available within a `Link`, since that inherits from `Button`.
|
||||
|
@ -80,6 +81,7 @@ export function useLink({
|
|||
})
|
||||
const isExternal = isExternalUrl(href)
|
||||
const {openModal, closeModal} = useModalControls()
|
||||
const openLink = useOpenLink()
|
||||
|
||||
const onPress = React.useCallback(
|
||||
(e: GestureResponderEvent) => {
|
||||
|
@ -106,7 +108,7 @@ export function useLink({
|
|||
e.preventDefault()
|
||||
|
||||
if (isExternal) {
|
||||
Linking.openURL(href)
|
||||
openLink(href)
|
||||
} else {
|
||||
/**
|
||||
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
|
||||
|
@ -124,7 +126,7 @@ export function useLink({
|
|||
href.startsWith('http') ||
|
||||
href.startsWith('mailto')
|
||||
) {
|
||||
Linking.openURL(href)
|
||||
openLink(href)
|
||||
} else {
|
||||
closeModal() // close any active modals
|
||||
|
||||
|
@ -145,15 +147,16 @@ export function useLink({
|
|||
}
|
||||
},
|
||||
[
|
||||
href,
|
||||
isExternal,
|
||||
warnOnMismatchingTextChild,
|
||||
navigation,
|
||||
action,
|
||||
displayText,
|
||||
closeModal,
|
||||
openModal,
|
||||
outerOnPress,
|
||||
warnOnMismatchingTextChild,
|
||||
displayText,
|
||||
isExternal,
|
||||
href,
|
||||
openModal,
|
||||
openLink,
|
||||
closeModal,
|
||||
action,
|
||||
navigation,
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -260,7 +263,7 @@ export function InlineLink({
|
|||
style={[
|
||||
{color: t.palette.primary_500},
|
||||
(hovered || focused || pressed) && {
|
||||
outline: 0,
|
||||
...web({outline: 0}),
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
|
||||
},
|
||||
|
|
|
@ -98,7 +98,7 @@ export function DateField({
|
|||
timeZoneName={'Etc/UTC'}
|
||||
display="spinner"
|
||||
// @ts-ignore applies in iOS only -prf
|
||||
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
|
||||
themeVariant={t.name === 'light' ? 'light' : 'dark'}
|
||||
value={new Date(value)}
|
||||
onChange={onChangeInternal}
|
||||
/>
|
||||
|
|
|
@ -47,7 +47,7 @@ export function DateField({
|
|||
mode="date"
|
||||
timeZoneName={'Etc/UTC'}
|
||||
display="spinner"
|
||||
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
|
||||
themeVariant={t.name === 'light' ? 'light' : 'dark'}
|
||||
value={new Date(value)}
|
||||
onChange={onChangeInternal}
|
||||
/>
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface LinkMeta {
|
|||
export async function getLinkMeta(
|
||||
agent: BskyAgent,
|
||||
url: string,
|
||||
timeout = 5e3,
|
||||
timeout = 15e3,
|
||||
): Promise<LinkMeta> {
|
||||
if (isBskyAppUrl(url)) {
|
||||
return extractBskyMeta(agent, url)
|
||||
|
|
|
@ -30,7 +30,7 @@ export type CommonNavigatorParams = {
|
|||
CopyrightPolicy: undefined
|
||||
AppPasswords: undefined
|
||||
SavedFeeds: undefined
|
||||
PreferencesHomeFeed: undefined
|
||||
PreferencesFollowingFeed: undefined
|
||||
PreferencesThreads: undefined
|
||||
PreferencesExternalEmbeds: undefined
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// Regex from the go implementation
|
||||
// https://github.com/bluesky-social/indigo/blob/main/atproto/syntax/handle.go#L10
|
||||
const VALIDATE_REGEX =
|
||||
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/
|
||||
|
||||
export function makeValidHandle(str: string): string {
|
||||
if (str.length > 20) {
|
||||
str = str.slice(0, 20)
|
||||
|
@ -19,3 +24,27 @@ export function isInvalidHandle(handle: string): boolean {
|
|||
export function sanitizeHandle(handle: string, prefix = ''): string {
|
||||
return isInvalidHandle(handle) ? '⚠Invalid Handle' : `${prefix}${handle}`
|
||||
}
|
||||
|
||||
export interface IsValidHandle {
|
||||
handleChars: boolean
|
||||
frontLength: boolean
|
||||
totalLength: boolean
|
||||
overall: boolean
|
||||
}
|
||||
|
||||
// More checks from https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/index.ts#L72
|
||||
export function validateHandle(str: string, userDomain: string): IsValidHandle {
|
||||
const fullHandle = createFullHandle(str, userDomain)
|
||||
|
||||
const results = {
|
||||
handleChars:
|
||||
!str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')),
|
||||
frontLength: str.length >= 3,
|
||||
totalLength: fullHandle.length <= 253,
|
||||
}
|
||||
|
||||
return {
|
||||
...results,
|
||||
overall: !Object.values(results).includes(false),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export function ago(date: number | string | Date): string {
|
|||
} else if (diffSeconds < DAY) {
|
||||
return `${Math.floor(diffSeconds / HOUR)}h`
|
||||
} else if (diffSeconds < MONTH) {
|
||||
return `${Math.floor(diffSeconds / DAY)}d`
|
||||
return `${Math.round(diffSeconds / DAY)}d`
|
||||
} else if (diffSeconds < YEAR) {
|
||||
return `${Math.floor(diffSeconds / MONTH)}mo`
|
||||
} else {
|
||||
|
|
|
@ -306,7 +306,7 @@ export const darkTheme: Theme = {
|
|||
|
||||
// non-standard
|
||||
textVeryLight: darkPalette.contrast_400,
|
||||
replyLine: darkPalette.contrast_100,
|
||||
replyLine: darkPalette.contrast_200,
|
||||
replyLineDot: darkPalette.contrast_200,
|
||||
unreadNotifBg: darkPalette.primary_975,
|
||||
unreadNotifBorder: darkPalette.primary_900,
|
||||
|
@ -355,10 +355,10 @@ export const dimTheme: Theme = {
|
|||
|
||||
// non-standard
|
||||
textVeryLight: dimPalette.contrast_400,
|
||||
replyLine: dimPalette.contrast_100,
|
||||
replyLine: dimPalette.contrast_200,
|
||||
replyLineDot: dimPalette.contrast_200,
|
||||
unreadNotifBg: dimPalette.primary_975,
|
||||
unreadNotifBorder: dimPalette.primary_900,
|
||||
unreadNotifBg: `hsl(211, 48%, 17%)`,
|
||||
unreadNotifBorder: `hsl(211, 48%, 30%)`,
|
||||
postCtrl: dimPalette.contrast_500,
|
||||
brandText: dimPalette.primary_500,
|
||||
emptyStateIcon: dimPalette.contrast_300,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -24,7 +24,7 @@ export const router = new Router({
|
|||
Debug: '/sys/debug',
|
||||
Log: '/sys/log',
|
||||
AppPasswords: '/settings/app-passwords',
|
||||
PreferencesHomeFeed: '/settings/home-feed',
|
||||
PreferencesFollowingFeed: '/settings/following-feed',
|
||||
PreferencesThreads: '/settings/threads',
|
||||
PreferencesExternalEmbeds: '/settings/external-embeds',
|
||||
SavedFeeds: '/settings/saved-feeds',
|
||||
|
|
|
@ -23,7 +23,7 @@ import {Step3} from './Step3'
|
|||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {TextLink} from '../../util/Link'
|
||||
import {getAgent} from 'state/session'
|
||||
import {createFullHandle} from 'lib/strings/handles'
|
||||
import {createFullHandle, validateHandle} from 'lib/strings/handles'
|
||||
|
||||
export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
||||
const {screen} = useAnalytics()
|
||||
|
@ -78,6 +78,10 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
|||
}
|
||||
|
||||
if (uiState.step === 2) {
|
||||
if (!validateHandle(uiState.handle, uiState.userDomain).overall) {
|
||||
return
|
||||
}
|
||||
|
||||
uiDispatch({type: 'set-processing', value: true})
|
||||
try {
|
||||
const res = await getAgent().resolveHandle({
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {CreateAccountState, CreateAccountDispatch} from './state'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {StepHeader} from './StepHeader'
|
||||
import {s} from 'lib/styles'
|
||||
import {TextInput} from '../util/TextInput'
|
||||
import {createFullHandle} from 'lib/strings/handles'
|
||||
import {
|
||||
createFullHandle,
|
||||
IsValidHandle,
|
||||
validateHandle,
|
||||
} from 'lib/strings/handles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
/** STEP 3: Your user handle
|
||||
* @field User handle
|
||||
|
@ -23,41 +30,111 @@ export function Step2({
|
|||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
const [validCheck, setValidCheck] = React.useState<IsValidHandle>({
|
||||
handleChars: false,
|
||||
frontLength: false,
|
||||
totalLength: true,
|
||||
overall: false,
|
||||
})
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
setValidCheck(validateHandle(uiState.handle, uiState.userDomain))
|
||||
|
||||
// Disabling this, because we only want to run this when we focus the screen
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []),
|
||||
)
|
||||
|
||||
const onHandleChange = React.useCallback(
|
||||
(value: string) => {
|
||||
if (uiState.error) {
|
||||
uiDispatch({type: 'set-error', value: ''})
|
||||
}
|
||||
|
||||
setValidCheck(validateHandle(value, uiState.userDomain))
|
||||
uiDispatch({type: 'set-handle', value})
|
||||
},
|
||||
[uiDispatch, uiState.error, uiState.userDomain],
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<StepHeader uiState={uiState} title={_(msg`Your user handle`)} />
|
||||
{uiState.error ? (
|
||||
<ErrorMessage message={uiState.error} style={styles.error} />
|
||||
) : undefined}
|
||||
<View style={s.pb10}>
|
||||
<TextInput
|
||||
testID="handleInput"
|
||||
icon="at"
|
||||
placeholder="e.g. alice"
|
||||
value={uiState.handle}
|
||||
editable
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
autoCorrect={false}
|
||||
onChange={value => uiDispatch({type: 'set-handle', value})}
|
||||
// TODO: Add explicit text label
|
||||
accessibilityLabel={_(msg`User handle`)}
|
||||
accessibilityHint={_(msg`Input your user handle`)}
|
||||
/>
|
||||
<Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
|
||||
<Trans>Your full handle will be</Trans>{' '}
|
||||
<Text type="lg-bold" style={pal.text}>
|
||||
@{createFullHandle(uiState.handle, uiState.userDomain)}
|
||||
<View style={s.mb20}>
|
||||
<TextInput
|
||||
testID="handleInput"
|
||||
icon="at"
|
||||
placeholder="e.g. alice"
|
||||
value={uiState.handle}
|
||||
editable
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
autoCorrect={false}
|
||||
onChange={onHandleChange}
|
||||
// TODO: Add explicit text label
|
||||
accessibilityLabel={_(msg`User handle`)}
|
||||
accessibilityHint={_(msg`Input your user handle`)}
|
||||
/>
|
||||
<Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
|
||||
<Trans>Your full handle will be</Trans>{' '}
|
||||
<Text type="lg-bold" style={pal.text}>
|
||||
@{createFullHandle(uiState.handle, uiState.userDomain)}
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
a.p_md,
|
||||
a.gap_sm,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{uiState.error ? (
|
||||
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<IsValidIcon valid={false} />
|
||||
<Text style={[t.atoms.text, a.text_md, a.flex]}>
|
||||
{uiState.error}
|
||||
</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<IsValidIcon valid={validCheck.handleChars} />
|
||||
<Text style={[t.atoms.text, a.text_md, a.flex]}>
|
||||
<Trans>May only contain letters and numbers</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<IsValidIcon
|
||||
valid={validCheck.frontLength && validCheck.totalLength}
|
||||
/>
|
||||
{!validCheck.totalLength ? (
|
||||
<Text style={[t.atoms.text]}>
|
||||
<Trans>May not be longer than 253 characters</Trans>
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={[t.atoms.text, a.text_md]}>
|
||||
<Trans>Must be at least 3 characters</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
error: {
|
||||
borderRadius: 6,
|
||||
marginBottom: 10,
|
||||
},
|
||||
})
|
||||
function IsValidIcon({valid}: {valid: boolean}) {
|
||||
const t = useTheme()
|
||||
|
||||
if (!valid) {
|
||||
return <Check size="md" style={{color: t.palette.negative_500}} />
|
||||
}
|
||||
|
||||
return <Times size="md" style={{color: t.palette.positive_700}} />
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {msg} from '@lingui/macro'
|
|||
import * as EmailValidator from 'email-validator'
|
||||
import {getAge} from 'lib/strings/time'
|
||||
import {logger} from '#/logger'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {createFullHandle, validateHandle} from '#/lib/strings/handles'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
||||
import {useSessionApi} from '#/state/session'
|
||||
|
@ -282,7 +282,8 @@ function compute(state: CreateAccountState): CreateAccountState {
|
|||
!!state.email &&
|
||||
!!state.password
|
||||
} else if (state.step === 2) {
|
||||
canNext = !!state.handle
|
||||
canNext =
|
||||
!!state.handle && validateHandle(state.handle, state.userDomain).overall
|
||||
} else if (state.step === 3) {
|
||||
// Step 3 will automatically redirect as soon as the captcha completes
|
||||
canNext = false
|
||||
|
|
|
@ -138,7 +138,7 @@ export function FeedPage({
|
|||
{hasSession && (
|
||||
<TextLink
|
||||
type="title-lg"
|
||||
href="/settings/home-feed"
|
||||
href="/settings/following-feed"
|
||||
style={{fontWeight: 'bold'}}
|
||||
accessibilityLabel={_(msg`Feed Preferences`)}
|
||||
accessibilityHint=""
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {HomeHeaderLayout} from './HomeHeaderLayout'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {TabBar} from '../pager/TabBar'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
|
||||
export function HomeHeader(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
) {
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
if (isDesktop) {
|
||||
return null
|
||||
}
|
||||
return <HomeHeaderInner {...props} />
|
||||
}
|
||||
|
||||
export function HomeHeaderInner(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
|
||||
const pal = usePalette('default')
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
const pinnedNames = feeds.map(f => f.displayName)
|
||||
|
||||
if (!hasPinnedCustom) {
|
||||
return pinnedNames.concat('Feeds ✨')
|
||||
}
|
||||
return pinnedNames
|
||||
}, [hasPinnedCustom, feeds])
|
||||
|
||||
const onPressFeedsLink = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
navigation.navigate('Feeds')
|
||||
} else {
|
||||
navigation.navigate('FeedsTab')
|
||||
navigation.popToTop()
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
if (!hasPinnedCustom && index === items.length - 1) {
|
||||
onPressFeedsLink()
|
||||
} else if (props.onSelect) {
|
||||
props.onSelect(index)
|
||||
}
|
||||
},
|
||||
[items.length, onPressFeedsLink, props, hasPinnedCustom],
|
||||
)
|
||||
|
||||
return (
|
||||
<HomeHeaderLayout>
|
||||
<TabBar
|
||||
key={items.join(',')}
|
||||
onPressSelected={props.onPressSelected}
|
||||
selectedPage={props.selectedPage}
|
||||
onSelect={onSelect}
|
||||
testID={props.testID}
|
||||
items={items}
|
||||
indicatorColor={pal.colors.link}
|
||||
/>
|
||||
</HomeHeaderLayout>
|
||||
)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {HomeHeaderLayoutMobile as HomeHeaderLayout} from './HomeHeaderLayoutMobile'
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
|
||||
export function HomeHeaderLayout({children}: {children: React.ReactNode}) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (isMobile) {
|
||||
return <HomeHeaderLayoutMobile>{children}</HomeHeaderLayoutMobile>
|
||||
} else {
|
||||
return <HomeHeaderLayoutTablet>{children}</HomeHeaderLayoutTablet>
|
||||
}
|
||||
}
|
||||
|
||||
function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) {
|
||||
const pal = usePalette('default')
|
||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||
const {headerHeight} = useShellLayout()
|
||||
|
||||
return (
|
||||
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
|
||||
<Animated.View
|
||||
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||
onLayout={e => {
|
||||
headerHeight.value = e.nativeEvent.layout.height
|
||||
}}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
// @ts-ignore Web only
|
||||
position: 'sticky',
|
||||
zIndex: 1,
|
||||
// @ts-ignore Web only -prf
|
||||
left: 'calc(50% - 300px)',
|
||||
width: 600,
|
||||
top: 0,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
})
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {TabBar} from 'view/com/pager/TabBar'
|
||||
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Link} from '../util/Link'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
|
@ -13,11 +11,7 @@ import {useLingui} from '@lingui/react'
|
|||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {useSession} from '#/state/session'
|
||||
import {usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
|
||||
import {IS_DEV} from '#/env'
|
||||
|
@ -25,49 +19,17 @@ import {atoms} from '#/alf'
|
|||
import {Link as Link2} from '#/components/Link'
|
||||
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
|
||||
|
||||
export function FeedsTabBar(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
) {
|
||||
export function HomeHeaderLayoutMobile({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {hasSession} = useSession()
|
||||
const {_} = useLingui()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
|
||||
const {headerHeight} = useShellLayout()
|
||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
if (!hasSession) return []
|
||||
|
||||
const pinnedNames = feeds.map(f => f.displayName)
|
||||
|
||||
if (!hasPinnedCustom) {
|
||||
return pinnedNames.concat('Feeds ✨')
|
||||
}
|
||||
return pinnedNames
|
||||
}, [hasSession, hasPinnedCustom, feeds])
|
||||
|
||||
const onPressFeedsLink = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
navigation.navigate('Feeds')
|
||||
} else {
|
||||
navigation.navigate('FeedsTab')
|
||||
navigation.popToTop()
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
if (hasSession && !hasPinnedCustom && index === items.length - 1) {
|
||||
onPressFeedsLink()
|
||||
} else if (props.onSelect) {
|
||||
props.onSelect(index)
|
||||
}
|
||||
},
|
||||
[items.length, onPressFeedsLink, props, hasSession, hasPinnedCustom],
|
||||
)
|
||||
|
||||
const onPressAvi = React.useCallback(() => {
|
||||
setDrawerOpen(true)
|
||||
}, [setDrawerOpen])
|
||||
|
@ -113,35 +75,21 @@ export function FeedsTabBar(
|
|||
<ColorPalette size="md" />
|
||||
</Link2>
|
||||
)}
|
||||
|
||||
{hasSession && (
|
||||
<Link
|
||||
testID="viewHeaderHomeFeedPrefsBtn"
|
||||
href="/settings/home-feed"
|
||||
hitSlop={HITSLOP_10}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Home Feed Preferences`)}
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
icon="sliders"
|
||||
style={pal.textLight as FontAwesomeIconStyle}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
testID="viewHeaderHomeFeedPrefsBtn"
|
||||
href="/settings/following-feed"
|
||||
hitSlop={HITSLOP_10}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Following Feed Preferences`)}
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
icon="sliders"
|
||||
style={pal.textLight as FontAwesomeIconStyle}
|
||||
/>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{items.length > 0 && (
|
||||
<TabBar
|
||||
key={items.join(',')}
|
||||
onPressSelected={props.onPressSelected}
|
||||
selectedPage={props.selectedPage}
|
||||
onSelect={onSelect}
|
||||
testID={props.testID}
|
||||
items={items}
|
||||
indicatorColor={pal.colors.link}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
|
@ -37,6 +37,7 @@ type Props = {
|
|||
onTap: () => void
|
||||
onZoom: (isZoomed: boolean) => void
|
||||
isScrollViewBeingDragged: boolean
|
||||
showControls: boolean
|
||||
}
|
||||
const ImageItem = ({
|
||||
imageSrc,
|
||||
|
|
|
@ -37,11 +37,18 @@ type Props = {
|
|||
onTap: () => void
|
||||
onZoom: (scaled: boolean) => void
|
||||
isScrollViewBeingDragged: boolean
|
||||
showControls: boolean
|
||||
}
|
||||
|
||||
const AnimatedImage = Animated.createAnimatedComponent(Image)
|
||||
|
||||
const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
|
||||
const ImageItem = ({
|
||||
imageSrc,
|
||||
onTap,
|
||||
onZoom,
|
||||
onRequestClose,
|
||||
showControls,
|
||||
}: Props) => {
|
||||
const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
|
||||
const translationY = useSharedValue(0)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
|
@ -144,7 +151,7 @@ const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
|
|||
accessibilityLabel={imageSrc.alt}
|
||||
accessibilityHint=""
|
||||
onLoad={() => setLoaded(true)}
|
||||
enableLiveTextInteraction={!scaled}
|
||||
enableLiveTextInteraction={showControls && !scaled}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
</GestureDetector>
|
||||
|
|
|
@ -10,6 +10,7 @@ type Props = {
|
|||
onTap: () => void
|
||||
onZoom: (scaled: boolean) => void
|
||||
isScrollViewBeingDragged: boolean
|
||||
showControls: boolean
|
||||
}
|
||||
|
||||
const ImageItem = (_props: Props) => {
|
||||
|
|
|
@ -122,6 +122,7 @@ function ImageViewing({
|
|||
imageSrc={imageSrc}
|
||||
onRequestClose={onRequestClose}
|
||||
isScrollViewBeingDragged={isDragging}
|
||||
showControls={showControls}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './FeedsTabBarMobile'
|
|
@ -1,138 +0,0 @@
|
|||
import React from 'react'
|
||||
import {View, StyleSheet} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {TabBar} from 'view/com/pager/TabBar'
|
||||
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||
import {useSession} from '#/state/session'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
|
||||
export function FeedsTabBar(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
) {
|
||||
const {isMobile, isTablet} = useWebMediaQueries()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
if (isMobile) {
|
||||
return <FeedsTabBarMobile {...props} />
|
||||
} else if (isTablet) {
|
||||
if (hasSession) {
|
||||
return <FeedsTabBarTablet {...props} />
|
||||
} else {
|
||||
return <FeedsTabBarPublic />
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function FeedsTabBarPublic() {
|
||||
const pal = usePalette('default')
|
||||
|
||||
return (
|
||||
<CenteredView sideBorders>
|
||||
<View
|
||||
style={[
|
||||
pal.view,
|
||||
{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
]}>
|
||||
<TextLink
|
||||
type="title-lg"
|
||||
href="/"
|
||||
style={[pal.text, {fontWeight: 'bold'}]}
|
||||
text="Bluesky "
|
||||
/>
|
||||
</View>
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedsTabBarTablet(
|
||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||
) {
|
||||
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
|
||||
const pal = usePalette('default')
|
||||
const {hasSession} = useSession()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||
const {headerHeight} = useShellLayout()
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
if (!hasSession) return []
|
||||
|
||||
const pinnedNames = feeds.map(f => f.displayName)
|
||||
|
||||
if (!hasPinnedCustom) {
|
||||
return pinnedNames.concat('Feeds ✨')
|
||||
}
|
||||
return pinnedNames
|
||||
}, [hasSession, hasPinnedCustom, feeds])
|
||||
|
||||
const onPressDiscoverFeeds = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
navigation.navigate('Feeds')
|
||||
} else {
|
||||
navigation.navigate('FeedsTab')
|
||||
navigation.popToTop()
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onSelect = React.useCallback(
|
||||
(index: number) => {
|
||||
if (hasSession && !hasPinnedCustom && index === items.length - 1) {
|
||||
onPressDiscoverFeeds()
|
||||
} else if (props.onSelect) {
|
||||
props.onSelect(index)
|
||||
}
|
||||
},
|
||||
[items.length, onPressDiscoverFeeds, props, hasSession, hasPinnedCustom],
|
||||
)
|
||||
|
||||
return (
|
||||
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
|
||||
<Animated.View
|
||||
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||
onLayout={e => {
|
||||
headerHeight.value = e.nativeEvent.layout.height
|
||||
}}>
|
||||
<TabBar
|
||||
key={items.join(',')}
|
||||
{...props}
|
||||
onSelect={onSelect}
|
||||
items={items}
|
||||
indicatorColor={pal.colors.link}
|
||||
/>
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
// @ts-ignore Web only
|
||||
position: 'sticky',
|
||||
zIndex: 1,
|
||||
// @ts-ignore Web only -prf
|
||||
left: 'calc(50% - 300px)',
|
||||
width: 600,
|
||||
top: 0,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
})
|
|
@ -449,7 +449,7 @@ let PostThreadItemLoaded = ({
|
|||
styles.replyLine,
|
||||
{
|
||||
flexGrow: 1,
|
||||
backgroundColor: pal.colors.border,
|
||||
backgroundColor: pal.colors.replyLine,
|
||||
marginBottom: 4,
|
||||
},
|
||||
]}
|
||||
|
@ -487,7 +487,7 @@ let PostThreadItemLoaded = ({
|
|||
styles.replyLine,
|
||||
{
|
||||
flexGrow: 1,
|
||||
backgroundColor: pal.colors.border,
|
||||
backgroundColor: pal.colors.replyLine,
|
||||
marginTop: 4,
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, {memo} from 'react'
|
|||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyFeedPost,
|
||||
|
@ -19,6 +20,8 @@ import * as Toast from '../Toast'
|
|||
import {EventStopper} from '../EventStopper'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {getCurrentRoute} from 'lib/routes/helpers'
|
||||
import {getTranslatorLink} from '#/locale/helpers'
|
||||
import {usePostDeleteMutation} from '#/state/queries/post'
|
||||
import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
|
||||
|
@ -63,6 +66,7 @@ let PostDropdownBtn = ({
|
|||
const hiddenPosts = useHiddenPosts()
|
||||
const {hidePost} = useHiddenPostsApi()
|
||||
const openLink = useOpenLink()
|
||||
const navigation = useNavigation()
|
||||
|
||||
const rootUri = record.reply?.root?.uri || postUri
|
||||
const isThreadMuted = mutedThreads.includes(rootUri)
|
||||
|
@ -82,13 +86,38 @@ let PostDropdownBtn = ({
|
|||
postDeleteMutation.mutateAsync({uri: postUri}).then(
|
||||
() => {
|
||||
Toast.show(_(msg`Post deleted`))
|
||||
|
||||
const route = getCurrentRoute(navigation.getState())
|
||||
if (route.name === 'PostThread') {
|
||||
const params = route.params as CommonNavigatorParams['PostThread']
|
||||
if (
|
||||
currentAccount &&
|
||||
isAuthor &&
|
||||
(params.name === currentAccount.handle ||
|
||||
params.name === currentAccount.did)
|
||||
) {
|
||||
const currentHref = makeProfileLink(postAuthor, 'post', params.rkey)
|
||||
if (currentHref === href && navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
e => {
|
||||
logger.error('Failed to delete post', {message: e})
|
||||
Toast.show(_(msg`Failed to delete post, please try again`))
|
||||
},
|
||||
)
|
||||
}, [postUri, postDeleteMutation, _])
|
||||
}, [
|
||||
navigation,
|
||||
postUri,
|
||||
postDeleteMutation,
|
||||
postAuthor,
|
||||
currentAccount,
|
||||
isAuthor,
|
||||
href,
|
||||
_,
|
||||
])
|
||||
|
||||
const onToggleThreadMute = React.useCallback(() => {
|
||||
try {
|
||||
|
|
|
@ -6,7 +6,7 @@ import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
|||
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
|
||||
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
|
||||
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
||||
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
|
||||
import {HomeHeader} from '../com/home/HomeHeader'
|
||||
import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
|
||||
import {FeedPage} from 'view/com/feeds/FeedPage'
|
||||
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
|
||||
|
@ -118,7 +118,7 @@ function HomeScreenReady({
|
|||
const renderTabBar = React.useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
return (
|
||||
<FeedsTabBar
|
||||
<HomeHeader
|
||||
key="FEEDS_TAB_BAR"
|
||||
selectedPage={props.selectedPage}
|
||||
onSelect={props.onSelect}
|
||||
|
|
|
@ -78,9 +78,9 @@ function RepliesThresholdInput({
|
|||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
'PreferencesHomeFeed'
|
||||
'PreferencesFollowingFeed'
|
||||
>
|
||||
export function PreferencesHomeFeed({navigation}: Props) {
|
||||
export function PreferencesFollowingFeed({navigation}: Props) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
|
@ -101,14 +101,14 @@ export function PreferencesHomeFeed({navigation}: Props) {
|
|||
styles.container,
|
||||
isTabletOrDesktop && styles.desktopContainer,
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Home Feed Preferences`)} showOnDesktop />
|
||||
<ViewHeader title={_(msg`Following Feed Preferences`)} showOnDesktop />
|
||||
<View
|
||||
style={[
|
||||
styles.titleSection,
|
||||
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
|
||||
]}>
|
||||
<Text type="xl" style={[pal.textLight, styles.description]}>
|
||||
<Trans>Fine-tune the content you see on your home screen.</Trans>
|
||||
<Trans>Fine-tune the content you see on your Following feed.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
@ -260,7 +260,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
|
|||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Set this setting to "Yes" to show samples of your saved feeds in
|
||||
your following feed. This is an experimental feature.
|
||||
your Following feed. This is an experimental feature.
|
||||
</Trans>
|
||||
</Text>
|
||||
<ToggleButton
|
|
@ -241,8 +241,8 @@ export function SettingsScreen({}: Props) {
|
|||
Toast.show(_(msg`Copied build version to clipboard`))
|
||||
}, [_])
|
||||
|
||||
const openHomeFeedPreferences = React.useCallback(() => {
|
||||
navigation.navigate('PreferencesHomeFeed')
|
||||
const openFollowingFeedPreferences = React.useCallback(() => {
|
||||
navigation.navigate('PreferencesFollowingFeed')
|
||||
}, [navigation])
|
||||
|
||||
const openThreadsPreferences = React.useCallback(() => {
|
||||
|
@ -529,7 +529,7 @@ export function SettingsScreen({}: Props) {
|
|||
pal.view,
|
||||
isSwitchingAccounts && styles.dimmed,
|
||||
]}
|
||||
onPress={openHomeFeedPreferences}
|
||||
onPress={openFollowingFeedPreferences}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint=""
|
||||
accessibilityLabel={_(msg`Opens the home feed preferences`)}>
|
||||
|
@ -540,7 +540,7 @@ export function SettingsScreen({}: Props) {
|
|||
/>
|
||||
</View>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Home Feed Preferences</Trans>
|
||||
<Trans>Following Feed Preferences</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
|
|
|
@ -2,99 +2,26 @@ import React from 'react'
|
|||
import {View} from 'react-native'
|
||||
|
||||
import * as tokens from '#/alf/tokens'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
export function Palette() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.gap_md]}>
|
||||
<View style={[a.flex_row, a.gap_md]}>
|
||||
<View
|
||||
style={[a.flex_1, {height: 60, backgroundColor: tokens.color.gray_0}]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_25},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_50},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_100},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_200},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_300},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_400},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_500},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_600},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_700},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_800},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_900},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_950},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_975},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
{height: 60, backgroundColor: tokens.color.gray_1000},
|
||||
]}
|
||||
/>
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_25, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_50, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_100, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_200, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_300, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_400, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_500, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_600, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_700, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_800, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_900, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_950, {height: 60}]} />
|
||||
<View style={[a.flex_1, t.atoms.bg_contrast_975, {height: 60}]} />
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_row, a.gap_md]}>
|
||||
|
|
Loading…
Reference in New Issue