Compare commits

..

1 Commits

Author SHA1 Message Date
Dan Abramov 493a4928f9 Refetch empty feed on focus 2024-12-17 10:45:19 +00:00
208 changed files with 43787 additions and 88794 deletions
@@ -64,7 +64,6 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ EAS Build
-1
View File
@@ -76,7 +76,6 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
- name: 🏗️ EAS Build
@@ -69,9 +69,6 @@ jobs:
- name: Lint check
run: yarn lint
- name: Lint lockfile
run: yarn lockfile-lint
- name: Prettier check
run: yarn prettier --check .
@@ -104,7 +101,6 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ Create Bundle
@@ -200,7 +196,6 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
- name: 🏗️ EAS Build
@@ -227,7 +222,7 @@ jobs:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-android
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
# available here
if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.changes-detected }}
@@ -278,7 +273,6 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ EAS Build
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
- name: Set up Go tooling
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.22'
- name: Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Check
@@ -36,7 +36,7 @@ jobs:
- name: Set up Go tooling
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: '1.22'
- name: Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Lint
-2
View File
@@ -28,8 +28,6 @@ jobs:
attempt_delay: 2000
- name: Lint check
run: yarn lint
- name: Lint lockfile
run: yarn lockfile-lint
- name: Prettier check
run: yarn prettier --check .
- name: Check & compile i18n
-46
View File
@@ -1,46 +0,0 @@
name: Lockfile
on:
pull_request:
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
verify-yarn-lock:
name: No manual yarn.lock edits
runs-on: ubuntu-latest
steps:
- name: Check out PR HEAD
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fetch base branch
run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Install node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Reset yarn.lock to base
run: git show "origin/${{ github.base_ref }}:yarn.lock" > yarn.lock
- name: Yarn install
uses: Wandalen/wretry.action@master
with:
# Fine to skip scripts since we don't run any code
command: yarn install --ignore-scripts
attempt_limit: 3
attempt_delay: 2000
- name: Verify yarn.lock
run: |
git diff --quiet --exit-code || {
echo '::error::`yarn.lock` does not match what Yarn would generate given the base `yarn.lock` and the head `package.json`.'
echo '::error:: - If this is intentional, you can ignore this check.'
echo '::error:: - If this is unintentional, apply the following diff:'
git --no-pager diff
exit 1
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.23-bullseye AS build-env
FROM golang:1.22-bullseye AS build-env
WORKDIR /usr/src/social-app
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.23-bullseye AS build-env
FROM golang:1.22-bullseye AS build-env
WORKDIR /usr/src/social-app
+8 -5
View File
@@ -26,7 +26,7 @@ appId: xyz.blueskyweb.app
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- tapOn: "People"
- tapOn: "About"
- assertVisible: "Good Ppl"
- assertVisible: "They good"
@@ -74,6 +74,7 @@ appId: xyz.blueskyweb.app
- tapOn: "Delete List"
- tapOn:
id: "confirmBtn"
- assertVisible: "This list is empty!"
- tapOn:
label: "Create a new curatelist"
@@ -92,12 +93,14 @@ appId: xyz.blueskyweb.app
- tapOn: "Save"
- assertNotVisible:
id: "createOrEditListModal"
- tapOn: "People"
- tapOn: "About"
- assertVisible: "Good Ppl"
- assertVisible: "They good"
- tapOn: "People"
- tapOn: "About"
- tapOn: "Start adding people"
- tapOn:
label: "Adds users on curatelists from the list"
id: "addUserBtn"
- assertVisible:
id: "listAddUserModal"
- tapOn:
@@ -142,7 +145,7 @@ appId: xyz.blueskyweb.app
id: "e2eGotoLists"
- tapOn: "Good Ppl"
- tapOn: "People"
- tapOn: "About"
- assertVisible:
label: "Removes users on curatelists from the list"
id: "user-bob.test"
+16 -8
View File
@@ -11,8 +11,10 @@ appId: xyz.blueskyweb.app
# Pin alice's feed
- extendedWaitUntil:
visible: "Open drawer menu"
- tapOn: "Open drawer menu"
visible:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "profileCardButton"
- tapOn:
@@ -30,7 +32,8 @@ appId: xyz.blueskyweb.app
text: "alice-favs"
# Set alice-favs first
- tapOn: "Open drawer menu"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
@@ -41,7 +44,8 @@ appId: xyz.blueskyweb.app
- tapOn:
label: "Save button"
id: "saveChangesBtn"
- tapOn: "Go back"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
@@ -50,7 +54,8 @@ appId: xyz.blueskyweb.app
text: "Following"
# Set following first
- tapOn: "Open drawer menu"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
@@ -61,7 +66,8 @@ appId: xyz.blueskyweb.app
- tapOn:
label: "Save button"
id: "saveChangesBtn"
- tapOn: "Go back"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "Following"
@@ -70,7 +76,8 @@ appId: xyz.blueskyweb.app
text: "alice-favs"
# Remove following
- tapOn: "Open drawer menu"
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
@@ -81,7 +88,8 @@ appId: xyz.blueskyweb.app
- tapOn:
label: "Save button"
id: "saveChangesBtn"
- tapOn: "Go back"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
+1 -3
View File
@@ -27,9 +27,7 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "bottomBarHomeBtn"
- tapOn:
id: "bottomBarHomeBtn"
- tapOn:
id: "bottomBarHomeBtn"
id: "viewHeaderDrawerBtn"
- assertNotVisible: "Feeds ✨"
- tapOn:
+1 -1
View File
@@ -14,7 +14,7 @@ appId: xyz.blueskyweb.app
id: "e2eGotoModeration"
- tapOn:
id: "moderationlistsBtn"
- tapOn: "New list"
- tapOn: "New"
- tapOn:
id: "editNameInput"
- inputText: "Muted Users"
-2
View File
@@ -6,8 +6,6 @@ appId: xyz.blueskyweb.app
- waitForAnimationToEnd
- tapOn: "http://localhost:8081"
- waitForAnimationToEnd
- extendedWaitUntil:
visible: "Continue"
- swipe:
from: "Bluesky"
direction: DOWN
+2 -9
View File
@@ -50,6 +50,7 @@ module.exports = function (config) {
runtimeVersion: {
policy: 'appVersion',
},
orientation: 'portrait',
icon: './assets/app-icons/ios_icon_default_light.png',
userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
@@ -76,7 +77,6 @@ module.exports = function (config) {
'ast',
'ca',
'de',
'el',
'es',
'fi',
'fr',
@@ -87,13 +87,10 @@ module.exports = function (config) {
'id',
'it',
'ja',
'km',
'ko',
'ne',
'nl',
'pl',
'pt-BR',
'ro',
'ru',
'th',
'tr',
@@ -191,7 +188,6 @@ module.exports = function (config) {
channel: UPDATES_CHANNEL,
},
plugins: [
'expo-video',
'expo-localization',
USE_SENTRY && [
'@sentry/react-native/expo',
@@ -226,14 +222,12 @@ module.exports = function (config) {
},
],
'react-native-compressor',
// TODO: Reenable when the build issue is fixed.
// '@bitdrift/react-native',
'@bitdrift/react-native',
'./plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
'./plugins/withAndroidManifestPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js',
'./plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js',
'./plugins/withAndroidNoJitpackPlugin.js',
'./plugins/shareExtension/withShareExtensions.js',
'./plugins/notificationsExtension/withNotificationsExtension.js',
'./plugins/withAppDelegateReferrer.js',
@@ -347,7 +341,6 @@ module.exports = function (config) {
},
},
],
['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
].filter(Boolean),
extra: {
eas: {
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 0-1 1v4.17a1 1 0 0 0 .667.944 2.001 2.001 0 0 1 0 3.772A1 1 0 0 0 2 14.83V19a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1v-4.17a1 1 0 0 0-.667-.944 2.001 2.001 0 0 1 0-3.772A1 1 0 0 0 22 9.17V5a1 1 0 0 0-1-1H3Zm1 4.535V6h16v2.535A4 4 0 0 0 18 12c0 1.482.805 2.773 2 3.465V18H4v-2.535A4 4 0 0 0 6 12a4 4 0 0 0-2-3.465ZM15 15a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-1-3a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm1-5a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z" clip-rule="evenodd"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" stroke="#000" stroke-linejoin="round" d="M4 5.5a.5.5 0 0 0-.5.5v2.535a.5.5 0 0 0 .25.433A3.5 3.5 0 0 1 5.5 12a3.5 3.5 0 0 1-1.75 3.032.5.5 0 0 0-.25.433V18a.5.5 0 0 0 .5.5h16a.5.5 0 0 0 .5-.5v-2.535a.5.5 0 0 0-.25-.433A3.5 3.5 0 0 1 18.5 12a3.5 3.5 0 0 1 1.75-3.032.5.5 0 0 0 .25-.433V6a.5.5 0 0 0-.5-.5H4ZM2.5 6A1.5 1.5 0 0 1 4 4.5h16A1.5 1.5 0 0 1 21.5 6v3.17a.5.5 0 0 1-.333.472 2.501 2.501 0 0 0 0 4.716.5.5 0 0 1 .333.471V18a1.5 1.5 0 0 1-1.5 1.5H4A1.5 1.5 0 0 1 2.5 18v-3.17a.5.5 0 0 1 .333-.472 2.501 2.501 0 0 0 0-4.716.5.5 0 0 1-.333-.471V6Zm12 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 756 B

-1
View File
@@ -4,7 +4,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bluesky Embed</title>
<link rel="preconnect" href="https://public.api.bsky.app">
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
-1
View File
@@ -4,7 +4,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bluesky Embed</title>
<link rel="preconnect" href="https://public.api.bsky.app">
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+1 -1
View File
@@ -24,7 +24,7 @@ Then build and copy over the big 'ol `bundle.web.js` file:
### Golang Daemon
Install golang. We generally develop against the current stable release of the language, as declared in `go.mod`.
Install golang. We are generally using v1.22+.
In this directory (`bskyweb/`):
-4
View File
@@ -235,7 +235,6 @@ func serve(cctx *cli.Context) error {
// generic routes
e.GET("/hashtag/:tag", server.WebGeneric)
e.GET("/topic/:topic", server.WebGeneric)
e.GET("/search", server.WebGeneric)
e.GET("/feeds", server.WebGeneric)
e.GET("/notifications", server.WebGeneric)
@@ -272,9 +271,6 @@ func serve(cctx *cli.Context) error {
e.GET("/messages", server.WebGeneric)
e.GET("/messages/:conversation", server.WebGeneric)
// temp
e.GET("/temp-vibe", server.WebGeneric)
// profile endpoints; only first populates info
e.GET("/profile/:handleOrDID", server.WebProfile)
e.GET("/profile/:handleOrDID/follows", server.WebGeneric)
+2 -2
View File
@@ -1,8 +1,8 @@
module github.com/bluesky-social/social-app/bskyweb
go 1.23
go 1.22
toolchain go1.23.4
toolchain go1.22.4
require (
github.com/bluesky-social/indigo v0.0.0-20240624223826-fd66f93ce141
+1 -1
View File
@@ -13,7 +13,7 @@
<!-- Hello Humans! API docs at https://atproto.com -->
<link rel="preload" as="font" type="font/woff2" href="{{ staticCDNHost }}/static/media/InterVariable.c504db5c06caaf7cdfba.woff2" crossorigin>
<link rel="preload" as="font" type="font/ttf" href="{{ staticCDNHost }}/static/media/InterVariable.c504db5c06caaf7cdfba.woff2">
<style>
/**
-16
View File
@@ -105,19 +105,3 @@ jest.mock('expo-modules-core', () => ({
return () => null
}),
}))
jest.mock('expo-localization', () => ({
getLocales: () => [],
}))
jest.mock('statsig-react-native-expo', () => ({
Statsig: {
initialize() {},
initializeCalled() {
return false
},
},
}))
jest.mock('../src/lib/bitdrift', () => ({}))
jest.mock('../src/lib/statsig/statsig', () => ({}))
-4
View File
@@ -6,7 +6,6 @@ module.exports = {
'ast',
'ca',
'de',
'el',
'en-GB',
'es',
'fi',
@@ -18,13 +17,10 @@ module.exports = {
'id',
'it',
'ja',
'km',
'ko',
'ne',
'nl',
'pl',
'pt-BR',
'ro',
'ru',
'th',
'tr',
+6 -30
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.97.0",
"version": "1.96.0",
"private": true,
"engines": {
"node": ">=20"
@@ -54,7 +54,8 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
"@atproto/api": "^0.13.21",
"@atproto/api": "^0.13.20",
"@bitdrift/react-native": "0.4.0",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
@@ -62,6 +63,7 @@
"@expo/webpack-config": "^19.0.0",
"@floating-ui/dom": "^1.6.3",
"@floating-ui/react-dom": "^2.0.8",
"@formatjs/intl-datetimeformat": "^6.17.1",
"@formatjs/intl-locale": "^4.2.8",
"@formatjs/intl-numberformat": "^8.15.1",
"@formatjs/intl-pluralrules": "^5.4.1",
@@ -69,7 +71,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-native-fontawesome": "^0.3.2",
"@haileyok/bluesky-video": "0.2.6",
"@haileyok/bluesky-video": "0.2.4",
"@ipld/dag-cbor": "^9.2.0",
"@lingui/react": "^4.14.1",
"@mattermost/react-native-paste-input": "^0.7.1",
@@ -138,14 +140,12 @@
"expo-media-library": "~17.0.3",
"expo-navigation-bar": "~4.0.4",
"expo-notifications": "~0.29.11",
"expo-screen-orientation": "^8.0.2",
"expo-sharing": "^13.0.0",
"expo-splash-screen": "~0.29.18",
"expo-status-bar": "~2.0.0",
"expo-system-ui": "^4.0.4",
"expo-task-manager": "~12.0.3",
"expo-updates": "~0.26.10",
"expo-video": "^2.0.5",
"expo-web-browser": "~14.0.1",
"fast-text-encoding": "^1.0.6",
"history": "^5.3.0",
@@ -172,7 +172,7 @@
"react-native": "0.76.3",
"react-native-compressor": "1.10.3",
"react-native-date-picker": "^5.0.7",
"react-native-drawer-layout": "^4.1.1",
"react-native-drawer-layout": "^4.0.4",
"react-native-gesture-handler": "2.20.2",
"react-native-get-random-values": "~1.11.0",
"react-native-image-crop-picker": "^0.41.6",
@@ -251,7 +251,6 @@
"jest-expo": "^52.0.2",
"jest-junit": "^16.0.0",
"lint-staged": "^13.2.3",
"lockfile-lint": "^4.14.0",
"metro-react-native-babel-preset": "^0.76.9",
"prettier": "^2.8.3",
"react-native-dotenv": "^3.4.11",
@@ -331,28 +330,5 @@
"assets/icons/*.svg": [
"svgo"
]
},
"lockfile-lint": {
"path": "yarn.lock",
"allowedHosts": [
"npm",
"yarn"
],
"allowedSchemes": [
"https:"
],
"allowedPackageNameAliases": [
"@babel/traverse--for-generate-function-map:@babel/traverse",
"string-width-cjs:string-width",
"strip-ansi-cjs:strip-ansi",
"wrap-ansi-cjs:wrap-ansi"
],
"allowedUrls": [
"https://codeload.github.com/bluesky-social/react-native-bottom-sheet/tar.gz/28a87d1bb55e10fc355fa1455545a30734995908",
"https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4"
],
"emptyHostname": false,
"validatePackageNames": true,
"validateIntegrity": true
}
}
@@ -1,157 +1,16 @@
diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
index e916023..5049c33 100644
index e916023..0564d97 100644
--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
+++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m
@@ -4,6 +4,7 @@
//
// Created by Elias Nahum on 04-11-20.
// Copyright © 2020 Facebook. All rights reserved.
+// Updated to remove parents default text view
//
#import "PasteInputView.h"
@@ -12,49 +13,78 @@
@implementation PasteInputView
{
- PasteInputTextView *_backedTextInputView;
+ // We'll store the custom text view in this ivar
+ PasteInputTextView *_customBackedTextView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
+ // Must call the supers designated initializer
if (self = [super initWithBridge:bridge]) {
- _backedTextInputView = [[PasteInputTextView alloc] initWithFrame:self.bounds];
- _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- _backedTextInputView.textInputDelegate = self;
+ // 1. The parent (RCTMultilineTextInputView) has already created
+ // its own _backedTextInputView = [RCTUITextView new] in super init.
+ // We can remove that subview:
- [self addSubview:_backedTextInputView];
- }
+ id<RCTBackedTextInputViewProtocol> parentInputView = super.backedTextInputView;
+ if ([parentInputView isKindOfClass:[UIView class]]) {
+ UIView *parentSubview = (UIView *)parentInputView;
+ if (parentSubview.superview == self) {
+ [parentSubview removeFromSuperview];
+ }
+ }
+ // 2. Now create our custom PasteInputTextView
+ _customBackedTextView = [[PasteInputTextView alloc] initWithFrame:self.bounds];
+ _customBackedTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ _customBackedTextView.textInputDelegate = self;
+
+ // Optional: disable inline predictions for iOS 17+
@@ -22,6 +22,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backedTextInputView.textInputDelegate = self;
+ // Disable inline predictions to prevent jank in the composer
+ if (@available(iOS 17.0, *)) {
+ _customBackedTextView.inlinePredictionType = UITextInlinePredictionTypeNo;
+ _backedTextInputView.inlinePredictionType = UITextInlinePredictionTypeNo;
+ }
+
+ // 3. Add your custom text view as the only subview
+ [self addSubview:_customBackedTextView];
+ }
return self;
}
+/**
+ * Override the parent's accessor so that anywhere in RN that calls
+ * `self.backedTextInputView` will get the custom PasteInputTextView.
+ */
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
{
- return _backedTextInputView;
+ return _customBackedTextView;
}
-- (void)setDisableCopyPaste:(BOOL)disableCopyPaste {
- _backedTextInputView.disableCopyPaste = disableCopyPaste;
+#pragma mark - Setters for React Props
+
+- (void)setDisableCopyPaste:(BOOL)disableCopyPaste
+{
+ _customBackedTextView.disableCopyPaste = disableCopyPaste;
}
-- (void)setOnPaste:(RCTDirectEventBlock)onPaste {
- _backedTextInputView.onPaste = onPaste;
+- (void)setOnPaste:(RCTDirectEventBlock)onPaste
+{
+ _customBackedTextView.onPaste = onPaste;
}
-- (void)setSmartPunctuation:(NSString *)smartPunctuation {
- if ([smartPunctuation isEqualToString:@"enable"]) {
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeYes];
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeYes];
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes];
- } else if ([smartPunctuation isEqualToString:@"disable"]) {
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeNo];
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeNo];
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
- } else {
- [_backedTextInputView setSmartDashesType:UITextSmartDashesTypeDefault];
- [_backedTextInputView setSmartQuotesType:UITextSmartQuotesTypeDefault];
- [_backedTextInputView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault];
- }
+- (void)setSmartPunctuation:(NSString *)smartPunctuation
+{
+ if ([smartPunctuation isEqualToString:@"enable"]) {
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeYes];
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeYes];
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeYes];
+ } else if ([smartPunctuation isEqualToString:@"disable"]) {
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeNo];
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeNo];
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeNo];
+ } else {
+ [_customBackedTextView setSmartDashesType:UITextSmartDashesTypeDefault];
+ [_customBackedTextView setSmartQuotesType:UITextSmartQuotesTypeDefault];
+ [_customBackedTextView setSmartInsertDeleteType:UITextSmartInsertDeleteTypeDefault];
+ }
}
#pragma mark - UIScrollViewDelegate
@@ -62,7 +92,6 @@
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
RCTDirectEventBlock onScroll = self.onScroll;
-
if (onScroll) {
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
@@ -71,22 +100,22 @@
onScroll(@{
@"contentOffset": @{
- @"x": @(contentOffset.x),
- @"y": @(contentOffset.y)
+ @"x": @(contentOffset.x),
+ @"y": @(contentOffset.y)
},
@"contentInset": @{
- @"top": @(contentInset.top),
- @"left": @(contentInset.left),
- @"bottom": @(contentInset.bottom),
- @"right": @(contentInset.right)
+ @"top": @(contentInset.top),
+ @"left": @(contentInset.left),
+ @"bottom": @(contentInset.bottom),
+ @"right": @(contentInset.right)
},
@"contentSize": @{
- @"width": @(contentSize.width),
- @"height": @(contentSize.height)
+ @"width": @(contentSize.width),
+ @"height": @(contentSize.height)
},
@"layoutMeasurement": @{
- @"width": @(size.width),
- @"height": @(size.height)
+ @"width": @(size.width),
+ @"height": @(size.height)
},
@"zoomScale": @(scrollView.zoomScale ?: 1),
});
[self addSubview:_backedTextInputView];
}
-19
View File
@@ -1,19 +0,0 @@
diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
index b928b8f..7175cf6 100644
--- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
+++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
@@ -99,12 +99,12 @@ class MediaLibraryModule : Module() {
}
AsyncFunction("createAssetAsync") { localUri: String, promise: Promise ->
- throwUnlessPermissionsGranted {
+ //throwUnlessPermissionsGranted {
withModuleScope(promise) {
CreateAsset(context, localUri, promise)
.execute()
}
- }
+ //}
}
AsyncFunction("addAssetsToAlbumAsync") { assetsId: List<String>, albumId: String, copyToAlbum: Boolean, promise: Promise ->
@@ -0,0 +1,876 @@
diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/index.js b/node_modules/react-native-drawer-layout/lib/commonjs/index.js
index 3dce76e..6c4b3e5 100644
--- a/node_modules/react-native-drawer-layout/lib/commonjs/index.js
+++ b/node_modules/react-native-drawer-layout/lib/commonjs/index.js
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "Drawer", {
return _Drawer.Drawer;
}
});
+Object.defineProperty(exports, "DrawerGestureContext", {
+ enumerable: true,
+ get: function () {
+ return _DrawerGestureContext.DrawerGestureContext;
+ }
+});
Object.defineProperty(exports, "DrawerProgressContext", {
enumerable: true,
get: function () {
@@ -21,6 +27,7 @@ Object.defineProperty(exports, "useDrawerProgress", {
return _useDrawerProgress.useDrawerProgress;
}
});
+var _DrawerGestureContext = require("./utils/DrawerGestureContext.js");
var _DrawerProgressContext = require("./utils/DrawerProgressContext.js");
var _useDrawerProgress = require("./utils/useDrawerProgress.js");
var _Drawer = require("./views/Drawer");
diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js b/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js
new file mode 100644
index 0000000..de6d793
--- /dev/null
+++ b/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js
@@ -0,0 +1,11 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.DrawerGestureContext = void 0;
+var React = _interopRequireWildcard(require("react"));
+function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
+function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
+const DrawerGestureContext = exports.DrawerGestureContext = /*#__PURE__*/React.createContext(undefined);
+//# sourceMappingURL=DrawerGestureContext.js.map
\ No newline at end of file
diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js b/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js
index fd65ab8..86a9a6a 100644
--- a/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js
+++ b/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js
@@ -8,6 +8,7 @@ var React = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
var _useLatestCallback = _interopRequireDefault(require("use-latest-callback"));
+var _DrawerGestureContext = require("../utils/DrawerGestureContext.js");
var _DrawerProgressContext = require("../utils/DrawerProgressContext.js");
var _getDrawerWidth = require("../utils/getDrawerWidth.js");
var _GestureHandler = require("./GestureHandler");
@@ -79,38 +80,38 @@ function Drawer({
return () => hideStatusBar(false);
}, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]);
const interactionHandleRef = React.useRef(null);
- const startInteraction = () => {
+ const startInteraction = React.useCallback(() => {
interactionHandleRef.current = _reactNative.InteractionManager.createInteractionHandle();
- };
- const endInteraction = () => {
+ }, []);
+ const endInteraction = React.useCallback(() => {
if (interactionHandleRef.current != null) {
_reactNative.InteractionManager.clearInteractionHandle(interactionHandleRef.current);
interactionHandleRef.current = null;
}
- };
- const hideKeyboard = () => {
+ }, []);
+ const hideKeyboard = React.useCallback(() => {
if (keyboardDismissMode === 'on-drag') {
_reactNative.Keyboard.dismiss();
}
- };
- const onGestureBegin = () => {
+ }, [keyboardDismissMode]);
+ const onGestureBegin = React.useCallback(() => {
onGestureStart?.();
startInteraction();
hideKeyboard();
hideStatusBar(true);
- };
- const onGestureFinish = () => {
+ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]);
+ const onGestureFinish = React.useCallback(() => {
onGestureEnd?.();
endInteraction();
- };
- const onGestureAbort = () => {
+ }, [onGestureEnd, endInteraction]);
+ const onGestureAbort = React.useCallback(() => {
onGestureCancel?.();
endInteraction();
- };
+ }, [onGestureCancel, endInteraction]);
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
// https://github.com/software-mansion/react-native-gesture-handler/issues/569
- const hitSlop = isRight ?
+ const hitSlop = React.useMemo(() => isRight ?
// Extend hitSlop to the side of the screen when drawer is closed
// This lets the user drag the drawer from the side of the screen
{
@@ -119,7 +120,7 @@ function Drawer({
} : {
left: 0,
width: isOpen ? undefined : swipeEdgeWidth
- };
+ }, [isRight, isOpen, swipeEdgeWidth]);
const touchStartX = (0, _reactNativeReanimated.useSharedValue)(0);
const touchX = (0, _reactNativeReanimated.useSharedValue)(0);
const translationX = (0, _reactNativeReanimated.useSharedValue)(getDrawerTranslationX(open));
@@ -158,40 +159,43 @@ function Drawer({
}, [getDrawerTranslationX, handleAnimationEnd, handleAnimationStart, onClose, onOpen, touchStartX, touchX, translationX]);
React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
const startX = (0, _reactNativeReanimated.useSharedValue)(0);
- let pan = _GestureHandler.Gesture?.Pan().onBegin(event => {
- 'worklet';
+ const pan = React.useMemo(() => {
+ let panGesture = _GestureHandler.Gesture?.Pan().onBegin(event => {
+ 'worklet';
- startX.value = translationX.value;
- gestureState.value = event.state;
- touchStartX.value = event.x;
- }).onStart(() => {
- 'worklet';
+ startX.value = translationX.value;
+ gestureState.value = event.state;
+ touchStartX.value = event.x;
+ }).onStart(() => {
+ 'worklet';
- (0, _reactNativeReanimated.runOnJS)(onGestureBegin)();
- }).onChange(event => {
- 'worklet';
+ (0, _reactNativeReanimated.runOnJS)(onGestureBegin)();
+ }).onChange(event => {
+ 'worklet';
- touchX.value = event.x;
- translationX.value = startX.value + event.translationX;
- gestureState.value = event.state;
- }).onEnd((event, success) => {
- 'worklet';
+ touchX.value = event.x;
+ translationX.value = startX.value + event.translationX;
+ gestureState.value = event.state;
+ }).onEnd((event, success) => {
+ 'worklet';
- gestureState.value = event.state;
- if (!success) {
- (0, _reactNativeReanimated.runOnJS)(onGestureAbort)();
+ gestureState.value = event.state;
+ if (!success) {
+ (0, _reactNativeReanimated.runOnJS)(onGestureAbort)();
+ }
+ const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ?
+ // If swiped to right, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 :
+ // If swiped to left, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open;
+ toggleDrawer(nextOpen, event.velocityX);
+ (0, _reactNativeReanimated.runOnJS)(onGestureFinish)();
+ }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled);
+ if (panGesture && configureGestureHandler) {
+ panGesture = configureGestureHandler(panGesture);
}
- const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ?
- // If swiped to right, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 :
- // If swiped to left, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open;
- toggleDrawer(nextOpen, event.velocityX);
- (0, _reactNativeReanimated.runOnJS)(onGestureFinish)();
- }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled);
- if (pan && configureGestureHandler) {
- pan = configureGestureHandler(pan);
- }
+ return panGesture;
+ }, [configureGestureHandler, drawerPosition, drawerType, gestureState, hitSlop, onGestureBegin, onGestureAbort, onGestureFinish, open, startX, swipeEnabled, swipeMinDistance, swipeMinVelocity, toggleDrawer, touchStartX, touchX, translationX]);
const translateX = (0, _reactNativeReanimated.useDerivedValue)(() => {
// Comment stolen from react-native-gesture-handler/DrawerLayout
//
@@ -254,35 +258,38 @@ function Drawer({
style: [styles.container, style],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DrawerProgressContext.DrawerProgressContext.Provider, {
value: progress,
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureHandler.GestureDetector, {
- gesture: pan,
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
- style: [styles.main, {
- flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row'
- }],
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
- style: [styles.content, contentAnimatedStyle],
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
- accessibilityElementsHidden: isOpen && drawerType !== 'permanent',
- importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto',
- style: styles.content,
- children: children
- }), drawerType !== 'permanent' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Overlay.Overlay, {
- open: open,
- progress: progress,
- onPress: () => toggleDrawer(false),
- style: overlayStyle,
- accessibilityLabel: overlayAccessibilityLabel
- }) : null]
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
- removeClippedSubviews: _reactNative.Platform.OS !== 'ios',
- style: [styles.drawer, {
- width: drawerWidth,
- position: drawerType === 'permanent' ? 'relative' : 'absolute',
- zIndex: drawerType === 'back' ? -1 : 0
- }, drawerAnimatedStyle, drawerStyle],
- children: renderDrawerContent()
- })]
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DrawerGestureContext.DrawerGestureContext.Provider, {
+ value: pan,
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureHandler.GestureDetector, {
+ gesture: pan,
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
+ style: [styles.main, {
+ flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row'
+ }],
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
+ style: [styles.content, contentAnimatedStyle],
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
+ accessibilityElementsHidden: isOpen && drawerType !== 'permanent',
+ importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto',
+ style: styles.content,
+ children: children
+ }), drawerType !== 'permanent' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Overlay.Overlay, {
+ open: open,
+ progress: progress,
+ onPress: () => toggleDrawer(false),
+ style: overlayStyle,
+ accessibilityLabel: overlayAccessibilityLabel
+ }) : null]
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
+ removeClippedSubviews: _reactNative.Platform.OS !== 'ios',
+ style: [styles.drawer, {
+ width: drawerWidth,
+ position: drawerType === 'permanent' ? 'relative' : 'absolute',
+ zIndex: drawerType === 'back' ? -1 : 0
+ }, drawerAnimatedStyle, drawerStyle],
+ children: renderDrawerContent()
+ })]
+ })
})
})
})
diff --git a/node_modules/react-native-drawer-layout/lib/module/index.js b/node_modules/react-native-drawer-layout/lib/module/index.js
index a600e51..f08275c 100644
--- a/node_modules/react-native-drawer-layout/lib/module/index.js
+++ b/node_modules/react-native-drawer-layout/lib/module/index.js
@@ -1,5 +1,6 @@
"use strict";
+export { DrawerGestureContext } from "./utils/DrawerGestureContext.js";
export { DrawerProgressContext } from "./utils/DrawerProgressContext.js";
export { useDrawerProgress } from "./utils/useDrawerProgress.js";
export { Drawer } from './views/Drawer';
diff --git a/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js b/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js
new file mode 100644
index 0000000..1adaa9c
--- /dev/null
+++ b/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js
@@ -0,0 +1,5 @@
+"use strict";
+
+import * as React from 'react';
+export const DrawerGestureContext = /*#__PURE__*/React.createContext(undefined);
+//# sourceMappingURL=DrawerGestureContext.js.map
\ No newline at end of file
diff --git a/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js b/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js
index 6d07126..981d9f8 100644
--- a/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js
+++ b/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js
@@ -4,6 +4,7 @@ import * as React from 'react';
import { I18nManager, InteractionManager, Keyboard, Platform, StatusBar, StyleSheet, useWindowDimensions, View } from 'react-native';
import Animated, { interpolate, ReduceMotion, runOnJS, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated';
import useLatestCallback from 'use-latest-callback';
+import { DrawerGestureContext } from "../utils/DrawerGestureContext.js";
import { DrawerProgressContext } from "../utils/DrawerProgressContext.js";
import { getDrawerWidth } from "../utils/getDrawerWidth.js";
import { Gesture, GestureDetector, GestureHandlerRootView, GestureState } from './GestureHandler';
@@ -72,38 +73,38 @@ export function Drawer({
return () => hideStatusBar(false);
}, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]);
const interactionHandleRef = React.useRef(null);
- const startInteraction = () => {
+ const startInteraction = React.useCallback(() => {
interactionHandleRef.current = InteractionManager.createInteractionHandle();
- };
- const endInteraction = () => {
+ }, []);
+ const endInteraction = React.useCallback(() => {
if (interactionHandleRef.current != null) {
InteractionManager.clearInteractionHandle(interactionHandleRef.current);
interactionHandleRef.current = null;
}
- };
- const hideKeyboard = () => {
+ }, []);
+ const hideKeyboard = React.useCallback(() => {
if (keyboardDismissMode === 'on-drag') {
Keyboard.dismiss();
}
- };
- const onGestureBegin = () => {
+ }, [keyboardDismissMode]);
+ const onGestureBegin = React.useCallback(() => {
onGestureStart?.();
startInteraction();
hideKeyboard();
hideStatusBar(true);
- };
- const onGestureFinish = () => {
+ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]);
+ const onGestureFinish = React.useCallback(() => {
onGestureEnd?.();
endInteraction();
- };
- const onGestureAbort = () => {
+ }, [onGestureEnd, endInteraction]);
+ const onGestureAbort = React.useCallback(() => {
onGestureCancel?.();
endInteraction();
- };
+ }, [onGestureCancel, endInteraction]);
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
// https://github.com/software-mansion/react-native-gesture-handler/issues/569
- const hitSlop = isRight ?
+ const hitSlop = React.useMemo(() => isRight ?
// Extend hitSlop to the side of the screen when drawer is closed
// This lets the user drag the drawer from the side of the screen
{
@@ -112,7 +113,7 @@ export function Drawer({
} : {
left: 0,
width: isOpen ? undefined : swipeEdgeWidth
- };
+ }, [isRight, isOpen, swipeEdgeWidth]);
const touchStartX = useSharedValue(0);
const touchX = useSharedValue(0);
const translationX = useSharedValue(getDrawerTranslationX(open));
@@ -151,40 +152,43 @@ export function Drawer({
}, [getDrawerTranslationX, handleAnimationEnd, handleAnimationStart, onClose, onOpen, touchStartX, touchX, translationX]);
React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
const startX = useSharedValue(0);
- let pan = Gesture?.Pan().onBegin(event => {
- 'worklet';
+ const pan = React.useMemo(() => {
+ let panGesture = Gesture?.Pan().onBegin(event => {
+ 'worklet';
- startX.value = translationX.value;
- gestureState.value = event.state;
- touchStartX.value = event.x;
- }).onStart(() => {
- 'worklet';
+ startX.value = translationX.value;
+ gestureState.value = event.state;
+ touchStartX.value = event.x;
+ }).onStart(() => {
+ 'worklet';
- runOnJS(onGestureBegin)();
- }).onChange(event => {
- 'worklet';
+ runOnJS(onGestureBegin)();
+ }).onChange(event => {
+ 'worklet';
- touchX.value = event.x;
- translationX.value = startX.value + event.translationX;
- gestureState.value = event.state;
- }).onEnd((event, success) => {
- 'worklet';
+ touchX.value = event.x;
+ translationX.value = startX.value + event.translationX;
+ gestureState.value = event.state;
+ }).onEnd((event, success) => {
+ 'worklet';
- gestureState.value = event.state;
- if (!success) {
- runOnJS(onGestureAbort)();
+ gestureState.value = event.state;
+ if (!success) {
+ runOnJS(onGestureAbort)();
+ }
+ const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ?
+ // If swiped to right, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 :
+ // If swiped to left, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open;
+ toggleDrawer(nextOpen, event.velocityX);
+ runOnJS(onGestureFinish)();
+ }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled);
+ if (panGesture && configureGestureHandler) {
+ panGesture = configureGestureHandler(panGesture);
}
- const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ?
- // If swiped to right, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 :
- // If swiped to left, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open;
- toggleDrawer(nextOpen, event.velocityX);
- runOnJS(onGestureFinish)();
- }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled);
- if (pan && configureGestureHandler) {
- pan = configureGestureHandler(pan);
- }
+ return panGesture;
+ }, [configureGestureHandler, drawerPosition, drawerType, gestureState, hitSlop, onGestureBegin, onGestureAbort, onGestureFinish, open, startX, swipeEnabled, swipeMinDistance, swipeMinVelocity, toggleDrawer, touchStartX, touchX, translationX]);
const translateX = useDerivedValue(() => {
// Comment stolen from react-native-gesture-handler/DrawerLayout
//
@@ -247,35 +251,38 @@ export function Drawer({
style: [styles.container, style],
children: /*#__PURE__*/_jsx(DrawerProgressContext.Provider, {
value: progress,
- children: /*#__PURE__*/_jsx(GestureDetector, {
- gesture: pan,
- children: /*#__PURE__*/_jsxs(Animated.View, {
- style: [styles.main, {
- flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row'
- }],
- children: [/*#__PURE__*/_jsxs(Animated.View, {
- style: [styles.content, contentAnimatedStyle],
- children: [/*#__PURE__*/_jsx(View, {
- accessibilityElementsHidden: isOpen && drawerType !== 'permanent',
- importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto',
- style: styles.content,
- children: children
- }), drawerType !== 'permanent' ? /*#__PURE__*/_jsx(Overlay, {
- open: open,
- progress: progress,
- onPress: () => toggleDrawer(false),
- style: overlayStyle,
- accessibilityLabel: overlayAccessibilityLabel
- }) : null]
- }), /*#__PURE__*/_jsx(Animated.View, {
- removeClippedSubviews: Platform.OS !== 'ios',
- style: [styles.drawer, {
- width: drawerWidth,
- position: drawerType === 'permanent' ? 'relative' : 'absolute',
- zIndex: drawerType === 'back' ? -1 : 0
- }, drawerAnimatedStyle, drawerStyle],
- children: renderDrawerContent()
- })]
+ children: /*#__PURE__*/_jsx(DrawerGestureContext.Provider, {
+ value: pan,
+ children: /*#__PURE__*/_jsx(GestureDetector, {
+ gesture: pan,
+ children: /*#__PURE__*/_jsxs(Animated.View, {
+ style: [styles.main, {
+ flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row'
+ }],
+ children: [/*#__PURE__*/_jsxs(Animated.View, {
+ style: [styles.content, contentAnimatedStyle],
+ children: [/*#__PURE__*/_jsx(View, {
+ accessibilityElementsHidden: isOpen && drawerType !== 'permanent',
+ importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto',
+ style: styles.content,
+ children: children
+ }), drawerType !== 'permanent' ? /*#__PURE__*/_jsx(Overlay, {
+ open: open,
+ progress: progress,
+ onPress: () => toggleDrawer(false),
+ style: overlayStyle,
+ accessibilityLabel: overlayAccessibilityLabel
+ }) : null]
+ }), /*#__PURE__*/_jsx(Animated.View, {
+ removeClippedSubviews: Platform.OS !== 'ios',
+ style: [styles.drawer, {
+ width: drawerWidth,
+ position: drawerType === 'permanent' ? 'relative' : 'absolute',
+ zIndex: drawerType === 'back' ? -1 : 0
+ }, drawerAnimatedStyle, drawerStyle],
+ children: renderDrawerContent()
+ })]
+ })
})
})
})
diff --git a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts
index 7e978f0..a8bce18 100644
--- a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts
+++ b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts
@@ -1,3 +1,4 @@
+export { DrawerGestureContext } from './utils/DrawerGestureContext';
export { DrawerProgressContext } from './utils/DrawerProgressContext';
export { useDrawerProgress } from './utils/useDrawerProgress';
export { Drawer } from './views/Drawer';
diff --git a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts
new file mode 100644
index 0000000..33ffbeb
--- /dev/null
+++ b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts
@@ -0,0 +1,3 @@
+import * as React from 'react';
+export declare const DrawerGestureContext: React.Context<import("react-native-gesture-handler/lib/typescript/handlers/gestures/panGesture").PanGesture | undefined>;
+//# sourceMappingURL=DrawerGestureContext.d.ts.map
\ No newline at end of file
diff --git a/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts
index 7e978f0..a8bce18 100644
--- a/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts
+++ b/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts
@@ -1,3 +1,4 @@
+export { DrawerGestureContext } from './utils/DrawerGestureContext';
export { DrawerProgressContext } from './utils/DrawerProgressContext';
export { useDrawerProgress } from './utils/useDrawerProgress';
export { Drawer } from './views/Drawer';
diff --git a/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts
new file mode 100644
index 0000000..33ffbeb
--- /dev/null
+++ b/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts
@@ -0,0 +1,3 @@
+import * as React from 'react';
+export declare const DrawerGestureContext: React.Context<import("react-native-gesture-handler/lib/typescript/handlers/gestures/panGesture").PanGesture | undefined>;
+//# sourceMappingURL=DrawerGestureContext.d.ts.map
\ No newline at end of file
diff --git a/node_modules/react-native-drawer-layout/src/index.tsx b/node_modules/react-native-drawer-layout/src/index.tsx
index 0aa6f53..61a37cf 100644
--- a/node_modules/react-native-drawer-layout/src/index.tsx
+++ b/node_modules/react-native-drawer-layout/src/index.tsx
@@ -1,3 +1,4 @@
+export { DrawerGestureContext } from './utils/DrawerGestureContext';
export { DrawerProgressContext } from './utils/DrawerProgressContext';
export { useDrawerProgress } from './utils/useDrawerProgress';
export { Drawer } from './views/Drawer';
diff --git a/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx b/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx
new file mode 100644
index 0000000..3aac957
--- /dev/null
+++ b/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import type { PanGesture } from 'react-native-gesture-handler';
+
+export const DrawerGestureContext = React.createContext<PanGesture | undefined>(
+ undefined
+);
diff --git a/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx b/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx
index 9c40f41..ee5d075 100644
--- a/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx
+++ b/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx
@@ -21,6 +21,7 @@ import Animated, {
import useLatestCallback from 'use-latest-callback';
import type { DrawerProps } from '../types';
+import { DrawerGestureContext } from '../utils/DrawerGestureContext';
import { DrawerProgressContext } from '../utils/DrawerProgressContext';
import { getDrawerWidth } from '../utils/getDrawerWidth';
import {
@@ -110,47 +111,51 @@ export function Drawer({
const interactionHandleRef = React.useRef<number | null>(null);
- const startInteraction = () => {
+ const startInteraction = React.useCallback(() => {
interactionHandleRef.current = InteractionManager.createInteractionHandle();
- };
+ }, []);
- const endInteraction = () => {
+ const endInteraction = React.useCallback(() => {
if (interactionHandleRef.current != null) {
InteractionManager.clearInteractionHandle(interactionHandleRef.current);
interactionHandleRef.current = null;
}
- };
+ }, []);
- const hideKeyboard = () => {
+ const hideKeyboard = React.useCallback(() => {
if (keyboardDismissMode === 'on-drag') {
Keyboard.dismiss();
}
- };
+ }, [keyboardDismissMode]);
- const onGestureBegin = () => {
+ const onGestureBegin = React.useCallback(() => {
onGestureStart?.();
startInteraction();
hideKeyboard();
hideStatusBar(true);
- };
+ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]);
- const onGestureFinish = () => {
+ const onGestureFinish = React.useCallback(() => {
onGestureEnd?.();
endInteraction();
- };
+ }, [onGestureEnd, endInteraction]);
- const onGestureAbort = () => {
+ const onGestureAbort = React.useCallback(() => {
onGestureCancel?.();
endInteraction();
- };
+ }, [onGestureCancel, endInteraction]);
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
// https://github.com/software-mansion/react-native-gesture-handler/issues/569
- const hitSlop = isRight
- ? // Extend hitSlop to the side of the screen when drawer is closed
- // This lets the user drag the drawer from the side of the screen
- { right: 0, width: isOpen ? undefined : swipeEdgeWidth }
- : { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
+ const hitSlop = React.useMemo(
+ () =>
+ isRight
+ ? // Extend hitSlop to the side of the screen when drawer is closed
+ // This lets the user drag the drawer from the side of the screen
+ { right: 0, width: isOpen ? undefined : swipeEdgeWidth }
+ : { left: 0, width: isOpen ? undefined : swipeEdgeWidth },
+ [isRight, isOpen, swipeEdgeWidth]
+ );
const touchStartX = useSharedValue(0);
const touchX = useSharedValue(0);
@@ -217,53 +222,76 @@ export function Drawer({
const startX = useSharedValue(0);
- let pan = Gesture?.Pan()
- .onBegin((event) => {
- 'worklet';
- startX.value = translationX.value;
- gestureState.value = event.state;
- touchStartX.value = event.x;
- })
- .onStart(() => {
- 'worklet';
- runOnJS(onGestureBegin)();
- })
- .onChange((event) => {
- 'worklet';
- touchX.value = event.x;
- translationX.value = startX.value + event.translationX;
- gestureState.value = event.state;
- })
- .onEnd((event, success) => {
- 'worklet';
- gestureState.value = event.state;
-
- if (!success) {
- runOnJS(onGestureAbort)();
- }
-
- const nextOpen =
- (Math.abs(event.translationX) > SWIPE_MIN_OFFSET &&
- Math.abs(event.translationX) > swipeMinVelocity) ||
- Math.abs(event.translationX) > swipeMinDistance
- ? drawerPosition === 'left'
- ? // If swiped to right, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0
- : // If swiped to left, open the drawer, otherwise close it
- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0
- : open;
-
- toggleDrawer(nextOpen, event.velocityX);
- runOnJS(onGestureFinish)();
- })
- .activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET])
- .failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET])
- .hitSlop(hitSlop)
- .enabled(drawerType !== 'permanent' && swipeEnabled);
-
- if (pan && configureGestureHandler) {
- pan = configureGestureHandler(pan);
- }
+ const pan = React.useMemo(() => {
+ let panGesture = Gesture?.Pan()
+ .onBegin((event) => {
+ 'worklet';
+ startX.value = translationX.value;
+ gestureState.value = event.state;
+ touchStartX.value = event.x;
+ })
+ .onStart(() => {
+ 'worklet';
+ runOnJS(onGestureBegin)();
+ })
+ .onChange((event) => {
+ 'worklet';
+ touchX.value = event.x;
+ translationX.value = startX.value + event.translationX;
+ gestureState.value = event.state;
+ })
+ .onEnd((event, success) => {
+ 'worklet';
+ gestureState.value = event.state;
+
+ if (!success) {
+ runOnJS(onGestureAbort)();
+ }
+
+ const nextOpen =
+ (Math.abs(event.translationX) > SWIPE_MIN_OFFSET &&
+ Math.abs(event.translationX) > swipeMinVelocity) ||
+ Math.abs(event.translationX) > swipeMinDistance
+ ? drawerPosition === 'left'
+ ? // If swiped to right, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) >
+ 0
+ : // If swiped to left, open the drawer, otherwise close it
+ (event.velocityX === 0 ? event.translationX : event.velocityX) <
+ 0
+ : open;
+
+ toggleDrawer(nextOpen, event.velocityX);
+ runOnJS(onGestureFinish)();
+ })
+ .activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET])
+ .failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET])
+ .hitSlop(hitSlop)
+ .enabled(drawerType !== 'permanent' && swipeEnabled);
+
+ if (panGesture && configureGestureHandler) {
+ panGesture = configureGestureHandler(panGesture);
+ }
+ return panGesture;
+ }, [
+ configureGestureHandler,
+ drawerPosition,
+ drawerType,
+ gestureState,
+ hitSlop,
+ onGestureBegin,
+ onGestureAbort,
+ onGestureFinish,
+ open,
+ startX,
+ swipeEnabled,
+ swipeMinDistance,
+ swipeMinVelocity,
+ toggleDrawer,
+ touchStartX,
+ touchX,
+ translationX,
+ ]);
const translateX = useDerivedValue(() => {
// Comment stolen from react-native-gesture-handler/DrawerLayout
@@ -376,64 +404,66 @@ export function Drawer({
return (
<GestureHandlerRootView style={[styles.container, style]}>
<DrawerProgressContext.Provider value={progress}>
- <GestureDetector gesture={pan}>
- {/* Immediate child of gesture handler needs to be an Animated.View */}
- <Animated.View
- style={[
- styles.main,
- {
- flexDirection:
- drawerType === 'permanent'
- ? (isRight && direction === 'ltr') ||
- (!isRight && direction === 'rtl')
- ? 'row'
- : 'row-reverse'
- : 'row',
- },
- ]}
- >
- <Animated.View style={[styles.content, contentAnimatedStyle]}>
- <View
- accessibilityElementsHidden={
- isOpen && drawerType !== 'permanent'
- }
- importantForAccessibility={
- isOpen && drawerType !== 'permanent'
- ? 'no-hide-descendants'
- : 'auto'
- }
- style={styles.content}
- >
- {children}
- </View>
- {drawerType !== 'permanent' ? (
- <Overlay
- open={open}
- progress={progress}
- onPress={() => toggleDrawer(false)}
- style={overlayStyle}
- accessibilityLabel={overlayAccessibilityLabel}
- />
- ) : null}
- </Animated.View>
+ <DrawerGestureContext.Provider value={pan}>
+ <GestureDetector gesture={pan}>
+ {/* Immediate child of gesture handler needs to be an Animated.View */}
<Animated.View
- removeClippedSubviews={Platform.OS !== 'ios'}
style={[
- styles.drawer,
+ styles.main,
{
- width: drawerWidth,
- position:
- drawerType === 'permanent' ? 'relative' : 'absolute',
- zIndex: drawerType === 'back' ? -1 : 0,
+ flexDirection:
+ drawerType === 'permanent'
+ ? (isRight && direction === 'ltr') ||
+ (!isRight && direction === 'rtl')
+ ? 'row'
+ : 'row-reverse'
+ : 'row',
},
- drawerAnimatedStyle,
- drawerStyle,
]}
>
- {renderDrawerContent()}
+ <Animated.View style={[styles.content, contentAnimatedStyle]}>
+ <View
+ accessibilityElementsHidden={
+ isOpen && drawerType !== 'permanent'
+ }
+ importantForAccessibility={
+ isOpen && drawerType !== 'permanent'
+ ? 'no-hide-descendants'
+ : 'auto'
+ }
+ style={styles.content}
+ >
+ {children}
+ </View>
+ {drawerType !== 'permanent' ? (
+ <Overlay
+ open={open}
+ progress={progress}
+ onPress={() => toggleDrawer(false)}
+ style={overlayStyle}
+ accessibilityLabel={overlayAccessibilityLabel}
+ />
+ ) : null}
+ </Animated.View>
+ <Animated.View
+ removeClippedSubviews={Platform.OS !== 'ios'}
+ style={[
+ styles.drawer,
+ {
+ width: drawerWidth,
+ position:
+ drawerType === 'permanent' ? 'relative' : 'absolute',
+ zIndex: drawerType === 'back' ? -1 : 0,
+ },
+ drawerAnimatedStyle,
+ drawerStyle,
+ ]}
+ >
+ {renderDrawerContent()}
+ </Animated.View>
</Animated.View>
- </Animated.View>
- </GestureDetector>
+ </GestureDetector>
+ </DrawerGestureContext.Provider>
</DrawerProgressContext.Provider>
</GestureHandlerRootView>
);
@@ -1,36 +0,0 @@
diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
index 43d11b4..5bf0d32 100644
--- a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
+++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
@@ -461,16 +461,23 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
- if ([_handlersToWaitFor count]) {
- RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
- if (handler != nil) {
- for (NSNumber *handlerTag in _handlersToWaitFor) {
- if ([handler.tag isEqual:handlerTag]) {
- return YES;
- }
- }
+ RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
+ if (handler == nil) {
+ return NO;
+ }
+
+ for (NSNumber *handlerTag in _handlersToWaitFor) {
+ if ([handler.tag isEqual:handlerTag]) {
+ return YES;
}
}
+
+ for (NSNumber *handlerTag in handler->_handlersThatShouldWait) {
+ if ([_tag isEqual:handlerTag]) {
+ return YES;
+ }
+ }
+
return NO;
}
@@ -1,47 +0,0 @@
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
index 391f303..8e2c3db 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
+++ b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml
@@ -24,7 +24,7 @@
<activity
android:name="com.yalantis.ucrop.UCropActivity"
- android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
+ android:theme="@style/Theme.UCropNoEdgeToEdge" />
</application>
</manifest>
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
new file mode 100644
index 0000000..396d5a8
--- /dev/null
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml
@@ -0,0 +1,5 @@
+<resources>
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar">
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ </style>
+</resources>
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
new file mode 100644
index 0000000..50129b6
--- /dev/null
+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml
@@ -0,0 +1,3 @@
+<resources>
+ <style name="Theme.UCropNoEdgeToEdge" parent="Theme.AppCompat.Light.NoActionBar"/>
+</resources>
diff --git a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
index 9f20973..68d4766 100644
--- a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
+++ b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
@@ -126,7 +126,8 @@ - (void) setConfiguration:(NSDictionary *)options
- (UIViewController*) getRootVC {
UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
- while (root.presentedViewController != nil) {
+ while (root.presentedViewController != nil &&
+ !root.presentedViewController.isBeingDismissed) {
root = root.presentedViewController;
}
-20
View File
@@ -1,20 +0,0 @@
const {withProjectBuildGradle} = require('@expo/config-plugins')
const jitpackRepository = "maven { url 'https://www.jitpack.io' }"
module.exports = function withAndroidNoJitpackPlugin(config) {
return withProjectBuildGradle(config, config => {
if (!config.modResults.contents.includes(jitpackRepository)) {
throw Error(
'Expected to find the jitpack string in the config. ' +
'You MUST verify whether it was actually removed upstream, ' +
'or if the format has changed and this plugin no longer recognizes it.',
)
}
config.modResults.contents = config.modResults.contents.replaceAll(
jitpackRepository,
'',
)
return config
})
}
+46 -51
View File
@@ -10,7 +10,6 @@ import {
initialWindowMetrics,
SafeAreaProvider,
} from 'react-native-safe-area-context'
import * as ScreenOrientation from 'expo-screen-orientation'
import * as SplashScreen from 'expo-splash-screen'
import * as SystemUI from 'expo-system-ui'
import {msg} from '@lingui/macro'
@@ -23,7 +22,7 @@ import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {isAndroid, isIOS} from '#/platform/detection'
import {isIOS} from '#/platform/detection'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -58,7 +57,6 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {Provider as TrendingConfigProvider} from '#/state/trending-config'
import {TestCtrls} from '#/view/com/testing/TestCtrls'
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
import * as Toast from '#/view/com/util/Toast'
@@ -69,6 +67,7 @@ import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {AppProfiler} from '#/AppProfiler'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
@@ -77,10 +76,6 @@ SplashScreen.preventAutoHideAsync()
if (isIOS) {
SystemUI.setBackgroundColorAsync('black')
}
if (isAndroid) {
// iOS is handled by the config plugin -sfn
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)
}
/**
* Begin geolocation ASAP
@@ -148,16 +143,12 @@ function InnerApp() {
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<ProgressGuideProvider>
<TrendingConfigProvider>
<GestureHandlerRootView
style={s.h100pct}>
<IntentDialogProvider>
<TestCtrls />
<Shell />
<NuxDialogs />
</IntentDialogProvider>
</GestureHandlerRootView>
</TrendingConfigProvider>
<GestureHandlerRootView
style={s.h100pct}>
<TestCtrls />
<Shell />
<NuxDialogs />
</GestureHandlerRootView>
</ProgressGuideProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
@@ -199,40 +190,44 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<GeolocationProvider>
<A11yProvider>
<KeyboardControllerProvider>
<SessionProvider>
<PrefsStateProvider>
<I18nProvider>
<ShellStateProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<BottomSheetProvider>
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<LightStatusBarProvider>
<InnerApp />
</LightStatusBarProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</ShellStateProvider>
</I18nProvider>
</PrefsStateProvider>
</SessionProvider>
</KeyboardControllerProvider>
</A11yProvider>
</GeolocationProvider>
<AppProfiler>
<GeolocationProvider>
<A11yProvider>
<KeyboardControllerProvider>
<SessionProvider>
<PrefsStateProvider>
<I18nProvider>
<ShellStateProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<BottomSheetProvider>
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<IntentDialogProvider>
<LightStatusBarProvider>
<InnerApp />
</LightStatusBarProvider>
</IntentDialogProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</ShellStateProvider>
</I18nProvider>
</PrefsStateProvider>
</SessionProvider>
</KeyboardControllerProvider>
</A11yProvider>
</GeolocationProvider>
</AppProfiler>
)
}
+7 -10
View File
@@ -47,7 +47,6 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {Provider as TrendingConfigProvider} from '#/state/trending-config'
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
import * as Toast from '#/view/com/util/Toast'
@@ -128,12 +127,8 @@ function InnerApp() {
<MutedThreadsProvider>
<SafeAreaProvider>
<ProgressGuideProvider>
<TrendingConfigProvider>
<IntentDialogProvider>
<Shell />
<NuxDialogs />
</IntentDialogProvider>
</TrendingConfigProvider>
<Shell />
<NuxDialogs />
</ProgressGuideProvider>
</SafeAreaProvider>
</MutedThreadsProvider>
@@ -189,9 +184,11 @@ function App() {
<LightboxStateProvider>
<PortalProvider>
<StarterPackProvider>
<LightStatusBarProvider>
<InnerApp />
</LightStatusBarProvider>
<IntentDialogProvider>
<LightStatusBarProvider>
<InnerApp />
</LightStatusBarProvider>
</IntentDialogProvider>
</StarterPackProvider>
</PortalProvider>
</LightboxStateProvider>
+29
View File
@@ -0,0 +1,29 @@
import React, {Profiler} from 'react'
// Don't let it get stripped out in profiling builds (which apply production Babel preset).
const log = (global as any)['con' + 'sole'].log
function onRender(id: string, phase: string, actualDuration: number) {
if (!__DEV__) {
// This block of code will exist in the production build of the app.
// However, only profiling builds of React call `onRender` so it's dead code in actual production.
const message = `<Profiler> ${id}:${phase} ${
actualDuration > 500
? '(╯°□°)╯ '
: actualDuration > 100
? '[!!] '
: actualDuration > 16
? '[!] '
: ''
}${Math.round(actualDuration)}ms`
log(message)
}
}
export function AppProfiler({children}: {children: React.ReactNode}) {
return (
<Profiler id="app" onRender={onRender}>
{children}
</Profiler>
)
}
-15
View File
@@ -86,7 +86,6 @@ import {
StarterPackScreenShort,
} from '#/screens/StarterPack/StarterPackScreen'
import {Wizard} from '#/screens/StarterPack/Wizard'
import {VideoFeed} from '#/screens/VideoFeed'
import {useTheme} from '#/alf'
import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army'
@@ -101,7 +100,6 @@ import {LanguageSettingsScreen} from './screens/Settings/LanguageSettings'
import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings'
import {SettingsScreen} from './screens/Settings/Settings'
import {ThreadPreferencesScreen} from './screens/Settings/ThreadPreferences'
import TopicScreen from './screens/Topic'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -378,11 +376,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => HashtagScreen}
options={{title: title(msg`Hashtag`)}}
/>
<Stack.Screen
name="Topic"
getComponent={() => TopicScreen}
options={{title: title(msg`Topic`)}}
/>
<Stack.Screen
name="MessagesConversation"
getComponent={() => MessagesConversationScreen}
@@ -423,14 +416,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => Wizard}
options={{title: title(msg`Edit your starter pack`), requireAuth: true}}
/>
<Stack.Screen
name="VideoFeed"
getComponent={() => VideoFeed}
options={{
title: title(msg`Video Feed`),
requireAuth: true,
}}
/>
</>
)
}
-18
View File
@@ -158,9 +158,6 @@ export const atoms = {
flex_wrap: {
flexWrap: 'wrap',
},
flex_nowrap: {
flexWrap: 'nowrap',
},
flex_0: {
flex: web('0 0 auto') || (native(0) as number),
},
@@ -305,18 +302,6 @@ export const atoms = {
border_0: {
borderWidth: 0,
},
border_t_0: {
borderTopWidth: 0,
},
border_b_0: {
borderBottomWidth: 0,
},
border_l_0: {
borderLeftWidth: 0,
},
border_r_0: {
borderRightWidth: 0,
},
border: {
borderWidth: StyleSheet.hairlineWidth,
},
@@ -961,9 +946,6 @@ export const atoms = {
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
transitionDuration: '100ms',
}),
transition_delay_50ms: web({
transitionDelay: '50ms',
}),
/**
* {@link Layout.SCROLLBAR_OFFSET}
+1 -1
View File
@@ -497,7 +497,7 @@ export function createThemes({
color: dimPalette.contrast_400,
},
text_contrast_medium: {
color: dimPalette.contrast_600,
color: dimPalette.contrast_700,
},
text_contrast_high: {
color: dimPalette.contrast_900,
-3
View File
@@ -1,5 +1,4 @@
import * as NavigationBar from 'expo-navigation-bar'
import * as SystemUI from 'expo-system-ui'
import {isAndroid} from '#/platform/detection'
import {Theme} from '../types'
@@ -10,12 +9,10 @@ export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
} else {
NavigationBar.setBackgroundColorAsync('black')
NavigationBar.setBorderColorAsync('black')
NavigationBar.setButtonStyleAsync('light')
SystemUI.setBackgroundColorAsync('black')
}
}
}
+1 -5
View File
@@ -131,11 +131,7 @@ function AccountItem({
(hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25,
]}>
<View style={a.p_md}>
<UserAvatar
avatar={profile?.avatar}
size={24}
type={profile?.associated?.labeler ? 'labeler' : 'user'}
/>
<UserAvatar avatar={profile?.avatar} size={24} />
</View>
<Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
<Text emoji style={[a.font_bold]}>
-1
View File
@@ -65,7 +65,6 @@ export function AvatarStack({
<UserAvatar
size={size - 2}
avatar={item.profile.avatar}
type={item.profile.associated?.labeler ? 'labeler' : 'user'}
moderation={item.moderation.ui('avatar')}
/>
)}
+16 -30
View File
@@ -3,12 +3,10 @@ import {
AccessibilityProps,
GestureResponderEvent,
MouseEvent,
NativeSyntheticEvent,
Pressable,
PressableProps,
StyleProp,
StyleSheet,
TargetedEvent,
TextProps,
TextStyle,
View,
@@ -78,8 +76,6 @@ export type ButtonProps = Pick<
| 'onHoverOut'
| 'onPressIn'
| 'onPressOut'
| 'onFocus'
| 'onBlur'
> &
AccessibilityProps &
VariantProps & {
@@ -120,12 +116,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
style,
hoverStyle: hoverStyleProp,
PressableComponent = Pressable,
onPressIn: onPressInOuter,
onPressOut: onPressOutOuter,
onHoverIn: onHoverInOuter,
onHoverOut: onHoverOutOuter,
onFocus: onFocusOuter,
onBlur: onBlurOuter,
...rest
},
ref,
@@ -137,6 +127,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
focused: false,
})
const onPressInOuter = rest.onPressIn
const onPressIn = React.useCallback(
(e: GestureResponderEvent) => {
setState(s => ({
@@ -147,6 +138,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
},
[setState, onPressInOuter],
)
const onPressOutOuter = rest.onPressOut
const onPressOut = React.useCallback(
(e: GestureResponderEvent) => {
setState(s => ({
@@ -157,6 +149,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
},
[setState, onPressOutOuter],
)
const onHoverInOuter = rest.onHoverIn
const onHoverIn = React.useCallback(
(e: MouseEvent) => {
setState(s => ({
@@ -167,6 +160,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
},
[setState, onHoverInOuter],
)
const onHoverOutOuter = rest.onHoverOut
const onHoverOut = React.useCallback(
(e: MouseEvent) => {
setState(s => ({
@@ -177,26 +171,18 @@ export const Button = React.forwardRef<View, ButtonProps>(
},
[setState, onHoverOutOuter],
)
const onFocus = React.useCallback(
(e: NativeSyntheticEvent<TargetedEvent>) => {
setState(s => ({
...s,
focused: true,
}))
onFocusOuter?.(e)
},
[setState, onFocusOuter],
)
const onBlur = React.useCallback(
(e: NativeSyntheticEvent<TargetedEvent>) => {
setState(s => ({
...s,
focused: false,
}))
onBlurOuter?.(e)
},
[setState, onBlurOuter],
)
const onFocus = React.useCallback(() => {
setState(s => ({
...s,
focused: true,
}))
}, [setState])
const onBlur = React.useCallback(() => {
setState(s => ({
...s,
focused: false,
}))
}, [setState])
const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = []
+1 -3
View File
@@ -27,7 +27,6 @@ import {useA11y} from '#/state/a11y'
import {useDialogStateControlContext} from '#/state/dialogs'
import {List, ListMethods, ListProps} from '#/view/com/util/List'
import {atoms as a, useTheme} from '#/alf'
import {useThemeName} from '#/alf/util/useColorModeTheme'
import {Context, useDialogContext} from '#/components/Dialog/context'
import {
DialogControlProps,
@@ -56,8 +55,7 @@ export function Outer({
nativeOptions,
testID,
}: React.PropsWithChildren<DialogOuterProps>) {
const themeName = useThemeName()
const t = useTheme(themeName)
const t = useTheme()
const ref = React.useRef<BottomSheetNativeComponent>(null)
const closeCallbacks = React.useRef<(() => void)[]>([])
const {setDialogIsOpen, setFullyExpandedCount} =
+1 -1
View File
@@ -280,8 +280,8 @@ export function ProfileGrid({
profile={profile}
moderationOpts={moderationOpts}
logContext="FeedInterstitial"
color="secondary_inverted"
shape="round"
colorInverted
/>
</ProfileCard.Header>
<ProfileCard.Description profile={profile} numberOfLines={2} />
+3 -5
View File
@@ -1,13 +1,11 @@
import {LinearGradient} from 'expo-linear-gradient'
import {atoms as a, tokens, ViewStyleProp} from '#/alf'
import {atoms as a, tokens} from '#/alf'
export function GradientFill({
gradient,
style,
}: ViewStyleProp & {
}: {
gradient:
| typeof tokens.gradients.primary
| typeof tokens.gradients.sky
| typeof tokens.gradients.midnight
| typeof tokens.gradients.sunrise
@@ -28,7 +26,7 @@ export function GradientFill({
}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0, style]}
style={[a.absolute, a.inset_0]}
/>
)
}
-1
View File
@@ -163,7 +163,6 @@ function KnownFollowersInner({
size={SIZE}
avatar={prof.avatar}
moderation={moderation.ui('avatar')}
type={prof.associated?.labeler ? 'labeler' : 'user'}
/>
</View>
))}
+12 -13
View File
@@ -15,7 +15,6 @@ import {
useBreakpoints,
useGutters,
useTheme,
web,
} from '#/alf'
import {Button, ButtonIcon, ButtonProps} from '#/components/Button'
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
@@ -30,13 +29,9 @@ import {Text} from '#/components/Typography'
export function Outer({
children,
noBottomBorder,
headerRef,
sticky = true,
}: {
children: React.ReactNode
noBottomBorder?: boolean
headerRef?: React.MutableRefObject<View | null>
sticky?: boolean
}) {
const t = useTheme()
const gutters = useGutters([0, 'base'])
@@ -45,14 +40,12 @@ export function Outer({
return (
<View
ref={headerRef}
style={[
a.w_full,
!noBottomBorder && a.border_b,
a.flex_row,
a.align_center,
a.gap_sm,
sticky && web([a.sticky, {top: 0}, a.z_10, t.atoms.bg]),
gutters,
platform({
native: [a.pb_xs, {minHeight: 48}],
@@ -92,7 +85,17 @@ export function Content({
}
export function Slot({children}: {children?: React.ReactNode}) {
return <View style={[a.z_50, {width: HEADER_SLOT_SIZE}]}>{children}</View>
return (
<View
style={[
a.z_50,
{
width: HEADER_SLOT_SIZE,
},
]}>
{children}
</View>
)
}
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
@@ -122,11 +125,7 @@ export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
shape="square"
onPress={onPressBack}
hitSlop={HITSLOP_30}
style={[
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
a.bg_transparent,
style,
]}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}, style]}
{...props}>
<ButtonIcon icon={ArrowLeft} size="lg" />
</Button>
+4 -10
View File
@@ -6,18 +6,12 @@ import {gradients} from '#/alf/tokens'
export function LinearGradientBackground({
style,
gradient = 'sky',
children,
start,
end,
}: {
style?: StyleProp<ViewStyle>
gradient?: keyof typeof gradients
children?: React.ReactNode
start?: [number, number]
end?: [number, number]
style: StyleProp<ViewStyle>
children: React.ReactNode
}) {
const colors = gradients[gradient].values.map(([_, color]) => {
const gradient = gradients.sky.values.map(([_, color]) => {
return color
}) as [string, string, ...string[]]
@@ -26,7 +20,7 @@ export function LinearGradientBackground({
}
return (
<LinearGradient colors={colors} style={style} start={start} end={end}>
<LinearGradient colors={gradient} style={style}>
{children}
</LinearGradient>
)
+3 -64
View File
@@ -4,7 +4,7 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import flattenReactChildren from 'react-keyed-flatten-children'
import {isAndroid, isIOS, isNative} from '#/platform/detection'
import {isNative} from '#/platform/detection'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -122,21 +122,9 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
onFocus={onFocus}
onBlur={onBlur}
onPress={async e => {
if (isAndroid) {
/**
* Below fix for iOS doesn't work for Android, this does.
*/
context.control.close(() => {
onPress?.(e)
context.control.close()
} else if (isIOS) {
/**
* Fixes a subtle bug on iOS
* {@link https://github.com/bluesky-social/social-app/pull/5849/files#diff-de516ef5e7bd9840cd639213301df38cf03acfcad5bda85a1d63efd249ba79deL124-L127}
*/
context.control.close(() => {
onPress?.(e)
})
}
})
}}
onPressIn={e => {
onPressIn()
@@ -202,55 +190,6 @@ export function ItemIcon({icon: Comp}: ItemIconProps) {
)
}
export function ItemRadio({selected}: {selected: boolean}) {
const t = useTheme()
return (
<View
style={[
a.justify_center,
a.align_center,
a.rounded_full,
t.atoms.border_contrast_high,
{
borderWidth: 1,
height: 20,
width: 20,
},
]}>
{selected ? (
<View
style={[
a.absolute,
a.rounded_full,
{height: 14, width: 14},
selected
? {
backgroundColor: t.palette.primary_500,
}
: {},
]}
/>
) : null}
</View>
)
}
export function LabelText({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<Text
style={[
a.font_bold,
t.atoms.text_contrast_medium,
{
marginBottom: -8,
},
]}>
{children}
</Text>
)
}
export function Group({children, style}: GroupProps) {
const t = useTheme()
return (
+1 -53
View File
@@ -202,7 +202,7 @@ export function Outer({
)
}
export function Item({children, label, onPress, style, ...rest}: ItemProps) {
export function Item({children, label, onPress, ...rest}: ItemProps) {
const t = useTheme()
const {control} = useMenuContext()
const {
@@ -248,7 +248,6 @@ export function Item({children, label, onPress, style, ...rest}: ItemProps) {
? t.atoms.bg_contrast_25
: t.atoms.bg_contrast_50,
],
style,
])}
{...web({
onMouseEnter,
@@ -305,57 +304,6 @@ export function ItemIcon({icon: Comp, position = 'left'}: ItemIconProps) {
)
}
export function ItemRadio({selected}: {selected: boolean}) {
const t = useTheme()
return (
<View
style={[
a.justify_center,
a.align_center,
a.rounded_full,
t.atoms.border_contrast_high,
{
borderWidth: 1,
height: 20,
width: 20,
},
]}>
{selected ? (
<View
style={[
a.absolute,
a.rounded_full,
{height: 14, width: 14},
selected
? {
backgroundColor: t.palette.primary_500,
}
: {},
]}
/>
) : null}
</View>
)
}
export function LabelText({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<Text
style={[
a.font_bold,
a.pt_md,
a.pb_sm,
t.atoms.text_contrast_low,
{
paddingHorizontal: 10,
},
]}>
{children}
</Text>
)
}
export function Group({children}: GroupProps) {
return children
}
+1 -1
View File
@@ -126,7 +126,7 @@ export function Label({
fill={t.atoms.text_contrast_medium.color}
/>
) : (
<UserAvatar avatar={desc.sourceAvi} type="user" size={avi} />
<UserAvatar avatar={desc.sourceAvi} size={avi} />
)}
<Text
+1 -6
View File
@@ -285,7 +285,6 @@ export type FollowButtonProps = {
moderationOpts: ModerationOpts
logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:unfollow']['logContext']
colorInverted?: boolean
} & Partial<ButtonProps>
export function FollowButton(props: FollowButtonProps) {
@@ -298,8 +297,6 @@ export function FollowButtonInner({
profile: profileUnshadowed,
moderationOpts,
logContext,
onPress: onPressProp,
colorInverted,
...rest
}: FollowButtonProps) {
const {_} = useLingui()
@@ -324,7 +321,6 @@ export function FollowButtonInner({
)}`,
),
)
onPressProp?.(e)
} catch (err: any) {
if (err?.name !== 'AbortError') {
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
@@ -345,7 +341,6 @@ export function FollowButtonInner({
)}`,
),
)
onPressProp?.(e)
} catch (err: any) {
if (err?.name !== 'AbortError') {
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
@@ -392,7 +387,7 @@ export function FollowButtonInner({
label={followLabel}
size="small"
variant="solid"
color={colorInverted ? 'secondary_inverted' : 'primary'}
color="primary"
{...rest}
onPress={onPressFollow}>
<ButtonIcon icon={Plus} position={isRound ? undefined : 'left'} />
@@ -1,833 +0,0 @@
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {ScrollView, TextInput, useWindowDimensions, View} from 'react-native'
import Animated, {
LayoutAnimationConfig,
LinearTransition,
ZoomInEasyDown,
} from 'react-native-reanimated'
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {logEvent} from '#/lib/statsig/statsig'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorSearchPaginated} from '#/state/queries/actor-search'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
import {useSession} from '#/state/session'
import {Follow10ProgressGuide} from '#/state/shell/progress-guide'
import {ListMethods} from '#/view/com/util/List'
import {
popularInterests,
useInterestsDisplayNames,
} from '#/screens/Onboarding/state'
import {
atoms as a,
native,
tokens,
useBreakpoints,
useTheme,
ViewStyleProp,
web,
} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import {ListFooter} from '../Lists'
import {ProgressGuideTask} from './Task'
type Item =
| {
type: 'profile'
key: string
profile: AppBskyActorDefs.ProfileView
isSuggestion: boolean
}
| {
type: 'empty'
key: string
message: string
}
| {
type: 'placeholder'
key: string
}
| {
type: 'error'
key: string
}
export function FollowDialog({guide}: {guide: Follow10ProgressGuide}) {
const {_} = useLingui()
const control = Dialog.useDialogControl()
const {gtMobile} = useBreakpoints()
const {height: minHeight} = useWindowDimensions()
return (
<>
<Button
label={_(msg`Find people to follow`)}
onPress={() => {
control.open()
logEvent('progressGuide:followDialog:open', {})
}}
size={gtMobile ? 'small' : 'large'}
color="primary"
variant="solid">
<ButtonIcon icon={PersonGroupIcon} />
<ButtonText>
<Trans>Find people to follow</Trans>
</ButtonText>
</Button>
<Dialog.Outer control={control} nativeOptions={{minHeight}}>
<Dialog.Handle />
<DialogInner guide={guide} />
</Dialog.Outer>
</>
)
}
// Fine to keep this top-level.
let lastSelectedInterest = ''
let lastSearchText = ''
function DialogInner({guide}: {guide: Follow10ProgressGuide}) {
const {_} = useLingui()
const interestsDisplayNames = useInterestsDisplayNames()
const {data: preferences} = usePreferencesQuery()
const personalizedInterests = preferences?.interests?.tags
const interests = Object.keys(interestsDisplayNames)
.sort(boostInterests(popularInterests))
.sort(boostInterests(personalizedInterests))
const [selectedInterest, setSelectedInterest] = useState(
() =>
lastSelectedInterest ||
(personalizedInterests && interests.includes(personalizedInterests[0])
? personalizedInterests[0]
: interests[0]),
)
const [searchText, setSearchText] = useState(lastSearchText)
const moderationOpts = useModerationOpts()
const listRef = useRef<ListMethods>(null)
const inputRef = useRef<TextInput>(null)
const [headerHeight, setHeaderHeight] = useState(0)
const {currentAccount} = useSession()
const [suggestedAccounts, setSuggestedAccounts] = useState<
Map<string, AppBskyActorDefs.ProfileView[]>
>(() => new Map())
useEffect(() => {
lastSearchText = searchText
lastSelectedInterest = selectedInterest
}, [searchText, selectedInterest])
const query = searchText || selectedInterest
const {
data: searchResults,
isFetching,
error,
isError,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = useActorSearchPaginated({
query,
})
const hasSearchText = !!searchText
const items = useMemo(() => {
const results = searchResults?.pages.flatMap(r => r.actors)
let _items: Item[] = []
const seen = new Set<string>()
if (isError) {
_items.push({
type: 'empty',
key: 'empty',
message: _(msg`We're having network issues, try again`),
})
} else if (results) {
// First pass: search results
for (const profile of results) {
if (profile.did === currentAccount?.did) continue
if (profile.viewer?.following) continue
// my sincere apologies to Jake Gold - your bio is too keyword-filled and
// your page-rank too high, so you're at the top of half the categories -sfn
if (
!hasSearchText &&
profile.did === 'did:plc:tpg43qhh4lw4ksiffs4nbda3' &&
// constrain to 'tech'
selectedInterest !== 'tech'
) {
continue
}
seen.add(profile.did)
_items.push({
type: 'profile',
// Don't share identity across tabs or typing attempts
key: query + ':' + profile.did,
profile,
isSuggestion: false,
})
}
// Second pass: suggestions
_items = _items.flatMap(item => {
if (item.type !== 'profile') {
return item
}
const suggestions = suggestedAccounts.get(item.profile.did)
if (!suggestions) {
return item
}
const itemWithSuggestions = [item]
for (const suggested of suggestions) {
if (seen.has(suggested.did)) {
// Skip search results from previous step or already seen suggestions
continue
}
seen.add(suggested.did)
itemWithSuggestions.push({
type: 'profile',
key: suggested.did,
profile: suggested,
isSuggestion: true,
})
if (itemWithSuggestions.length === 1 + 3) {
break
}
}
return itemWithSuggestions
})
} else {
const placeholders: Item[] = Array(10)
.fill(0)
.map((__, i) => ({
type: 'placeholder',
key: i + '',
}))
_items.push(...placeholders)
}
return _items
}, [
_,
searchResults,
isError,
currentAccount?.did,
hasSearchText,
selectedInterest,
suggestedAccounts,
query,
])
if (searchText && !isFetching && !items.length && !isError) {
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
}
const renderItems = useCallback(
({item, index}: {item: Item; index: number}) => {
switch (item.type) {
case 'profile': {
return (
<FollowProfileCard
profile={item.profile}
isSuggestion={item.isSuggestion}
moderationOpts={moderationOpts!}
setSuggestedAccounts={setSuggestedAccounts}
noBorder={index === 0}
/>
)
}
case 'placeholder': {
return <ProfileCardSkeleton key={item.key} />
}
case 'empty': {
return <Empty key={item.key} message={item.message} />
}
default:
return null
}
},
[moderationOpts],
)
const onSelectTab = useCallback(
(interest: string) => {
setSelectedInterest(interest)
inputRef.current?.clear()
setSearchText('')
listRef.current?.scrollToOffset({
offset: 0,
animated: false,
})
},
[setSelectedInterest, setSearchText],
)
const listHeader = (
<Header
guide={guide}
inputRef={inputRef}
listRef={listRef}
searchText={searchText}
onSelectTab={onSelectTab}
setHeaderHeight={setHeaderHeight}
setSearchText={setSearchText}
interests={interests}
selectedInterest={selectedInterest}
interestsDisplayNames={interestsDisplayNames}
/>
)
const onEndReached = useCallback(async () => {
if (isFetchingNextPage || !hasNextPage || isError) return
try {
await fetchNextPage()
} catch (err) {
logger.error('Failed to load more people to follow', {message: err})
}
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
return (
<Dialog.InnerFlatList
ref={listRef}
data={items}
renderItem={renderItems}
ListHeaderComponent={listHeader}
stickyHeaderIndices={[0]}
keyExtractor={(item: Item) => item.key}
style={[
a.px_0,
web([a.py_0, {height: '100vh', maxHeight: 600}]),
native({height: '100%'}),
]}
webInnerContentContainerStyle={a.py_0}
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
keyboardDismissMode="on-drag"
scrollIndicatorInsets={{top: headerHeight}}
initialNumToRender={8}
maxToRenderPerBatch={8}
onEndReached={onEndReached}
itemLayoutAnimation={LinearTransition}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
/>
}
/>
)
}
let Header = ({
guide,
inputRef,
listRef,
searchText,
onSelectTab,
setHeaderHeight,
setSearchText,
interests,
selectedInterest,
interestsDisplayNames,
}: {
guide: Follow10ProgressGuide
inputRef: React.RefObject<TextInput>
listRef: React.RefObject<ListMethods>
onSelectTab: (v: string) => void
searchText: string
setHeaderHeight: (v: number) => void
setSearchText: (v: string) => void
interests: string[]
selectedInterest: string
interestsDisplayNames: Record<string, string>
}): React.ReactNode => {
const t = useTheme()
const control = Dialog.useDialogContext()
return (
<View
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
style={[
a.relative,
web(a.pt_lg),
native(a.pt_4xl),
a.pb_xs,
a.border_b,
t.atoms.border_contrast_low,
t.atoms.bg,
]}>
<HeaderTop guide={guide} />
<View style={[web(a.pt_xs), a.pb_xs]}>
<SearchInput
inputRef={inputRef}
defaultValue={searchText}
onChangeText={text => {
setSearchText(text)
listRef.current?.scrollToOffset({offset: 0, animated: false})
}}
onEscape={control.close}
/>
<Tabs
onSelectTab={onSelectTab}
interests={interests}
selectedInterest={selectedInterest}
hasSearchText={!!searchText}
interestsDisplayNames={interestsDisplayNames}
/>
</View>
</View>
)
}
Header = memo(Header)
function HeaderTop({guide}: {guide: Follow10ProgressGuide}) {
const {_} = useLingui()
const t = useTheme()
const control = Dialog.useDialogContext()
return (
<View
style={[
a.px_lg,
a.relative,
a.flex_row,
a.justify_between,
a.align_center,
]}>
<Text
style={[
a.z_10,
a.text_lg,
a.font_heavy,
a.leading_tight,
t.atoms.text_contrast_high,
]}>
<Trans>Find people to follow</Trans>
</Text>
<View style={isWeb && {paddingRight: 36}}>
<ProgressGuideTask
current={guide.numFollows + 1}
total={10 + 1}
title={`${guide.numFollows} / 10`}
tabularNumsTitle
/>
</View>
{isWeb ? (
<Button
label={_(msg`Close`)}
size="small"
shape="round"
variant={isWeb ? 'ghost' : 'solid'}
color="secondary"
style={[
a.absolute,
a.z_20,
web({right: -4}),
native({right: 0}),
native({height: 32, width: 32, borderRadius: 16}),
]}
onPress={() => control.close()}>
<ButtonIcon icon={X} size="md" />
</Button>
) : null}
</View>
)
}
let Tabs = ({
onSelectTab,
interests,
selectedInterest,
hasSearchText,
interestsDisplayNames,
}: {
onSelectTab: (tab: string) => void
interests: string[]
selectedInterest: string
hasSearchText: boolean
interestsDisplayNames: Record<string, string>
}): React.ReactNode => {
const listRef = useRef<ScrollView>(null)
const [scrollX, setScrollX] = useState(0)
const [totalWidth, setTotalWidth] = useState(0)
const pendingTabOffsets = useRef<{x: number; width: number}[]>([])
const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([])
const onInitialLayout = useNonReactiveCallback(() => {
const index = interests.indexOf(selectedInterest)
scrollIntoViewIfNeeded(index)
})
useEffect(() => {
if (tabOffsets) {
onInitialLayout()
}
}, [tabOffsets, onInitialLayout])
function scrollIntoViewIfNeeded(index: number) {
const btnLayout = tabOffsets[index]
if (!btnLayout) return
const viewportLeftEdge = scrollX
const viewportRightEdge = scrollX + totalWidth
const shouldScrollToLeftEdge = viewportLeftEdge > btnLayout.x
const shouldScrollToRightEdge =
viewportRightEdge < btnLayout.x + btnLayout.width
if (shouldScrollToLeftEdge) {
listRef.current?.scrollTo({
x: btnLayout.x - tokens.space.lg,
animated: true,
})
} else if (shouldScrollToRightEdge) {
listRef.current?.scrollTo({
x: btnLayout.x - totalWidth + btnLayout.width + tokens.space.lg,
animated: true,
})
}
}
function handleSelectTab(index: number) {
const tab = interests[index]
onSelectTab(tab)
scrollIntoViewIfNeeded(index)
}
function handleTabLayout(index: number, x: number, width: number) {
if (!tabOffsets.length) {
pendingTabOffsets.current[index] = {x, width}
if (pendingTabOffsets.current.length === interests.length) {
setTabOffsets(pendingTabOffsets.current)
}
}
}
return (
<ScrollView
ref={listRef}
horizontal
contentContainerStyle={[a.gap_sm, a.px_lg]}
showsHorizontalScrollIndicator={false}
decelerationRate="fast"
snapToOffsets={
tabOffsets.length === interests.length
? tabOffsets.map(o => o.x - tokens.space.xl)
: undefined
}
onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)}
scrollEventThrottle={200} // big throttle
onScroll={evt => setScrollX(evt.nativeEvent.contentOffset.x)}>
{interests.map((interest, i) => {
const active = interest === selectedInterest && !hasSearchText
return (
<Tab
key={interest}
onSelectTab={handleSelectTab}
active={active}
index={i}
interest={interest}
interestsDisplayName={interestsDisplayNames[interest]}
onLayout={handleTabLayout}
/>
)
})}
</ScrollView>
)
}
Tabs = memo(Tabs)
let Tab = ({
onSelectTab,
interest,
active,
index,
interestsDisplayName,
onLayout,
}: {
onSelectTab: (index: number) => void
interest: string
active: boolean
index: number
interestsDisplayName: string
onLayout: (index: number, x: number, width: number) => void
}): React.ReactNode => {
const {_} = useLingui()
const activeText = active ? _(msg` (active)`) : ''
return (
<View
key={interest}
onLayout={e =>
onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width)
}>
<Button
label={_(msg`Search for "${interestsDisplayName}"${activeText}`)}
variant={active ? 'solid' : 'outline'}
color={active ? 'primary' : 'secondary'}
size="small"
onPress={() => onSelectTab(index)}>
<ButtonIcon icon={SearchIcon} />
<ButtonText>{interestsDisplayName}</ButtonText>
</Button>
</View>
)
}
Tab = memo(Tab)
let FollowProfileCard = ({
profile,
moderationOpts,
isSuggestion,
setSuggestedAccounts,
noBorder,
}: {
profile: AppBskyActorDefs.ProfileView
moderationOpts: ModerationOpts
isSuggestion: boolean
setSuggestedAccounts: (
updater: (
v: Map<string, AppBskyActorDefs.ProfileView[]>,
) => Map<string, AppBskyActorDefs.ProfileView[]>,
) => void
noBorder?: boolean
}): React.ReactNode => {
const [hasFollowed, setHasFollowed] = useState(false)
const followupSuggestion = useSuggestedFollowsByActorQuery({
did: profile.did,
enabled: hasFollowed,
})
const candidates = followupSuggestion.data?.suggestions
useEffect(() => {
// TODO: Move out of effect.
if (hasFollowed && candidates && candidates.length > 0) {
setSuggestedAccounts(suggestions => {
const newSuggestions = new Map(suggestions)
newSuggestions.set(profile.did, candidates)
return newSuggestions
})
}
}, [hasFollowed, profile.did, candidates, setSuggestedAccounts])
return (
<LayoutAnimationConfig skipEntering={!isSuggestion}>
<Animated.View entering={native(ZoomInEasyDown)}>
<FollowProfileCardInner
profile={profile}
moderationOpts={moderationOpts}
onFollow={() => setHasFollowed(true)}
noBorder={noBorder}
/>
</Animated.View>
</LayoutAnimationConfig>
)
}
FollowProfileCard = memo(FollowProfileCard)
function FollowProfileCardInner({
profile,
moderationOpts,
onFollow,
noBorder,
}: {
profile: AppBskyActorDefs.ProfileView
moderationOpts: ModerationOpts
onFollow?: () => void
noBorder?: boolean
}) {
const control = Dialog.useDialogContext()
const t = useTheme()
return (
<ProfileCard.Link
profile={profile}
style={[a.flex_1]}
onPress={() => control.close()}>
{({hovered, pressed}) => (
<CardOuter
style={[
a.flex_1,
noBorder && a.border_t_0,
(hovered || pressed) && t.atoms.border_contrast_high,
]}>
<ProfileCard.Outer>
<ProfileCard.Header>
<ProfileCard.Avatar
profile={profile}
moderationOpts={moderationOpts}
/>
<ProfileCard.NameAndHandle
profile={profile}
moderationOpts={moderationOpts}
/>
<ProfileCard.FollowButton
profile={profile}
moderationOpts={moderationOpts}
logContext="PostOnboardingFindFollows"
shape="round"
onPress={onFollow}
colorInverted
/>
</ProfileCard.Header>
<ProfileCard.Description profile={profile} numberOfLines={2} />
</ProfileCard.Outer>
</CardOuter>
)}
</ProfileCard.Link>
)
}
function CardOuter({
children,
style,
}: {children: React.ReactNode | React.ReactNode[]} & ViewStyleProp) {
const t = useTheme()
return (
<View
style={[
a.w_full,
a.py_md,
a.px_lg,
a.border_t,
t.atoms.border_contrast_low,
style,
]}>
{children}
</View>
)
}
function SearchInput({
onChangeText,
onEscape,
inputRef,
defaultValue,
}: {
onChangeText: (text: string) => void
onEscape: () => void
inputRef: React.RefObject<TextInput>
defaultValue: string
}) {
const t = useTheme()
const {_} = useLingui()
const {
state: hovered,
onIn: onMouseEnter,
onOut: onMouseLeave,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const interacted = hovered || focused
return (
<View
{...web({
onMouseEnter,
onMouseLeave,
})}
style={[a.flex_row, a.align_center, a.gap_sm, a.px_lg, a.py_xs]}>
<SearchIcon
size="md"
fill={interacted ? t.palette.primary_500 : t.palette.contrast_300}
/>
<TextInput
ref={inputRef}
placeholder={_(msg`Search by name or interest`)}
defaultValue={defaultValue}
onChangeText={onChangeText}
onFocus={onFocus}
onBlur={onBlur}
style={[a.flex_1, a.py_md, a.text_md, t.atoms.text]}
placeholderTextColor={t.palette.contrast_500}
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
returnKeyType="search"
clearButtonMode="while-editing"
maxLength={50}
onKeyPress={({nativeEvent}) => {
if (nativeEvent.key === 'Escape') {
onEscape()
}
}}
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
accessibilityLabel={_(msg`Search profiles`)}
accessibilityHint={_(msg`Search profiles`)}
/>
</View>
)
}
function ProfileCardSkeleton() {
const t = useTheme()
return (
<View
style={[
a.flex_1,
a.py_md,
a.px_lg,
a.gap_md,
a.align_center,
a.flex_row,
]}>
<View
style={[
a.rounded_full,
{width: 42, height: 42},
t.atoms.bg_contrast_25,
]}
/>
<View style={[a.flex_1, a.gap_sm]}>
<View
style={[
a.rounded_xs,
{width: 80, height: 14},
t.atoms.bg_contrast_25,
]}
/>
<View
style={[
a.rounded_xs,
{width: 120, height: 10},
t.atoms.bg_contrast_25,
]}
/>
</View>
</View>
)
}
function Empty({message}: {message: string}) {
const t = useTheme()
return (
<View style={[a.p_lg, a.py_xl, a.align_center, a.gap_md]}>
<Text style={[a.text_sm, a.italic, t.atoms.text_contrast_high]}>
{message}
</Text>
<Text style={[a.text_xs, t.atoms.text_contrast_low]}>(°°) </Text>
</View>
)
}
function boostInterests(boosts?: string[]) {
return (_a: string, _b: string) => {
const indexA = boosts?.indexOf(_a) ?? -1
const indexB = boosts?.indexOf(_b) ?? -1
const rankA = indexA === -1 ? Infinity : indexA
const rankB = indexB === -1 ? Infinity : indexB
return rankA - rankB
}
}
+13 -31
View File
@@ -10,15 +10,12 @@ import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
import {Text} from '#/components/Typography'
import {FollowDialog} from './FollowDialog'
import {ProgressGuideTask} from './Task'
export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
const t = useTheme()
const {_} = useLingui()
const followProgressGuide = useProgressGuide('follow-10')
const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7')
const guide = followProgressGuide || followAndLikeProgressGuide
const guide = useProgressGuide('like-10-and-follow-7')
const {endProgressGuide} = useProgressGuideControls()
if (guide) {
@@ -44,33 +41,18 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
<ButtonIcon icon={Times} size="sm" />
</Button>
</View>
{guide.guide === 'follow-10' && (
<>
<ProgressGuideTask
current={guide.numFollows + 1}
total={10 + 1}
title={_(msg`Follow 10 accounts`)}
subtitle={_(msg`Bluesky is better with friends!`)}
/>
<FollowDialog guide={guide} />
</>
)}
{guide.guide === 'like-10-and-follow-7' && (
<>
<ProgressGuideTask
current={guide.numLikes + 1}
total={10 + 1}
title={_(msg`Like 10 posts`)}
subtitle={_(msg`Teach our algorithm what you like`)}
/>
<ProgressGuideTask
current={guide.numFollows + 1}
total={7 + 1}
title={_(msg`Follow 7 accounts`)}
subtitle={_(msg`Bluesky is better with friends!`)}
/>
</>
)}
<ProgressGuideTask
current={guide.numLikes + 1}
total={10 + 1}
title={_(msg`Like 10 posts`)}
subtitle={_(msg`Teach our algorithm what you like`)}
/>
<ProgressGuideTask
current={guide.numFollows + 1}
total={7 + 1}
title={_(msg`Follow 7 accounts`)}
subtitle={_(msg`Bluesky is better with friends!`)}
/>
</View>
)
}
+2 -12
View File
@@ -10,13 +10,11 @@ export function ProgressGuideTask({
total,
title,
subtitle,
tabularNumsTitle,
}: {
current: number
total: number
title: string
subtitle?: string
tabularNumsTitle?: boolean
}) {
const t = useTheme()
@@ -35,16 +33,8 @@ export function ProgressGuideTask({
/>
)}
<View style={[a.flex_col, a.gap_2xs, subtitle && {marginTop: -2}]}>
<Text
style={[
a.text_sm,
a.font_bold,
a.leading_tight,
tabularNumsTitle && {fontVariant: ['tabular-nums']},
]}>
{title}
</Text>
<View style={[a.flex_col, a.gap_2xs, {marginTop: -2}]}>
<Text style={[a.text_sm, a.font_bold, a.leading_tight]}>{title}</Text>
{subtitle && (
<Text
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}>
@@ -25,7 +25,9 @@ import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from
import {Text} from '#/components/Typography'
import {ReportDialogProps} from './types'
export function SelectReportOptionView(props: {
export function SelectReportOptionView({
...props
}: {
params: ReportDialogProps['params']
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
onSelectReportOption: (reportOption: ReportOption) => void
+1 -9
View File
@@ -19,7 +19,7 @@ import {Text, TextProps} from '#/components/Typography'
const WORD_WRAP = {wordWrap: 1}
export type RichTextProps = TextStyleProp &
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
Pick<TextProps, 'selectable'> & {
value: RichTextAPI | string
testID?: string
numberOfLines?: number
@@ -43,8 +43,6 @@ export function RichText({
onLinkPress,
interactiveStyle,
emojiMultiplier = 1.85,
onLayout,
onTextLayout,
}: RichTextProps) {
const richText = React.useMemo(
() =>
@@ -72,8 +70,6 @@ export function RichText({
selectable={selectable}
testID={testID}
style={[plainStyles, {fontSize}]}
onLayout={onLayout}
onTextLayout={onTextLayout}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
@@ -87,8 +83,6 @@ export function RichText({
testID={testID}
style={plainStyles}
numberOfLines={numberOfLines}
onLayout={onLayout}
onTextLayout={onTextLayout}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
@@ -169,8 +163,6 @@ export function RichText({
testID={testID}
style={plainStyles}
numberOfLines={numberOfLines}
onLayout={onLayout}
onTextLayout={onTextLayout}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{els}
@@ -228,7 +228,7 @@ function Empty() {
a.justify_between,
a.gap_lg,
a.shadow_lg,
{marginTop: 1},
{marginTop: 2},
]}>
<View style={[a.gap_xs]}>
<Text
-228
View File
@@ -1,228 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {AtUri} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {PressableScale} from '#/lib/custom-animations/PressableScale'
// import {makeProfileLink} from '#/lib/routes/links'
// import {feedUriToHref} from '#/lib/strings/url-helpers'
// import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
// import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote'
// import {UserAvatar} from '#/view/com/util/UserAvatar'
import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
import {atoms as a, native, useTheme, ViewStyleProp} from '#/alf'
import {Link as InternalLink, LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
export function TrendingTopic({
topic: raw,
size,
style,
}: {topic: TrendingTopic; size?: 'large' | 'small'} & ViewStyleProp) {
const t = useTheme()
const topic = useTopic(raw)
const isSmall = size === 'small'
// const hasAvi = topic.type === 'feed' || topic.type === 'profile'
// const aviSize = isSmall ? 16 : 20
// const iconSize = isSmall ? 16 : 20
return (
<View
style={[
a.flex_row,
a.align_center,
a.rounded_full,
a.border,
t.atoms.border_contrast_medium,
t.atoms.bg,
isSmall
? [
{
paddingVertical: 5,
paddingHorizontal: 10,
},
]
: [a.py_sm, a.px_md],
style,
/*
{
padding: 6,
gap: hasAvi ? 4 : 2,
},
a.pr_md,
*/
]}>
{/*
<View
style={[
a.align_center,
a.justify_center,
a.rounded_full,
a.overflow_hidden,
{
width: aviSize,
height: aviSize,
},
]}>
{topic.type === 'tag' ? (
<Hashtag width={iconSize} />
) : topic.type === 'topic' ? (
<Quote width={iconSize - 2} />
) : topic.type === 'feed' ? (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
) : (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
)}
</View>
*/}
<Text
style={[
a.font_bold,
a.leading_tight,
isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}],
]}
numberOfLines={1}>
{topic.displayName}
</Text>
</View>
)
}
export function TrendingTopicSkeleton({
size = 'large',
index = 0,
}: {
size?: 'large' | 'small'
index?: number
}) {
const t = useTheme()
const isSmall = size === 'small'
return (
<View
style={[
a.rounded_full,
a.border,
t.atoms.border_contrast_medium,
t.atoms.bg_contrast_25,
isSmall
? {
width: index % 2 === 0 ? 75 : 90,
height: 27,
}
: {
width: index % 2 === 0 ? 90 : 110,
height: 36,
},
]}
/>
)
}
export function TrendingTopicLink({
topic: raw,
children,
...rest
}: {
topic: TrendingTopic
} & Omit<LinkProps, 'to' | 'label'>) {
const topic = useTopic(raw)
return (
<InternalLink
label={topic.label}
to={topic.url}
PressableComponent={native(PressableScale)}
{...rest}>
{children}
</InternalLink>
)
}
type ParsedTrendingTopic =
| {
type: 'topic' | 'tag' | 'unknown'
label: string
displayName: string
url: string
uri: undefined
}
| {
type: 'profile' | 'feed'
label: string
displayName: string
url: string
uri: AtUri
}
export function useTopic(raw: TrendingTopic): ParsedTrendingTopic {
const {_} = useLingui()
return React.useMemo(() => {
const {topic: displayName, link} = raw
if (link.startsWith('/search')) {
return {
type: 'topic',
label: _(msg`Browse posts about ${displayName}`),
displayName,
uri: undefined,
url: link,
}
} else if (link.startsWith('/hashtag')) {
return {
type: 'tag',
label: _(msg`Browse posts tagged with ${displayName}`),
displayName,
// displayName: displayName.replace(/^#/, ''),
uri: undefined,
url: link,
}
}
/*
if (!link.startsWith('at://')) {
// above logic
} else {
const urip = new AtUri(link)
switch (urip.collection) {
case 'app.bsky.actor.profile': {
return {
type: 'profile',
label: _(msg`View ${displayName}'s profile`),
displayName,
uri: urip,
url: makeProfileLink({did: urip.host, handle: urip.host}),
}
}
case 'app.bsky.feed.generator': {
return {
type: 'feed',
label: _(msg`Browse the ${displayName} feed`),
displayName,
uri: urip,
url: feedUriToHref(link),
}
}
}
}
*/
return {
type: 'unknown',
label: _(msg`Browse topic ${displayName}`),
displayName,
uri: undefined,
url: link,
}
}, [_, raw])
}
-221
View File
@@ -1,221 +0,0 @@
import {View} from 'react-native'
import {Image} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient'
import {AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {sanitizeHandle} from '#/lib/strings/handles'
import {formatCount} from '#/view/com/util/numeric/format'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
import {atoms as a, useTheme} from '#/alf'
import {BLUE_HUE} from '#/alf/util/colorGeneration'
import {select} from '#/alf/util/themeSelector'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Heart2_Stroke2_Corner0_Rounded as Heart} from '#/components/icons/Heart2'
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
import {Link} from '#/components/Link'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
export function VideoPostCard({
post,
sourceContext,
}: {
post: AppBskyFeedDefs.PostView
sourceContext: VideoFeedSourceContext
}) {
const t = useTheme()
const {_, i18n} = useLingui()
const embed = post.embed
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
/**
* Filtering should be done at a higher level, such as `PostFeed` or
* `PostFeedVideoGridRow`, but we need to protect here as well.
*/
if (!AppBskyEmbedVideo.isView(embed)) return null
const text = AppBskyFeedPost.isRecord(post.record) ? post.record?.text : ''
const likeCount = post?.likeCount ?? 0
const repostCount = post?.repostCount ?? 0
const {thumbnail} = embed
const black = select(t.name, {
light: t.palette.black,
dark: t.atoms.bg_contrast_25.backgroundColor,
dim: `hsl(${BLUE_HUE}, 28%, 6%)`,
})
return (
<Link
label={_(msg`View video`)}
to={{
screen: 'VideoFeed',
params: {
...sourceContext,
initialPostUri: post.uri,
},
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
style={[
a.flex_col,
{
alignItems: undefined,
justifyContent: undefined,
},
]}>
<View
style={[
a.justify_center,
a.rounded_md,
a.overflow_hidden,
{
backgroundColor: black,
aspectRatio: 9 / 16,
},
]}>
<Image
source={{uri: thumbnail}}
style={[a.w_full, a.h_full, {opacity: pressed ? 0.8 : 1}]}
accessibilityIgnoresInvertColors
/>
<MediaInsetBorder />
<View style={[a.absolute, a.inset_0]}>
<View
style={[
a.absolute,
a.inset_0,
a.pt_2xl,
{
top: 'auto',
},
]}>
<LinearGradient
colors={[black, 'rgba(22, 24, 35, 0)']}
locations={[0.02, 1]}
start={{x: 0, y: 1}}
end={{x: 0, y: 0}}
style={[a.absolute, a.inset_0, {opacity: 0.9}]}
/>
<View style={[a.relative, a.z_10, a.p_md, a.flex_row, a.gap_md]}>
{likeCount > 0 && (
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Heart size="sm" fill="white" />
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
{formatCount(i18n, likeCount)}
</Text>
</View>
)}
{repostCount > 0 && (
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Repost size="sm" fill="white" />
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
{formatCount(i18n, repostCount)}
</Text>
</View>
)}
</View>
</View>
</View>
</View>
<View style={[a.pr_xs, {paddingTop: 6, gap: 4}]}>
{text && (
<Text style={[a.text_md, a.leading_snug]} numberOfLines={2}>
{text}
</Text>
)}
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
<PreviewableUserAvatar type="user" size={20} profile={post.author} />
<Text
style={[
a.flex_1,
a.text_sm,
a.leading_tight,
t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
{sanitizeHandle(post.author.handle, '@')}
</Text>
</View>
</View>
</Link>
)
}
export function VideoPostCardPlaceholder() {
const t = useTheme()
const black = select(t.name, {
light: t.palette.black,
dark: t.atoms.bg_contrast_25.backgroundColor,
dim: `hsl(${BLUE_HUE}, 28%, 6%)`,
})
return (
<View style={[a.flex_1]}>
<View
style={[
a.rounded_md,
a.overflow_hidden,
{
backgroundColor: black,
aspectRatio: 9 / 16,
},
]}>
<MediaInsetBorder />
</View>
<View style={[a.pr_xs, {paddingTop: 8, gap: 6}]}>
<View
style={[
a.w_full,
a.rounded_xs,
t.atoms.bg_contrast_50,
{
height: 14,
},
]}
/>
<View
style={[
a.w_full,
a.rounded_xs,
t.atoms.bg_contrast_50,
{
height: 14,
width: '70%',
},
]}
/>
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
<View
style={[
a.rounded_full,
t.atoms.bg_contrast_50,
{
width: 20,
height: 20,
},
]}
/>
<View
style={[
a.rounded_xs,
t.atoms.bg_contrast_25,
{
height: 12,
width: '75%',
},
]}
/>
</View>
</View>
</View>
)
}
+1 -16
View File
@@ -35,7 +35,6 @@ import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/
import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble'
import {ReportDialog} from './ReportDialog'
let ConvoMenu = ({
convo: initialConvo,
@@ -45,7 +44,6 @@ let ConvoMenu = ({
showMarkAsRead,
hideTrigger,
blockInfo,
latestReportableMessage,
style,
}: {
convo: ChatBskyConvoDefs.ConvoView
@@ -58,7 +56,6 @@ let ConvoMenu = ({
listBlocks: ModerationCause[]
userBlock?: ModerationCause
}
latestReportableMessage?: ChatBskyConvoDefs.MessageView
style?: ViewStyleProp['style']
}): React.ReactNode => {
const navigation = useNavigation<NavigationProp>()
@@ -225,19 +222,7 @@ let ConvoMenu = ({
convoId={convo.id}
currentScreen={currentScreen}
/>
{latestReportableMessage ? (
<ReportDialog
params={{
type: 'convoMessage',
convoId: convo.id,
message: latestReportableMessage,
}}
control={reportControl}
/>
) : (
<ReportConversationPrompt control={reportControl} />
)}
<ReportConversationPrompt control={reportControl} />
<BlockedByListDialog
control={blockedByListControl}
listBlocks={listBlocks}
-10
View File
@@ -151,15 +151,6 @@ function HeaderReady({
moderation.ui('displayName'),
)
const latestMessageFromOther = convoState.items.findLast(
item => item.type === 'message' && item.message.sender.did === profile.did,
)
const latestReportableMessage =
latestMessageFromOther?.type === 'message'
? latestMessageFromOther.message
: undefined
return (
<View style={[a.flex_1]}>
<View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
@@ -217,7 +208,6 @@ function HeaderReady({
profile={profile}
currentScreen="conversation"
blockInfo={blockInfo}
latestReportableMessage={latestReportableMessage}
/>
)}
</View>
@@ -63,7 +63,6 @@ export function SearchablePeopleList({
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const control = Dialog.useDialogContext()
const [headerHeight, setHeaderHeight] = useState(0)
const listRef = useRef<ListMethods>(null)
const {currentAccount} = useSession()
const inputRef = useRef<TextInput>(null)
@@ -238,7 +237,6 @@ export function SearchablePeopleList({
const listHeader = useMemo(() => {
return (
<View
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
style={[
a.relative,
web(a.pt_lg),
@@ -317,7 +315,6 @@ export function SearchablePeopleList({
]}
webInnerContentContainerStyle={a.py_0}
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
scrollIndicatorInsets={{top: headerHeight}}
keyboardDismissMode="on-drag"
/>
)
@@ -1,53 +0,0 @@
import {View} from 'react-native'
import {AppBskyEmbedVideo} from '@atproto/api'
import {FeedPostSliceItem} from '#/state/queries/post-feed'
import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
import {atoms as a, useGutters} from '#/alf'
import {
VideoPostCard,
VideoPostCardPlaceholder,
} from '#/components/VideoPostCard'
export function PostFeedVideoGridRow({
slices,
sourceContext,
}: {
slices: FeedPostSliceItem[]
sourceContext: VideoFeedSourceContext
}) {
const gutters = useGutters(['base', 'base', 0, 'base'])
const posts = slices
.filter(slice => AppBskyEmbedVideo.isView(slice.post.embed))
.map(slice => slice.post)
/**
* This should not happen because we should be filtering out posts without
* videos within the `PostFeed` component.
*/
if (posts.length !== slices.length) return null
return (
<View style={[gutters]}>
<View style={[a.flex_row, a.gap_sm]}>
{posts.map(post => (
<View key={post.uri} style={[a.flex_1]}>
<VideoPostCard post={post} sourceContext={sourceContext} />
</View>
))}
</View>
</View>
)
}
export function PostFeedVideoGridRowPlaceholder() {
const gutters = useGutters(['base', 'base', 0, 'base'])
return (
<View style={[gutters]}>
<View style={[a.flex_row, a.gap_sm]}>
<VideoPostCardPlaceholder />
<VideoPostCardPlaceholder />
</View>
</View>
)
}
+60 -83
View File
@@ -6,23 +6,21 @@ import {useLingui} from '@lingui/react'
import {toNiceDomain} from '#/lib/strings/url-helpers'
import {isAndroid} from '#/platform/detection'
import {ServerInputDialog} from '#/view/com/auth/server-input'
import {atoms as a, tokens, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
import {Text} from '#/components/Typography'
import {atoms as a, useTheme} from '#/alf'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {PencilLine_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
import {Button} from '../Button'
import {useDialogControl} from '../Dialog'
import {Text} from '../Typography'
export function HostingProvider({
serviceUrl,
onSelectServiceUrl,
onOpenDialog,
minimal,
}: {
serviceUrl: string
onSelectServiceUrl: (provider: string) => void
onOpenDialog?: () => void
minimal?: boolean
}) {
const serverInputControl = useDialogControl()
const t = useTheme()
@@ -31,7 +29,9 @@ export function HostingProvider({
const onPressSelectService = React.useCallback(() => {
Keyboard.dismiss()
serverInputControl.open()
onOpenDialog?.()
if (onOpenDialog) {
onOpenDialog()
}
}, [onOpenDialog, serverInputControl])
return (
@@ -40,80 +40,57 @@ export function HostingProvider({
control={serverInputControl}
onSelect={onSelectServiceUrl}
/>
{minimal ? (
<View style={[a.flex_row, a.align_center, a.flex_wrap]}>
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
You are creating an account on{' '}
</Text>
<Button
label={toNiceDomain(serviceUrl)}
accessibilityHint={_(msg`Press to change hosting provider`)}
onPress={onPressSelectService}
variant="ghost"
color="secondary"
size="tiny"
style={[a.px_xs, {marginLeft: tokens.space.xs * -1}]}>
<ButtonText style={[a.text_sm]}>
{toNiceDomain(serviceUrl)}
</ButtonText>
<ButtonIcon icon={PencilIcon} />
</Button>
</View>
) : (
<Button
testID="selectServiceButton"
label={toNiceDomain(serviceUrl)}
accessibilityHint={_(msg`Press to change hosting provider`)}
variant="solid"
color="secondary"
style={[
a.w_full,
a.flex_row,
a.align_center,
a.rounded_sm,
a.px_md,
a.pr_sm,
a.gap_xs,
{paddingVertical: isAndroid ? 14 : 9},
]}
onPress={onPressSelectService}>
{({hovered, pressed}) => {
const interacted = hovered || pressed
return (
<>
<View style={a.pr_xs}>
<GlobeIcon
size="md"
fill={
interacted
? t.palette.contrast_800
: t.palette.contrast_500
}
/>
</View>
<Text style={[a.text_md]}>{toNiceDomain(serviceUrl)}</Text>
<View
style={[
a.rounded_sm,
interacted
? t.atoms.bg_contrast_300
: t.atoms.bg_contrast_100,
{marginLeft: 'auto', padding: 6},
]}>
<PencilIcon
size="sm"
style={{
color: interacted
? t.palette.contrast_800
: t.palette.contrast_500,
}}
/>
</View>
</>
)
}}
</Button>
)}
<Button
testID="selectServiceButton"
label={toNiceDomain(serviceUrl)}
accessibilityHint={_(msg`Press to change hosting provider`)}
variant="solid"
color="secondary"
style={[
a.w_full,
a.flex_row,
a.align_center,
a.rounded_sm,
a.px_md,
a.pr_sm,
a.gap_xs,
{paddingVertical: isAndroid ? 14 : 9},
]}
onPress={onPressSelectService}>
{({hovered, pressed}) => {
const interacted = hovered || pressed
return (
<>
<View style={a.pr_xs}>
<Globe
size="md"
fill={
interacted ? t.palette.contrast_800 : t.palette.contrast_500
}
/>
</View>
<Text style={[a.text_md]}>{toNiceDomain(serviceUrl)}</Text>
<View
style={[
a.rounded_sm,
interacted
? t.atoms.bg_contrast_300
: t.atoms.bg_contrast_100,
{marginLeft: 'auto', padding: 6},
]}>
<Pencil
size="sm"
style={{
color: interacted
? t.palette.contrast_800
: t.palette.contrast_500,
}}
/>
</View>
</>
)
}}
</Button>
</>
)
}
+3 -10
View File
@@ -12,7 +12,6 @@ import {formatDistance, Locale} from 'date-fns'
import {
ca,
de,
el,
enGB,
es,
fi,
@@ -23,12 +22,10 @@ import {
id,
it,
ja,
km,
ko,
nl,
pl,
ptBR,
ro,
ru,
th,
tr,
@@ -51,7 +48,6 @@ const locales: Record<AppLanguage, Locale | undefined> = {
ast: undefined,
ca,
de,
el,
['en-GB']: enGB,
es,
fi,
@@ -63,21 +59,18 @@ const locales: Record<AppLanguage, Locale | undefined> = {
id,
it,
ja,
km,
ko,
ne: undefined,
nl,
pl,
['pt-BR']: ptBR,
ro,
ru,
th,
tr,
uk,
vi,
['zh-Hans-CN']: zhCN,
['zh-Hant-HK']: zhHK,
['zh-Hant-TW']: zhTW,
['zh-CN']: zhCN,
['zh-HK']: zhHK,
['zh-TW']: zhTW,
}
/**
+1 -1
View File
@@ -12,5 +12,5 @@ export function useHeaderOffset() {
const tabBarPad = 10 + 10 + 3 // padding + border
const normalLineHeight = 20 // matches tab bar
const tabBarText = normalLineHeight * fontScale
return navBarHeight + tabBarPad + tabBarText - 4 // for some reason, this calculation is wrong by 4 pixels, which we adjust
return navBarHeight + tabBarPad + tabBarText
}
+1 -1
View File
@@ -1,5 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Ticket_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3 4a1 1 0 0 0-1 1v4.17a1 1 0 0 0 .667.944 2.001 2.001 0 0 1 0 3.772A1 1 0 0 0 2 14.83V19a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1v-4.17a1 1 0 0 0-.667-.944 2.001 2.001 0 0 1 0-3.772A1 1 0 0 0 22 9.17V5a1 1 0 0 0-1-1H3Zm1 4.535V6h16v2.535A4 4 0 0 0 18 12c0 1.482.805 2.773 2 3.465V18H4v-2.535A4 4 0 0 0 6 12a4 4 0 0 0-2-3.465ZM15 15a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-1-3a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm1-5a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z',
path: 'M4 5.5a.5.5 0 0 0-.5.5v2.535a.5.5 0 0 0 .25.433A3.498 3.498 0 0 1 5.5 12a3.498 3.498 0 0 1-1.75 3.032.5.5 0 0 0-.25.433V18a.5.5 0 0 0 .5.5h16a.5.5 0 0 0 .5-.5v-2.535a.5.5 0 0 0-.25-.433A3.498 3.498 0 0 1 18.5 12a3.5 3.5 0 0 1 1.75-3.032.5.5 0 0 0 .25-.433V6a.5.5 0 0 0-.5-.5H4ZM2.5 6A1.5 1.5 0 0 1 4 4.5h16A1.5 1.5 0 0 1 21.5 6v3.17a.5.5 0 0 1-.333.472 2.501 2.501 0 0 0 0 4.716.5.5 0 0 1 .333.471V18a1.5 1.5 0 0 1-1.5 1.5H4A1.5 1.5 0 0 1 2.5 18v-3.17a.5.5 0 0 1 .333-.472 2.501 2.501 0 0 0 0-4.716.5.5 0 0 1-.333-.471V6Zm12 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Z',
})
-141
View File
@@ -1,141 +0,0 @@
import React, {useContext} from 'react'
import {ScrollView, View} from 'react-native'
import {DrawerGestureContext} from 'react-native-drawer-layout'
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig'
import {
useTrendingSettings,
useTrendingSettingsApi,
} from '#/state/preferences/trending'
import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
import {useTrendingConfig} from '#/state/trending-config'
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {atoms as a, useGutters, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2'
import * as Prompt from '#/components/Prompt'
import {TrendingTopicLink} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography'
export function TrendingInterstitial() {
const {enabled} = useTrendingConfig()
const {trendingDisabled} = useTrendingSettings()
return enabled && !trendingDisabled ? <Inner /> : null
}
export function Inner() {
const t = useTheme()
const {_} = useLingui()
const gutters = useGutters([0, 'base', 0, 'base'])
const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDisabled} = useTrendingSettingsApi()
const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length
const onConfirmHide = React.useCallback(() => {
logEvent('trendingTopics:hide', {context: 'interstitial'})
setTrendingDisabled(true)
}, [setTrendingDisabled])
const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web
const trendingScrollGesture =
Gesture.Native().blocksExternalGesture(drawerGesture)
return error || noTopics ? null : (
<View style={[t.atoms.border_contrast_low, a.border_t]}>
<GestureDetector gesture={trendingScrollGesture}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
decelerationRate="fast">
<View style={[gutters, a.flex_row, a.align_center, a.gap_lg]}>
<View style={{paddingLeft: 4, paddingRight: 2}}>
<Graph size="sm" />
</View>
{isLoading ? (
<View style={[a.py_lg, a.flex_row, a.gap_lg, a.align_center]}>
<LoadingPlaceholder
width={80}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={50}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={120}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={30}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<LoadingPlaceholder
width={180}
height={undefined}
style={{alignSelf: 'stretch'}}
/>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_sm,
a.font_bold,
]}>
{' '}
</Text>
</View>
) : !trending?.topics ? null : (
<>
{trending.topics.map(topic => (
<TrendingTopicLink
key={topic.link}
topic={topic}
onPress={() => {
logEvent('trendingTopic:click', {context: 'interstitial'})
}}>
<View style={[a.py_lg]}>
<Text
style={[
t.atoms.text,
a.text_sm,
a.font_bold,
{opacity: 0.7}, // NOTE: we use opacity 0.7 instead of a color to match the color of the home pager tab bar
]}>
{topic.topic}
</Text>
</View>
</TrendingTopicLink>
))}
<Button
label={_(msg`Hide trending topics`)}
size="tiny"
variant="ghost"
color="secondary"
shape="round"
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={X} />
</Button>
</>
)}
</View>
</ScrollView>
</GestureDetector>
<Prompt.Basic
control={trendingPrompt}
title={_(msg`Hide trending topics?`)}
description={_(msg`You can update this later from your settings.`)}
confirmButtonCta={_(msg`Hide`)}
onConfirm={onConfirmHide}
/>
</View>
)
}
+17 -24
View File
@@ -1,27 +1,20 @@
// import {init} from '@bitdrift/react-native'
// import {Statsig} from 'statsig-react-native-expo'
// export {debug, error, info, warn} from '@bitdrift/react-native'
import {init} from '@bitdrift/react-native'
import {Statsig} from 'statsig-react-native-expo'
// import {initPromise} from './statsig/statsig'
import {initPromise} from './statsig/statsig'
// const BITDRIFT_API_KEY = process.env.BITDRIFT_API_KEY
const BITDRIFT_API_KEY = process.env.BITDRIFT_API_KEY
// initPromise.then(() => {
// let isEnabled = false
// try {
// if (Statsig.checkGate('enable_bitdrift')) {
// isEnabled = true
// }
// } catch (e) {
// // Statsig may complain about it being called too early.
// }
// if (isEnabled && BITDRIFT_API_KEY) {
// init(BITDRIFT_API_KEY, {url: 'https://api-bsky.bitdrift.io'})
// }
// })
// TODO: Reenable when the build issue is fixed.
export function debug(_message: string) {}
export function error(_message: string) {}
export function info(_message: string) {}
export function warn(_message: string) {}
initPromise.then(() => {
let isEnabled = false
try {
if (Statsig.checkGate('enable_bitdrift')) {
isEnabled = true
}
} catch (e) {
// Statsig may complain about it being called too early.
}
if (isEnabled && BITDRIFT_API_KEY) {
init(BITDRIFT_API_KEY, {url: 'https://api-bsky.bitdrift.io'})
}
})
-4
View File
@@ -1,4 +0,0 @@
export function debug() {}
export function error() {}
export function info() {}
export function warn() {}
+1 -3
View File
@@ -21,7 +21,7 @@ export const STARTER_PACK_MAX_SIZE = 150
// code and update this number with each release until we can get the
// server route done.
// -prf
export const JOINED_THIS_WEEK = 560000 // estimate as of 12/18/24
export const JOINED_THIS_WEEK = 2880000 // estimate as of 11/26/24
const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
export function FEEDBACK_FORM_URL({
@@ -109,8 +109,6 @@ export const BSKY_FEED_OWNER_DIDS = [
export const DISCOVER_FEED_URI =
'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'
export const VIDEO_FEED_URI =
'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids'
export const DISCOVER_SAVED_FEED = {
type: 'feed',
value: DISCOVER_FEED_URI,
+1 -1
View File
@@ -215,7 +215,7 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
)
}
async function moveToPermanentPath(path: string, ext: string): Promise<string> {
async function moveToPermanentPath(path: string, ext = 'jpg'): Promise<string> {
/*
Since this package stores images in a temp directory, we need to move the file to a permanent location.
Relevant: IOS bug when trying to open a second time:
@@ -1,11 +0,0 @@
export function useNotificationsRegistration() {}
export function useRequestNotificationsPermission() {
return async (
_context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home',
) => {}
}
export async function decrementBadgeCount(_by: number) {}
export async function resetBadgeCount() {}
-6
View File
@@ -1,8 +1,6 @@
import {NavigationState, PartialState} from '@react-navigation/native'
import type {NativeStackNavigationProp} from '@react-navigation/native-stack'
import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
export type {NativeStackScreenProps} from '@react-navigation/native-stack'
export type CommonNavigatorParams = {
@@ -49,7 +47,6 @@ export type CommonNavigatorParams = {
AppIconSettings: undefined
Search: {q?: string}
Hashtag: {tag: string; author?: string}
Topic: {topic: string}
MessagesConversation: {conversation: string; embed?: string}
MessagesSettings: undefined
NotificationSettings: undefined
@@ -59,7 +56,6 @@ export type CommonNavigatorParams = {
StarterPackShort: {code: string}
StarterPackWizard: undefined
StarterPackEdit: {rkey?: string}
VideoFeed: VideoFeedSourceContext
}
export type BottomTabNavigatorParams = CommonNavigatorParams & {
@@ -96,7 +92,6 @@ export type FlatNavigatorParams = CommonNavigatorParams & {
Feeds: undefined
Notifications: undefined
Hashtag: {tag: string; author?: string}
Topic: {topic: string}
Messages: {pushToConversation?: string; animation?: 'push' | 'pop'}
}
@@ -110,7 +105,6 @@ export type AllNavigatorParams = CommonNavigatorParams & {
Notifications: undefined
MyProfileTab: undefined
Hashtag: {tag: string; author?: string}
Topic: {topic: string}
MessagesTab: undefined
Messages: {animation?: 'push' | 'pop'}
Start: {name: string; rkey: string}
+4 -22
View File
@@ -131,16 +131,16 @@ export type LogEvents = {
doesPosterFollowLiker: boolean | undefined
likerClout: number | undefined
postClout: number | undefined
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
}
'post:repost': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
}
'post:unlike': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
}
'post:unrepost': {
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
}
'post:mute': {}
'post:unmute': {}
@@ -162,7 +162,6 @@ export type LogEvents = {
| 'StarterPackProfilesList'
| 'FeedInterstitial'
| 'ProfileHeaderSuggestedFollows'
| 'PostOnboardingFindFollows'
}
'profile:unfollow': {
logContext:
@@ -178,7 +177,6 @@ export type LogEvents = {
| 'StarterPackProfilesList'
| 'FeedInterstitial'
| 'ProfileHeaderSuggestedFollows'
| 'PostOnboardingFindFollows'
}
'chat:create': {
logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog'
@@ -236,20 +234,4 @@ export type LogEvents = {
'tmd:share': {}
'tmd:download': {}
'tmd:post': {}
'trendingTopics:show': {
context: 'settings'
}
'trendingTopics:hide': {
context: 'settings' | 'sidebar' | 'interstitial' | 'explore:trending'
}
'trendingTopic:click': {
context: 'sidebar' | 'interstitial' | 'explore'
}
'recommendedTopic:click': {
context: 'explore'
}
'progressGuide:hide': {}
'progressGuide:followDialog:open': {}
}
+1 -7
View File
@@ -1,9 +1,3 @@
export type Gate =
// Keep this alphabetic please.
| 'debug_show_feedcontext'
| 'debug_subscriptions'
| 'new_postonboarding'
| 'remove_show_latest_button'
| 'test_gate_1'
| 'test_gate_2'
| 'trending_topics_beta'
'debug_show_feedcontext' | 'debug_subscriptions' | 'remove_show_latest_button'
+3 -25
View File
@@ -5,8 +5,6 @@ import {sha256} from 'js-sha256'
import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
// TODO: Reenable when the build issue is fixed.
// import * as bitdrift from '#/lib/bitdrift'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import * as persisted from '#/state/persisted'
@@ -99,39 +97,19 @@ export function logEvent<E extends keyof LogEvents>(
rawMetadata: LogEvents[E] & FlatJSONRecord,
) {
try {
const fullMetadata = toStringRecord(rawMetadata)
const fullMetadata = {
...rawMetadata,
} as Record<string, string> // Statsig typings are unnecessarily strict here.
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
if (Statsig.initializeCalled()) {
Statsig.logEvent(eventName, null, fullMetadata)
}
// Intentionally bypass the logger abstraction to log rich objects.
console.groupCollapsed(eventName)
console.log(fullMetadata)
console.groupEnd()
// TODO: Reenable when the build issue is fixed.
// bitdrift.info(eventName, fullMetadata)
} catch (e) {
// A log should never interrupt the calling code, whatever happens.
logger.error('Failed to log an event', {message: e})
}
}
function toStringRecord<E extends keyof LogEvents>(
metadata: LogEvents[E] & FlatJSONRecord,
): Record<string, string> {
const record: Record<string, string> = {}
for (let key in metadata) {
if (metadata.hasOwnProperty(key)) {
if (typeof metadata[key] === 'string') {
record[key] = metadata[key]
} else {
record[key] = JSON.stringify(metadata[key])
}
}
}
return record
}
// We roll our own cache in front of Statsig because it is a singleton
// and it's been difficult to get it to behave in a predictable way.
// Our own cache ensures consistent evaluation within a single session.
+2 -10
View File
@@ -19,10 +19,6 @@ export function createFullHandle(name: string, domain: string): string {
return `${name}.${domain}`
}
export function maxServiceHandleLength(domain: string): number {
return 30 - `.${(domain || '').replace(/^[.]+/, '')}`.length
}
export function isInvalidHandle(handle: string): boolean {
return handle === 'handle.invalid'
}
@@ -42,11 +38,7 @@ export interface IsValidHandle {
}
// 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,
isServiceHandle?: boolean,
): IsValidHandle {
export function validateHandle(str: string, userDomain: string): IsValidHandle {
const fullHandle = createFullHandle(str, userDomain)
const results = {
@@ -54,7 +46,7 @@ export function validateHandle(
!str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')),
hyphenStartOrEnd: !str.startsWith('-') && !str.endsWith('-'),
frontLength: str.length >= 3,
totalLength: fullHandle.length <= (isServiceHandle ? 30 : 253),
totalLength: fullHandle.length <= 253,
}
return {
-2
View File
@@ -5,7 +5,6 @@ import {AppLanguage} from '#/locale/languages'
test('sanitizeAppLanguageSetting', () => {
expect(sanitizeAppLanguageSetting('en')).toBe(AppLanguage.en)
expect(sanitizeAppLanguageSetting('el')).toBe(AppLanguage.el)
expect(sanitizeAppLanguageSetting('pt-BR')).toBe(AppLanguage.pt_BR)
expect(sanitizeAppLanguageSetting('hi')).toBe(AppLanguage.hi)
expect(sanitizeAppLanguageSetting('id')).toBe(AppLanguage.id)
@@ -13,5 +12,4 @@ test('sanitizeAppLanguageSetting', () => {
expect(sanitizeAppLanguageSetting('en,foo')).toBe(AppLanguage.en)
expect(sanitizeAppLanguageSetting('foo,en')).toBe(AppLanguage.en)
expect(sanitizeAppLanguageSetting('vi')).toBe(AppLanguage.vi)
expect(sanitizeAppLanguageSetting('ne')).toBe(AppLanguage.ne)
})
+10 -12
View File
@@ -41,19 +41,17 @@ export function getLocales() {
}
if (typeof locale.languageTag === 'string') {
if (
locale.languageTag.startsWith('zh-Hans') ||
locale.languageTag === 'zh-CN'
) {
// Simplified Chinese to zh-Hans-CN
locale.languageTag = 'zh-Hans-CN'
if (locale.languageTag.startsWith('zh-Hans')) {
// Simplified Chinese to zh-CN
locale.languageTag = 'zh-CN'
}
if (
locale.languageTag.startsWith('zh-Hant') ||
locale.languageTag === 'zh-TW'
) {
// Traditional Chinese to zh-Hant-TW
locale.languageTag = 'zh-Hant-TW'
if (locale.languageTag.startsWith('zh-Hant')) {
// Traditional Chinese to zh-TW
locale.languageTag = 'zh-TW'
}
if (locale.languageTag.startsWith('yue')) {
// Cantonese (Yue) to zh-HK
locale.languageTag = 'zh-HK'
}
}
+6 -50
View File
@@ -5,7 +5,6 @@ import lande from 'lande'
import {hasProp} from '#/lib/type-guards'
import {
AppLanguage,
type Language,
LANGUAGES_MAP_CODE2,
LANGUAGES_MAP_CODE3,
} from './languages'
@@ -32,44 +31,9 @@ export function code3ToCode2Strict(lang: string): string | undefined {
return undefined
}
function getLocalizedLanguage(
langCode: string,
appLang: string,
): string | undefined {
try {
const allNames = new Intl.DisplayNames([appLang], {
type: 'language',
fallback: 'none',
languageDisplay: 'standard',
})
const translatedName = allNames.of(langCode)
if (translatedName) {
// force simple title case (as languages do not always start with an uppercase in Unicode data)
return translatedName[0].toLocaleUpperCase() + translatedName.slice(1)
}
} catch (e) {
// ignore RangeError from Intl.DisplayNames APIs
if (!(e instanceof RangeError)) {
throw e
}
}
}
export function languageName(language: Language, appLang: string): string {
// if Intl.DisplayNames is unavailable on the target, display the English name
if (!(Intl as any).DisplayNames) {
return language.name
}
return getLocalizedLanguage(language.code2, appLang) || language.name
}
export function codeToLanguageName(lang2or3: string, appLang: string): string {
const code2 = code3ToCode2(lang2or3)
const knownLanguage = LANGUAGES_MAP_CODE2[code2]
return knownLanguage ? languageName(knownLanguage, appLang) : code2
export function codeToLanguageName(lang: string): string {
const lang2 = code3ToCode2(lang)
return LANGUAGES_MAP_CODE2[lang2]?.name || lang
}
export function getPostLanguage(
@@ -163,8 +127,6 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
return AppLanguage.ca
case 'de':
return AppLanguage.de
case 'el':
return AppLanguage.el
case 'en-GB':
return AppLanguage.en_GB
case 'es':
@@ -187,20 +149,14 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
return AppLanguage.it
case 'ja':
return AppLanguage.ja
case 'km':
return AppLanguage.km
case 'ko':
return AppLanguage.ko
case 'ne':
return AppLanguage.ne
case 'nl':
return AppLanguage.nl
case 'pl':
return AppLanguage.pl
case 'pt-BR':
return AppLanguage.pt_BR
case 'ro':
return AppLanguage.ro
case 'ru':
return AppLanguage.ru
case 'th':
@@ -211,11 +167,11 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
return AppLanguage.uk
case 'vi':
return AppLanguage.vi
case 'zh-Hans-CN':
case 'zh-CN':
return AppLanguage.zh_CN
case 'zh-Hant-HK':
case 'zh-HK':
return AppLanguage.zh_HK
case 'zh-Hant-TW':
case 'zh-TW':
return AppLanguage.zh_TW
default:
continue
+29 -32
View File
@@ -1,8 +1,10 @@
// Don't remove -force from these because detection is VERY slow on low-end Android.
// https://github.com/formatjs/formatjs/issues/4463#issuecomment-2176070577
import '@formatjs/intl-locale/polyfill-force'
import '@formatjs/intl-datetimeformat/polyfill-force'
import '@formatjs/intl-pluralrules/polyfill-force'
import '@formatjs/intl-numberformat/polyfill-force'
import '@formatjs/intl-datetimeformat/locale-data/en'
import '@formatjs/intl-pluralrules/locale-data/en'
import '@formatjs/intl-numberformat/locale-data/en'
@@ -15,7 +17,6 @@ import {messages as messagesAn} from '#/locale/locales/an/messages'
import {messages as messagesAst} from '#/locale/locales/ast/messages'
import {messages as messagesCa} from '#/locale/locales/ca/messages'
import {messages as messagesDe} from '#/locale/locales/de/messages'
import {messages as messagesEl} from '#/locale/locales/el/messages'
import {messages as messagesEn} from '#/locale/locales/en/messages'
import {messages as messagesEn_GB} from '#/locale/locales/en-GB/messages'
import {messages as messagesEs} from '#/locale/locales/es/messages'
@@ -28,13 +29,10 @@ import {messages as messagesHu} from '#/locale/locales/hu/messages'
import {messages as messagesId} from '#/locale/locales/id/messages'
import {messages as messagesIt} from '#/locale/locales/it/messages'
import {messages as messagesJa} from '#/locale/locales/ja/messages'
import {messages as messagesKm} from '#/locale/locales/km/messages'
import {messages as messagesKo} from '#/locale/locales/ko/messages'
import {messages as messagesNe} from '#/locale/locales/ne/messages'
import {messages as messagesNl} from '#/locale/locales/nl/messages'
import {messages as messagesPl} from '#/locale/locales/pl/messages'
import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages'
import {messages as messagesRo} from '#/locale/locales/ro/messages'
import {messages as messagesRu} from '#/locale/locales/ru/messages'
import {messages as messagesTh} from '#/locale/locales/th/messages'
import {messages as messagesTr} from '#/locale/locales/tr/messages'
@@ -53,6 +51,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.an: {
i18n.loadAndActivate({locale, messages: messagesAn})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/es'),
import('@formatjs/intl-pluralrules/locale-data/an'),
import('@formatjs/intl-numberformat/locale-data/es'),
])
@@ -61,6 +60,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.ast: {
i18n.loadAndActivate({locale, messages: messagesAst})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ast'),
import('@formatjs/intl-pluralrules/locale-data/ast'),
import('@formatjs/intl-numberformat/locale-data/ast'),
])
@@ -69,6 +69,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.ca: {
i18n.loadAndActivate({locale, messages: messagesCa})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ca'),
import('@formatjs/intl-pluralrules/locale-data/ca'),
import('@formatjs/intl-numberformat/locale-data/ca'),
])
@@ -77,22 +78,16 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.de: {
i18n.loadAndActivate({locale, messages: messagesDe})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/de'),
import('@formatjs/intl-pluralrules/locale-data/de'),
import('@formatjs/intl-numberformat/locale-data/de'),
])
break
}
case AppLanguage.el: {
i18n.loadAndActivate({locale, messages: messagesEl})
await Promise.all([
import('@formatjs/intl-pluralrules/locale-data/el'),
import('@formatjs/intl-numberformat/locale-data/el'),
])
break
}
case AppLanguage.en_GB: {
i18n.loadAndActivate({locale, messages: messagesEn_GB})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/en-GB'),
import('@formatjs/intl-pluralrules/locale-data/en'),
import('@formatjs/intl-numberformat/locale-data/en-GB'),
])
@@ -101,6 +96,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.es: {
i18n.loadAndActivate({locale, messages: messagesEs})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/es'),
import('@formatjs/intl-pluralrules/locale-data/es'),
import('@formatjs/intl-numberformat/locale-data/es'),
])
@@ -109,6 +105,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.fi: {
i18n.loadAndActivate({locale, messages: messagesFi})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/fi'),
import('@formatjs/intl-pluralrules/locale-data/fi'),
import('@formatjs/intl-numberformat/locale-data/fi'),
])
@@ -117,6 +114,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.fr: {
i18n.loadAndActivate({locale, messages: messagesFr})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/fr'),
import('@formatjs/intl-pluralrules/locale-data/fr'),
import('@formatjs/intl-numberformat/locale-data/fr'),
])
@@ -125,6 +123,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.ga: {
i18n.loadAndActivate({locale, messages: messagesGa})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ga'),
import('@formatjs/intl-pluralrules/locale-data/ga'),
import('@formatjs/intl-numberformat/locale-data/ga'),
])
@@ -133,6 +132,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.gl: {
i18n.loadAndActivate({locale, messages: messagesGl})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/gl'),
import('@formatjs/intl-pluralrules/locale-data/gl'),
import('@formatjs/intl-numberformat/locale-data/gl'),
])
@@ -141,6 +141,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.hi: {
i18n.loadAndActivate({locale, messages: messagesHi})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/hi'),
import('@formatjs/intl-pluralrules/locale-data/hi'),
import('@formatjs/intl-numberformat/locale-data/hi'),
])
@@ -149,6 +150,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.hu: {
i18n.loadAndActivate({locale, messages: messagesHu})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/hu'),
import('@formatjs/intl-pluralrules/locale-data/hu'),
import('@formatjs/intl-numberformat/locale-data/hu'),
])
@@ -157,6 +159,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.id: {
i18n.loadAndActivate({locale, messages: messagesId})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/id'),
import('@formatjs/intl-pluralrules/locale-data/id'),
import('@formatjs/intl-numberformat/locale-data/id'),
])
@@ -165,6 +168,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.it: {
i18n.loadAndActivate({locale, messages: messagesIt})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/it'),
import('@formatjs/intl-pluralrules/locale-data/it'),
import('@formatjs/intl-numberformat/locale-data/it'),
])
@@ -173,34 +177,25 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.ja: {
i18n.loadAndActivate({locale, messages: messagesJa})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ja'),
import('@formatjs/intl-pluralrules/locale-data/ja'),
import('@formatjs/intl-numberformat/locale-data/ja'),
])
break
}
case AppLanguage.km: {
i18n.loadAndActivate({locale, messages: messagesKm})
await Promise.all([
import('@formatjs/intl-pluralrules/locale-data/km'),
import('@formatjs/intl-numberformat/locale-data/km'),
])
break
}
case AppLanguage.ko: {
i18n.loadAndActivate({locale, messages: messagesKo})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ko'),
import('@formatjs/intl-pluralrules/locale-data/ko'),
import('@formatjs/intl-numberformat/locale-data/ko'),
])
break
}
case AppLanguage.ne: {
i18n.loadAndActivate({locale, messages: messagesNe})
break
}
case AppLanguage.nl: {
i18n.loadAndActivate({locale, messages: messagesNl})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/nl'),
import('@formatjs/intl-pluralrules/locale-data/nl'),
import('@formatjs/intl-numberformat/locale-data/nl'),
])
@@ -209,6 +204,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.pl: {
i18n.loadAndActivate({locale, messages: messagesPl})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/pl'),
import('@formatjs/intl-pluralrules/locale-data/pl'),
import('@formatjs/intl-numberformat/locale-data/pl'),
])
@@ -217,22 +213,16 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.pt_BR: {
i18n.loadAndActivate({locale, messages: messagesPt_BR})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/pt'),
import('@formatjs/intl-pluralrules/locale-data/pt'),
import('@formatjs/intl-numberformat/locale-data/pt'),
])
break
}
case AppLanguage.ro: {
i18n.loadAndActivate({locale, messages: messagesRo})
await Promise.all([
import('@formatjs/intl-pluralrules/locale-data/ro'),
import('@formatjs/intl-numberformat/locale-data/ro'),
])
break
}
case AppLanguage.ru: {
i18n.loadAndActivate({locale, messages: messagesRu})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/ru'),
import('@formatjs/intl-pluralrules/locale-data/ru'),
import('@formatjs/intl-numberformat/locale-data/ru'),
])
@@ -241,6 +231,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.th: {
i18n.loadAndActivate({locale, messages: messagesTh})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/th'),
import('@formatjs/intl-pluralrules/locale-data/th'),
import('@formatjs/intl-numberformat/locale-data/th'),
])
@@ -249,6 +240,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.tr: {
i18n.loadAndActivate({locale, messages: messagesTr})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/tr'),
import('@formatjs/intl-pluralrules/locale-data/tr'),
import('@formatjs/intl-numberformat/locale-data/tr'),
])
@@ -257,6 +249,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.uk: {
i18n.loadAndActivate({locale, messages: messagesUk})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/uk'),
import('@formatjs/intl-pluralrules/locale-data/uk'),
import('@formatjs/intl-numberformat/locale-data/uk'),
])
@@ -265,6 +258,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.vi: {
i18n.loadAndActivate({locale, messages: messagesVi})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/vi'),
import('@formatjs/intl-pluralrules/locale-data/vi'),
import('@formatjs/intl-numberformat/locale-data/vi'),
])
@@ -273,6 +267,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.zh_CN: {
i18n.loadAndActivate({locale, messages: messagesZh_CN})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/zh-Hans'),
import('@formatjs/intl-pluralrules/locale-data/zh'),
import('@formatjs/intl-numberformat/locale-data/zh'),
])
@@ -281,6 +276,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.zh_HK: {
i18n.loadAndActivate({locale, messages: messagesZh_HK})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/yue'),
import('@formatjs/intl-pluralrules/locale-data/zh'),
import('@formatjs/intl-numberformat/locale-data/zh'),
])
@@ -289,6 +285,7 @@ export async function dynamicActivate(locale: AppLanguage) {
case AppLanguage.zh_TW: {
i18n.loadAndActivate({locale, messages: messagesZh_TW})
await Promise.all([
import('@formatjs/intl-datetimeformat/locale-data/zh-Hant'),
import('@formatjs/intl-pluralrules/locale-data/zh'),
import('@formatjs/intl-numberformat/locale-data/zh'),
])
-16
View File
@@ -28,10 +28,6 @@ export async function dynamicActivate(locale: AppLanguage) {
mod = await import(`./locales/de/messages`)
break
}
case AppLanguage.el: {
mod = await import(`./locales/el/messages`)
break
}
case AppLanguage.en_GB: {
mod = await import(`./locales/en-GB/messages`)
break
@@ -76,18 +72,10 @@ export async function dynamicActivate(locale: AppLanguage) {
mod = await import(`./locales/ja/messages`)
break
}
case AppLanguage.km: {
mod = await import(`./locales/km/messages`)
break
}
case AppLanguage.ko: {
mod = await import(`./locales/ko/messages`)
break
}
case AppLanguage.ne: {
mod = await import(`./locales/ne/messages`)
break
}
case AppLanguage.nl: {
mod = await import(`./locales/nl/messages`)
break
@@ -100,10 +88,6 @@ export async function dynamicActivate(locale: AppLanguage) {
mod = await import(`./locales/pt-BR/messages`)
break
}
case AppLanguage.ro: {
mod = await import(`./locales/ro/messages`)
break
}
case AppLanguage.ru: {
mod = await import(`./locales/ru/messages`)
break
+4 -12
View File
@@ -1,4 +1,4 @@
export interface Language {
interface Language {
code3: string
code2: string
name: string
@@ -10,7 +10,6 @@ export enum AppLanguage {
ast = 'ast',
ca = 'ca',
de = 'de',
el = 'el',
en_GB = 'en-GB',
es = 'es',
fi = 'fi',
@@ -22,21 +21,18 @@ export enum AppLanguage {
id = 'id',
it = 'it',
ja = 'ja',
km = 'km',
ko = 'ko',
ne = 'ne',
nl = 'nl',
pl = 'pl',
pt_BR = 'pt-BR',
ro = 'ro',
ru = 'ru',
th = 'th',
tr = 'tr',
uk = 'uk',
vi = 'vi',
zh_CN = 'zh-Hans-CN',
zh_HK = 'zh-Hant-HK',
zh_TW = 'zh-Hant-TW',
zh_CN = 'zh-CN',
zh_HK = 'zh-HK',
zh_TW = 'zh-TW',
}
interface AppLanguageConfig {
@@ -50,7 +46,6 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
{code2: AppLanguage.ast, name: 'Asturianu Asturian'},
{code2: AppLanguage.ca, name: 'Català Catalan'},
{code2: AppLanguage.de, name: 'Deutsch German'},
{code2: AppLanguage.el, name: 'Ελληνικά Greek'},
{code2: AppLanguage.en_GB, name: 'English (UK)'},
{code2: AppLanguage.es, name: 'Español Spanish'},
{code2: AppLanguage.fi, name: 'Suomi Finnish'},
@@ -62,13 +57,10 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
{code2: AppLanguage.id, name: 'Bahasa Indonesia Indonesian'},
{code2: AppLanguage.it, name: 'Italiano Italian'},
{code2: AppLanguage.ja, name: '日本語 Japanese'},
{code2: AppLanguage.km, name: 'ភាសាខ្មែរ Khmer'},
{code2: AppLanguage.ko, name: '한국어 Korean'},
{code2: AppLanguage.ne, name: 'नेपाली Nepali'},
{code2: AppLanguage.nl, name: 'Nederlands Dutch'},
{code2: AppLanguage.pl, name: 'Polski Polish'},
{code2: AppLanguage.pt_BR, name: 'Português (BR) Portuguese (BR)'},
{code2: AppLanguage.ro, name: 'Română Romanian'},
{code2: AppLanguage.ru, name: 'Русский Russian'},
{code2: AppLanguage.th, name: 'ภาษาไทย Thai'},
{code2: AppLanguage.tr, name: 'Türkçe Turkish'},
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
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

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