From 3ed237b6290c3f3d6ce143e8c63e2e888cf7b47a Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 16 Dec 2024 21:46:39 +0000 Subject: [PATCH 01/55] Bump more native deps (#7129) * Undo expo modules patch * Upgrade expo modules release * Patch facebook/react-native#46944 * Remove explicit prebuild config dep * Bump to rm duplicates --- package.json | 7 +- patches/expo-modules-core+2.1.1.patch | 35 ---- ...re+2.1.1.md => expo-modules-core+2.1.2.md} | 0 patches/expo-modules-core+2.1.2.patch | 15 ++ patches/react-native+0.76.3.patch | 37 ++++- yarn.lock | 157 +++++++++--------- 6 files changed, 135 insertions(+), 116 deletions(-) delete mode 100644 patches/expo-modules-core+2.1.1.patch rename patches/{expo-modules-core+2.1.1.md => expo-modules-core+2.1.2.md} (100%) create mode 100644 patches/expo-modules-core+2.1.2.patch diff --git a/package.json b/package.json index 2b22284936..f5f4601d6c 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "emoji-mart": "^5.5.2", "emoji-regex": "^10.4.0", "eventemitter3": "^5.0.1", - "expo": "~52.0.17", + "expo": "~52.0.19", "expo-application": "^6.0.1", "expo-blur": "^14.0.1", "expo-build-properties": "^0.13.1", @@ -128,7 +128,7 @@ "expo-clipboard": "^7.0.0", "expo-dev-client": "^5.0.4", "expo-device": "~7.0.1", - "expo-file-system": "^18.0.4", + "expo-file-system": "^18.0.6", "expo-font": "~13.0.1", "expo-haptics": "^14.0.0", "expo-image": "~2.0.3", @@ -141,7 +141,7 @@ "expo-navigation-bar": "~4.0.4", "expo-notifications": "~0.29.11", "expo-sharing": "^13.0.0", - "expo-splash-screen": "~0.29.16", + "expo-splash-screen": "~0.29.18", "expo-status-bar": "~2.0.0", "expo-system-ui": "^4.0.4", "expo-task-manager": "~12.0.3", @@ -213,7 +213,6 @@ "@babel/runtime": "^7.26.0", "@did-plc/server": "^0.0.1", "@expo/config-plugins": "9.0.10", - "@expo/prebuild-config": "8.0.22", "@lingui/cli": "^4.14.1", "@lingui/macro": "^4.14.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", diff --git a/patches/expo-modules-core+2.1.1.patch b/patches/expo-modules-core+2.1.1.patch deleted file mode 100644 index 5695b8f44a..0000000000 --- a/patches/expo-modules-core+2.1.1.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -index 47c4d15..afe138d 100644 ---- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -@@ -125,6 +125,10 @@ internal fun peekResponseBody( - } - - internal fun shouldParseBody(response: Response): Boolean { -+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") { -+ return false -+ } -+ - // Check for Content-Type - val skipContentTypes = listOf( - "text/event-stream", // Server Sent Events -diff --git a/node_modules/expo-modules-core/src/uuid/uuid.ts b/node_modules/expo-modules-core/src/uuid/uuid.ts -index 148beac..fabdff5 100644 ---- a/node_modules/expo-modules-core/src/uuid/uuid.ts -+++ b/node_modules/expo-modules-core/src/uuid/uuid.ts -@@ -5,6 +5,7 @@ const nativeUuidv4 = globalThis?.expo?.uuidv4; - const nativeUuidv5 = globalThis?.expo?.uuidv5; - - function uuidv4(): string { -+ const nativeUuidv4 = globalThis?.expo?.uuidv4; - if (!nativeUuidv4) { - throw Error( - "Native UUID version 4 generator implementation wasn't found in `expo-modules-core`" -@@ -23,6 +24,7 @@ function uuidv5(name: string, namespace: string | number[]) { - throw new Error('`namespace` must be a valid UUID string or an Array of 16 byte values'); - } - -+ const nativeUuidv5 = globalThis?.expo?.uuidv5; - if (!nativeUuidv5) { - throw Error("Native UUID type 5 generator implementation wasn't found in `expo-modules-core`"); - } diff --git a/patches/expo-modules-core+2.1.1.md b/patches/expo-modules-core+2.1.2.md similarity index 100% rename from patches/expo-modules-core+2.1.1.md rename to patches/expo-modules-core+2.1.2.md diff --git a/patches/expo-modules-core+2.1.2.patch b/patches/expo-modules-core+2.1.2.patch new file mode 100644 index 0000000000..f3d9bfd149 --- /dev/null +++ b/patches/expo-modules-core+2.1.2.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +index 47c4d15..afe138d 100644 +--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt ++++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +@@ -125,6 +125,10 @@ internal fun peekResponseBody( + } + + internal fun shouldParseBody(response: Response): Boolean { ++ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") { ++ return false ++ } ++ + // Check for Content-Type + val skipContentTypes = listOf( + "text/event-stream", // Server Sent Events diff --git a/patches/react-native+0.76.3.patch b/patches/react-native+0.76.3.patch index 6f71097a18..5af24a372b 100644 --- a/patches/react-native+0.76.3.patch +++ b/patches/react-native+0.76.3.patch @@ -222,7 +222,7 @@ index 40aaf9c..1c60164 100644 { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m -index e9ce48c..ccd9ad6 100644 +index e9ce48c..84a6fca 100644 --- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m @@ -159,26 +159,8 @@ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view @@ -329,3 +329,38 @@ index e9ce48c..ccd9ad6 100644 } } +@@ -1055,6 +1082,22 @@ -(type)getter \ + RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL) + RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat); + ++- (void)setScrollIndicatorInsets:(UIEdgeInsets)value ++{ ++ [_scrollView setScrollIndicatorInsets:value]; ++} ++ ++- (UIEdgeInsets)scrollIndicatorInsets ++{ ++ UIEdgeInsets verticalScrollIndicatorInsets = [_scrollView verticalScrollIndicatorInsets]; ++ UIEdgeInsets horizontalScrollIndicatorInsets = [_scrollView horizontalScrollIndicatorInsets]; ++ return UIEdgeInsetsMake( ++ verticalScrollIndicatorInsets.top, ++ horizontalScrollIndicatorInsets.left, ++ verticalScrollIndicatorInsets.bottom, ++ horizontalScrollIndicatorInsets.right); ++} ++ + - (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0)) + { + // `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13. +diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m +index cd1e7eb..c1d0172 100644 +--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m ++++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m +@@ -83,6 +83,7 @@ - (UIView *)view + RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) + RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) + RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) ++RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) + RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets) + RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL) + RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) diff --git a/yarn.lock b/yarn.lock index 13b9841c66..f6598f8b8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3695,10 +3695,10 @@ mv "~2" safe-json-stringify "~1" -"@expo/cli@0.22.3": - version "0.22.3" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.3.tgz#8dbcc9396abf01b2dd91fe7f34fc23fdd2d8cc7f" - integrity sha512-1HBtqInFDFHUJWzTJ1CJj5MR3JwvOiozmRUWF2kVQAeq/bKzSYM6We6B3XoZBM5XP6z6WtnrG87C7BjeW5E/cA== +"@expo/cli@0.22.6": + version "0.22.6" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.6.tgz#dc27b685d2252027549d839437c5285c7814ec3f" + integrity sha512-eDjCnc3uHl2+SJ6aZ5seK0FkMp0W12oAdSI4A/yV8ecYtXzG8X87sfKAISEWt44B4DqJ0a1LEqCD6Vtvc783Mg== dependencies: "@0no-co/graphql.web" "^1.0.8" "@babel/runtime" "^7.20.0" @@ -3709,15 +3709,15 @@ "@expo/env" "~0.4.0" "@expo/image-utils" "^0.6.0" "@expo/json-file" "^9.0.0" - "@expo/metro-config" "~0.19.0" + "@expo/metro-config" "~0.19.8" "@expo/osascript" "^2.0.31" "@expo/package-manager" "^1.5.0" "@expo/plist" "^0.2.0" - "@expo/prebuild-config" "^8.0.22" + "@expo/prebuild-config" "^8.0.23" "@expo/rudder-sdk-node" "^1.1.1" "@expo/spawn-async" "^1.7.2" "@expo/xcpretty" "^4.3.0" - "@react-native/dev-middleware" "0.76.3" + "@react-native/dev-middleware" "0.76.5" "@urql/core" "^5.0.6" "@urql/exchange-retry" "^1.3.0" accepts "^1.3.8" @@ -3893,10 +3893,10 @@ dotenv-expand "~11.0.6" getenv "^1.0.0" -"@expo/fingerprint@0.11.3": - version "0.11.3" - resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.3.tgz#e370ae8f83e0642f752b058e2102e984a0a5bc98" - integrity sha512-9lgXmcIePvZ7Wef63XtvuN3HfCUevF4E4tQPdEbH9/dUWwpOvvwQ3KT4OJ9jdh8JJ3nTdO9eDQ/8k8xr1aQ5Kg== +"@expo/fingerprint@0.11.4": + version "0.11.4" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.4.tgz#f40bbc784e10a065b783091e0d060a7428d41a7c" + integrity sha512-FfcvHjrWjOJ17wiMfr1iQ1YDyjlj8qfxG+GDce0khrjNSkzRjVdCOIFsMvfVSBPnOPX5NuZlgMRvMkcPUtGClA== dependencies: "@expo/spawn-async" "^1.7.2" arg "^5.0.2" @@ -3948,34 +3948,10 @@ json5 "^2.2.3" write-file-atomic "^2.3.0" -"@expo/metro-config@0.19.6": - version "0.19.6" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.6.tgz#81bbe70c77a322d6c688738fd7b736a3cbb7c5bd" - integrity sha512-pRwZyOstsQa1+Ecss3wOqC28wjyjq9qxvJaQL3LH4G8Sef9x2PX+ySRApeQ01nl4ZN5nlyez6iVDF51tn/WhOw== - dependencies: - "@babel/core" "^7.20.0" - "@babel/generator" "^7.20.5" - "@babel/parser" "^7.20.0" - "@babel/types" "^7.20.0" - "@expo/config" "~10.0.4" - "@expo/env" "~0.4.0" - "@expo/json-file" "~9.0.0" - "@expo/spawn-async" "^1.7.2" - chalk "^4.1.0" - debug "^4.3.2" - fs-extra "^9.1.0" - getenv "^1.0.0" - glob "^10.4.2" - jsc-safe-url "^0.2.4" - lightningcss "~1.27.0" - minimatch "^3.0.4" - postcss "~8.4.32" - resolve-from "^5.0.0" - -"@expo/metro-config@~0.19.0": - version "0.19.4" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.4.tgz#940b6fad7809a92a8ffdb1bbe87aa805f5822c6b" - integrity sha512-2SWwYN8MZvMIRawWEr+1RBYncitPwu2VMACRYig+wBycJ9fsPb6BMVmBYi+3MHDUlJHNy/Bqfw++jn1eqBFETQ== +"@expo/metro-config@0.19.8", "@expo/metro-config@~0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.8.tgz#f1ea552b6fa5217093fe364ff5ca78a7e261a28b" + integrity sha512-dVAOetouQYuOTEJ2zR0OTLNPOH6zPkeEt5fY53TK0Wxi1QmtsmH6vEWg05U4zkSJ6f1aXmQ0Za77R8QxuukESA== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.5" @@ -4031,17 +4007,17 @@ base64-js "^1.2.3" xmlbuilder "^14.0.0" -"@expo/prebuild-config@8.0.22", "@expo/prebuild-config@^8.0.22": - version "8.0.22" - resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.22.tgz#6e2762a5f333a0519f83ae05b69da45e3e26a913" - integrity sha512-Kwlf3ymHH37W2nuNA9FzYgZvrImJScLA98939kapnOxfNGAPhmhEw26sfIGmBWAa8ymdL6p+HXQ3+b/xJ74bOg== +"@expo/prebuild-config@^8.0.23": + version "8.0.23" + resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.23.tgz#2ec6d5464f35d308bdb94ba75b7e6aba0ebb507d" + integrity sha512-Zf01kFiN2PISmLb0DhIAJh76v3J2oYUKSjiAtGZLOH0HUz59by/qdyU4mGHWdeyRdCCrLUA21Rct2MBykvRMsg== dependencies: "@expo/config" "~10.0.4" "@expo/config-plugins" "~9.0.10" "@expo/config-types" "^52.0.0" "@expo/image-utils" "^0.6.0" "@expo/json-file" "^9.0.0" - "@react-native/normalize-colors" "0.76.3" + "@react-native/normalize-colors" "0.76.5" debug "^4.3.1" fs-extra "^9.0.0" resolve-from "^5.0.0" @@ -5616,7 +5592,7 @@ dependencies: "@react-native/codegen" "0.76.1" -"@react-native/babel-preset@0.76.1", "@react-native/babel-preset@0.76.3": +"@react-native/babel-preset@0.76.1", "@react-native/babel-preset@0.76.3", "@react-native/babel-preset@0.76.5": version "0.76.1" resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.1.tgz#2b9fd113e7c7889c1e87d6a36b7cb0f36118e7a6" integrity sha512-b6YRmA13CmVuTQKHRen/Q0glHwmZFZoEDs+MJ1NL0UNHq9V5ytvdwTW1ntkmjtXuTnPMzkwYvumJBN9UTZjkBA== @@ -5717,6 +5693,11 @@ resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.3.tgz#531e616f6dad159a58117efc69cec20422d15b0d" integrity sha512-pMHQ3NpPB28RxXciSvm2yD+uDx3pkhzfuWkc7VFgOduyzPSIr0zotUiOJzsAtrj8++bPbOsAraCeQhCqoOTWQw== +"@react-native/debugger-frontend@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.5.tgz#0e89940543fb5029506690b83f12547d0bf42cc4" + integrity sha512-5gtsLfBaSoa9WP8ToDb/8NnDBLZjv4sybQQj7rDKytKOdsXm3Pr2y4D7x7GQQtP1ZQRqzU0X0OZrhRz9xNnOqA== + "@react-native/dev-middleware@0.76.3": version "0.76.3" resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.3.tgz#52edc76c88e0c2c436eb989551b827bf69f2a56f" @@ -5734,6 +5715,23 @@ serve-static "^1.13.1" ws "^6.2.3" +"@react-native/dev-middleware@0.76.5": + version "0.76.5" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.5.tgz#10d02fcc6c3c9d24f6dc147c2ef95d6fa6bd3787" + integrity sha512-f8eimsxpkvMgJia7POKoUu9uqjGF6KgkxX4zqr/a6eoR1qdEAWUd6PonSAqtag3PAqvEaJpB99gLH2ZJI1nDGg== + dependencies: + "@isaacs/ttlcache" "^1.4.1" + "@react-native/debugger-frontend" "0.76.5" + chrome-launcher "^0.15.2" + chromium-edge-launcher "^0.2.0" + connect "^3.6.5" + debug "^2.2.0" + nullthrows "^1.1.1" + open "^7.0.3" + selfsigned "^2.4.1" + serve-static "^1.13.1" + ws "^6.2.3" + "@react-native/eslint-config@^0.76.2": version "0.76.2" resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.76.2.tgz#2741eee69ff194b8adc15281c0cb9695ba015ef0" @@ -5778,7 +5776,7 @@ hermes-parser "0.23.1" nullthrows "^1.1.1" -"@react-native/normalize-colors@0.76.1", "@react-native/normalize-colors@0.76.3", "@react-native/normalize-colors@^0.73.0", "@react-native/normalize-colors@^0.74.1": +"@react-native/normalize-colors@0.76.1", "@react-native/normalize-colors@0.76.3", "@react-native/normalize-colors@0.76.5", "@react-native/normalize-colors@^0.73.0", "@react-native/normalize-colors@^0.74.1": version "0.76.1" resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.1.tgz#df8d54d78917a9f075283382fec834f5ccaecefd" integrity sha512-/+CUk/wGWIdXbJYVLw/q6Fs8Z0x91zzfXIbNiZUdSW1TNEDmytkF371H8a1/Nx3nWa1RqCMVsaZHCG4zqxeDvg== @@ -8088,10 +8086,10 @@ babel-preset-expo@^12.0.2: babel-plugin-react-native-web "~0.19.13" react-refresh "^0.14.2" -babel-preset-expo@~12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.3.tgz#2ad62fe007517704841788cfea38b333e307663f" - integrity sha512-1695e8y3U/HjifKx33vcNnFMSUSXwPWwhFxRlL6NRx2TENN6gySH82gPOWgxcra6gi+EJgXx52xG3PcqTjwW6w== +babel-preset-expo@~12.0.4: + version "12.0.4" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.4.tgz#ec965530d866c8905aac1fa478562cb08ab32a55" + integrity sha512-SAzAwqpyjA+/OFrU95OOioj6oTeCv4+rRfrNmBTy5S/gJswrZKBSPJioFudIaJBy43W+BL7HA5AspBIF6tO/aA== dependencies: "@babel/plugin-proposal-decorators" "^7.12.9" "@babel/plugin-transform-export-namespace-from" "^7.22.11" @@ -8099,7 +8097,7 @@ babel-preset-expo@~12.0.3: "@babel/plugin-transform-parameters" "^7.22.15" "@babel/preset-react" "^7.22.15" "@babel/preset-typescript" "^7.23.0" - "@react-native/babel-preset" "0.76.3" + "@react-native/babel-preset" "0.76.5" babel-plugin-react-native-web "~0.19.13" react-refresh "^0.14.2" @@ -10455,10 +10453,10 @@ expo-eas-client@~0.13.0: resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.13.1.tgz#ebca627f3f58a54906394eb3f5d22f41a1822618" integrity sha512-IyeDiM6YSJG0c45kbuEo0qt76z0KTEZtisEFEtle+b+vfn9I3N+r3jbPscaI4yS3P6gpuoDyHv81YDVC6Dmkhw== -expo-file-system@^18.0.4, expo-file-system@~18.0.4: - version "18.0.4" - resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.4.tgz#eecf8dc0b3b545e9ac5cd00352665afe2d57732f" - integrity sha512-aAWEDwnu0XHOBYvQ9Q0+QIa+483vYJaC4IDsXyWQ73Rtsg273NZh5kYowY+cAocvoSmA99G6htrLBn11ax2bTQ== +expo-file-system@^18.0.6, expo-file-system@~18.0.6: + version "18.0.6" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.6.tgz#43f7718530d0e2aa1f49bca7ccb721007acabf2c" + integrity sha512-gGEwIJCXV3/wpIJ/wRyhmieLOSAY7HeFFjb+wEfHs04aE63JYR+rXXV4b7rBpEh1ZgNV9U91zfet/iQG7J8HBQ== dependencies: web-streams-polyfill "^3.3.2" @@ -10541,10 +10539,10 @@ expo-media-library@~17.0.3: resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-17.0.3.tgz#4ee3e6a8a2544887d910a72eaf2a15858b78cc0e" integrity sha512-vo8AqWxv1C8+U8dA5W43qs8+3dgD3VZDvcCkZBQTBnGr/2Rs7x6nNQD5s7UfYyr6qmW6102JB3+OUKHpkwEssg== -expo-modules-autolinking@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.3.tgz#c0de0129bedf1b6f9aa36093e435d00509f27fcd" - integrity sha512-Q/ALJ54eS7Cr7cmbP+unEDTkHFQivQerWWrqZxuXOrSFYGCYU22+/xAZXaJOpZwseOVsP74zSkoRY/wBimVs7w== +expo-modules-autolinking@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.4.tgz#28fcd12fb0d066a2933cca3bf3b597da0f6b2f2a" + integrity sha512-e0p+19NhmD50U7s7BV7kWIypWmTNC9n/VlJKlXS05hM/zX7pe6JKmXyb+BFnXJq3SLBalLCUY0tu2gEUF3XeVg== dependencies: "@expo/spawn-async" "^1.7.2" chalk "^4.1.0" @@ -10555,7 +10553,14 @@ expo-modules-autolinking@2.0.3: require-from-string "^2.0.2" resolve-from "^5.0.0" -expo-modules-core@2.1.1, expo-modules-core@^2.1.1: +expo-modules-core@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.2.tgz#258be4fbd162b69eb4ad2789131ac2dc7e85fc08" + integrity sha512-0OhMU5S8zf9c/CRh1MwiXfOInI9wzz6yiIh5RuR/9J7N6xHRum68hInsPbaSc1UQpo08ZZLM4MPsbpoNRUoqIg== + dependencies: + invariant "^2.2.4" + +expo-modules-core@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.1.tgz#970af4cfd70c8aa6fc0096dd0a6578aa003a479f" integrity sha512-yQzYCLR2mre4BNMXuqkeJ0oSNgmGEMI6BcmIzeNZbC2NFEjiaDpKvlV9bclYCtyVhUEVNbJcEPYMr6c1Y4eR4w== @@ -10598,12 +10603,12 @@ expo-sharing@^13.0.0: resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f" integrity sha512-b23ymicRmYn/Pjj05sl9tFZHN5cH9I1f0yiqY1Yk8Q3oCx0Aznri82DnTYA4T/J6D9vrkraX0wQ4jWVMOffmlg== -expo-splash-screen@~0.29.16: - version "0.29.16" - resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.16.tgz#80b49af2605213e661a40022d724caa1ea48ccb3" - integrity sha512-1WnExDA23hEJhz+djUthVUWxUvVtDT9sqRrpCgU4srG2OfBN0NryJ+Fbnoc1V2xw2uYc4Ij3ru0nH9a1TNvW9w== +expo-splash-screen@~0.29.18: + version "0.29.18" + resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.18.tgz#96ccce3d5a03389a9061743903b0a77c22a16796" + integrity sha512-bTBY+LF6YtYen2j60yGNh2SX/tG4UXZAyBCMMriOSiZZ7LSCs3ARyEufaSiWk+ckWShTeMqItOnaAN/CAF8MJA== dependencies: - "@expo/prebuild-config" "^8.0.22" + "@expo/prebuild-config" "^8.0.23" expo-status-bar@~2.0.0: version "2.0.0" @@ -10660,26 +10665,26 @@ expo-web-browser@~14.0.1: resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.1.tgz#97f3f141b0897364bc8364d90d6e29df0beec8aa" integrity sha512-QM9F3ie+UyIOoBvqFmT6CZojb1vMc2H+7ZlMT5dEu1PL2jtYyOeK2hLfbt/EMt7CBm/w+P29H9W9Y9gdebOkuQ== -expo@~52.0.17: - version "52.0.17" - resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.17.tgz#8a3edc20dabdb69a47f7b4b92e1bb96284044c14" - integrity sha512-f0WBD2T6p9r/a8v8MqkoWQq7TmbbAgPUg2zZtOp+kBrSCb3obHeNAsPDAUFzh+jEgug2qDVVkauBJa6ACe9AMg== +expo@~52.0.19: + version "52.0.19" + resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.19.tgz#1b881c96ea595da0c5f3c13f578bc368c5261f4a" + integrity sha512-wOb/wbiQa0xqQRhgVBuOhLRus05TSw6fgThVMrPQgdLo24EPuT/ZAiRVcVRdjrEbwOqCDumgQCB7636B9J+jKg== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "0.22.3" + "@expo/cli" "0.22.6" "@expo/config" "~10.0.6" "@expo/config-plugins" "~9.0.12" - "@expo/fingerprint" "0.11.3" - "@expo/metro-config" "0.19.6" + "@expo/fingerprint" "0.11.4" + "@expo/metro-config" "0.19.8" "@expo/vector-icons" "^14.0.0" - babel-preset-expo "~12.0.3" + babel-preset-expo "~12.0.4" expo-asset "~11.0.1" expo-constants "~17.0.3" - expo-file-system "~18.0.4" + expo-file-system "~18.0.6" expo-font "~13.0.1" expo-keep-awake "~14.0.1" - expo-modules-autolinking "2.0.3" - expo-modules-core "2.1.1" + expo-modules-autolinking "2.0.4" + expo-modules-core "2.1.2" fbemitter "^3.0.0" web-streams-polyfill "^3.3.2" whatwg-url-without-unicode "8.0.0-3" From c339dc5b05282ba917bed9d02c914453cf38d437 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 16 Dec 2024 22:06:15 +0000 Subject: [PATCH 02/55] disable automaticallAdjustsScrollIndicatorInsets (#7131) --- src/view/com/util/List.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index 31fd0aa1d0..62c91cec66 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -53,6 +53,7 @@ let List = React.forwardRef( headerOffset, style, progressViewOffset, + automaticallyAdjustsScrollIndicatorInsets = false, ...props }, ref, @@ -151,6 +152,9 @@ let List = React.forwardRef( return ( Date: Mon, 16 Dec 2024 22:26:40 +0000 Subject: [PATCH 03/55] Adjust gates (#7132) * Remove dead gate * Adjust gates * No need to disable exposures --- src/lib/constants.ts | 10 ---------- src/lib/statsig/gates.ts | 4 +--- src/screens/Settings/AppIconSettings/index.tsx | 8 ++++---- src/screens/Settings/AppearanceSettings.tsx | 9 ++++----- src/state/queries/post-feed.ts | 5 +---- src/view/com/util/post-ctrls/PostCtrls.tsx | 7 ++++--- 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 5d7b5eb456..279c6f913d 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -23,16 +23,6 @@ export const STARTER_PACK_MAX_SIZE = 150 // -prf export const JOINED_THIS_WEEK = 2880000 // estimate as of 11/26/24 -export const DISCOVER_DEBUG_DIDS: Record = { - 'did:plc:oisofpd7lj26yvgiivf3lxsi': true, // hailey.at - 'did:plc:fpruhuo22xkm5o7ttr2ktxdo': true, // danabra.mov - 'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team - 'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com - 'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team - 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol - 'did:plc:vjug55kidv6sye7ykr5faxxn': true, // emilyliu.me -} - const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new` export function FEEDBACK_FORM_URL({ email, diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 3767ec1e5d..6876f18c53 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,5 +1,3 @@ export type Gate = // Keep this alphabetic please. - | 'debug_show_feedcontext' // DISABLED DUE TO EME - | 'post_feed_lang_window' // DISABLED DUE TO EME - | 'remove_show_latest_button' + 'debug_show_feedcontext' | 'debug_subscriptions' | 'remove_show_latest_button' diff --git a/src/screens/Settings/AppIconSettings/index.tsx b/src/screens/Settings/AppIconSettings/index.tsx index 0fefca29b2..0be2894d52 100644 --- a/src/screens/Settings/AppIconSettings/index.tsx +++ b/src/screens/Settings/AppIconSettings/index.tsx @@ -5,11 +5,11 @@ import {useLingui} from '@lingui/react' import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' +import {IS_INTERNAL} from '#/lib/app-info' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {CommonNavigatorParams} from '#/lib/routes/types' +import {useGate} from '#/lib/statsig/statsig' import {isAndroid} from '#/platform/detection' -import {useSession} from '#/state/session' import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' @@ -23,7 +23,7 @@ export function AppIconSettingsScreen({}: Props) { const t = useTheme() const {_} = useLingui() const sets = useAppIconSets() - const {currentAccount} = useSession() + const gate = useGate() const [currentAppIcon, setCurrentAppIcon] = useState(() => getAppIconName(DynamicAppIcon.getAppIcon()), ) @@ -86,7 +86,7 @@ export function AppIconSettingsScreen({}: Props) { ))} - {DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( + {IS_INTERNAL && gate('debug_subscriptions') && ( <> export function AppearanceSettingsScreen({}: Props) { const {_} = useLingui() const {fonts} = useAlf() + const gate = useGate() const {colorMode, darkTheme} = useThemePrefs() const {setColorMode, setDarkTheme} = useSetThemePrefs() @@ -74,8 +75,6 @@ export function AppearanceSettingsScreen({}: Props) { [fonts], ) - const {currentAccount} = useSession() - return ( @@ -178,7 +177,7 @@ export function AppearanceSettingsScreen({}: Props) { onChange={onChangeFontScale} /> - {isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( + {isNative && IS_INTERNAL && gate('debug_subscriptions') && ( <> diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 696e28f9c2..2eb604627e 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -144,11 +144,8 @@ export function usePostFeedQuery( /** * The number of posts to fetch in a single request. Because we filter * unwanted content, we may over-fetch here to try and fill pages by - * `MIN_POSTS`. + * `MIN_POSTS`. But if you're doing this, ask @why if it's ok first. */ - - // TEMPORARILY DISABLING GATE TO PREVENT EVENT CONSUMPTION @TODO EME-GATE - // const fetchLimit = gate('post_feed_lang_window') ? 100 : MIN_POSTS const fetchLimit = MIN_POSTS // Make sure this doesn't invalidate unless really needed. diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 39caaf0987..607a480ff2 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -18,12 +18,13 @@ import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import {IS_INTERNAL} from '#/lib/app-info' -import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants' +import {POST_CTRL_HITSLOP} from '#/lib/constants' import {CountWheel} from '#/lib/custom-animations/CountWheel' import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon' import {useHaptics} from '#/lib/haptics' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' +import {useGate} from '#/lib/statsig/statsig' import {toShareUrl} from '#/lib/strings/url-helpers' import {Shadow} from '#/state/cache/types' import {useFeedFeedbackContext} from '#/state/feed-feedback' @@ -85,8 +86,8 @@ let PostCtrls = ({ const {sendInteraction} = useFeedFeedbackContext() const {captureAction} = useProgressGuideControls() const playHaptic = useHaptics() - const isDiscoverDebugUser = - IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] + const gate = useGate() + const isDiscoverDebugUser = IS_INTERNAL || gate('debug_show_feedcontext') const isBlocked = Boolean( post.author.viewer?.blocking || post.author.viewer?.blockedBy || From 1a160df3962dab21fd9b4514e266652da1a14a4f Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 17 Dec 2024 00:02:56 +0000 Subject: [PATCH 04/55] add safe area view to profile error screen (#7134) --- src/view/screens/Profile.tsx | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index ebf1d955d8..24e8719e17 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useMemo} from 'react' import {StyleSheet} from 'react-native' +import {SafeAreaView} from 'react-native-safe-area-context' import { AppBskyActorDefs, AppBskyGraphGetActorStarterPacks, @@ -122,13 +123,15 @@ function ProfileScreenInner({route}: Props) { } if (resolveError || profileError) { return ( - + + + ) } if (profile && moderationOpts) { @@ -144,13 +147,15 @@ function ProfileScreenInner({route}: Props) { } // should never happen return ( - + + + ) } From be33c626b03bafd3c4543a3962e87f9422d7d05d Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 17 Dec 2024 00:11:03 +0000 Subject: [PATCH 05/55] alf error screen (#7135) --- src/view/com/util/error/ErrorScreen.tsx | 137 ++++++++++-------------- 1 file changed, 55 insertions(+), 82 deletions(-) diff --git a/src/view/com/util/error/ErrorScreen.tsx b/src/view/com/util/error/ErrorScreen.tsx index 846f4d2951..e9b0e50a3c 100644 --- a/src/view/com/util/error/ErrorScreen.tsx +++ b/src/view/com/util/error/ErrorScreen.tsx @@ -1,4 +1,4 @@ -import {StyleSheet, View} from 'react-native' +import {View} from 'react-native' import { FontAwesomeIcon, FontAwesomeIconStyle, @@ -7,12 +7,11 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {useTheme} from '#/lib/ThemeContext' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {Button} from '../forms/Button' -import {Text} from '../text/Text' -import {CenteredView} from '../Views' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise' +import * as Layout from '#/components/Layout' +import {Text} from '#/components/Typography' export function ErrorScreen({ title, @@ -29,22 +28,32 @@ export function ErrorScreen({ testID?: string showHeader?: boolean }) { - const theme = useTheme() - const {isMobile} = useWebMediaQueries() + const t = useTheme() const pal = usePalette('default') const {_} = useLingui() return ( - <> - {showHeader && isMobile && ( - + + {showHeader && ( + + + + + Error + + + + )} - - + + - + {title} - {message} + {message} {details && ( - - {details} - + + + {details} + + )} {onPressTryAgain && ( - + )} - - + + ) } - -const styles = StyleSheet.create({ - outer: { - flex: 1, - paddingVertical: 30, - paddingHorizontal: 14, - }, - title: { - textAlign: 'center', - marginBottom: 10, - }, - message: { - textAlign: 'center', - marginBottom: 20, - }, - details: { - textAlign: 'center', - paddingVertical: 10, - paddingHorizontal: 14, - overflow: 'hidden', - marginBottom: 20, - }, - btnContainer: { - alignItems: 'center', - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 10, - }, - btnText: { - marginLeft: 5, - }, - errorIconContainer: { - alignItems: 'center', - marginBottom: 10, - }, - errorIcon: { - borderRadius: 25, - width: 50, - height: 50, - alignItems: 'center', - justifyContent: 'center', - }, -}) From c9e429760d426df7549bfcd6292a0dc5e7fe7d7e Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 17 Dec 2024 00:11:21 +0000 Subject: [PATCH 06/55] [ELI5] Tweak wording on the signup screen (#7136) * Tweak signup wordings * Update translations --- src/locale/locales/an/messages.po | 6 +++--- src/locale/locales/ast/messages.po | 6 +++--- src/locale/locales/ca/messages.po | 6 +++--- src/locale/locales/de/messages.po | 6 +++--- src/locale/locales/en-GB/messages.po | 6 +++--- src/locale/locales/en/messages.po | 6 +++--- src/locale/locales/es/messages.po | 6 +++--- src/locale/locales/fi/messages.po | 6 +++--- src/locale/locales/fr/messages.po | 6 +++--- src/locale/locales/ga/messages.po | 6 +++--- src/locale/locales/gl/messages.po | 6 +++--- src/locale/locales/hi/messages.po | 6 +++--- src/locale/locales/hu/messages.po | 6 +++--- src/locale/locales/id/messages.po | 6 +++--- src/locale/locales/it/messages.po | 6 +++--- src/locale/locales/ja/messages.po | 6 +++--- src/locale/locales/ko/messages.po | 6 +++--- src/locale/locales/nl/messages.po | 6 +++--- src/locale/locales/pl/messages.po | 6 +++--- src/locale/locales/pt-BR/messages.po | 6 +++--- src/locale/locales/ru/messages.po | 6 +++--- src/locale/locales/th/messages.po | 6 +++--- src/locale/locales/tr/messages.po | 6 +++--- src/locale/locales/uk/messages.po | 6 +++--- src/locale/locales/vi/messages.po | 6 +++--- src/locale/locales/zh-CN/messages.po | 6 +++--- src/locale/locales/zh-HK/messages.po | 6 +++--- src/locale/locales/zh-TW/messages.po | 6 +++--- src/screens/Signup/StepHandle.tsx | 4 ++-- src/screens/Signup/index.tsx | 2 +- 30 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/locale/locales/an/messages.po b/src/locale/locales/an/messages.po index ab8872e2dd..86e6a20f1e 100644 --- a/src/locale/locales/an/messages.po +++ b/src/locale/locales/an/messages.po @@ -3613,7 +3613,7 @@ msgstr "Escribe la tuya clau" #~ msgstr "Escribe lo tuyo furnidor d'aloch preferiu" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Escribe lo tuyo identificador" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8877,7 +8877,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "La tuya canal de Seguindo ye vueda! Sigue a mas usuarios pa veyer las suyas publicacions aquí." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Lo tuyo identificador completo será" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8921,5 +8921,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Lo tuyo reporte s'ha ninviau a lo servicio de moderación de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Lo tuyo identificador" diff --git a/src/locale/locales/ast/messages.po b/src/locale/locales/ast/messages.po index 73634d0b36..406c8ab8a4 100644 --- a/src/locale/locales/ast/messages.po +++ b/src/locale/locales/ast/messages.po @@ -3509,7 +3509,7 @@ msgstr "" #~ msgstr "" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "¡El feed siguiente ta baleru! Sigui a más usuarios pa ver qué pasó." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "L'identificador completu va ser" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "L'informe unvióse al serviciu de moderación de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "" diff --git a/src/locale/locales/ca/messages.po b/src/locale/locales/ca/messages.po index f4dee376f7..fcaf1fc2eb 100644 --- a/src/locale/locales/ca/messages.po +++ b/src/locale/locales/ca/messages.po @@ -4268,7 +4268,7 @@ msgstr "Introdueix la teva contrasenya" #~ msgstr "Introdueix el teu proveïdor d'allotjament preferit" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Introdueix el teu identificador d'usuari" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -10404,7 +10404,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "El teu canal de seguint està buit! Segueix a més usuaris per a saber què està passant." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "El teu identificador complet serà" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -10458,5 +10458,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "El teu informe s'enviarà al servei de moderació de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "El teu identificador d'usuari" diff --git a/src/locale/locales/de/messages.po b/src/locale/locales/de/messages.po index 1101991e0c..afe5d2f354 100644 --- a/src/locale/locales/de/messages.po +++ b/src/locale/locales/de/messages.po @@ -3742,7 +3742,7 @@ msgstr "Gib dein Passwort ein" #~ msgstr "" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Gib deinen Handle ein" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9663,7 +9663,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Dein Following-Feed ist leer! Folge mehr Benutzern, um auf dem Laufenden zu bleiben." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Dein vollständiger Handle lautet" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9707,5 +9707,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Dein Benutzerhandle" diff --git a/src/locale/locales/en-GB/messages.po b/src/locale/locales/en-GB/messages.po index 5e7badcc26..a61e096e8b 100644 --- a/src/locale/locales/en-GB/messages.po +++ b/src/locale/locales/en-GB/messages.po @@ -3509,7 +3509,7 @@ msgstr "" #~ msgstr "" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "" diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index 97ee908555..05d57b0048 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -3509,7 +3509,7 @@ msgstr "" #~ msgstr "" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "" diff --git a/src/locale/locales/es/messages.po b/src/locale/locales/es/messages.po index ef1159f7eb..c7380350b3 100644 --- a/src/locale/locales/es/messages.po +++ b/src/locale/locales/es/messages.po @@ -3896,7 +3896,7 @@ msgstr "Introduce tu contraseña" #~ msgstr "Introduce tu proveedor de alojamiento preferido" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Introduce tu nombre de usuario" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9518,7 +9518,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "¡Tu feed de Siguiendo esta vacío! Sigue a más usuarios para ver sus posts aquí." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Tu nombre de usuario completo será" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9562,5 +9562,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Tu reporte ha sido enviado al servicio de moderación de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Tu nombre de usuario" diff --git a/src/locale/locales/fi/messages.po b/src/locale/locales/fi/messages.po index 679b3f153c..da2375ca30 100644 --- a/src/locale/locales/fi/messages.po +++ b/src/locale/locales/fi/messages.po @@ -3970,7 +3970,7 @@ msgstr "Syötä salasanasi" #~ msgstr "Syötä haluamasi palveluntarjoaja" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Syötä käyttäjätunnuksesi" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9627,7 +9627,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Seuraamiesi syöte on tyhjä! Seuraa lisää käyttäjiä nähdäksesi, mitä tapahtuu." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Käyttäjätunnuksesi tulee olemaan" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9671,5 +9671,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Käyttäjätunnuksesi" diff --git a/src/locale/locales/fr/messages.po b/src/locale/locales/fr/messages.po index 64f3ac7cc9..c0973e4953 100644 --- a/src/locale/locales/fr/messages.po +++ b/src/locale/locales/fr/messages.po @@ -3501,7 +3501,7 @@ msgstr "Entrez votre mot de passe" #~ msgstr "Entrez votre hébergeur préféré" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Entrez votre pseudo" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8519,7 +8519,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Votre fil d’actu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Votre nom complet sera" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8563,5 +8563,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Votre rapport sera envoyé au Service de Modération de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Votre pseudo" diff --git a/src/locale/locales/ga/messages.po b/src/locale/locales/ga/messages.po index 2e77bfda24..ff34bca6e2 100644 --- a/src/locale/locales/ga/messages.po +++ b/src/locale/locales/ga/messages.po @@ -3508,7 +3508,7 @@ msgstr "Cuir isteach do phasfhocal" #~ msgstr "Cuir isteach an soláthraí óstála is fearr leat" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Cuir isteach do leasainm" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8542,7 +8542,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Tá an fotha de na daoine a leanann tú folamh! Lean tuilleadh úsáideoirí le feiceáil céard atá ar siúl." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Do leasainm iomlán anseo:" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8586,5 +8586,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Seolfar do thuairisc go dtí Seirbhís Modhnóireachta Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Do leasainm" diff --git a/src/locale/locales/gl/messages.po b/src/locale/locales/gl/messages.po index 87730db003..ddb9961ea9 100644 --- a/src/locale/locales/gl/messages.po +++ b/src/locale/locales/gl/messages.po @@ -3896,7 +3896,7 @@ msgstr "Introduce o teu contrasinal" #~ msgstr "Introduce o teu proveedor de aloxamento preferido" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Introduce o teu alcume" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9523,7 +9523,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "A seguinte canle está baleira! Sigue a máis persoas para ver o que está a acontecer." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "O teu alcume completo será" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9567,5 +9567,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "O teu informe enviarase ao Servizo de moderación de Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "O teu alcume" diff --git a/src/locale/locales/hi/messages.po b/src/locale/locales/hi/messages.po index 358afb5b5a..2396970553 100644 --- a/src/locale/locales/hi/messages.po +++ b/src/locale/locales/hi/messages.po @@ -3780,7 +3780,7 @@ msgstr "अपना पासवर्ड दर्ज करें" #~ msgstr "अपना पसंदीदा होस्टिंग प्रदाता दर्ज करें" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "अपना उपयोगकर्ता हैंडल दर्ज करें" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9026,7 +9026,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "आपका फ़ॉलोइंग फ़ीड खाली है! क्या चल रहा है जानने के लिए और उपयोगकर्ताओं को फ़ॉलो करें।" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "आपका पूरा हैंडल होगा" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9070,5 +9070,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "आपके शिकायत को Bluesky मॉडरेशन सेवा को भेजा जाएगा।" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "आपका उपयोगकर्ता हैंडल" diff --git a/src/locale/locales/hu/messages.po b/src/locale/locales/hu/messages.po index 45fcf69bb3..21f2c0ad56 100644 --- a/src/locale/locales/hu/messages.po +++ b/src/locale/locales/hu/messages.po @@ -3465,7 +3465,7 @@ msgstr "Jelszó megadása" #~ msgstr "Kívánt tárhelyszolgáltató megadása" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Felhasználónév megadása" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8439,7 +8439,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "A Követett hírfolyamod üres. Kövess több felhasználót, hogy lásd, mi történik!" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "A teljes felhasználóneved:" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8482,5 +8482,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Ez a jelentés a Bluesky moderálási szolgáltatásának lesz elküldve" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Saját felhasználónév" diff --git a/src/locale/locales/id/messages.po b/src/locale/locales/id/messages.po index a064bb3a3f..69beaff7c1 100644 --- a/src/locale/locales/id/messages.po +++ b/src/locale/locales/id/messages.po @@ -4007,7 +4007,7 @@ msgstr "Masukkan kata sandi Anda" #~ msgstr "Masukkan penyedia hosting pilihan Anda" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Masukkan panggilan Anda" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9678,7 +9678,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Feed mengikuti Anda kosong! Ikuti lebih banyak pengguna untuk melihat apa yang terjadi." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Panggilan lengkap Anda akan menjadi" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9722,5 +9722,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Laporan Anda akan dikirim ke Layanan Moderasi Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Panggilan Anda" diff --git a/src/locale/locales/it/messages.po b/src/locale/locales/it/messages.po index a8c3122a13..ef715f3298 100644 --- a/src/locale/locales/it/messages.po +++ b/src/locale/locales/it/messages.po @@ -3318,7 +3318,7 @@ msgid "Input your password" msgstr "Inserisci la tua password" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Inserisci il tuo nome utente" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8016,7 +8016,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Il tuo feed seguente è vuoto! Segui più utenti per vedere cosa sta succedendo." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Il tuo nome utente completo sarà" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8056,5 +8056,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "La tua segnalazione verrà inviata al Servizio Moderazione di Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Il tuo nome utente" diff --git a/src/locale/locales/ja/messages.po b/src/locale/locales/ja/messages.po index 1652fe5742..c8b9721d6c 100644 --- a/src/locale/locales/ja/messages.po +++ b/src/locale/locales/ja/messages.po @@ -3317,7 +3317,7 @@ msgid "Input your password" msgstr "あなたのパスワードを入力" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "あなたのユーザーハンドルを入力" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8047,7 +8047,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Followingフィードは空です!もっと多くのユーザーをフォローして、近況を確認しましょう。" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "フルハンドルは" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8087,5 +8087,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "あなたの報告はBluesky Moderation Serviceに送られます" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "あなたのユーザーハンドル" diff --git a/src/locale/locales/ko/messages.po b/src/locale/locales/ko/messages.po index ff932b4297..fe0b852b18 100644 --- a/src/locale/locales/ko/messages.po +++ b/src/locale/locales/ko/messages.po @@ -3317,7 +3317,7 @@ msgid "Input your password" msgstr "비밀번호를 입력합니다" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "사용자 핸들을 입력합니다" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8019,7 +8019,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "팔로우 중 피드가 비어 있습니다. 더 많은 사용자를 팔로우하여 무슨 일이 일어나고 있는지 확인하세요." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "내 전체 핸들:" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8059,5 +8059,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "신고가 Bluesky Moderation Service로 보내집니다." #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "내 사용자 핸들" diff --git a/src/locale/locales/nl/messages.po b/src/locale/locales/nl/messages.po index b33050a355..217db6a6a8 100644 --- a/src/locale/locales/nl/messages.po +++ b/src/locale/locales/nl/messages.po @@ -3496,7 +3496,7 @@ msgstr "Vul je wachtwoord in" #~ msgstr "Vul je voorkeurshostingprovider in" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Vul je gebruikershandle in." #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8506,7 +8506,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Je volgende feed is leeg! Volg meer gebruikers om te zien wat er gebeurt." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Je volledige handle wordt" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8550,5 +8550,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Je melding wordt verzonden naar de Bluesky-moderatieservice" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Je gebruikershandle" diff --git a/src/locale/locales/pl/messages.po b/src/locale/locales/pl/messages.po index 06886f883d..d8c9c0098b 100644 --- a/src/locale/locales/pl/messages.po +++ b/src/locale/locales/pl/messages.po @@ -3317,7 +3317,7 @@ msgid "Input your password" msgstr "Wpisz hasło" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Wpisz nazwę użytkownika" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8031,7 +8031,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Twój kanał osób obserwowanych jest pusty! Zaobserwuj trochę więcej osób." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Twoją pełna nazwa będzie" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8071,5 +8071,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Zgłoszenie będzie wysłane do usługi moderacji Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Twoja nazwa użytkownika" diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po index e5d8cf4600..9297c95741 100644 --- a/src/locale/locales/pt-BR/messages.po +++ b/src/locale/locales/pt-BR/messages.po @@ -4003,7 +4003,7 @@ msgstr "Insira sua senha" #~ msgstr "Insira seu provedor de hospedagem" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Insira o usuário" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9667,7 +9667,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Seu feed inicial está vazio! Siga mais usuários para acompanhar o que está acontecendo." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Seu identificador completo será" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9711,5 +9711,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Sua denúncia será enviada para o serviço de moderação do Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Seu identificador de usuário" diff --git a/src/locale/locales/ru/messages.po b/src/locale/locales/ru/messages.po index 6a6755c6a6..4ec66227bb 100644 --- a/src/locale/locales/ru/messages.po +++ b/src/locale/locales/ru/messages.po @@ -3578,7 +3578,7 @@ msgstr "Введите ваш пароль" #~ msgstr "Введите желаемого хостинг-провайдера" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Введите ваш псевдоним" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8768,7 +8768,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Ваша домашняя лента пуста! Подпишитесь на больше пользователей чтобы получать больше постов." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Ваш полный псевдоним будет" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8812,5 +8812,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Ваш отчет будет отправлен в Службу Модерации Bluesky." #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Ваш псевдоним" diff --git a/src/locale/locales/th/messages.po b/src/locale/locales/th/messages.po index 61014c89cf..7eb3cd8a8a 100644 --- a/src/locale/locales/th/messages.po +++ b/src/locale/locales/th/messages.po @@ -3994,7 +3994,7 @@ msgstr "ป้อนรหัสผ่านของคุณ" #~ msgstr "กรอกผู้ให้บริการโฮสติ้งที่คุณต้องการ" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "กรอกชื่อผู้ใช้ของคุณ" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9665,7 +9665,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "ฟีดผู้ติดตามของคุณว่างเปล่า! ติดตามผู้ใช้เพิ่มเติมเพื่อดูความเคลื่อนไหว" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "ชื่อผู้ใช้เต็มของคุณจะเป็น" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9709,5 +9709,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "รายงานของคุณจะถูกส่งไปยัง Bluesky Moderation Service" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "แฮนด์เดิลผู้ใช้ของคุณ" diff --git a/src/locale/locales/tr/messages.po b/src/locale/locales/tr/messages.po index d8fc0c23f3..f990961f74 100644 --- a/src/locale/locales/tr/messages.po +++ b/src/locale/locales/tr/messages.po @@ -4211,7 +4211,7 @@ msgstr "Şifrenizi girin" #~ msgstr "" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Kullanıcı adınızı girin" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -10212,7 +10212,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Takip ettiğiniz besleme boş! Neler olduğunu görmek için daha fazla kullanıcı takip edin." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Tam kullanıcı adınız" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -10261,5 +10261,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Kullanıcı adınız" diff --git a/src/locale/locales/uk/messages.po b/src/locale/locales/uk/messages.po index 97685ea93b..a6141a7f90 100644 --- a/src/locale/locales/uk/messages.po +++ b/src/locale/locales/uk/messages.po @@ -4007,7 +4007,7 @@ msgstr "Введіть ваш пароль" #~ msgstr "Введіть бажаного хостинг-провайдера" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Введіть ваш псевдонім" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -9678,7 +9678,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Ваша домашня стрічка порожня! Підпишіться на більше користувачів щоб отримувати більше постів." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Ваш повний псевдонім буде" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -9722,5 +9722,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Ваш псевдонім" diff --git a/src/locale/locales/vi/messages.po b/src/locale/locales/vi/messages.po index ce99522bcb..dbb00bf5e7 100644 --- a/src/locale/locales/vi/messages.po +++ b/src/locale/locales/vi/messages.po @@ -3509,7 +3509,7 @@ msgstr "Nhập mật khẩu của bạn" #~ msgstr "Nhập nhà cung cấp lưu trữ theo lựa chọn của bạn" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "Nhập tên người dùng của bạn" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "Bảng tin theo dõi còn trống! Hãy theo dõi thêm người dùng để biết có gì đang diễn ra." #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "Tên người dùng đầy đủ của bạn sẽ là" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "Báo cáo của bạn sẽ được gởi đến dịch vụ kiểm duyệt của Bluesky" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "Tên người dùng của bạn" diff --git a/src/locale/locales/zh-CN/messages.po b/src/locale/locales/zh-CN/messages.po index f3d392c398..dfdd5102aa 100644 --- a/src/locale/locales/zh-CN/messages.po +++ b/src/locale/locales/zh-CN/messages.po @@ -3314,7 +3314,7 @@ msgid "Input your password" msgstr "输入你的密码" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "输入你的账户代码" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "你的“Following”动态源是空的!关注更多用户去看看他们发了什么。" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "你的完整账户代码将修改为" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "你的举报将发送至 Bluesky 内容审核服务" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "你的账户代码" diff --git a/src/locale/locales/zh-HK/messages.po b/src/locale/locales/zh-HK/messages.po index a5fccf312d..368d4f3115 100644 --- a/src/locale/locales/zh-HK/messages.po +++ b/src/locale/locales/zh-HK/messages.po @@ -3314,7 +3314,7 @@ msgid "Input your password" msgstr "輸入你嘅密碼" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "輸入你嘅帳號頭銜" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "你嘅「Following」動態源得個吉!跟多啲用戶睇吓發生緊啲咩事。" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "你嘅完整帳號頭銜會係" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "你嘅上報會傳送去 Bluesky 審核服務" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "你嘅帳號頭銜" diff --git a/src/locale/locales/zh-TW/messages.po b/src/locale/locales/zh-TW/messages.po index 64f56c054c..00790512f7 100644 --- a/src/locale/locales/zh-TW/messages.po +++ b/src/locale/locales/zh-TW/messages.po @@ -3314,7 +3314,7 @@ msgid "Input your password" msgstr "輸入您的密碼" #: src/screens/Signup/StepHandle.tsx:114 -msgid "Input your user handle" +msgid "Type your desired username" msgstr "輸入您的帳號代碼" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 @@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening." msgstr "您的「Following」動態源是空的!跟隨更多用戶來看看發生了什麼事情。" #: src/screens/Signup/StepHandle.tsx:125 -msgid "Your full handle will be" +msgid "Your full username will be" msgstr "您的完整帳號代碼將修改為" #: src/screens/Settings/components/ChangeHandleDialog.tsx:219 @@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service" msgstr "您的檢舉將傳送至 Bluesky 內容管理服務" #: src/screens/Signup/index.tsx:142 -msgid "Your user handle" +msgid "Choose your username" msgstr "您的帳號代碼" diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx index 0ff0506f4e..dee7df8488 100644 --- a/src/screens/Signup/StepHandle.tsx +++ b/src/screens/Signup/StepHandle.tsx @@ -111,7 +111,7 @@ export function StepHandle() { handleValueRef.current = val setDraftValue(val) }} - label={_(msg`Input your user handle`)} + label={_(msg`Type your desired username`)} defaultValue={draftValue} autoCapitalize="none" autoCorrect={false} @@ -122,7 +122,7 @@ export function StepHandle() { {draftValue !== '' && ( - Your full handle will be{' '} + Your full username will be{' '} @{createFullHandle(draftValue, state.userDomain)} diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index 1857981a0f..5f406eb7ae 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -139,7 +139,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { {state.activeStep === SignupStep.INFO ? ( Your account ) : state.activeStep === SignupStep.HANDLE ? ( - Your user handle + Choose your username ) : ( Complete the challenge )} From 20d4266e2f8ef82b6ed1244fd929dd07c01486dc Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 17 Dec 2024 03:13:51 +0000 Subject: [PATCH 07/55] Read storage on window.onstorage (#7137) --- src/state/persisted/index.web.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/state/persisted/index.web.ts b/src/state/persisted/index.web.ts index 4cfc87cdb1..f28b197715 100644 --- a/src/state/persisted/index.web.ts +++ b/src/state/persisted/index.web.ts @@ -24,6 +24,7 @@ const _emitter = new EventEmitter() export async function init() { broadcast.onmessage = onBroadcastMessage + window.onstorage = onStorage const stored = readFromStorage() if (stored) { _state = stored @@ -90,6 +91,17 @@ export async function clearStorage() { } clearStorage satisfies PersistedApi['clearStorage'] +function onStorage() { + const next = readFromStorage() + if (next === _state) { + return + } + if (next) { + _state = next + _emitter.emit('update') + } +} + async function onBroadcastMessage({data}: MessageEvent) { if ( typeof data === 'object' && From 31d2aa6716007871d50892be8b866085309ae92c Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 17 Dec 2024 11:09:06 +0000 Subject: [PATCH 08/55] Refetch empty feed on focus (#7139) --- src/view/com/posts/PostFeed.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 6746703379..55d7ba053f 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -187,12 +187,16 @@ let PostFeed = ({ } try { if (await pollLatest(data.pages[0])) { - onHasNew(true) + if (isEmpty) { + refetch() + } else { + onHasNew(true) + } } } catch (e) { logger.error('Poll latest failed', {feed, message: String(e)}) } - }, [feed, data, isFetching, onHasNew, enabled, disablePoll]) + }, [feed, data, isFetching, isEmpty, onHasNew, enabled, disablePoll, refetch]) const myDid = currentAccount?.did || '' const onPostCreated = React.useCallback(() => { @@ -220,20 +224,15 @@ let PostFeed = ({ React.useEffect(() => { if (enabled && !disablePoll) { const timeSinceFirstLoad = Date.now() - lastFetchRef.current - // DISABLED need to check if this is causing random feed refreshes -prf - /*if (timeSinceFirstLoad > REFRESH_AFTER) { - // do a full refresh - scrollElRef?.current?.scrollToOffset({offset: 0, animated: false}) - queryClient.resetQueries({queryKey: RQKEY(feed)}) - } else*/ if ( - timeSinceFirstLoad > CHECK_LATEST_AFTER && + if ( + (isEmpty || timeSinceFirstLoad > CHECK_LATEST_AFTER) && checkForNewRef.current ) { // check for new on enable (aka on focus) checkForNewRef.current() } } - }, [enabled, disablePoll, feed, queryClient, scrollElRef]) + }, [enabled, disablePoll, feed, queryClient, scrollElRef, isEmpty]) React.useEffect(() => { let cleanup1: () => void | undefined, cleanup2: () => void | undefined const subscription = AppState.addEventListener('change', nextAppState => { From 07b7250682c2841e9428c509707d08c17a4317df Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 17 Dec 2024 11:22:49 +0000 Subject: [PATCH 09/55] Fix notifications borders (#7140) --- src/view/com/notifications/NotificationFeed.tsx | 2 +- src/view/com/pager/TabBar.web.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/view/com/notifications/NotificationFeed.tsx b/src/view/com/notifications/NotificationFeed.tsx index 0b814e68dc..5fa40b30b5 100644 --- a/src/view/com/notifications/NotificationFeed.tsx +++ b/src/view/com/notifications/NotificationFeed.tsx @@ -134,7 +134,7 @@ export function NotificationFeed({ highlightUnread={filter === 'all'} item={item} moderationOpts={moderationOpts!} - hideTopBorder={index === 0 && item.notification.isRead} + hideTopBorder={index === 0} /> ) }, diff --git a/src/view/com/pager/TabBar.web.tsx b/src/view/com/pager/TabBar.web.tsx index cabd955f02..d44b7b60c4 100644 --- a/src/view/com/pager/TabBar.web.tsx +++ b/src/view/com/pager/TabBar.web.tsx @@ -145,7 +145,7 @@ export function TabBar({ const desktopStyles = StyleSheet.create({ outer: { flexDirection: 'row', - width: 598, + width: 600, }, contentContainer: { flexGrow: 1, From 32611391a35cbfe3f4a57882c117d52de022fb89 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 17 Dec 2024 12:22:09 +0000 Subject: [PATCH 10/55] Pipe statsig events to logger (#7141) * Pipe statsig events to logger * Log rich objects to bitdrift * Fix tests * Consolidate mocks, fix tests * Reduce log trash on native --- jest/jestSetup.js | 16 +++++++++++++ src/lib/bitdrift.ts | 1 + src/lib/bitdrift.web.ts | 4 ++++ src/lib/statsig/statsig.tsx | 26 ++++++++++++++++++--- src/logger/bitdriftTransport.ts | 5 ++-- src/logger/bitdriftTransport.web.ts | 7 ------ src/state/session/__tests__/session-test.ts | 13 ----------- 7 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 src/lib/bitdrift.web.ts delete mode 100644 src/logger/bitdriftTransport.web.ts diff --git a/jest/jestSetup.js b/jest/jestSetup.js index 0ed7201180..c3160df3bc 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -105,3 +105,19 @@ 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', () => ({})) diff --git a/src/lib/bitdrift.ts b/src/lib/bitdrift.ts index 02d074e76c..3f892f6b8c 100644 --- a/src/lib/bitdrift.ts +++ b/src/lib/bitdrift.ts @@ -1,5 +1,6 @@ import {init} from '@bitdrift/react-native' import {Statsig} from 'statsig-react-native-expo' +export {debug, error, info, warn} from '@bitdrift/react-native' import {initPromise} from './statsig/statsig' diff --git a/src/lib/bitdrift.web.ts b/src/lib/bitdrift.web.ts new file mode 100644 index 0000000000..5db69450fa --- /dev/null +++ b/src/lib/bitdrift.web.ts @@ -0,0 +1,4 @@ +export function debug() {} +export function error() {} +export function info() {} +export function warn() {} diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 21fa4bb57c..e0882806d5 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -5,6 +5,7 @@ import {sha256} from 'js-sha256' import {Statsig, StatsigProvider} from 'statsig-react-native-expo' import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info' +import * as bitdrift from '#/lib/bitdrift' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' @@ -97,19 +98,38 @@ export function logEvent( rawMetadata: LogEvents[E] & FlatJSONRecord, ) { try { - const fullMetadata = { - ...rawMetadata, - } as Record // Statsig typings are unnecessarily strict here. + const fullMetadata = toStringRecord(rawMetadata) 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() + 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( + metadata: LogEvents[E] & FlatJSONRecord, +): Record { + const record: Record = {} + 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. diff --git a/src/logger/bitdriftTransport.ts b/src/logger/bitdriftTransport.ts index c2235e0d4b..159b863004 100644 --- a/src/logger/bitdriftTransport.ts +++ b/src/logger/bitdriftTransport.ts @@ -3,8 +3,7 @@ import { error as bdError, info as bdInfo, warn as bdWarn, -} from '@bitdrift/react-native' - +} from '../lib/bitdrift' import {LogLevel, Transport} from './types' export function createBitdriftTransport(): Transport { @@ -18,6 +17,6 @@ export function createBitdriftTransport(): Transport { return (level, message) => { const log = logFunctions[level] - log(message.toString()) + log('' + message) } } diff --git a/src/logger/bitdriftTransport.web.ts b/src/logger/bitdriftTransport.web.ts deleted file mode 100644 index ecea3f6f3b..0000000000 --- a/src/logger/bitdriftTransport.web.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {Transport} from './index' - -export function createBitdriftTransport(): Transport { - return (_level, _message) => { - // noop - } -} diff --git a/src/state/session/__tests__/session-test.ts b/src/state/session/__tests__/session-test.ts index 7a5ddfa974..dec8ec48bd 100644 --- a/src/state/session/__tests__/session-test.ts +++ b/src/state/session/__tests__/session-test.ts @@ -4,25 +4,12 @@ import {describe, expect, it, jest} from '@jest/globals' import {agentToSessionAccountOrThrow} from '../agent' import {Action, getInitialState, reducer, State} from '../reducer' -jest.mock('statsig-react-native-expo', () => ({ - Statsig: { - initialize() {}, - initializeCalled() { - return false - }, - }, -})) - jest.mock('jwt-decode', () => ({ jwtDecode(_token: string) { return {} }, })) -jest.mock('expo-localization', () => ({ - getLocales: () => [], -})) - describe('session', () => { it('can log in and out', () => { let state = getInitialState([]) From 0cbb03cd14c226bcbfd146a586d97c62a0fc4c9d Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 17 Dec 2024 17:13:18 +0000 Subject: [PATCH 11/55] New progress guide - 10 follows (#7128) * new follow-10 progress guide * find follows dialog * wip tabs * flatlist version with search * hardcode out jake gold * lazy load followup suggestions * Update src/components/ProgressGuide/FollowDialog.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * comment out replacing, enable paging * rm autofocus * find shadow profiles in paginated search * clear search when press tabs * better tab a11y * fix label * adjust scroll indicator insets * do the same scroll indicator adjustment for searchable people list * hardcode jake to just be 'tech' * Retain state on close/reopen * only change follow btn color when not followed * add guide to inside dialog * fix task alignment * Enable contextual suggestions * WIP: show multiple suggestions * Rework so it animates well * Show more items * remove card style * move tabs to own component * split out header top * scroll active tab into view * rm log * Improve perf a bit * boost popular interests over alphabetical ones * scroll active tab into view * revert back to round buttons * Fix overrenders of the tab bar items * Fix unintended animation * Scroll initial into view if needed * Unlift state, the dialog thing breaks lifting * Persist simply * Fix empty state * Fix incorrect gate exposure * Fix another bad useGate * Nit --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Dan Abramov --- src/alf/atoms.ts | 12 + src/components/FeedInterstitials.tsx | 2 +- src/components/ProfileCard.tsx | 7 +- src/components/ProgressGuide/FollowDialog.tsx | 829 ++++++++++++++++++ src/components/ProgressGuide/List.tsx | 44 +- src/components/ProgressGuide/Task.tsx | 14 +- .../dms/dialogs/SearchablePeopleList.tsx | 3 + src/lib/statsig/events.ts | 2 + src/lib/statsig/gates.ts | 5 +- src/screens/Onboarding/StepFinished.tsx | 8 +- src/screens/Onboarding/state.ts | 13 + src/state/queries/actor-search.ts | 26 +- src/state/queries/suggested-follows.ts | 9 +- src/state/shell/progress-guide.tsx | 96 +- src/view/com/posts/PostFeed.tsx | 6 +- src/view/com/util/List.tsx | 6 +- src/view/com/util/Toast.tsx | 4 +- 17 files changed, 1039 insertions(+), 47 deletions(-) create mode 100644 src/components/ProgressGuide/FollowDialog.tsx diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index ad4929ec85..df2b29d8ac 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -302,6 +302,18 @@ 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, }, diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 85ed58280d..ec224eeae0 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -280,8 +280,8 @@ export function ProfileGrid({ profile={profile} moderationOpts={moderationOpts} logContext="FeedInterstitial" - color="secondary_inverted" shape="round" + colorInverted /> diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 668bd0f3c7..7bec14b9cc 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -285,6 +285,7 @@ export type FollowButtonProps = { moderationOpts: ModerationOpts logContext: LogEvents['profile:follow']['logContext'] & LogEvents['profile:unfollow']['logContext'] + colorInverted?: boolean } & Partial export function FollowButton(props: FollowButtonProps) { @@ -297,6 +298,8 @@ export function FollowButtonInner({ profile: profileUnshadowed, moderationOpts, logContext, + onPress: onPressProp, + colorInverted, ...rest }: FollowButtonProps) { const {_} = useLingui() @@ -321,6 +324,7 @@ export function FollowButtonInner({ )}`, ), ) + onPressProp?.(e) } catch (err: any) { if (err?.name !== 'AbortError') { Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') @@ -341,6 +345,7 @@ export function FollowButtonInner({ )}`, ), ) + onPressProp?.(e) } catch (err: any) { if (err?.name !== 'AbortError') { Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') @@ -387,7 +392,7 @@ export function FollowButtonInner({ label={followLabel} size="small" variant="solid" - color="primary" + color={colorInverted ? 'secondary_inverted' : 'primary'} {...rest} onPress={onPressFollow}> diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx new file mode 100644 index 0000000000..6ac3200df1 --- /dev/null +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -0,0 +1,829 @@ +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 {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 ( + <> + + + + + + + ) +} + +// 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(null) + const inputRef = useRef(null) + const [headerHeight, setHeaderHeight] = useState(0) + const {currentAccount} = useSession() + const [suggestedAccounts, setSuggestedAccounts] = useState< + Map + >(() => 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() + + 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 ( + + ) + } + case 'placeholder': { + return + } + case 'empty': { + return + } + 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 = ( +
+ ) + + 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 ( + 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={ + + } + /> + ) +} + +let Header = ({ + guide, + inputRef, + listRef, + searchText, + onSelectTab, + setHeaderHeight, + setSearchText, + interests, + selectedInterest, + interestsDisplayNames, +}: { + guide: Follow10ProgressGuide + inputRef: React.RefObject + listRef: React.RefObject + onSelectTab: (v: string) => void + searchText: string + setHeaderHeight: (v: number) => void + setSearchText: (v: string) => void + interests: string[] + selectedInterest: string + interestsDisplayNames: Record +}): React.ReactNode => { + const t = useTheme() + const control = Dialog.useDialogContext() + return ( + 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, + ]}> + + + + { + setSearchText(text) + listRef.current?.scrollToOffset({offset: 0, animated: false}) + }} + onEscape={control.close} + /> + + + + ) +} +Header = memo(Header) + +function HeaderTop({guide}: {guide: Follow10ProgressGuide}) { + const {_} = useLingui() + const t = useTheme() + const control = Dialog.useDialogContext() + return ( + + + Find people to follow + + + + + {isWeb ? ( + + ) : null} + + ) +} + +let Tabs = ({ + onSelectTab, + interests, + selectedInterest, + hasSearchText, + interestsDisplayNames, +}: { + onSelectTab: (tab: string) => void + interests: string[] + selectedInterest: string + hasSearchText: boolean + interestsDisplayNames: Record +}): React.ReactNode => { + const listRef = useRef(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 ( + 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 ( + + ) + })} + + ) +} +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 ( + + onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width) + }> + + + ) +} +Tab = memo(Tab) + +let FollowProfileCard = ({ + profile, + moderationOpts, + isSuggestion, + setSuggestedAccounts, + noBorder, +}: { + profile: AppBskyActorDefs.ProfileView + moderationOpts: ModerationOpts + isSuggestion: boolean + setSuggestedAccounts: ( + updater: ( + v: Map, + ) => Map, + ) => 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 ( + + + setHasFollowed(true)} + noBorder={noBorder} + /> + + + ) +} +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 ( + control.close()}> + {({hovered, pressed}) => ( + + + + + + + + + + + )} + + ) +} + +function CardOuter({ + children, + style, +}: {children: React.ReactNode | React.ReactNode[]} & ViewStyleProp) { + const t = useTheme() + return ( + + {children} + + ) +} + +function SearchInput({ + onChangeText, + onEscape, + inputRef, + defaultValue, +}: { + onChangeText: (text: string) => void + onEscape: () => void + inputRef: React.RefObject + 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 ( + + + + { + if (nativeEvent.key === 'Escape') { + onEscape() + } + }} + autoCorrect={false} + autoComplete="off" + autoCapitalize="none" + accessibilityLabel={_(msg`Search profiles`)} + accessibilityHint={_(msg`Search profiles`)} + /> + + ) +} + +function ProfileCardSkeleton() { + const t = useTheme() + + return ( + + + + + + + + + ) +} + +function Empty({message}: {message: string}) { + const t = useTheme() + return ( + + + {message} + + + (╯°□°)╯︵ ┻━┻ + + ) +} + +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 + } +} diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 299d1e69fc..bbc5a0177f 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -10,12 +10,15 @@ 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}) { const t = useTheme() const {_} = useLingui() - const guide = useProgressGuide('like-10-and-follow-7') + const followProgressGuide = useProgressGuide('follow-10') + const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7') + const guide = followProgressGuide || followAndLikeProgressGuide const {endProgressGuide} = useProgressGuideControls() if (guide) { @@ -41,18 +44,33 @@ export function ProgressGuideList({style}: {style?: StyleProp}) { - - + {guide.guide === 'follow-10' && ( + <> + + + + )} + {guide.guide === 'like-10-and-follow-7' && ( + <> + + + + )} ) } diff --git a/src/components/ProgressGuide/Task.tsx b/src/components/ProgressGuide/Task.tsx index 973ee1ac7f..b9ba3fd9ab 100644 --- a/src/components/ProgressGuide/Task.tsx +++ b/src/components/ProgressGuide/Task.tsx @@ -10,11 +10,13 @@ export function ProgressGuideTask({ total, title, subtitle, + tabularNumsTitle, }: { current: number total: number title: string subtitle?: string + tabularNumsTitle?: boolean }) { const t = useTheme() @@ -33,8 +35,16 @@ export function ProgressGuideTask({ /> )} - - {title} + + + {title} + {subtitle && ( diff --git a/src/components/dms/dialogs/SearchablePeopleList.tsx b/src/components/dms/dialogs/SearchablePeopleList.tsx index 0946d2a27c..50090cbcbb 100644 --- a/src/components/dms/dialogs/SearchablePeopleList.tsx +++ b/src/components/dms/dialogs/SearchablePeopleList.tsx @@ -63,6 +63,7 @@ export function SearchablePeopleList({ const {_} = useLingui() const moderationOpts = useModerationOpts() const control = Dialog.useDialogContext() + const [headerHeight, setHeaderHeight] = useState(0) const listRef = useRef(null) const {currentAccount} = useSession() const inputRef = useRef(null) @@ -237,6 +238,7 @@ export function SearchablePeopleList({ const listHeader = useMemo(() => { return ( setHeaderHeight(evt.nativeEvent.layout.height)} style={[ a.relative, web(a.pt_lg), @@ -315,6 +317,7 @@ export function SearchablePeopleList({ ]} webInnerContentContainerStyle={a.py_0} webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]} + scrollIndicatorInsets={{top: headerHeight}} keyboardDismissMode="on-drag" /> ) diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index e6c9c5d135..f1dfb0a947 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -162,6 +162,7 @@ export type LogEvents = { | 'StarterPackProfilesList' | 'FeedInterstitial' | 'ProfileHeaderSuggestedFollows' + | 'PostOnboardingFindFollows' } 'profile:unfollow': { logContext: @@ -177,6 +178,7 @@ export type LogEvents = { | 'StarterPackProfilesList' | 'FeedInterstitial' | 'ProfileHeaderSuggestedFollows' + | 'PostOnboardingFindFollows' } 'chat:create': { logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 6876f18c53..a6c2492548 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,3 +1,6 @@ export type Gate = // Keep this alphabetic please. - 'debug_show_feedcontext' | 'debug_subscriptions' | 'remove_show_latest_button' + | 'debug_show_feedcontext' + | 'debug_subscriptions' + | 'new_postonboarding' + | 'remove_show_latest_button' diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index 0d8971b6f6..fc0ea6a247 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -14,7 +14,7 @@ import { TIMELINE_SAVED_FEED, } from '#/lib/constants' import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' -import {logEvent} from '#/lib/statsig/statsig' +import {logEvent, useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' import {getAllListMembers} from '#/state/queries/list-members' @@ -57,6 +57,7 @@ export function StepFinished() { const setActiveStarterPack = useSetActiveStarterPack() const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() const {startProgressGuide} = useProgressGuideControls() + const gate = useGate() const finishOnboarding = React.useCallback(async () => { setSaving(true) @@ -190,7 +191,9 @@ export function StepFinished() { setSaving(false) setActiveStarterPack(undefined) setHasCheckedForStarterPack(true) - startProgressGuide('like-10-and-follow-7') + startProgressGuide( + gate('new_postonboarding') ? 'follow-10' : 'like-10-and-follow-7', + ) dispatch({type: 'finish'}) onboardDispatch({type: 'finish'}) logEvent('onboarding:finished:nextPressed', { @@ -221,6 +224,7 @@ export function StepFinished() { setActiveStarterPack, setHasCheckedForStarterPack, startProgressGuide, + gate, ]) return ( diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts index 70fa696408..20d3ef2170 100644 --- a/src/screens/Onboarding/state.ts +++ b/src/screens/Onboarding/state.ts @@ -72,6 +72,19 @@ export type ApiResponseMap = { } } +// most popular selected interests +export const popularInterests = [ + 'art', + 'gaming', + 'sports', + 'comics', + 'music', + 'politics', + 'photography', + 'science', + 'news', +] + export function useInterestsDisplayNames() { const {_} = useLingui() diff --git a/src/state/queries/actor-search.ts b/src/state/queries/actor-search.ts index 479fc1a9f0..6d6c46e040 100644 --- a/src/state/queries/actor-search.ts +++ b/src/state/queries/actor-search.ts @@ -1,6 +1,7 @@ import {AppBskyActorDefs, AppBskyActorSearchActors} from '@atproto/api' import { InfiniteData, + keepPreviousData, QueryClient, QueryKey, useInfiniteQuery, @@ -13,10 +14,8 @@ import {useAgent} from '#/state/session' const RQKEY_ROOT = 'actor-search' export const RQKEY = (query: string) => [RQKEY_ROOT, query] -export const RQKEY_PAGINATED = (query: string) => [ - `${RQKEY_ROOT}_paginated`, - query, -] +const RQKEY_ROOT_PAGINATED = `${RQKEY_ROOT}_paginated` +export const RQKEY_PAGINATED = (query: string) => [RQKEY_ROOT_PAGINATED, query] export function useActorSearch({ query, @@ -42,9 +41,11 @@ export function useActorSearch({ export function useActorSearchPaginated({ query, enabled, + maintainData, }: { query: string enabled?: boolean + maintainData?: boolean }) { const agent = useAgent() return useInfiniteQuery< @@ -67,6 +68,7 @@ export function useActorSearchPaginated({ enabled: enabled && !!query, initialPageParam: undefined, getNextPageParam: lastPage => lastPage.cursor, + placeholderData: maintainData ? keepPreviousData : undefined, }) } @@ -89,4 +91,20 @@ export function* findAllProfilesInQueryData( } } } + + const queryDatasPaginated = queryClient.getQueriesData< + InfiniteData + >({ + queryKey: [RQKEY_ROOT_PAGINATED], + }) + for (const [_queryKey, queryData] of queryDatasPaginated) { + if (!queryData) { + continue + } + for (const actor of queryData.pages.flatMap(page => page.actors)) { + if (actor.did === did) { + yield actor + } + } + } } diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 07e16946e7..22033c0a8c 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -103,7 +103,13 @@ export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) { }) } -export function useSuggestedFollowsByActorQuery({did}: {did: string}) { +export function useSuggestedFollowsByActorQuery({ + did, + enabled, +}: { + did: string + enabled?: boolean +}) { const agent = useAgent() return useQuery({ queryKey: suggestedFollowsByActorQueryKey(did), @@ -116,6 +122,7 @@ export function useSuggestedFollowsByActorQuery({did}: {did: string}) { : res.data.suggestions.filter(profile => !profile.viewer?.following) return {suggestions} }, + enabled, }) } diff --git a/src/state/shell/progress-guide.tsx b/src/state/shell/progress-guide.tsx index d64e9984f5..af3d60ebbd 100644 --- a/src/state/shell/progress-guide.tsx +++ b/src/state/shell/progress-guide.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useMemo} from 'react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -16,20 +16,32 @@ export enum ProgressGuideAction { Follow = 'follow', } -type ProgressGuideName = 'like-10-and-follow-7' +type ProgressGuideName = 'like-10-and-follow-7' | 'follow-10' +/** + * Progress Guides that extend this interface must specify their name in the `guide` field, so it can be used as a discriminated union + */ interface BaseProgressGuide { - guide: string + guide: ProgressGuideName isComplete: boolean [key: string]: any } -interface Like10AndFollow7ProgressGuide extends BaseProgressGuide { +export interface Like10AndFollow7ProgressGuide extends BaseProgressGuide { + guide: 'like-10-and-follow-7' numLikes: number numFollows: number } -type ProgressGuide = Like10AndFollow7ProgressGuide | undefined +export interface Follow10ProgressGuide extends BaseProgressGuide { + guide: 'follow-10' + numFollows: number +} + +export type ProgressGuide = + | Like10AndFollow7ProgressGuide + | Follow10ProgressGuide + | undefined const ProgressGuideContext = React.createContext(undefined) @@ -61,15 +73,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const {mutateAsync, variables, isPending} = useSetActiveProgressGuideMutation() - const activeProgressGuide = ( - isPending ? variables : preferences?.bskyAppState?.activeProgressGuide - ) as ProgressGuide + const activeProgressGuide = useMemo(() => { + const rawProgressGuide = ( + isPending ? variables : preferences?.bskyAppState?.activeProgressGuide + ) as ProgressGuide - // ensure the unspecced attributes have the correct types - if (activeProgressGuide?.guide === 'like-10-and-follow-7') { - activeProgressGuide.numLikes = Number(activeProgressGuide.numLikes) || 0 - activeProgressGuide.numFollows = Number(activeProgressGuide.numFollows) || 0 - } + if (!rawProgressGuide) return undefined + + // ensure the unspecced attributes have the correct types + // clone then mutate + const {...maybeWronglyTypedProgressGuide} = rawProgressGuide + if (maybeWronglyTypedProgressGuide?.guide === 'like-10-and-follow-7') { + maybeWronglyTypedProgressGuide.numLikes = + Number(maybeWronglyTypedProgressGuide.numLikes) || 0 + maybeWronglyTypedProgressGuide.numFollows = + Number(maybeWronglyTypedProgressGuide.numFollows) || 0 + } else if (maybeWronglyTypedProgressGuide?.guide === 'follow-10') { + maybeWronglyTypedProgressGuide.numFollows = + Number(maybeWronglyTypedProgressGuide.numFollows) || 0 + } + + return maybeWronglyTypedProgressGuide + }, [isPending, variables, preferences]) const [localGuideState, setLocalGuideState] = React.useState(undefined) @@ -82,7 +107,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const firstLikeToastRef = React.useRef(null) const fifthLikeToastRef = React.useRef(null) const tenthLikeToastRef = React.useRef(null) - const guideCompleteToastRef = React.useRef(null) + + const fifthFollowToastRef = React.useRef(null) + const tenthFollowToastRef = React.useRef(null) const controls = React.useMemo(() => { return { @@ -93,7 +120,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) { numLikes: 0, numFollows: 0, isComplete: false, - } + } satisfies ProgressGuide + setLocalGuideState(guideObj) + mutateAsync(guideObj) + } else if (guide === 'follow-10') { + const guideObj = { + guide: 'follow-10', + numFollows: 0, + isComplete: false, + } satisfies ProgressGuide setLocalGuideState(guideObj) mutateAsync(guideObj) } @@ -137,6 +172,26 @@ export function Provider({children}: React.PropsWithChildren<{}>) { isComplete: true, } } + } else if (guide?.guide === 'follow-10') { + if (action === ProgressGuideAction.Follow) { + guide = { + ...guide, + numFollows: (Number(guide.numFollows) || 0) + count, + } + + if (guide.numFollows === 5) { + fifthFollowToastRef.current?.open() + } + if (guide.numFollows === 10) { + tenthFollowToastRef.current?.open() + } + } + if (Number(guide.numFollows) >= 10) { + guide = { + ...guide, + isComplete: true, + } + } } setLocalGuideState(guide) @@ -167,9 +222,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) { subtitle={_(msg`The Discover feed now knows what you like`)} /> + )} diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 55d7ba053f..10eb47d0a4 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -253,9 +253,11 @@ let PostFeed = ({ } }, [pollInterval]) - const progressGuide = useProgressGuide('like-10-and-follow-7') + const followProgressGuide = useProgressGuide('follow-10') + const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7') const {isDesktop} = useWebMediaQueries() - const showProgressIntersitial = progressGuide && !isDesktop + const showProgressIntersitial = + (followProgressGuide || followAndLikeProgressGuide) && !isDesktop const feedItems: FeedRow[] = React.useMemo(() => { let feedKind: 'following' | 'discover' | 'profile' | undefined diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index 62c91cec66..5084af6129 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -155,7 +155,11 @@ let List = React.forwardRef( automaticallyAdjustsScrollIndicatorInsets={ automaticallyAdjustsScrollIndicatorInsets } - scrollIndicatorInsets={{top: headerOffset, right: 1}} + scrollIndicatorInsets={{ + top: headerOffset, + right: 1, + ...props.scrollIndicatorInsets, + }} contentOffset={contentOffset} refreshControl={refreshControl} onScroll={scrollHandler} diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index 7dc6837e23..c602886745 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -196,7 +196,9 @@ function Toast({ /> - {message} + + {message} + From 7dd6f6c3dca1a53034935d58ef7f78c6b7386d27 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 3 Dec 2024 17:54:26 -0800 Subject: [PATCH 12/55] Add WIP UIs for trending topics and suggested starterpacks --- src/components/StarterPack/Suggestions.tsx | 64 ++++++++++ src/components/TrendingTopics.tsx | 79 ++++++++++++ src/view/screens/Search/Explore.tsx | 140 +++++++++++++++++++-- 3 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 src/components/StarterPack/Suggestions.tsx create mode 100644 src/components/TrendingTopics.tsx diff --git a/src/components/StarterPack/Suggestions.tsx b/src/components/StarterPack/Suggestions.tsx new file mode 100644 index 0000000000..53750bd1a7 --- /dev/null +++ b/src/components/StarterPack/Suggestions.tsx @@ -0,0 +1,64 @@ +import {View} from 'react-native' +import {ScrollView} from 'react-native-gesture-handler' +import {AppBskyGraphDefs} from '@atproto/api' + +import {logger} from '#/logger' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {atoms as a, useBreakpoints, web} from '#/alf' +import {Embed} from './StarterPackCard' + +const DESKTOP_CARD_WIDTH = 400 +const MOBILE_CARD_WIDTH = 300 + +export function Grid({ + isSuggestionsLoading, + error, + starterPacks, +}: { + isSuggestionsLoading: boolean + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + error: Error | null +}) { + const moderationOpts = useModerationOpts() + const {gtMobile} = useBreakpoints() + const width = gtMobile ? DESKTOP_CARD_WIDTH : MOBILE_CARD_WIDTH + const isLoading = isSuggestionsLoading || !moderationOpts + const maxLength = gtMobile ? 4 : 6 + + const content = isLoading ? ( + Array(maxLength) + .fill(0) + .map((_, i) => ( + + {/* TODO */} + + )) + ) : error || !starterPacks.length ? null : ( + <> + {starterPacks.slice(0, maxLength).map(starterPack => ( + + + + ))} + + ) + + if (error || (!isLoading && !starterPacks.length)) { + logger.debug(`Not enough starter packs to show suggested starter packs`) + return null + } + + return ( + + + {content} + + + ) +} diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx new file mode 100644 index 0000000000..9f8fb69b79 --- /dev/null +++ b/src/components/TrendingTopics.tsx @@ -0,0 +1,79 @@ +import {View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' +import {Link as InternalLink, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function Grid({topics}: {topics: string[]}) { + return ( + + {topics.map(topic => ( + + ))} + + ) +} + +export function Item({topic}: {topic: string}) { + return ( + + + + ) +} + +export function Card({topic}: {topic: string}) { + const t = useTheme() + return ( + + + + ) +} + +export function Link({ + topic, + children, + style, + ...rest +}: { + topic: string +} & Omit) { + const {_} = useLingui() + return ( + + {children} + + ) +} + +export function Topic({topic}: {topic: string}) { + return ( + + + {topic} + + + ) +} diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index bd2ebe5d5d..22cc7b760e 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import { AppBskyActorDefs, AppBskyFeedDefs, + AppBskyGraphDefs, moderateProfile, ModerationDecision, ModerationOpts, @@ -31,8 +32,12 @@ import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/i import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Props as SVGIconProps} from '#/components/icons/common' import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' +import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroup} from '#/components/icons/Person' +import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' import {Loader} from '#/components/Loader' +import * as StarterPackSuggestions from '#/components/StarterPack/Suggestions' +import * as TrendingTopics from '#/components/TrendingTopics' import {Text} from '#/components/Typography' function SuggestedItemsHeader({ @@ -239,6 +244,16 @@ type ExploreScreenItems = style?: ViewStyleProp['style'] icon: React.ComponentType } + | { + type: 'trendingTopics' + key: string + topics: string[] + } + | { + type: 'suggestedStarterPacks' + key: string + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + } | { type: 'profile' key: string @@ -325,17 +340,108 @@ export function Explore() { ]) const items = React.useMemo(() => { - const i: ExploreScreenItems[] = [ - { - type: 'header', - key: 'suggested-follows-header', - title: _(msg`Suggested accounts`), - description: _( - msg`Follow more accounts to get connected to your interests and build your network.`, - ), - icon: Person, - }, - ] + const i: ExploreScreenItems[] = [] + + i.push({ + type: 'header', + key: 'trending-topics-header', + title: _(msg`Trending (beta)`), + description: _(msg`What people are posting about now.`), + icon: Trending, + }) + i.push({ + type: 'trendingTopics', + key: `trending-topics`, + topics: [ + '#atproto', + 'South Korea', + 'Wired', + 'Basket Weaving', + 'Coup', + 'Chappel Roan', + 'the juice', + 'Superman', + '#FCF', + 'Open Web', + ], + }) + + i.push({ + type: 'header', + key: 'starter-packs-header', + title: _(msg`Starter packs`), + description: _(msg`Find accounts to follow based on your interests.`), + icon: PersonGroup, + }) + i.push({ + type: 'suggestedStarterPacks', + key: 'suggested-starter-packs', + starterPacks: [ + { + uri: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.starterpack/3kvaphb2hpf2u', + cid: '', + indexedAt: '', + record: { + $type: 'app.bsky.graph.starterpack', + description: + 'Engineers who work on or discuss the ATProtocol, both at Bluesky and in the community', + list: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3kvaphagg4e2l', + name: 'ATProtocol Hackers', + }, + creator: { + did: 'did:plc:ragtjsm2j2vknwkz3zp4oxrd', + handle: 'pfrazee.com', + displayName: 'Paul Frazee', + }, + }, + { + uri: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.starterpack/3kvuh7u324m2c', + cid: '', + indexedAt: '', + record: { + $type: 'app.bsky.graph.starterpack', + createdAt: '2024-06-26T23:23:01.073Z', + description: + 'Legal practice, scholarship, journalism, and argument', + feeds: [], + list: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.list/3kvuh7twnjc2m', + name: 'Bluesky for Law', + }, + creator: { + did: 'did:plc:qrllvid7s54k4hnwtqxwetrf', + handle: 'joshuajfriedman.com', + displayName: 'Joshua J. Friedman', + }, + }, + { + uri: 'at://did:plc:7exlcsle4mjfhu3wnhcgizz6/app.bsky.graph.starterpack/3laotg2fqva2h', + cid: '', + indexedAt: '', + record: { + $type: 'app.bsky.graph.starterpack', + createdAt: '2024-11-11T17:22:22.743Z', + description: 'people who work at the website, the verge dot com', + list: 'at://did:plc:7exlcsle4mjfhu3wnhcgizz6/app.bsky.graph.list/3laotg2bjok2d', + name: 'The Verge staff', + }, + creator: { + did: 'did:plc:7exlcsle4mjfhu3wnhcgizz6', + handle: 'theverge.com', + displayName: 'The Verge', + }, + }, + ], + }) + + i.push({ + type: 'header', + key: 'suggested-follows-header', + title: _(msg`Suggested accounts`), + description: _( + msg`Follow more accounts to get connected to your interests and build your network.`, + ), + icon: Person, + }) if (profiles) { // Currently the responses contain duplicate items. @@ -490,6 +596,18 @@ export function Explore() { /> ) } + case 'trendingTopics': { + return + } + case 'suggestedStarterPacks': { + return ( + + ) + } case 'profile': { return ( From 352302a1a45ef0a42e13fc67fe7f4ac236737583 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Dec 2024 14:39:52 -0600 Subject: [PATCH 13/55] Disable SPs for now --- src/view/screens/Search/Explore.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 22cc7b760e..6d12353f09 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -32,7 +32,6 @@ import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/i import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Props as SVGIconProps} from '#/components/icons/common' import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' -import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroup} from '#/components/icons/Person' import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' import {Loader} from '#/components/Loader' @@ -366,6 +365,7 @@ export function Explore() { ], }) + /* i.push({ type: 'header', key: 'starter-packs-header', @@ -432,6 +432,7 @@ export function Explore() { }, ], }) + */ i.push({ type: 'header', From 6287bf6c445d8f42766abdae9dc62bc40feac39a Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Dec 2024 16:37:26 -0600 Subject: [PATCH 14/55] Improve explore treatment a bit, add some polish to cards --- src/components/GradientFill.tsx | 1 + src/components/TrendingTopics.tsx | 60 +++++++++++++---------------- src/view/screens/Search/Explore.tsx | 35 ++++++++++++----- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/components/GradientFill.tsx b/src/components/GradientFill.tsx index 3dff404d74..e7d505d6fc 100644 --- a/src/components/GradientFill.tsx +++ b/src/components/GradientFill.tsx @@ -6,6 +6,7 @@ export function GradientFill({ gradient, }: { gradient: + | typeof tokens.gradients.primary | typeof tokens.gradients.sky | typeof tokens.gradients.midnight | typeof tokens.gradients.sunrise diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 9f8fb69b79..bede088575 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -2,42 +2,25 @@ import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, useGutters, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' -export function Grid({topics}: {topics: string[]}) { - return ( - - {topics.map(topic => ( - - ))} - - ) -} - -export function Item({topic}: {topic: string}) { - return ( - - - - ) -} - -export function Card({topic}: {topic: string}) { +export function Topic({topic, style}: {topic: string} & ViewStyleProp) { const t = useTheme() return ( - + + {topic} + ) } @@ -65,15 +48,26 @@ export function Link({ ) } -export function Topic({topic}: {topic: string}) { +export function Grid({topics}: {topics: string[]}) { + const t = useTheme() + const gutters = useGutters([0, 'compact']) return ( - - - {topic} - + + {topics.map(topic => ( + + {({hovered}) => ( + + )} + + ))} ) } diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 6d12353f09..3aa1464594 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -25,9 +25,10 @@ import { ProfileCardFeedLoadingPlaceholder, } from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {atoms as a, tokens,useTheme, ViewStyleProp} from '#/alf' import {Button} from '#/components/Button' import * as FeedCard from '#/components/FeedCard' +import {GradientFill} from '#/components/GradientFill' import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Props as SVGIconProps} from '#/components/icons/common' @@ -41,11 +42,13 @@ import {Text} from '#/components/Typography' function SuggestedItemsHeader({ title, + titleChild, description, style, icon: Icon, }: { title: string + titleChild?: React.ReactNode description: string icon: React.ComponentType } & ViewStyleProp) { @@ -69,6 +72,7 @@ function SuggestedItemsHeader({ style={{marginLeft: -2}} /> {title} + {titleChild} {description} @@ -341,13 +345,6 @@ export function Explore() { const items = React.useMemo(() => { const i: ExploreScreenItems[] = [] - i.push({ - type: 'header', - key: 'trending-topics-header', - title: _(msg`Trending (beta)`), - description: _(msg`What people are posting about now.`), - icon: Trending, - }) i.push({ type: 'trendingTopics', key: `trending-topics`, @@ -598,7 +595,25 @@ export function Explore() { ) } case 'trendingTopics': { - return + return ( + <> + + + BETA + + } + /> + + + + + ) } case 'suggestedStarterPacks': { return ( @@ -680,7 +695,7 @@ export function Explore() { } } }, - [t, moderationOpts], + [_, t, moderationOpts], ) // note: actually not a screen, instead it's nested within From 0ff6151ab8714007fcb20e15bf681238c1cc6456 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Dec 2024 17:03:53 -0600 Subject: [PATCH 15/55] Add tiny option in RightNav --- src/components/TrendingTopics.tsx | 43 ++++++++++++++++++++++-- src/view/shell/desktop/RightNav.tsx | 51 ++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index bede088575..dbf9259bd5 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -6,7 +6,7 @@ import {atoms as a, useGutters, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' -export function Topic({topic, style}: {topic: string} & ViewStyleProp) { +export function TopicLarge({topic, style}: {topic: string} & ViewStyleProp) { const t = useTheme() return ( - + + {topic} + + + ) +} + +export function TopicSmall({topic, style}: {topic: string} & ViewStyleProp) { + const t = useTheme() + return ( + + {topic} @@ -56,7 +79,7 @@ export function Grid({topics}: {topics: string[]}) { {topics.map(topic => ( {({hovered}) => ( - ) } + +// temp +export const TOPICS = [ + '#atproto', + 'South Korea', + 'Wired', + 'Basket Weaving', + 'Coup', + 'Chappel Roan', + 'the juice', + 'Superman', + '#FCF', + 'Open Web', +] diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 895d160211..052275e204 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -9,8 +9,11 @@ import {useSession} from '#/state/session' import {DesktopFeeds} from '#/view/shell/desktop/Feeds' import {DesktopSearch} from '#/view/shell/desktop/Search' import {atoms as a, useGutters, useTheme, web} from '#/alf' +import {Divider} from '#/components/Divider' +import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2' import {InlineLinkText} from '#/components/Link' import {ProgressGuideList} from '#/components/ProgressGuide/List' +import * as Trending from '#/components/TrendingTopics' import {Text} from '#/components/Typography' export function DesktopRightNav({routeName}: {routeName: string}) { @@ -19,6 +22,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { const {hasSession, currentAccount} = useSession() const kawaii = useKawaiiMode() const gutters = useGutters(['base', 0, 'base', 'wide']) + const isSearchScreen = routeName === 'Search' const {isTablet} = useWebMediaQueries() if (isTablet) { @@ -29,6 +33,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { - {routeName !== 'Search' && ( - - - - )} + {!isSearchScreen && } + {hasSession && ( <> - - - + + + + + )} + + {!isSearchScreen && ( + <> + + + + + Trending + + + + + {Trending.TOPICS.slice(0, 8).map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} From 55b573a35a5a920b790c4acfd77ef936341852d2 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Dec 2024 17:55:58 -0600 Subject: [PATCH 16/55] Add persisted option to hide trending from sidebar --- src/state/persisted/schema.ts | 1 + src/view/shell/desktop/RightNav.tsx | 117 ++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index f70d774630..2554d8041b 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -125,6 +125,7 @@ const schema = z.object({ subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), + hideSidebarTrendingTopics: z.boolean().optional(), }) export type Schema = z.infer diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 052275e204..8f8d5052c3 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,18 +1,23 @@ +import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import * as persisted from '#/state/persisted' import {useKawaiiMode} from '#/state/preferences/kawaii' import {useSession} from '#/state/session' import {DesktopFeeds} from '#/view/shell/desktop/Feeds' import {DesktopSearch} from '#/view/shell/desktop/Search' import {atoms as a, useGutters, useTheme, web} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' import {Divider} from '#/components/Divider' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2' import {InlineLinkText} from '#/components/Link' import {ProgressGuideList} from '#/components/ProgressGuide/List' +import * as Prompt from '#/components/Prompt' import * as Trending from '#/components/TrendingTopics' import {Text} from '#/components/Typography' @@ -58,38 +63,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { )} - {!isSearchScreen && ( - <> - - - - - Trending - - - - - {Trending.TOPICS.slice(0, 8).map(topic => ( - - {({hovered}) => ( - - )} - - ))} - - - - - )} + {!isSearchScreen && } {hasSession && ( @@ -137,3 +111,82 @@ export function DesktopRightNav({routeName}: {routeName: string}) { ) } + +function TrendingTopics() { + const t = useTheme() + const {_} = useLingui() + const trendingPrompt = Prompt.usePromptControl() + + const [showTrending, setShowTrending] = React.useState( + () => !persisted.get('hideSidebarTrendingTopics'), + ) + + const onConfirmHideTrending = React.useCallback(() => { + setShowTrending(false) + persisted.write('hideSidebarTrendingTopics', true) + }, [setShowTrending]) + + // persisted.write('hideSidebarTrendingTopics', undefined) + + React.useEffect(() => { + return persisted.onUpdate('hideSidebarTrendingTopics', value => { + setShowTrending(!value) + }) + }, [setShowTrending]) + + return showTrending ? ( + <> + + + + + Trending + + + + + + {Trending.TOPICS.slice(0, 8).map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + + + + + ) : null +} From e3d110207e1ad3b72ef18afcb1862add583c1c1f Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Dec 2024 18:03:25 -0600 Subject: [PATCH 17/55] Add to settings, abstract state, not updating in tab --- .../Settings/ContentAndMediaSettings.tsx | 21 ++++++++++++++ src/state/trending.ts | 28 +++++++++++++++++++ src/view/shell/desktop/RightNav.tsx | 22 ++------------- 3 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 src/state/trending.ts diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 17f8fa5067..cec56911d1 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -9,6 +9,7 @@ import { useInAppBrowser, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' +import {useTrendingTopicsSidebarSetting} from '#/state/trending' import * as SettingsList from '#/screens/Settings/components/SettingsList' import * as Toggle from '#/components/forms/Toggle' import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' @@ -16,6 +17,7 @@ import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home' import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh' import {Play_Stroke2_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' +import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import * as Layout from '#/components/Layout' @@ -29,6 +31,7 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() + const [showTrending, setShowTrending] = useTrendingTopicsSidebarSetting() return ( @@ -104,6 +107,24 @@ export function ContentAndMediaSettingsScreen({}: Props) { + {!isNative && ( + <> + + setShowTrending(value)}> + + + + Show trending topics in your sidebar + + + + + + )} diff --git a/src/state/trending.ts b/src/state/trending.ts new file mode 100644 index 0000000000..412a684975 --- /dev/null +++ b/src/state/trending.ts @@ -0,0 +1,28 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +// TODO only updates in other tabs, not current one +export function useTrendingTopicsSidebarSetting() { + const [_show, setShow] = React.useState( + () => !persisted.get('hideSidebarTrendingTopics'), + ) + + const set = React.useCallback( + (show: boolean) => { + setShow(show) + persisted.write('hideSidebarTrendingTopics', !show) + }, + [setShow], + ) + + // persisted.write('hideSidebarTrendingTopics', undefined) + + React.useEffect(() => { + return persisted.onUpdate('hideSidebarTrendingTopics', value => { + setShow(!value) + }) + }, [setShow]) + + return [_show, set] as const +} diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 8f8d5052c3..f777905f85 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,13 +1,12 @@ -import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import * as persisted from '#/state/persisted' import {useKawaiiMode} from '#/state/preferences/kawaii' import {useSession} from '#/state/session' +import {useTrendingTopicsSidebarSetting} from '#/state/trending' import {DesktopFeeds} from '#/view/shell/desktop/Feeds' import {DesktopSearch} from '#/view/shell/desktop/Search' import {atoms as a, useGutters, useTheme, web} from '#/alf' @@ -117,22 +116,7 @@ function TrendingTopics() { const {_} = useLingui() const trendingPrompt = Prompt.usePromptControl() - const [showTrending, setShowTrending] = React.useState( - () => !persisted.get('hideSidebarTrendingTopics'), - ) - - const onConfirmHideTrending = React.useCallback(() => { - setShowTrending(false) - persisted.write('hideSidebarTrendingTopics', true) - }, [setShowTrending]) - - // persisted.write('hideSidebarTrendingTopics', undefined) - - React.useEffect(() => { - return persisted.onUpdate('hideSidebarTrendingTopics', value => { - setShowTrending(!value) - }) - }, [setShowTrending]) + const [showTrending, setShowTrending] = useTrendingTopicsSidebarSetting() return showTrending ? ( <> @@ -184,7 +168,7 @@ function TrendingTopics() { msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`, )} confirmButtonCta={_(msg`Hide`)} - onConfirm={onConfirmHideTrending} + onConfirm={() => setShowTrending(false)} /> From 99db939beb2b0f1e22830e4c77feea0fa8c71b71 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 12 Dec 2024 10:53:56 -0600 Subject: [PATCH 18/55] Fix up hide/show toggle state, WITH broadcast hacK --- .../Settings/ContentAndMediaSettings.tsx | 12 ++-- src/state/persisted/index.web.ts | 6 +- src/state/persisted/schema.ts | 3 +- src/state/preferences/index.tsx | 5 +- src/state/preferences/trending.tsx | 66 +++++++++++++++++++ src/state/trending.ts | 28 -------- src/view/shell/desktop/RightNav.tsx | 14 ++-- 7 files changed, 93 insertions(+), 41 deletions(-) create mode 100644 src/state/preferences/trending.tsx delete mode 100644 src/state/trending.ts diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index cec56911d1..488b0f9f8b 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -9,7 +9,10 @@ import { useInAppBrowser, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' -import {useTrendingTopicsSidebarSetting} from '#/state/trending' +import { + useTrendingSettings, + useTrendingSettingsApi, +} from '#/state/preferences/trending' import * as SettingsList from '#/screens/Settings/components/SettingsList' import * as Toggle from '#/components/forms/Toggle' import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' @@ -31,7 +34,8 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() - const [showTrending, setShowTrending] = useTrendingTopicsSidebarSetting() + const {trendingSidebarHidden} = useTrendingSettings() + const {setTrendingSidebarHidden} = useTrendingSettingsApi() return ( @@ -113,8 +117,8 @@ export function ContentAndMediaSettingsScreen({}: Props) { setShowTrending(value)}> + value={!trendingSidebarHidden} + onChange={value => setTrendingSidebarHidden(!value)}> diff --git a/src/state/persisted/index.web.ts b/src/state/persisted/index.web.ts index f28b197715..5e3430f6ee 100644 --- a/src/state/persisted/index.web.ts +++ b/src/state/persisted/index.web.ts @@ -63,8 +63,10 @@ export async function write( [key]: value, }) writeToStorage(_state) - broadcast.postMessage({event: {type: UPDATE_EVENT, key}}) - broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading + setTimeout(() => { + broadcast.postMessage({event: {type: UPDATE_EVENT, key}}) + broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading + }, 0) } write satisfies PersistedApi['write'] diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 2554d8041b..4ab56f850f 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -125,7 +125,7 @@ const schema = z.object({ subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), - hideSidebarTrendingTopics: z.boolean().optional(), + trendingSidebarHidden: z.boolean().optional(), }) export type Schema = z.infer @@ -171,6 +171,7 @@ export const defaults: Schema = { kawaii: false, hasCheckedForStarterPack: false, subtitlesEnabled: true, + trendingSidebarHidden: false, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index c7eaf27261..8530a8d0c8 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -10,6 +10,7 @@ import {Provider as KawaiiProvider} from './kawaii' import {Provider as LanguagesProvider} from './languages' import {Provider as LargeAltBadgeProvider} from './large-alt-badge' import {Provider as SubtitlesProvider} from './subtitles' +import {Provider as TrendingSettingsProvider} from './trending' import {Provider as UsedStarterPacksProvider} from './used-starter-packs' export { @@ -39,7 +40,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/state/preferences/trending.tsx b/src/state/preferences/trending.tsx new file mode 100644 index 0000000000..7df7cc115c --- /dev/null +++ b/src/state/preferences/trending.tsx @@ -0,0 +1,66 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = { + trendingSidebarHidden: persisted.Schema['trendingSidebarHidden'] +} +type ApiContext = { + setTrendingSidebarHidden( + hidden: Exclude, + ): void +} + +const StateContext = React.createContext({ + trendingSidebarHidden: persisted.defaults.trendingSidebarHidden, +}) +const ApiContext = React.createContext({ + setTrendingSidebarHidden() {}, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [trendingSidebarHidden, _setTrendingSidebarHidden] = React.useState( + () => { + return Boolean(persisted.get('trendingSidebarHidden')) + }, + ) + + const setTrendingSidebarHidden = React.useCallback< + ApiContext['setTrendingSidebarHidden'] + >( + hidden => { + _setTrendingSidebarHidden(hidden) + persisted.write('trendingSidebarHidden', hidden) + }, + [_setTrendingSidebarHidden], + ) + + React.useEffect(() => { + return persisted.onUpdate('trendingSidebarHidden', hidden => { + _setTrendingSidebarHidden(Boolean(hidden)) + }) + }, [_setTrendingSidebarHidden]) + + const state = React.useMemo( + () => ({trendingSidebarHidden}), + [trendingSidebarHidden], + ) + const api = React.useMemo( + () => ({setTrendingSidebarHidden}), + [setTrendingSidebarHidden], + ) + + return ( + + {children} + + ) +} + +export function useTrendingSettings() { + return React.useContext(StateContext) +} + +export function useTrendingSettingsApi() { + return React.useContext(ApiContext) +} diff --git a/src/state/trending.ts b/src/state/trending.ts deleted file mode 100644 index 412a684975..0000000000 --- a/src/state/trending.ts +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' - -import * as persisted from '#/state/persisted' - -// TODO only updates in other tabs, not current one -export function useTrendingTopicsSidebarSetting() { - const [_show, setShow] = React.useState( - () => !persisted.get('hideSidebarTrendingTopics'), - ) - - const set = React.useCallback( - (show: boolean) => { - setShow(show) - persisted.write('hideSidebarTrendingTopics', !show) - }, - [setShow], - ) - - // persisted.write('hideSidebarTrendingTopics', undefined) - - React.useEffect(() => { - return persisted.onUpdate('hideSidebarTrendingTopics', value => { - setShow(!value) - }) - }, [setShow]) - - return [_show, set] as const -} diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index f777905f85..45e0752693 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -5,8 +5,11 @@ import {useLingui} from '@lingui/react' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useKawaiiMode} from '#/state/preferences/kawaii' +import { + useTrendingSettings, + useTrendingSettingsApi, +} from '#/state/preferences/trending' import {useSession} from '#/state/session' -import {useTrendingTopicsSidebarSetting} from '#/state/trending' import {DesktopFeeds} from '#/view/shell/desktop/Feeds' import {DesktopSearch} from '#/view/shell/desktop/Search' import {atoms as a, useGutters, useTheme, web} from '#/alf' @@ -116,9 +119,10 @@ function TrendingTopics() { const {_} = useLingui() const trendingPrompt = Prompt.usePromptControl() - const [showTrending, setShowTrending] = useTrendingTopicsSidebarSetting() + const {trendingSidebarHidden} = useTrendingSettings() + const {setTrendingSidebarHidden} = useTrendingSettingsApi() - return showTrending ? ( + return trendingSidebarHidden ? null : ( <> @@ -168,9 +172,9 @@ function TrendingTopics() { msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`, )} confirmButtonCta={_(msg`Hide`)} - onConfirm={() => setShowTrending(false)} + onConfirm={() => setTrendingSidebarHidden(true)} /> - ) : null + ) } From be3c36fcf63a0dfdf20ceead856cb3c47f9d69c5 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 12 Dec 2024 15:47:25 -0600 Subject: [PATCH 19/55] Clean up persisted code, add new setting --- src/state/persisted/schema.ts | 10 +++++ src/state/preferences/trending.tsx | 64 ++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 4ab56f850f..e4852e2f72 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -125,7 +125,12 @@ const schema = z.object({ subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), + + /* + * Trending + */ trendingSidebarHidden: z.boolean().optional(), + trendingDiscoverHidden: z.boolean().optional(), }) export type Schema = z.infer @@ -171,7 +176,12 @@ export const defaults: Schema = { kawaii: false, hasCheckedForStarterPack: false, subtitlesEnabled: true, + + /* + * Trending + */ trendingSidebarHidden: false, + trendingDiscoverHidden: false, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/trending.tsx b/src/state/preferences/trending.tsx index 7df7cc115c..61a026c22e 100644 --- a/src/state/preferences/trending.tsx +++ b/src/state/preferences/trending.tsx @@ -3,51 +3,71 @@ import React from 'react' import * as persisted from '#/state/persisted' type StateContext = { - trendingSidebarHidden: persisted.Schema['trendingSidebarHidden'] + trendingSidebarHidden: Exclude< + persisted.Schema['trendingSidebarHidden'], + undefined + > + trendingDiscoverHidden: Exclude< + persisted.Schema['trendingDiscoverHidden'], + undefined + > } type ApiContext = { setTrendingSidebarHidden( hidden: Exclude, ): void + setTrendingDiscoverHidden( + hidden: Exclude, + ): void } const StateContext = React.createContext({ - trendingSidebarHidden: persisted.defaults.trendingSidebarHidden, + trendingSidebarHidden: Boolean(persisted.defaults.trendingSidebarHidden), + trendingDiscoverHidden: Boolean(persisted.defaults.trendingDiscoverHidden), }) const ApiContext = React.createContext({ setTrendingSidebarHidden() {}, + setTrendingDiscoverHidden() {}, }) -export function Provider({children}: React.PropsWithChildren<{}>) { - const [trendingSidebarHidden, _setTrendingSidebarHidden] = React.useState( - () => { - return Boolean(persisted.get('trendingSidebarHidden')) - }, - ) - - const setTrendingSidebarHidden = React.useCallback< - ApiContext['setTrendingSidebarHidden'] +function usePersistedBooleanValue(key: T) { + const [value, _set] = React.useState(() => { + return Boolean(persisted.get(key)) + }) + const set = React.useCallback< + (value: Exclude) => void >( hidden => { - _setTrendingSidebarHidden(hidden) - persisted.write('trendingSidebarHidden', hidden) + _set(Boolean(hidden)) + persisted.write(key, hidden) }, - [_setTrendingSidebarHidden], + [key, _set], ) - React.useEffect(() => { - return persisted.onUpdate('trendingSidebarHidden', hidden => { - _setTrendingSidebarHidden(Boolean(hidden)) + return persisted.onUpdate(key, hidden => { + _set(Boolean(hidden)) }) - }, [_setTrendingSidebarHidden]) + }, [key, _set]) + return [value, set] as const +} + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [trendingSidebarHidden, setTrendingSidebarHidden] = + usePersistedBooleanValue('trendingSidebarHidden') + const [trendingDiscoverHidden, setTrendingDiscoverHidden] = + usePersistedBooleanValue('trendingDiscoverHidden') + + /* + * Context + */ const state = React.useMemo( - () => ({trendingSidebarHidden}), - [trendingSidebarHidden], + () => ({trendingSidebarHidden, trendingDiscoverHidden}), + [trendingSidebarHidden, trendingDiscoverHidden], ) const api = React.useMemo( - () => ({setTrendingSidebarHidden}), - [setTrendingSidebarHidden], + () => ({setTrendingSidebarHidden, setTrendingDiscoverHidden}), + [setTrendingSidebarHidden, setTrendingDiscoverHidden], ) return ( From b03a85ad5380ba17198a6d928e6bfaba57ac9877 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 12 Dec 2024 16:45:45 -0600 Subject: [PATCH 20/55] Add new interstitial to Discover --- src/components/GradientFill.tsx | 7 +- src/components/TrendingTopics.tsx | 2 + src/components/interstitials/Trending.tsx | 84 +++++++++++++++++++ .../Settings/ContentAndMediaSettings.tsx | 48 +++++++---- src/view/com/posts/PostFeed.tsx | 18 +++- 5 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 src/components/interstitials/Trending.tsx diff --git a/src/components/GradientFill.tsx b/src/components/GradientFill.tsx index e7d505d6fc..9ad6ed7dc6 100644 --- a/src/components/GradientFill.tsx +++ b/src/components/GradientFill.tsx @@ -1,10 +1,11 @@ import {LinearGradient} from 'expo-linear-gradient' -import {atoms as a, tokens} from '#/alf' +import {atoms as a, tokens, ViewStyleProp} from '#/alf' export function GradientFill({ gradient, -}: { + style, +}: ViewStyleProp & { gradient: | typeof tokens.gradients.primary | typeof tokens.gradients.sky @@ -27,7 +28,7 @@ export function GradientFill({ } start={{x: 0, y: 0}} end={{x: 1, y: 1}} - style={[a.absolute, a.inset_0]} + style={[a.absolute, a.inset_0, style]} /> ) } diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index dbf9259bd5..090415ae61 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -16,6 +16,7 @@ export function TopicLarge({topic, style}: {topic: string} & ViewStyleProp) { a.rounded_md, a.border, t.atoms.border_contrast_medium, + t.atoms.bg, style, ]}> + + + + + Trending + + + + BETA + + + + + + + + {Trending.TOPICS.slice(0, 8).map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + + setTrendingDiscoverHidden(true)} + /> + + ) +} diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 488b0f9f8b..7782a94ff8 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -34,8 +34,9 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() - const {trendingSidebarHidden} = useTrendingSettings() - const {setTrendingSidebarHidden} = useTrendingSettingsApi() + const {trendingSidebarHidden, trendingDiscoverHidden} = useTrendingSettings() + const {setTrendingSidebarHidden, setTrendingDiscoverHidden} = + useTrendingSettingsApi() return ( @@ -111,24 +112,35 @@ export function ContentAndMediaSettingsScreen({}: Props) { + {!isNative && ( - <> - - setTrendingSidebarHidden(!value)}> - - - - Show trending topics in your sidebar - - - - - + setTrendingSidebarHidden(!value)}> + + + + Show trending topics in your sidebar + + + + )} + setTrendingDiscoverHidden(!value)}> + + + + Show trending topics in your feed + + + + diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 10eb47d0a4..d88d16955b 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -23,6 +23,7 @@ import {logger} from '#/logger' import {isIOS, isWeb} from '#/platform/detection' import {listenPostCreated} from '#/state/events' import {useFeedFeedbackContext} from '#/state/feed-feedback' +import {useTrendingSettings} from '#/state/preferences/trending' import {STALE} from '#/state/queries' import { FeedDescriptor, @@ -35,6 +36,7 @@ import { import {useSession} from '#/state/session' import {useProgressGuide} from '#/state/shell/progress-guide' import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials' +import {TrendingInterstitial} from '#/components/interstitials/Trending' import {List, ListRef} from '../util/List' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' @@ -90,6 +92,10 @@ type FeedRow = type: 'interstitialProgressGuide' key: string } + | { + type: 'interstitialTrending' + key: string + } export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null { if (feedRow.type === 'sliceItem') { @@ -259,6 +265,8 @@ let PostFeed = ({ const showProgressIntersitial = (followProgressGuide || followAndLikeProgressGuide) && !isDesktop + const {trendingDiscoverHidden} = useTrendingSettings() + const feedItems: FeedRow[] = React.useMemo(() => { let feedKind: 'following' | 'discover' | 'profile' | undefined if (feedType === 'following') { @@ -304,7 +312,12 @@ let PostFeed = ({ type: 'interstitialProgressGuide', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, }) - } else if (sliceIndex === 20) { + } else if (sliceIndex === 15 && !trendingDiscoverHidden) { + arr.push({ + type: 'interstitialTrending', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } else if (sliceIndex === 30) { arr.push({ type: 'interstitialFollows', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, @@ -390,6 +403,7 @@ let PostFeed = ({ feedTab, hasSession, showProgressIntersitial, + trendingDiscoverHidden, ]) // events @@ -476,6 +490,8 @@ let PostFeed = ({ return } else if (row.type === 'interstitialProgressGuide') { return + } else if (row.type === 'interstitialTrending') { + return } else if (row.type === 'sliceItem') { const slice = row.slice if (slice.isFallbackMarker) { From 72cf85d38b0a635c2be1020eeff1f8674eb9c8d2 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 12 Dec 2024 19:27:04 -0600 Subject: [PATCH 21/55] Exploration --- src/components/TrendingTopics.tsx | 147 ++++++++++++++++++++++ src/components/interstitials/Trending.tsx | 9 +- 2 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 090415ae61..b0a7e10b7f 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -1,10 +1,17 @@ +import React from 'react' import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {AtUri} from '@atproto/api' import {atoms as a, useGutters, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' +import {BSKY_APP_HOST, feedUriToHref} from '#/lib/strings/url-helpers' +import {makeProfileLink, makeSearchLink} from '#/lib/routes/links' +import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' +import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' +import {UserAvatar} from '#/view/com/util/UserAvatar' export function TopicLarge({topic, style}: {topic: string} & ViewStyleProp) { const t = useTheme() @@ -50,6 +57,58 @@ export function TopicSmall({topic, style}: {topic: string} & ViewStyleProp) { ) } +export function Topic({ + topic: raw, + size, + style, +}: {topic: RawTopic; size?: 'large' | 'small'} & ViewStyleProp) { + const t = useTheme() + const topic = useTopic(raw) + + if (!topic) return null + + const hasAvi = topic.type === 'feed' || topic.type === 'profile' + + return ( + + + {topic.type === 'tag' ? ( + + ) : topic.type === 'topic' ? ( + + ) : topic.type === 'feed' ? ( + + ) : ( + + )} + + + + {topic.name} + + + ) +} + export function Link({ topic, children, @@ -110,3 +169,91 @@ export const TOPICS = [ '#FCF', 'Open Web', ] + +export const TOP = [ + { + name: '#atproto', + uri: 'https://bsky.app/hashtag/atproto', + }, + { + name: 'South Korea', + uri: BSKY_APP_HOST + makeSearchLink({query: 'South Korea'}), + }, + { + name: 'Paul Frazee', + uri: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.actor.profile/self' + }, + { + name: 'Wired', + uri: BSKY_APP_HOST + makeSearchLink({query: 'Wired'}), + }, + { + name: 'Quiet Posters', + uri: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/infreq', + }, +] + +type RawTopic = { + name: string + uri: string +} + +type Topic = + | { + type: 'topic' | 'tag' + name: string + url: string + uri: undefined + } + | { + type: 'profile' | 'feed' + name: string + url: string + uri: AtUri + } + +export function useTopic(topic: RawTopic): Topic | undefined { + return React.useMemo(() => { + const {name, uri} = topic + + if (uri.startsWith('https')) { + const url = new URL(uri) + if (url.origin !== BSKY_APP_HOST) return + if (url.pathname.startsWith('/search')) { + return { + type: 'topic', + name, + url: url.pathname, + uri: undefined, + } + } else if (url.pathname.startsWith('/hashtag')) { + return { + type: 'tag', + name: name.replace(/^#/, ''), + url: url.pathname, + uri: undefined, + } + } + } else { + const urip = new AtUri(uri) + switch (urip.collection) { + case 'app.bsky.actor.profile': { + return { + type: 'profile', + name, + uri: urip, + url: makeProfileLink({did: urip.host, handle: urip.host}), + } + } + case 'app.bsky.feed.generator': { + return { + type: 'feed', + name, + uri: urip, + url: feedUriToHref(uri), + } + } + } + } + }, [topic]) +} diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index b6705158fd..cee0524cf5 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -53,7 +53,12 @@ export function TrendingInterstitial() { - {Trending.TOPICS.slice(0, 8).map(topic => ( + {Trending.TOP.slice(0, 8).map(topic => ( + + ))} + {/*Trending.TOPICS.slice(0, 8).map(topic => ( {({hovered}) => ( )} - ))} + ))*/} Date: Thu, 12 Dec 2024 19:37:41 -0600 Subject: [PATCH 22/55] First hack at mute words --- src/components/TrendingTopics.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index b0a7e10b7f..862ab5619b 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {AtUri} from '@atproto/api' +import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' import {atoms as a, useGutters, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' @@ -12,6 +13,7 @@ import {makeProfileLink, makeSearchLink} from '#/lib/routes/links' import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' import {UserAvatar} from '#/view/com/util/UserAvatar' +import {usePreferencesQuery} from '#/state/queries/preferences' export function TopicLarge({topic, style}: {topic: string} & ViewStyleProp) { const t = useTheme() @@ -212,10 +214,23 @@ type Topic = uri: AtUri } +export function useMutedWords() { + const {data: preferences} = usePreferencesQuery() + return React.useMemo(() => { + return preferences?.moderationPrefs?.mutedWords || [] + }, [preferences?.moderationPrefs?.mutedWords]) +} + export function useTopic(topic: RawTopic): Topic | undefined { + const mutedWords = useMutedWords() return React.useMemo(() => { const {name, uri} = topic + if (hasMutedWord({ + mutedWords, + text: name + })) return + if (uri.startsWith('https')) { const url = new URL(uri) if (url.origin !== BSKY_APP_HOST) return @@ -255,5 +270,5 @@ export function useTopic(topic: RawTopic): Topic | undefined { } } } - }, [topic]) + }, [topic, mutedWords]) } From c8a6e3b3235412f83cca743737d89af6c1f39881 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 13:59:30 -0600 Subject: [PATCH 23/55] Wire up interstitial and Explore page --- src/components/TrendingTopics.tsx | 136 ++++++------------ src/components/interstitials/Trending.tsx | 44 +++--- .../components/ExploreTrendingTopics.tsx | 35 +++++ .../queries/trending/useTrendingTopics.ts | 56 ++++++++ src/view/screens/Search/Explore.tsx | 3 +- 5 files changed, 161 insertions(+), 113 deletions(-) create mode 100644 src/screens/Search/components/ExploreTrendingTopics.tsx create mode 100644 src/state/queries/trending/useTrendingTopics.ts diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 862ab5619b..ec0a6a00c1 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -5,37 +5,16 @@ import {useLingui} from '@lingui/react' import {AtUri} from '@atproto/api' import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' -import {atoms as a, useGutters, useTheme, ViewStyleProp} from '#/alf' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' -import {BSKY_APP_HOST, feedUriToHref} from '#/lib/strings/url-helpers' -import {makeProfileLink, makeSearchLink} from '#/lib/routes/links' +import {feedUriToHref} from '#/lib/strings/url-helpers' +import {makeProfileLink} from '#/lib/routes/links' import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' import {UserAvatar} from '#/view/com/util/UserAvatar' import {usePreferencesQuery} from '#/state/queries/preferences' - -export function TopicLarge({topic, style}: {topic: string} & ViewStyleProp) { - const t = useTheme() - return ( - - - {topic} - - - ) -} +import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' export function TopicSmall({topic, style}: {topic: string} & ViewStyleProp) { const t = useTheme() @@ -59,11 +38,11 @@ export function TopicSmall({topic, style}: {topic: string} & ViewStyleProp) { ) } -export function Topic({ +export function TrendingTopic({ topic: raw, size, style, -}: {topic: RawTopic; size?: 'large' | 'small'} & ViewStyleProp) { +}: {topic: TrendingTopic; size?: 'large' | 'small'} & ViewStyleProp) { const t = useTheme() const topic = useTopic(raw) @@ -111,6 +90,29 @@ export function Topic({ ) } +export function TrendingTopicLink({ + topic: raw, + children, + style, + ...rest +}: { + topic: TrendingTopic +} & Omit) { + const topic = useTopic(raw) + + if (!topic) return null + + return ( + + {children} + + ) +} + export function Link({ topic, children, @@ -134,30 +136,6 @@ export function Link({ ) } -export function Grid({topics}: {topics: string[]}) { - const t = useTheme() - const gutters = useGutters([0, 'compact']) - return ( - - {topics.map(topic => ( - - {({hovered}) => ( - - )} - - ))} - - ) -} - // temp export const TOPICS = [ '#atproto', @@ -172,43 +150,17 @@ export const TOPICS = [ 'Open Web', ] -export const TOP = [ - { - name: '#atproto', - uri: 'https://bsky.app/hashtag/atproto', - }, - { - name: 'South Korea', - uri: BSKY_APP_HOST + makeSearchLink({query: 'South Korea'}), - }, - { - name: 'Paul Frazee', - uri: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.actor.profile/self' - }, - { - name: 'Wired', - uri: BSKY_APP_HOST + makeSearchLink({query: 'Wired'}), - }, - { - name: 'Quiet Posters', - uri: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/infreq', - }, -] - -type RawTopic = { - name: string - uri: string -} - -type Topic = +type ParsedTrendingTopic = | { type: 'topic' | 'tag' + label: string name: string url: string uri: undefined } | { type: 'profile' | 'feed' + label: string name: string url: string uri: AtUri @@ -221,32 +173,34 @@ export function useMutedWords() { }, [preferences?.moderationPrefs?.mutedWords]) } -export function useTopic(topic: RawTopic): Topic | undefined { +export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { + const {_} = useLingui() const mutedWords = useMutedWords() + return React.useMemo(() => { - const {name, uri} = topic + const {topic: name, link: uri} = raw if (hasMutedWord({ mutedWords, text: name })) return - if (uri.startsWith('https')) { - const url = new URL(uri) - if (url.origin !== BSKY_APP_HOST) return - if (url.pathname.startsWith('/search')) { + if (!uri.startsWith('at://')) { + if (uri.startsWith('/search')) { return { type: 'topic', + label: _(msg`Browse posts about ${name}`), name, - url: url.pathname, uri: undefined, + url: uri, } - } else if (url.pathname.startsWith('/hashtag')) { + } else if (uri.startsWith('/hashtag')) { return { type: 'tag', + label: _(msg`Browse posts tagged with ${name}`), name: name.replace(/^#/, ''), - url: url.pathname, uri: undefined, + url: uri, } } } else { @@ -255,6 +209,7 @@ export function useTopic(topic: RawTopic): Topic | undefined { case 'app.bsky.actor.profile': { return { type: 'profile', + label: _(msg`View ${name}'s profile`), name, uri: urip, url: makeProfileLink({did: urip.host, handle: urip.host}), @@ -263,6 +218,7 @@ export function useTopic(topic: RawTopic): Topic | undefined { case 'app.bsky.feed.generator': { return { type: 'feed', + label: _(msg`Browse the ${name} feed`), name, uri: urip, url: feedUriToHref(uri), @@ -270,5 +226,5 @@ export function useTopic(topic: RawTopic): Topic | undefined { } } } - }, [topic, mutedWords]) + }, [raw, mutedWords]) } diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index cee0524cf5..2dd2c84af2 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -9,15 +9,16 @@ import {GradientFill} from '#/components/GradientFill' 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 * as Trending from '#/components/TrendingTopics' +import {TrendingTopic, TrendingTopicLink} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' +import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics' export function TrendingInterstitial() { const t = useTheme() const {_} = useLingui() const gutters = useGutters(['base']) const trendingPrompt = Prompt.usePromptControl() - + const {data: topics, error, isLoading} = useTrendingTopics() const {setTrendingDiscoverHidden} = useTrendingSettingsApi() return ( @@ -53,26 +54,25 @@ export function TrendingInterstitial() { - {Trending.TOP.slice(0, 8).map(topic => ( - - ))} - {/*Trending.TOPICS.slice(0, 8).map(topic => ( - - {({hovered}) => ( - - )} - - ))*/} + {isLoading ? null : error || !topics ? null : ( + <> + {topics.map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} + {isLoading ? null : error || !topics ? null : ( + <> + {topics.map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} + + ) +} diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts new file mode 100644 index 0000000000..6bae41b03d --- /dev/null +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -0,0 +1,56 @@ +import {useQuery} from '@tanstack/react-query' + +// TEMP +import {makeSearchLink} from '#/lib/routes/links' + +export type TrendingTopic = { + topic: string + displayName: string + description: string + link: string +} + +export const trendingTopicsQueryKey = ['trending-topics'] + +// TODO ideally handle down-state faster + +export function useTrendingTopics() { + return useQuery({ + queryKey: trendingTopicsQueryKey, + async queryFn() { + const topics: TrendingTopic[] = [ + { + topic: '#atproto', + displayName: '#atproto', + description: '', + link: '/hashtag/atproto', + }, + { + topic: 'South Korea', + displayName: 'South Korea', + description: '', + link: makeSearchLink({query: 'South Korea'}), + }, + { + topic: 'Paul Frazee', + displayName: 'Paul Frazee', + description: '', + link: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.actor.profile/self', + }, + { + topic: 'Wired', + displayName: 'Wired', + description: '', + link: makeSearchLink({query: 'Wired'}), + }, + { + topic: 'Quiet Posters', + displayName: 'Quiet Posters', + description: '', + link: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/infreq', + }, + ] + return topics + }, + }) +} diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 3aa1464594..2ce2f923f0 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -39,6 +39,7 @@ import {Loader} from '#/components/Loader' import * as StarterPackSuggestions from '#/components/StarterPack/Suggestions' import * as TrendingTopics from '#/components/TrendingTopics' import {Text} from '#/components/Typography' +import {ExploreTrendingTopics} from "#/screens/Search/components/ExploreTrendingTopics" function SuggestedItemsHeader({ title, @@ -610,7 +611,7 @@ export function Explore() { } /> - + ) From 111ca28fb960f94be304c296e3b69bdbb11def41 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 14:58:28 -0600 Subject: [PATCH 24/55] Align components --- src/components/TrendingTopics.tsx | 155 ++++++++---------- src/view/shell/desktop/RightNav.tsx | 77 +-------- .../shell/desktop/SidebarTrendingTopics.tsx | 92 +++++++++++ 3 files changed, 161 insertions(+), 163 deletions(-) create mode 100644 src/view/shell/desktop/SidebarTrendingTopics.tsx diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index ec0a6a00c1..f20e1f5268 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -1,42 +1,20 @@ import React from 'react' import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' import {AtUri} from '@atproto/api' import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +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 {usePreferencesQuery} from '#/state/queries/preferences' +import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' -import {feedUriToHref} from '#/lib/strings/url-helpers' -import {makeProfileLink} from '#/lib/routes/links' -import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' -import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' -import {UserAvatar} from '#/view/com/util/UserAvatar' -import {usePreferencesQuery} from '#/state/queries/preferences' -import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' - -export function TopicSmall({topic, style}: {topic: string} & ViewStyleProp) { - const t = useTheme() - return ( - - - {topic} - - - ) -} export function TrendingTopic({ topic: raw, @@ -48,41 +26,76 @@ export function TrendingTopic({ if (!topic) return null - const hasAvi = topic.type === 'feed' || topic.type === 'profile' + const isSmall = size === 'small' + // const hasAvi = topic.type === 'feed' || topic.type === 'profile' + // const aviSize = isSmall ? 16 : 20 + // const iconSize = isSmall ? 16 : 20 return ( - + {/* + {topic.type === 'tag' ? ( - + ) : topic.type === 'topic' ? ( - + ) : topic.type === 'feed' ? ( - + ) : ( - + )} + */} {topic.name} @@ -113,43 +126,6 @@ export function TrendingTopicLink({ ) } -export function Link({ - topic, - children, - style, - ...rest -}: { - topic: string -} & Omit) { - const {_} = useLingui() - return ( - - {children} - - ) -} - -// temp -export const TOPICS = [ - '#atproto', - 'South Korea', - 'Wired', - 'Basket Weaving', - 'Coup', - 'Chappel Roan', - 'the juice', - 'Superman', - '#FCF', - 'Open Web', -] - type ParsedTrendingTopic = | { type: 'topic' | 'tag' @@ -180,10 +156,13 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { return React.useMemo(() => { const {topic: name, link: uri} = raw - if (hasMutedWord({ - mutedWords, - text: name - })) return + if ( + hasMutedWord({ + mutedWords, + text: name, + }) + ) + return if (!uri.startsWith('at://')) { if (uri.startsWith('/search')) { @@ -226,5 +205,5 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { } } } - }, [raw, mutedWords]) + }, [_, raw, mutedWords]) } diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 45e0752693..1732245e38 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -5,22 +5,14 @@ import {useLingui} from '@lingui/react' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useKawaiiMode} from '#/state/preferences/kawaii' -import { - useTrendingSettings, - useTrendingSettingsApi, -} from '#/state/preferences/trending' import {useSession} from '#/state/session' import {DesktopFeeds} from '#/view/shell/desktop/Feeds' import {DesktopSearch} from '#/view/shell/desktop/Search' +import {SidebarTrendingTopics} from '#/view/shell/desktop/SidebarTrendingTopics' import {atoms as a, useGutters, useTheme, web} from '#/alf' -import {Button, ButtonIcon} from '#/components/Button' import {Divider} from '#/components/Divider' -import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' -import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending2' import {InlineLinkText} from '#/components/Link' import {ProgressGuideList} from '#/components/ProgressGuide/List' -import * as Prompt from '#/components/Prompt' -import * as Trending from '#/components/TrendingTopics' import {Text} from '#/components/Typography' export function DesktopRightNav({routeName}: {routeName: string}) { @@ -65,7 +57,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { )} - {!isSearchScreen && } + {!isSearchScreen && } {hasSession && ( @@ -113,68 +105,3 @@ export function DesktopRightNav({routeName}: {routeName: string}) { ) } - -function TrendingTopics() { - const t = useTheme() - const {_} = useLingui() - const trendingPrompt = Prompt.usePromptControl() - - const {trendingSidebarHidden} = useTrendingSettings() - const {setTrendingSidebarHidden} = useTrendingSettingsApi() - - return trendingSidebarHidden ? null : ( - <> - - - - - Trending - - - - - - {Trending.TOPICS.slice(0, 8).map(topic => ( - - {({hovered}) => ( - - )} - - ))} - - - setTrendingSidebarHidden(true)} - /> - - - ) -} diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx new file mode 100644 index 0000000000..c62d0c8898 --- /dev/null +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -0,0 +1,92 @@ +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import { + useTrendingSettings, + useTrendingSettingsApi, +} from '#/state/preferences/trending' +import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' +import {Divider} from '#/components/Divider' +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 {TrendingTopic, TrendingTopicLink} from '#/components/TrendingTopics' +import {Text} from '#/components/Typography' + +export function SidebarTrendingTopics() { + const {trendingSidebarHidden} = useTrendingSettings() + return trendingSidebarHidden ? null : +} + +function Inner() { + const t = useTheme() + const {_} = useLingui() + const trendingPrompt = Prompt.usePromptControl() + const {setTrendingSidebarHidden} = useTrendingSettingsApi() + + const {data: topics, error, isLoading} = useTrendingTopics() + + return ( + <> + + + + + Trending + + + + + + {isLoading ? null : error || !topics ? null : ( + <> + {topics.map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} + + + setTrendingSidebarHidden(true)} + /> + + + ) +} From 9d7ebe3ee789e83d02e8fb6ca8a36188c13e4816 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 15:26:12 -0600 Subject: [PATCH 25/55] Some skeleton UI --- src/components/TrendingTopics.tsx | 24 +++++++++++++++++++ .../shell/desktop/SidebarTrendingTopics.tsx | 15 +++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index f20e1f5268..e765f03ea4 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -103,6 +103,30 @@ export function TrendingTopic({ ) } +export function TrendingTopicSkeleton({size}: {size: 'large' | 'small'}) { + const t = useTheme() + const isSmall = size === 'small' + return ( + + ) +} + export function TrendingTopicLink({ topic: raw, children, diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index c62d0c8898..28f47d9810 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -13,7 +13,11 @@ import {Divider} from '#/components/Divider' 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 {TrendingTopic, TrendingTopicLink} from '#/components/TrendingTopics' +import { + TrendingTopic, + TrendingTopicLink, + TrendingTopicSkeleton, +} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' export function SidebarTrendingTopics() { @@ -28,8 +32,9 @@ function Inner() { const {setTrendingSidebarHidden} = useTrendingSettingsApi() const {data: topics, error, isLoading} = useTrendingTopics() + const noTopics = !isLoading && !error && !topics - return ( + return error || noTopics ? null : ( <> @@ -55,7 +60,11 @@ function Inner() { - {isLoading ? null : error || !topics ? null : ( + {isLoading ? ( + Array(8) + .fill(0) + .map((_, i) => ) + ) : error || !topics ? null : ( <> {topics.map(topic => ( From 2097b67ff09e0fce705348b3fb468d8cec8b1fb9 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:08:56 -0600 Subject: [PATCH 26/55] Handle service config, enablement, load states, update lex contract --- src/App.native.tsx | 15 ++- src/App.web.tsx | 7 +- src/components/TrendingTopics.tsx | 20 ++-- src/components/interstitials/Trending.tsx | 42 +++++-- .../components/ExploreTrendingTopics.tsx | 111 ++++++++++++++---- src/state/queries/service-config.ts | 35 ++++++ .../queries/trending/useTrendingTopics.ts | 33 +++++- src/state/shell/trending-config.tsx | 31 +++++ src/state/trending-config.tsx | 32 +++++ src/view/screens/Search/Explore.tsx | 111 +----------------- .../shell/desktop/SidebarTrendingTopics.tsx | 24 ++-- 11 files changed, 289 insertions(+), 172 deletions(-) create mode 100644 src/state/queries/service-config.ts create mode 100644 src/state/shell/trending-config.tsx create mode 100644 src/state/trending-config.tsx diff --git a/src/App.native.tsx b/src/App.native.tsx index 39ab7ca92c..780295ddce 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -57,6 +57,7 @@ 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' @@ -143,12 +144,14 @@ function InnerApp() { - - - - - + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 8d13a826e7..8a2e13600f 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -47,6 +47,7 @@ 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' @@ -127,8 +128,10 @@ function InnerApp() { - - + + + + diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index e765f03ea4..41c086b83c 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -91,7 +91,6 @@ export function TrendingTopic({ + {children} ) diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index 2dd2c84af2..a19d1dfd80 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -2,26 +2,44 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useTrendingSettingsApi} from '#/state/preferences/trending' +import { + useTrendingSettings, + useTrendingSettingsApi, +} from '#/state/preferences/trending' +import { + DEFAULT_LIMIT as TRENDING_TOPICS_COUNT, + useTrendingTopics, +} from '#/state/queries/trending/useTrendingTopics' +import {useTrendingConfig} from '#/state/trending-config' import {atoms as a, tokens, useGutters, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {GradientFill} from '#/components/GradientFill' 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 {TrendingTopic, TrendingTopicLink} from '#/components/TrendingTopics' +import { + TrendingTopic, + TrendingTopicLink, + TrendingTopicSkeleton, +} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' -import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics' export function TrendingInterstitial() { + const {enabled} = useTrendingConfig() + const {trendingDiscoverHidden} = useTrendingSettings() + return !enabled ? null : trendingDiscoverHidden ? null : +} + +export function Inner() { const t = useTheme() const {_} = useLingui() const gutters = useGutters(['base']) const trendingPrompt = Prompt.usePromptControl() - const {data: topics, error, isLoading} = useTrendingTopics() const {setTrendingDiscoverHidden} = useTrendingSettingsApi() + const {data: trending, error, isLoading} = useTrendingTopics() + const noTopics = !isLoading && !error && !trending?.topics - return ( + return error || noTopics ? null : ( - + Trending - BETA + + BETA + @@ -54,9 +74,13 @@ export function TrendingInterstitial() { - {isLoading ? null : error || !topics ? null : ( + {isLoading ? ( + Array(TRENDING_TOPICS_COUNT) + .fill(0) + .map((_n, i) => ) + ) : !trending?.topics ? null : ( <> - {topics.map(topic => ( + {trending.topics.map(topic => ( {({hovered}) => ( : null +} + +function Inner() { const t = useTheme() const gutters = useGutters([0, 'compact']) - const {data: topics, error, isLoading} = useTrendingTopics() + const {data: trending, error, isLoading} = useTrendingTopics() + const noTopics = !isLoading && !error && !trending?.topics - return ( - - {isLoading ? null : error || !topics ? null : ( - <> - {topics.map(topic => ( - - {({hovered}) => ( - - )} - - ))} - - )} - + return error || noTopics ? null : ( + <> + + + + + + Trending + + + + + BETA + + + + + What people are posting about now + + + + + + + {isLoading ? ( + Array(TRENDING_TOPICS_COUNT) + .fill(0) + .map((_, i) => ) + ) : !trending?.topics ? null : ( + <> + {trending.topics.map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} + + + ) } diff --git a/src/state/queries/service-config.ts b/src/state/queries/service-config.ts new file mode 100644 index 0000000000..8540227e3e --- /dev/null +++ b/src/state/queries/service-config.ts @@ -0,0 +1,35 @@ +import {useQuery} from '@tanstack/react-query' + +import {useAgent} from '#/state/session' + +type ServiceConfig = { + checkEmailConfirmed: boolean + trendingTopicsEnabled: boolean + trendingTopicsLangs: string[] +} + +export function useServiceConfigQuery() { + const agent = useAgent() + return useQuery({ + queryKey: ['service-config'], + queryFn: async () => { + try { + const {data} = await agent.api.app.bsky.unspecced.getConfig() + return { + checkEmailConfirmed: Boolean(data.checkEmailConfirmed), + // TODO + trendingTopicsEnabled: true, // Boolean(data.trendingTopicsEnabled), + // TODO + trendingTopicsLangs: ['en'], // data.trendingTopicsLangs ?? [], + } + } catch (e) { + return { + checkEmailConfirmed: false, + trendingTopicsEnabled: false, + trendingTopicsLangs: [], + } + } + }, + staleTime: 5 * 60 * 1000, + }) +} diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts index 6bae41b03d..ad2cfa220c 100644 --- a/src/state/queries/trending/useTrendingTopics.ts +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -10,14 +10,38 @@ export type TrendingTopic = { link: string } -export const trendingTopicsQueryKey = ['trending-topics'] +export const DEFAULT_LIMIT = 12 -// TODO ideally handle down-state faster +export const trendingTopicsQueryKey = ['trending-topics'] export function useTrendingTopics() { return useQuery({ queryKey: trendingTopicsQueryKey, async queryFn() { + /* + try { + const params = new URLSearchParams() + params.set('viewer', agent.session?.did || '') + const res = await fetch( + ``, + { + headers: { + Authorization: + 'Bearer ', + }, + }, + ) + + if (!res.ok) { + throw new Error('Failed to fetch trending topics') + } + + const data = await res.json() + return data.topics + } catch (e) { + console.error(e) + } + */ const topics: TrendingTopic[] = [ { topic: '#atproto', @@ -50,7 +74,10 @@ export function useTrendingTopics() { link: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/infreq', }, ] - return topics + return { + topics, + recommended: [], + } }, }) } diff --git a/src/state/shell/trending-config.tsx b/src/state/shell/trending-config.tsx new file mode 100644 index 0000000000..ccb4357066 --- /dev/null +++ b/src/state/shell/trending-config.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +import {useLanguagePrefs} from '#/state/preferences/languages' +import {useServiceConfigQuery} from '#/state/queries/service-config' + +type Context = { + enabled: boolean +} + +const Context = React.createContext({ + enabled: false, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const langPrefs = useLanguagePrefs() + const {data: config} = useServiceConfigQuery() + const ctx = React.useMemo(() => { + const serviceEnabled = Boolean(config?.trendingTopicsEnabled) + const languageIsSupported = langPrefs.contentLanguages.some(lang => { + return (config?.trendingTopicsLangs ?? []).includes(lang) + }) + return { + enabled: serviceEnabled && languageIsSupported, + } + }, [config, langPrefs]) + return {children} +} + +export function useTrendingConfig() { + return React.useContext(Context) +} diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx new file mode 100644 index 0000000000..22d8829876 --- /dev/null +++ b/src/state/trending-config.tsx @@ -0,0 +1,32 @@ +import React from 'react' + +import {useLanguagePrefs} from '#/state/preferences/languages' +import {useServiceConfigQuery} from '#/state/queries/service-config' + +type Context = { + enabled: boolean +} + +const Context = React.createContext({ + enabled: false, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const langPrefs = useLanguagePrefs() + const {data: config} = useServiceConfigQuery() + const ctx = React.useMemo(() => { + // TODO maybe default to true + const serviceEnabled = Boolean(config?.trendingTopicsEnabled) + const languageIsSupported = langPrefs.contentLanguages.some(lang => { + return (config?.trendingTopicsLangs ?? []).includes(lang) + }) + return { + enabled: serviceEnabled && languageIsSupported, + } + }, [config, langPrefs]) + return {children} +} + +export function useTrendingConfig() { + return React.useContext(Context) +} diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 2ce2f923f0..14a7f4348a 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -25,21 +25,18 @@ import { ProfileCardFeedLoadingPlaceholder, } from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, tokens,useTheme, ViewStyleProp} from '#/alf' +import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Button} from '#/components/Button' import * as FeedCard from '#/components/FeedCard' -import {GradientFill} from '#/components/GradientFill' import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Props as SVGIconProps} from '#/components/icons/common' import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' -import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' import {Loader} from '#/components/Loader' import * as StarterPackSuggestions from '#/components/StarterPack/Suggestions' -import * as TrendingTopics from '#/components/TrendingTopics' import {Text} from '#/components/Typography' -import {ExploreTrendingTopics} from "#/screens/Search/components/ExploreTrendingTopics" function SuggestedItemsHeader({ title, @@ -251,7 +248,6 @@ type ExploreScreenItems = | { type: 'trendingTopics' key: string - topics: string[] } | { type: 'suggestedStarterPacks' @@ -349,89 +345,8 @@ export function Explore() { i.push({ type: 'trendingTopics', key: `trending-topics`, - topics: [ - '#atproto', - 'South Korea', - 'Wired', - 'Basket Weaving', - 'Coup', - 'Chappel Roan', - 'the juice', - 'Superman', - '#FCF', - 'Open Web', - ], }) - /* - i.push({ - type: 'header', - key: 'starter-packs-header', - title: _(msg`Starter packs`), - description: _(msg`Find accounts to follow based on your interests.`), - icon: PersonGroup, - }) - i.push({ - type: 'suggestedStarterPacks', - key: 'suggested-starter-packs', - starterPacks: [ - { - uri: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.starterpack/3kvaphb2hpf2u', - cid: '', - indexedAt: '', - record: { - $type: 'app.bsky.graph.starterpack', - description: - 'Engineers who work on or discuss the ATProtocol, both at Bluesky and in the community', - list: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3kvaphagg4e2l', - name: 'ATProtocol Hackers', - }, - creator: { - did: 'did:plc:ragtjsm2j2vknwkz3zp4oxrd', - handle: 'pfrazee.com', - displayName: 'Paul Frazee', - }, - }, - { - uri: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.starterpack/3kvuh7u324m2c', - cid: '', - indexedAt: '', - record: { - $type: 'app.bsky.graph.starterpack', - createdAt: '2024-06-26T23:23:01.073Z', - description: - 'Legal practice, scholarship, journalism, and argument', - feeds: [], - list: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.list/3kvuh7twnjc2m', - name: 'Bluesky for Law', - }, - creator: { - did: 'did:plc:qrllvid7s54k4hnwtqxwetrf', - handle: 'joshuajfriedman.com', - displayName: 'Joshua J. Friedman', - }, - }, - { - uri: 'at://did:plc:7exlcsle4mjfhu3wnhcgizz6/app.bsky.graph.starterpack/3laotg2fqva2h', - cid: '', - indexedAt: '', - record: { - $type: 'app.bsky.graph.starterpack', - createdAt: '2024-11-11T17:22:22.743Z', - description: 'people who work at the website, the verge dot com', - list: 'at://did:plc:7exlcsle4mjfhu3wnhcgizz6/app.bsky.graph.list/3laotg2bjok2d', - name: 'The Verge staff', - }, - creator: { - did: 'did:plc:7exlcsle4mjfhu3wnhcgizz6', - handle: 'theverge.com', - displayName: 'The Verge', - }, - }, - ], - }) - */ - i.push({ type: 'header', key: 'suggested-follows-header', @@ -596,25 +511,7 @@ export function Explore() { ) } case 'trendingTopics': { - return ( - <> - - - BETA - - } - /> - - - - - ) + return } case 'suggestedStarterPacks': { return ( @@ -696,7 +593,7 @@ export function Explore() { } } }, - [_, t, moderationOpts], + [t, moderationOpts], ) // note: actually not a screen, instead it's nested within diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index 28f47d9810..8ca9b63f4e 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -6,7 +6,11 @@ import { useTrendingSettings, useTrendingSettingsApi, } from '#/state/preferences/trending' -import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics' +import { + DEFAULT_LIMIT as TRENDING_TOPICS_COUNT, + useTrendingTopics, +} from '#/state/queries/trending/useTrendingTopics' +import {useTrendingConfig} from '#/state/trending-config' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {Divider} from '#/components/Divider' @@ -21,8 +25,9 @@ import { import {Text} from '#/components/Typography' export function SidebarTrendingTopics() { + const {enabled} = useTrendingConfig() const {trendingSidebarHidden} = useTrendingSettings() - return trendingSidebarHidden ? null : + return !enabled ? null : trendingSidebarHidden ? null : } function Inner() { @@ -30,9 +35,8 @@ function Inner() { const {_} = useLingui() const trendingPrompt = Prompt.usePromptControl() const {setTrendingSidebarHidden} = useTrendingSettingsApi() - - const {data: topics, error, isLoading} = useTrendingTopics() - const noTopics = !isLoading && !error && !topics + const {data: trending, error, isLoading} = useTrendingTopics() + const noTopics = !isLoading && !error && !trending?.topics return error || noTopics ? null : ( <> @@ -61,12 +65,14 @@ function Inner() { {isLoading ? ( - Array(8) + Array(TRENDING_TOPICS_COUNT) .fill(0) - .map((_, i) => ) - ) : error || !topics ? null : ( + .map((_n, i) => ( + + )) + ) : !trending?.topics ? null : ( <> - {topics.map(topic => ( + {trending.topics.map(topic => ( {({hovered}) => ( Date: Mon, 16 Dec 2024 17:24:09 -0600 Subject: [PATCH 27/55] Centralize mute word handling --- src/components/TrendingTopics.tsx | 57 ++++++++----------- .../queries/trending/useTrendingTopics.ts | 15 ++++- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 41c086b83c..eb25d042cb 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -1,7 +1,6 @@ import React from 'react' import {View} from 'react-native' import {AtUri} from '@atproto/api' -import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -10,7 +9,6 @@ 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 {usePreferencesQuery} from '#/state/queries/preferences' import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Link as InternalLink, LinkProps} from '#/components/Link' @@ -96,7 +94,7 @@ export function TrendingTopic({ isSmall ? [a.text_sm] : [a.text_md, {paddingBottom: 1}], ]} numberOfLines={1}> - {topic.name} + {topic.displayName} ) @@ -152,56 +150,39 @@ export function TrendingTopicLink({ type ParsedTrendingTopic = | { - type: 'topic' | 'tag' + type: 'topic' | 'tag' | 'unknown' label: string - name: string + displayName: string url: string uri: undefined } | { type: 'profile' | 'feed' label: string - name: string + displayName: string url: string uri: AtUri } -export function useMutedWords() { - const {data: preferences} = usePreferencesQuery() - return React.useMemo(() => { - return preferences?.moderationPrefs?.mutedWords || [] - }, [preferences?.moderationPrefs?.mutedWords]) -} - -export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { +export function useTopic(raw: TrendingTopic): ParsedTrendingTopic { const {_} = useLingui() - const mutedWords = useMutedWords() - return React.useMemo(() => { - const {topic: name, link: uri} = raw - - if ( - hasMutedWord({ - mutedWords, - text: name, - }) - ) - return + const {topic: displayName, link: uri} = raw if (!uri.startsWith('at://')) { if (uri.startsWith('/search')) { return { type: 'topic', - label: _(msg`Browse posts about ${name}`), - name, + label: _(msg`Browse posts about ${displayName}`), + displayName, uri: undefined, url: uri, } } else if (uri.startsWith('/hashtag')) { return { type: 'tag', - label: _(msg`Browse posts tagged with ${name}`), - name: name.replace(/^#/, ''), + label: _(msg`Browse posts tagged with ${displayName}`), + displayName: displayName.replace(/^#/, ''), uri: undefined, url: uri, } @@ -212,8 +193,8 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { case 'app.bsky.actor.profile': { return { type: 'profile', - label: _(msg`View ${name}'s profile`), - name, + label: _(msg`View ${displayName}'s profile`), + displayName, uri: urip, url: makeProfileLink({did: urip.host, handle: urip.host}), } @@ -221,13 +202,21 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic | undefined { case 'app.bsky.feed.generator': { return { type: 'feed', - label: _(msg`Browse the ${name} feed`), - name, + label: _(msg`Browse the ${displayName} feed`), + displayName, uri: urip, url: feedUriToHref(uri), } } } } - }, [_, raw, mutedWords]) + + return { + type: 'unknown', + label: _(msg`Browse topic ${displayName}`), + displayName, + uri: undefined, + url: uri, + } + }, [_, raw]) } diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts index ad2cfa220c..2523c8ce56 100644 --- a/src/state/queries/trending/useTrendingTopics.ts +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -1,7 +1,10 @@ +import React from 'react' +import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' import {useQuery} from '@tanstack/react-query' // TEMP import {makeSearchLink} from '#/lib/routes/links' +import {usePreferencesQuery} from '#/state/queries/preferences' export type TrendingTopic = { topic: string @@ -15,6 +18,11 @@ export const DEFAULT_LIMIT = 12 export const trendingTopicsQueryKey = ['trending-topics'] export function useTrendingTopics() { + const {data: preferences} = usePreferencesQuery() + const mutedWords = React.useMemo(() => { + return preferences?.moderationPrefs?.mutedWords || [] + }, [preferences?.moderationPrefs]) + return useQuery({ queryKey: trendingTopicsQueryKey, async queryFn() { @@ -75,7 +83,12 @@ export function useTrendingTopics() { }, ] return { - topics, + topics: topics.filter(t => { + return !hasMutedWord({ + mutedWords, + text: t.topic + ' ' + t.displayName + ' ' + t.description, + }) + }), recommended: [], } }, From e03a52b6c4f60c754414255a238af8497ea73f08 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:32:56 -0600 Subject: [PATCH 28/55] Stale time to 30m --- src/state/queries/index.ts | 1 + src/state/queries/trending/useTrendingTopics.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/state/queries/index.ts b/src/state/queries/index.ts index 0635bf316b..d4b9d94c45 100644 --- a/src/state/queries/index.ts +++ b/src/state/queries/index.ts @@ -6,6 +6,7 @@ export const STALE = { MINUTES: { ONE: 1e3 * 60, FIVE: 1e3 * 60 * 5, + THIRTY: 1e3 * 60 * 30, }, HOURS: { ONE: 1e3 * 60 * 60, diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts index 2523c8ce56..5f44082691 100644 --- a/src/state/queries/trending/useTrendingTopics.ts +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -4,6 +4,7 @@ import {useQuery} from '@tanstack/react-query' // TEMP import {makeSearchLink} from '#/lib/routes/links' +import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' export type TrendingTopic = { @@ -24,6 +25,8 @@ export function useTrendingTopics() { }, [preferences?.moderationPrefs]) return useQuery({ + refetchOnWindowFocus: true, + staleTime: STALE.MINUTES.THIRTY, queryKey: trendingTopicsQueryKey, async queryFn() { /* From 82a069fdafa678e32df89206712958432d595f15 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:41:00 -0600 Subject: [PATCH 29/55] Cache enabled value for reloads, use real data for service config --- src/state/queries/service-config.ts | 10 +++++----- src/state/trending-config.tsx | 19 +++++++++++++------ src/storage/schema.ts | 1 + 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/state/queries/service-config.ts b/src/state/queries/service-config.ts index 8540227e3e..fc053d2d04 100644 --- a/src/state/queries/service-config.ts +++ b/src/state/queries/service-config.ts @@ -1,5 +1,6 @@ import {useQuery} from '@tanstack/react-query' +import {STALE} from '#/state/queries' import {useAgent} from '#/state/session' type ServiceConfig = { @@ -11,16 +12,16 @@ type ServiceConfig = { export function useServiceConfigQuery() { const agent = useAgent() return useQuery({ + refetchOnWindowFocus: true, + staleTime: STALE.MINUTES.ONE, queryKey: ['service-config'], queryFn: async () => { try { const {data} = await agent.api.app.bsky.unspecced.getConfig() return { checkEmailConfirmed: Boolean(data.checkEmailConfirmed), - // TODO - trendingTopicsEnabled: true, // Boolean(data.trendingTopicsEnabled), - // TODO - trendingTopicsLangs: ['en'], // data.trendingTopicsLangs ?? [], + trendingTopicsEnabled: Boolean(data.trendingTopicsEnabled), + trendingTopicsLangs: (data.trendingTopicsLangs ?? []) as string[], } } catch (e) { return { @@ -30,6 +31,5 @@ export function useServiceConfigQuery() { } } }, - staleTime: 5 * 60 * 1000, }) } diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 22d8829876..30176b88a3 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -2,6 +2,7 @@ import React from 'react' import {useLanguagePrefs} from '#/state/preferences/languages' import {useServiceConfigQuery} from '#/state/queries/service-config' +import {device} from '#/storage' type Context = { enabled: boolean @@ -13,17 +14,23 @@ const Context = React.createContext({ export function Provider({children}: React.PropsWithChildren<{}>) { const langPrefs = useLanguagePrefs() - const {data: config} = useServiceConfigQuery() + // refetches at most every minute + const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { - // TODO maybe default to true + // previously cached value + const cachedEnabled = device.get(['trendingBetaEnabled']) + if (isInitialLoad) { + return {enabled: Boolean(cachedEnabled)} + } const serviceEnabled = Boolean(config?.trendingTopicsEnabled) const languageIsSupported = langPrefs.contentLanguages.some(lang => { return (config?.trendingTopicsLangs ?? []).includes(lang) }) - return { - enabled: serviceEnabled && languageIsSupported, - } - }, [config, langPrefs]) + const enabled = serviceEnabled && languageIsSupported + // update cache + device.set(['trendingBetaEnabled'], enabled) + return {enabled} + }, [isInitialLoad, config, langPrefs]) return {children} } diff --git a/src/storage/schema.ts b/src/storage/schema.ts index cf410c77de..cfca9131c7 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -8,4 +8,5 @@ export type Device = { geolocation?: { countryCode: string | undefined } + trendingBetaEnabled: boolean } From 369373d2aa4b8b93c008caba2225e412d811fb10 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:47:24 -0600 Subject: [PATCH 30/55] Remove broadcast hack --- src/components/StarterPack/Suggestions.tsx | 64 ---------------------- src/state/persisted/index.web.ts | 6 +- src/view/screens/Search/Explore.tsx | 16 ------ 3 files changed, 2 insertions(+), 84 deletions(-) delete mode 100644 src/components/StarterPack/Suggestions.tsx diff --git a/src/components/StarterPack/Suggestions.tsx b/src/components/StarterPack/Suggestions.tsx deleted file mode 100644 index 53750bd1a7..0000000000 --- a/src/components/StarterPack/Suggestions.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import {View} from 'react-native' -import {ScrollView} from 'react-native-gesture-handler' -import {AppBskyGraphDefs} from '@atproto/api' - -import {logger} from '#/logger' -import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {atoms as a, useBreakpoints, web} from '#/alf' -import {Embed} from './StarterPackCard' - -const DESKTOP_CARD_WIDTH = 400 -const MOBILE_CARD_WIDTH = 300 - -export function Grid({ - isSuggestionsLoading, - error, - starterPacks, -}: { - isSuggestionsLoading: boolean - starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] - error: Error | null -}) { - const moderationOpts = useModerationOpts() - const {gtMobile} = useBreakpoints() - const width = gtMobile ? DESKTOP_CARD_WIDTH : MOBILE_CARD_WIDTH - const isLoading = isSuggestionsLoading || !moderationOpts - const maxLength = gtMobile ? 4 : 6 - - const content = isLoading ? ( - Array(maxLength) - .fill(0) - .map((_, i) => ( - - {/* TODO */} - - )) - ) : error || !starterPacks.length ? null : ( - <> - {starterPacks.slice(0, maxLength).map(starterPack => ( - - - - ))} - - ) - - if (error || (!isLoading && !starterPacks.length)) { - logger.debug(`Not enough starter packs to show suggested starter packs`) - return null - } - - return ( - - - {content} - - - ) -} diff --git a/src/state/persisted/index.web.ts b/src/state/persisted/index.web.ts index 5e3430f6ee..f28b197715 100644 --- a/src/state/persisted/index.web.ts +++ b/src/state/persisted/index.web.ts @@ -63,10 +63,8 @@ export async function write( [key]: value, }) writeToStorage(_state) - setTimeout(() => { - broadcast.postMessage({event: {type: UPDATE_EVENT, key}}) - broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading - }, 0) + broadcast.postMessage({event: {type: UPDATE_EVENT, key}}) + broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading } write satisfies PersistedApi['write'] diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 14a7f4348a..35499613db 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native' import { AppBskyActorDefs, AppBskyFeedDefs, - AppBskyGraphDefs, moderateProfile, ModerationDecision, ModerationOpts, @@ -35,7 +34,6 @@ import {Props as SVGIconProps} from '#/components/icons/common' import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' import {Loader} from '#/components/Loader' -import * as StarterPackSuggestions from '#/components/StarterPack/Suggestions' import {Text} from '#/components/Typography' function SuggestedItemsHeader({ @@ -249,11 +247,6 @@ type ExploreScreenItems = type: 'trendingTopics' key: string } - | { - type: 'suggestedStarterPacks' - key: string - starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] - } | { type: 'profile' key: string @@ -513,15 +506,6 @@ export function Explore() { case 'trendingTopics': { return } - case 'suggestedStarterPacks': { - return ( - - ) - } case 'profile': { return ( From 305a9e63466732823ff95fdd74e8c7dd2f191312 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:50:44 -0600 Subject: [PATCH 31/55] Remove titleChild --- src/view/screens/Search/Explore.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 35499613db..c1ebc10146 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -38,13 +38,11 @@ import {Text} from '#/components/Typography' function SuggestedItemsHeader({ title, - titleChild, description, style, icon: Icon, }: { title: string - titleChild?: React.ReactNode description: string icon: React.ComponentType } & ViewStyleProp) { @@ -68,7 +66,6 @@ function SuggestedItemsHeader({ style={{marginLeft: -2}} /> {title} - {titleChild} {description} From 57ff6f184d7953a66bd88c3c86cabcc27ed59eb4 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 17:53:06 -0600 Subject: [PATCH 32/55] Gate settings too --- .../Settings/ContentAndMediaSettings.tsx | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 7782a94ff8..0f7e679dc4 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -13,6 +13,7 @@ import { useTrendingSettings, useTrendingSettingsApi, } from '#/state/preferences/trending' +import {useTrendingConfig} from '#/state/trending-config' import * as SettingsList from '#/screens/Settings/components/SettingsList' import * as Toggle from '#/components/forms/Toggle' import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' @@ -34,6 +35,7 @@ export function ContentAndMediaSettingsScreen({}: Props) { const setAutoplayDisabledPref = useSetAutoplayDisabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() + const {enabled: trendingEnabled} = useTrendingConfig() const {trendingSidebarHidden, trendingDiscoverHidden} = useTrendingSettings() const {setTrendingSidebarHidden, setTrendingDiscoverHidden} = useTrendingSettingsApi() @@ -112,35 +114,39 @@ export function ContentAndMediaSettingsScreen({}: Props) { - - {!isNative && ( - setTrendingSidebarHidden(!value)}> - - - - Show trending topics in your sidebar - - - - + {trendingEnabled && ( + <> + + {!isNative && ( + setTrendingSidebarHidden(!value)}> + + + + Show trending topics in your sidebar + + + + + )} + setTrendingDiscoverHidden(!value)}> + + + + Show trending topics in your feed + + + + + )} - setTrendingDiscoverHidden(!value)}> - - - - Show trending topics in your feed - - - - From cd52e1bcdaee7988fb662f3ef68afaa33974c847 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 18:18:53 -0600 Subject: [PATCH 33/55] Update package, rm langs --- package.json | 2 +- src/state/queries/service-config.ts | 9 ++--- src/state/trending-config.tsx | 10 +---- yarn.lock | 60 +++++++++-------------------- 4 files changed, 24 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index f5f4601d6c..ff2223b489 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.13.20", + "@atproto/api": "^0.13.21", "@bitdrift/react-native": "0.4.0", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", diff --git a/src/state/queries/service-config.ts b/src/state/queries/service-config.ts index fc053d2d04..8fc7049bbe 100644 --- a/src/state/queries/service-config.ts +++ b/src/state/queries/service-config.ts @@ -5,8 +5,7 @@ import {useAgent} from '#/state/session' type ServiceConfig = { checkEmailConfirmed: boolean - trendingTopicsEnabled: boolean - trendingTopicsLangs: string[] + topicsEnabled: boolean } export function useServiceConfigQuery() { @@ -20,14 +19,12 @@ export function useServiceConfigQuery() { const {data} = await agent.api.app.bsky.unspecced.getConfig() return { checkEmailConfirmed: Boolean(data.checkEmailConfirmed), - trendingTopicsEnabled: Boolean(data.trendingTopicsEnabled), - trendingTopicsLangs: (data.trendingTopicsLangs ?? []) as string[], + topicsEnabled: Boolean(data.topicsEnabled), } } catch (e) { return { checkEmailConfirmed: false, - trendingTopicsEnabled: false, - trendingTopicsLangs: [], + topicsEnabled: false, } } }, diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 30176b88a3..a1845f0de4 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -1,6 +1,5 @@ import React from 'react' -import {useLanguagePrefs} from '#/state/preferences/languages' import {useServiceConfigQuery} from '#/state/queries/service-config' import {device} from '#/storage' @@ -13,7 +12,6 @@ const Context = React.createContext({ }) export function Provider({children}: React.PropsWithChildren<{}>) { - const langPrefs = useLanguagePrefs() // refetches at most every minute const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { @@ -22,15 +20,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) { if (isInitialLoad) { return {enabled: Boolean(cachedEnabled)} } - const serviceEnabled = Boolean(config?.trendingTopicsEnabled) - const languageIsSupported = langPrefs.contentLanguages.some(lang => { - return (config?.trendingTopicsLangs ?? []).includes(lang) - }) - const enabled = serviceEnabled && languageIsSupported + const enabled = Boolean(config?.topicsEnabled) // update cache device.set(['trendingBetaEnabled'], enabled) return {enabled} - }, [isInitialLoad, config, langPrefs]) + }, [isInitialLoad, config]) return {children} } diff --git a/yarn.lock b/yarn.lock index f6598f8b8d..61ed0f66c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,6 +72,20 @@ tlds "^1.234.0" zod "^3.23.8" +"@atproto/api@^0.13.21": + version "0.13.21" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.21.tgz#8ee27a07e5a024b5bf32408d9bd623dd598ad1cc" + integrity sha512-iOxSj2YS3Fx9IPz1NivKrSsdYPNbBgpnUH7+WhKYAMvDFDUe2PZe7taau8wsUjJAu/H3S0Mk2TDh5e/7tCRwHA== + dependencies: + "@atproto/common-web" "^0.3.1" + "@atproto/lexicon" "^0.4.4" + "@atproto/syntax" "^0.3.1" + "@atproto/xrpc" "^0.6.5" + await-lock "^2.2.2" + multiformats "^9.9.0" + tlds "^1.234.0" + zod "^3.23.8" + "@atproto/aws@^0.2.10": version "0.2.10" resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.10.tgz#e0b888fd50308cc24b7086cf3ec209587c13bbe4" @@ -3247,7 +3261,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== @@ -3308,19 +3322,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== - dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" - "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" - debug "^4.3.1" - globals "^11.1.0" - "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" @@ -17456,16 +17457,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17565,7 +17557,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17579,13 +17571,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -18860,7 +18845,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18878,15 +18863,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 4a8fb8255d114cf17a1cfbf1a267f0488f4c8c44 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 16 Dec 2024 18:34:33 -0600 Subject: [PATCH 34/55] Add feature gate --- src/lib/statsig/gates.ts | 1 + src/state/trending-config.tsx | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index a6c2492548..455a703458 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -4,3 +4,4 @@ export type Gate = | 'debug_subscriptions' | 'new_postonboarding' | 'remove_show_latest_button' + | 'trending_topics_beta' diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index a1845f0de4..470526d2cf 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -1,5 +1,6 @@ import React from 'react' +import {useGate} from '#/lib/statsig/statsig' import {useServiceConfigQuery} from '#/state/queries/service-config' import {device} from '#/storage' @@ -12,19 +13,40 @@ const Context = React.createContext({ }) export function Provider({children}: React.PropsWithChildren<{}>) { + const gate = useGate() // refetches at most every minute const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { - // previously cached value + /* + * While loading, use cached value + */ const cachedEnabled = device.get(['trendingBetaEnabled']) if (isInitialLoad) { return {enabled: Boolean(cachedEnabled)} } + + /* + * Doing an extra check here to reduce hits to statsig. If it's disabled on + * the server, we can exit early. + */ const enabled = Boolean(config?.topicsEnabled) + if (!enabled) { + // cache for next reload + device.set(['trendingBetaEnabled'], enabled) + return {enabled: false} + } + + /* + * Service is enabled, but also check statsig in case we're rolling back. + */ + const gateEnabled = gate('trending_topics_beta') + const _enabled = enabled && gateEnabled + // update cache - device.set(['trendingBetaEnabled'], enabled) - return {enabled} - }, [isInitialLoad, config]) + device.set(['trendingBetaEnabled'], _enabled) + + return {enabled: _enabled} + }, [isInitialLoad, config, gate]) return {children} } From b269b1979d6046d0979a457ef9311493b1e7d516 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 11:28:31 -0600 Subject: [PATCH 35/55] Only english during beta period --- src/state/trending-config.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 470526d2cf..4dcbac4c99 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -1,6 +1,7 @@ import React from 'react' import {useGate} from '#/lib/statsig/statsig' +import {useLanguagePrefs} from '#/state/preferences/languages' import {useServiceConfigQuery} from '#/state/queries/service-config' import {device} from '#/storage' @@ -14,9 +15,17 @@ const Context = React.createContext({ export function Provider({children}: React.PropsWithChildren<{}>) { const gate = useGate() + const langPrefs = useLanguagePrefs() // refetches at most every minute const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { + /* + * Only English during beta period + */ + if (!langPrefs.contentLanguages.includes('en')) { + return {enabled: false} + } + /* * While loading, use cached value */ @@ -46,7 +55,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { device.set(['trendingBetaEnabled'], _enabled) return {enabled: _enabled} - }, [isInitialLoad, config, gate]) + }, [isInitialLoad, config, gate, langPrefs.contentLanguages]) return {children} } From 3bf151d9b6a96df99f8af88b27b37fa5065b6b1f Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 11:41:58 -0600 Subject: [PATCH 36/55] Hook up real data --- .../queries/trending/useTrendingTopics.ts | 85 ++++++------------- src/state/trending-config.tsx | 7 +- 2 files changed, 32 insertions(+), 60 deletions(-) diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts index 5f44082691..05ac7f6b1b 100644 --- a/src/state/queries/trending/useTrendingTopics.ts +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -2,10 +2,12 @@ import React from 'react' import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' import {useQuery} from '@tanstack/react-query' -// TEMP -import {makeSearchLink} from '#/lib/routes/links' import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' +import {useAgent} from '#/state/session' + +const HOST = `` +const TOKEN = `` export type TrendingTopic = { topic: string @@ -14,11 +16,12 @@ export type TrendingTopic = { link: string } -export const DEFAULT_LIMIT = 12 +export const DEFAULT_LIMIT = 14 export const trendingTopicsQueryKey = ['trending-topics'] export function useTrendingTopics() { + const agent = useAgent() const {data: preferences} = usePreferencesQuery() const mutedWords = React.useMemo(() => { return preferences?.moderationPrefs?.mutedWords || [] @@ -29,62 +32,23 @@ export function useTrendingTopics() { staleTime: STALE.MINUTES.THIRTY, queryKey: trendingTopicsQueryKey, async queryFn() { - /* - try { - const params = new URLSearchParams() - params.set('viewer', agent.session?.did || '') - const res = await fetch( - ``, - { - headers: { - Authorization: - 'Bearer ', - }, - }, - ) + const params = new URLSearchParams() + params.set('viewer', agent.session?.did || '') + params.set('limit', String(DEFAULT_LIMIT)) + const res = await fetch(`${HOST}?${params.toString()}`, { + headers: { + Authorization: `Bearer ${TOKEN}`, + }, + }) - if (!res.ok) { - throw new Error('Failed to fetch trending topics') - } - - const data = await res.json() - return data.topics - } catch (e) { - console.error(e) + if (!res.ok) { + throw new Error('Failed to fetch trending topics') + } + + const {topics, recommended} = (await res.json()) as { + topics: TrendingTopic[] + recommended: TrendingTopic[] } - */ - const topics: TrendingTopic[] = [ - { - topic: '#atproto', - displayName: '#atproto', - description: '', - link: '/hashtag/atproto', - }, - { - topic: 'South Korea', - displayName: 'South Korea', - description: '', - link: makeSearchLink({query: 'South Korea'}), - }, - { - topic: 'Paul Frazee', - displayName: 'Paul Frazee', - description: '', - link: 'at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.actor.profile/self', - }, - { - topic: 'Wired', - displayName: 'Wired', - description: '', - link: makeSearchLink({query: 'Wired'}), - }, - { - topic: 'Quiet Posters', - displayName: 'Quiet Posters', - description: '', - link: 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/infreq', - }, - ] return { topics: topics.filter(t => { return !hasMutedWord({ @@ -92,7 +56,12 @@ export function useTrendingTopics() { text: t.topic + ' ' + t.displayName + ' ' + t.description, }) }), - recommended: [], + recommended: recommended.filter(t => { + return !hasMutedWord({ + mutedWords, + text: t.topic + ' ' + t.displayName + ' ' + t.description, + }) + }), } }, }) diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 4dcbac4c99..4676c4db44 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -22,7 +22,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) { /* * Only English during beta period */ - if (!langPrefs.contentLanguages.includes('en')) { + if ( + !!langPrefs.contentLanguages.length && + !langPrefs.contentLanguages.includes('en') + ) { return {enabled: false} } @@ -48,7 +51,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { /* * Service is enabled, but also check statsig in case we're rolling back. */ - const gateEnabled = gate('trending_topics_beta') + const gateEnabled = __DEV__ || gate('trending_topics_beta') const _enabled = enabled && gateEnabled // update cache From 87e88c31e88b0c5c4c1b49635f7777ca2e6b9df1 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 11:53:51 -0600 Subject: [PATCH 37/55] Tweak config --- src/state/trending-config.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 4676c4db44..1f3e8d4254 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -19,6 +19,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) { // refetches at most every minute const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { + if (__DEV__) { + return {enabled: true} + } + /* * Only English during beta period */ @@ -51,7 +55,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { /* * Service is enabled, but also check statsig in case we're rolling back. */ - const gateEnabled = __DEV__ || gate('trending_topics_beta') + const gateEnabled = gate('trending_topics_beta') const _enabled = enabled && gateEnabled // update cache From 7cb96949716acbf36a10a37a10a94ebded8d4e0e Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 11:54:05 -0600 Subject: [PATCH 38/55] Straight passthrough links --- src/components/TrendingTopics.tsx | 55 ++++++++++++++++--------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index eb25d042cb..6881f24bd5 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -4,8 +4,8 @@ import {AtUri} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {makeProfileLink} from '#/lib/routes/links' -import {feedUriToHref} from '#/lib/strings/url-helpers' +// 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' @@ -22,8 +22,6 @@ export function TrendingTopic({ const t = useTheme() const topic = useTopic(raw) - if (!topic) return null - const isSmall = size === 'small' // const hasAvi = topic.type === 'feed' || topic.type === 'profile' // const aviSize = isSmall ? 16 : 20 @@ -139,8 +137,6 @@ export function TrendingTopicLink({ } & Omit) { const topic = useTopic(raw) - if (!topic) return null - return ( {children} @@ -167,28 +163,32 @@ type ParsedTrendingTopic = export function useTopic(raw: TrendingTopic): ParsedTrendingTopic { const {_} = useLingui() return React.useMemo(() => { - const {topic: displayName, link: uri} = raw + const {topic: displayName, link} = raw - if (!uri.startsWith('at://')) { - if (uri.startsWith('/search')) { - return { - type: 'topic', - label: _(msg`Browse posts about ${displayName}`), - displayName, - uri: undefined, - url: uri, - } - } else if (uri.startsWith('/hashtag')) { - return { - type: 'tag', - label: _(msg`Browse posts tagged with ${displayName}`), - displayName: displayName.replace(/^#/, ''), - uri: undefined, - url: uri, - } + 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(uri) + const urip = new AtUri(link) switch (urip.collection) { case 'app.bsky.actor.profile': { return { @@ -205,18 +205,19 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic { label: _(msg`Browse the ${displayName} feed`), displayName, uri: urip, - url: feedUriToHref(uri), + url: feedUriToHref(link), } } } } + */ return { type: 'unknown', label: _(msg`Browse topic ${displayName}`), displayName, uri: undefined, - url: uri, + url: link, } }, [_, raw]) } From eda3c35155c809a814803b427147cf347c99a2cb Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 13:15:06 -0600 Subject: [PATCH 39/55] Hook up prod agent --- .../queries/trending/useTrendingTopics.ts | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/state/queries/trending/useTrendingTopics.ts b/src/state/queries/trending/useTrendingTopics.ts index 05ac7f6b1b..310f64e9f2 100644 --- a/src/state/queries/trending/useTrendingTopics.ts +++ b/src/state/queries/trending/useTrendingTopics.ts @@ -1,4 +1,5 @@ import React from 'react' +import {AppBskyUnspeccedDefs} from '@atproto/api' import {hasMutedWord} from '@atproto/api/dist/moderation/mutewords' import {useQuery} from '@tanstack/react-query' @@ -6,15 +7,7 @@ import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' import {useAgent} from '#/state/session' -const HOST = `` -const TOKEN = `` - -export type TrendingTopic = { - topic: string - displayName: string - description: string - link: string -} +export type TrendingTopic = AppBskyUnspeccedDefs.TrendingTopic export const DEFAULT_LIMIT = 14 @@ -32,23 +25,11 @@ export function useTrendingTopics() { staleTime: STALE.MINUTES.THIRTY, queryKey: trendingTopicsQueryKey, async queryFn() { - const params = new URLSearchParams() - params.set('viewer', agent.session?.did || '') - params.set('limit', String(DEFAULT_LIMIT)) - const res = await fetch(`${HOST}?${params.toString()}`, { - headers: { - Authorization: `Bearer ${TOKEN}`, - }, + const {data} = await agent.api.app.bsky.unspecced.getTrendingTopics({ + limit: DEFAULT_LIMIT, }) - if (!res.ok) { - throw new Error('Failed to fetch trending topics') - } - - const {topics, recommended} = (await res.json()) as { - topics: TrendingTopic[] - recommended: TrendingTopic[] - } + const {topics, suggested} = data return { topics: topics.filter(t => { return !hasMutedWord({ @@ -56,7 +37,7 @@ export function useTrendingTopics() { text: t.topic + ' ' + t.displayName + ' ' + t.description, }) }), - recommended: recommended.filter(t => { + suggested: suggested.filter(t => { return !hasMutedWord({ mutedWords, text: t.topic + ' ' + t.displayName + ' ' + t.description, From 90ed779cc3c39ab8bf7dc99abcf8f85293a6cd86 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 13:15:15 -0600 Subject: [PATCH 40/55] Fix no-show logic --- src/components/interstitials/Trending.tsx | 2 +- src/screens/Search/components/ExploreTrendingTopics.tsx | 2 +- src/view/shell/desktop/SidebarTrendingTopics.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index a19d1dfd80..cec5e45c6a 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -37,7 +37,7 @@ export function Inner() { const trendingPrompt = Prompt.usePromptControl() const {setTrendingDiscoverHidden} = useTrendingSettingsApi() const {data: trending, error, isLoading} = useTrendingTopics() - const noTopics = !isLoading && !error && !trending?.topics + const noTopics = !isLoading && !error && !trending?.topics?.length return error || noTopics ? null : ( diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index 8ca9b63f4e..55324687f9 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -36,7 +36,7 @@ function Inner() { const trendingPrompt = Prompt.usePromptControl() const {setTrendingSidebarHidden} = useTrendingSettingsApi() const {data: trending, error, isLoading} = useTrendingTopics() - const noTopics = !isLoading && !error && !trending?.topics + const noTopics = !isLoading && !error && !trending?.topics?.length return error || noTopics ? null : ( <> From b3f253dbbac90c0e989bd39df9df82b51300102b Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 13:58:09 -0600 Subject: [PATCH 41/55] Up config query to 5 min --- src/state/queries/service-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/queries/service-config.ts b/src/state/queries/service-config.ts index 8fc7049bbe..9a9db78659 100644 --- a/src/state/queries/service-config.ts +++ b/src/state/queries/service-config.ts @@ -12,7 +12,7 @@ export function useServiceConfigQuery() { const agent = useAgent() return useQuery({ refetchOnWindowFocus: true, - staleTime: STALE.MINUTES.ONE, + staleTime: STALE.MINUTES.FIVE, queryKey: ['service-config'], queryFn: async () => { try { From d0b6000d2213ca926e7791b4455e6410ac72394f Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 13:58:59 -0600 Subject: [PATCH 42/55] Remove old file --- src/state/shell/trending-config.tsx | 31 ----------------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/state/shell/trending-config.tsx diff --git a/src/state/shell/trending-config.tsx b/src/state/shell/trending-config.tsx deleted file mode 100644 index ccb4357066..0000000000 --- a/src/state/shell/trending-config.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' - -import {useLanguagePrefs} from '#/state/preferences/languages' -import {useServiceConfigQuery} from '#/state/queries/service-config' - -type Context = { - enabled: boolean -} - -const Context = React.createContext({ - enabled: false, -}) - -export function Provider({children}: React.PropsWithChildren<{}>) { - const langPrefs = useLanguagePrefs() - const {data: config} = useServiceConfigQuery() - const ctx = React.useMemo(() => { - const serviceEnabled = Boolean(config?.trendingTopicsEnabled) - const languageIsSupported = langPrefs.contentLanguages.some(lang => { - return (config?.trendingTopicsLangs ?? []).includes(lang) - }) - return { - enabled: serviceEnabled && languageIsSupported, - } - }, [config, langPrefs]) - return {children} -} - -export function useTrendingConfig() { - return React.useContext(Context) -} From 511a13ec2f1c73db448bf912e718e20a6f4957f0 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 13:59:17 -0600 Subject: [PATCH 43/55] Remove comment --- src/state/trending-config.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/state/trending-config.tsx b/src/state/trending-config.tsx index 1f3e8d4254..a7694993fb 100644 --- a/src/state/trending-config.tsx +++ b/src/state/trending-config.tsx @@ -16,7 +16,6 @@ const Context = React.createContext({ export function Provider({children}: React.PropsWithChildren<{}>) { const gate = useGate() const langPrefs = useLanguagePrefs() - // refetches at most every minute const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery() const ctx = React.useMemo(() => { if (__DEV__) { From 30207577b715bca578cbef8fd9942b6de9b043cc Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 14:13:20 -0600 Subject: [PATCH 44/55] Remove stray flex_1 --- src/view/shell/desktop/Feeds.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 83b5420ce7..3bf9a80e3d 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -32,7 +32,6 @@ export function DesktopFeeds() { return ( Date: Tue, 17 Dec 2024 17:39:54 -0600 Subject: [PATCH 45/55] Make trending setting global --- src/components/interstitials/Trending.tsx | 12 +++--- .../components/ExploreTrendingTopics.tsx | 4 +- .../Settings/ContentAndMediaSettings.tsx | 30 ++++----------- src/state/persisted/schema.ts | 14 +------ src/state/preferences/trending.tsx | 37 +++++-------------- src/view/com/posts/PostFeed.tsx | 6 +-- .../shell/desktop/SidebarTrendingTopics.tsx | 10 ++--- 7 files changed, 36 insertions(+), 77 deletions(-) diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index cec5e45c6a..7809365e5c 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -26,8 +26,8 @@ import {Text} from '#/components/Typography' export function TrendingInterstitial() { const {enabled} = useTrendingConfig() - const {trendingDiscoverHidden} = useTrendingSettings() - return !enabled ? null : trendingDiscoverHidden ? null : + const {trendingDisabled} = useTrendingSettings() + return enabled && !trendingDisabled ? : null } export function Inner() { @@ -35,7 +35,7 @@ export function Inner() { const {_} = useLingui() const gutters = useGutters(['base']) const trendingPrompt = Prompt.usePromptControl() - const {setTrendingDiscoverHidden} = useTrendingSettingsApi() + const {setTrendingDisabled} = useTrendingSettingsApi() const {data: trending, error, isLoading} = useTrendingTopics() const noTopics = !isLoading && !error && !trending?.topics?.length @@ -101,12 +101,12 @@ export function Inner() { setTrendingDiscoverHidden(true)} + onConfirm={() => setTrendingDisabled(true)} /> ) diff --git a/src/screens/Search/components/ExploreTrendingTopics.tsx b/src/screens/Search/components/ExploreTrendingTopics.tsx index 4d928e9ea3..8831d39658 100644 --- a/src/screens/Search/components/ExploreTrendingTopics.tsx +++ b/src/screens/Search/components/ExploreTrendingTopics.tsx @@ -2,6 +2,7 @@ import {View} from 'react-native' import {Trans} from '@lingui/macro' import {isWeb} from '#/platform/detection' +import {useTrendingSettings} from '#/state/preferences/trending' import { DEFAULT_LIMIT as TRENDING_TOPICS_COUNT, useTrendingTopics, @@ -19,7 +20,8 @@ import {Text} from '#/components/Typography' export function ExploreTrendingTopics() { const {enabled} = useTrendingConfig() - return enabled ? : null + const {trendingDisabled} = useTrendingSettings() + return enabled && !trendingDisabled ? : null } function Inner() { diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 0f7e679dc4..bdbe1d191b 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -36,9 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) { const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() const {enabled: trendingEnabled} = useTrendingConfig() - const {trendingSidebarHidden, trendingDiscoverHidden} = useTrendingSettings() - const {setTrendingSidebarHidden, setTrendingDiscoverHidden} = - useTrendingSettingsApi() + const {trendingDisabled} = useTrendingSettings() + const {setTrendingDisabled} = useTrendingSettingsApi() return ( @@ -117,30 +116,15 @@ export function ContentAndMediaSettingsScreen({}: Props) { {trendingEnabled && ( <> - {!isNative && ( - setTrendingSidebarHidden(!value)}> - - - - Show trending topics in your sidebar - - - - - )} setTrendingDiscoverHidden(!value)}> + name="show_trending_topics" + label={_(msg`Enable trending topics`)} + value={!trendingDisabled} + onChange={value => setTrendingDisabled(!value)}> - Show trending topics in your feed + Enable trending topics diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index e4852e2f72..0a9e5b2c07 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -125,12 +125,7 @@ const schema = z.object({ subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), - - /* - * Trending - */ - trendingSidebarHidden: z.boolean().optional(), - trendingDiscoverHidden: z.boolean().optional(), + trendingDisabled: z.boolean().optional(), }) export type Schema = z.infer @@ -176,12 +171,7 @@ export const defaults: Schema = { kawaii: false, hasCheckedForStarterPack: false, subtitlesEnabled: true, - - /* - * Trending - */ - trendingSidebarHidden: false, - trendingDiscoverHidden: false, + trendingDisabled: false, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/trending.tsx b/src/state/preferences/trending.tsx index 61a026c22e..bf5d8f13cc 100644 --- a/src/state/preferences/trending.tsx +++ b/src/state/preferences/trending.tsx @@ -3,31 +3,19 @@ import React from 'react' import * as persisted from '#/state/persisted' type StateContext = { - trendingSidebarHidden: Exclude< - persisted.Schema['trendingSidebarHidden'], - undefined - > - trendingDiscoverHidden: Exclude< - persisted.Schema['trendingDiscoverHidden'], - undefined - > + trendingDisabled: Exclude } type ApiContext = { - setTrendingSidebarHidden( - hidden: Exclude, - ): void - setTrendingDiscoverHidden( - hidden: Exclude, + setTrendingDisabled( + hidden: Exclude, ): void } const StateContext = React.createContext({ - trendingSidebarHidden: Boolean(persisted.defaults.trendingSidebarHidden), - trendingDiscoverHidden: Boolean(persisted.defaults.trendingDiscoverHidden), + trendingDisabled: Boolean(persisted.defaults.trendingDisabled), }) const ApiContext = React.createContext({ - setTrendingSidebarHidden() {}, - setTrendingDiscoverHidden() {}, + setTrendingDisabled() {}, }) function usePersistedBooleanValue(key: T) { @@ -53,21 +41,16 @@ function usePersistedBooleanValue(key: T) { } export function Provider({children}: React.PropsWithChildren<{}>) { - const [trendingSidebarHidden, setTrendingSidebarHidden] = - usePersistedBooleanValue('trendingSidebarHidden') - const [trendingDiscoverHidden, setTrendingDiscoverHidden] = - usePersistedBooleanValue('trendingDiscoverHidden') + const [trendingDisabled, setTrendingDisabled] = + usePersistedBooleanValue('trendingDisabled') /* * Context */ - const state = React.useMemo( - () => ({trendingSidebarHidden, trendingDiscoverHidden}), - [trendingSidebarHidden, trendingDiscoverHidden], - ) + const state = React.useMemo(() => ({trendingDisabled}), [trendingDisabled]) const api = React.useMemo( - () => ({setTrendingSidebarHidden, setTrendingDiscoverHidden}), - [setTrendingSidebarHidden, setTrendingDiscoverHidden], + () => ({setTrendingDisabled}), + [setTrendingDisabled], ) return ( diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index d88d16955b..f83de84643 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -265,7 +265,7 @@ let PostFeed = ({ const showProgressIntersitial = (followProgressGuide || followAndLikeProgressGuide) && !isDesktop - const {trendingDiscoverHidden} = useTrendingSettings() + const {trendingDisabled} = useTrendingSettings() const feedItems: FeedRow[] = React.useMemo(() => { let feedKind: 'following' | 'discover' | 'profile' | undefined @@ -312,7 +312,7 @@ let PostFeed = ({ type: 'interstitialProgressGuide', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, }) - } else if (sliceIndex === 15 && !trendingDiscoverHidden) { + } else if (sliceIndex === 15 && !trendingDisabled) { arr.push({ type: 'interstitialTrending', key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, @@ -403,7 +403,7 @@ let PostFeed = ({ feedTab, hasSession, showProgressIntersitial, - trendingDiscoverHidden, + trendingDisabled, ]) // events diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index 55324687f9..a50ea9719d 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -26,15 +26,15 @@ import {Text} from '#/components/Typography' export function SidebarTrendingTopics() { const {enabled} = useTrendingConfig() - const {trendingSidebarHidden} = useTrendingSettings() - return !enabled ? null : trendingSidebarHidden ? null : + const {trendingDisabled} = useTrendingSettings() + return !enabled ? null : trendingDisabled ? null : } function Inner() { const t = useTheme() const {_} = useLingui() const trendingPrompt = Prompt.usePromptControl() - const {setTrendingSidebarHidden} = useTrendingSettingsApi() + const {setTrendingDisabled} = useTrendingSettingsApi() const {data: trending, error, isLoading} = useTrendingTopics() const noTopics = !isLoading && !error && !trending?.topics?.length @@ -96,10 +96,10 @@ function Inner() { control={trendingPrompt} title={_(msg`Hide trending topics?`)} description={_( - msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`, + msg`This will disable trending topics for all accounts on this device. You can update this later from your settings.`, )} confirmButtonCta={_(msg`Hide`)} - onConfirm={() => setTrendingSidebarHidden(true)} + onConfirm={() => setTrendingDisabled(true)} /> From dbadf04fdb99746ef33cf568f2c53a58de719418 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 17:47:37 -0600 Subject: [PATCH 46/55] Quick placeholder state --- src/view/shell/desktop/Feeds.tsx | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 3bf9a80e3d..1d515df558 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -14,7 +14,7 @@ import {createStaticClick, InlineLinkText} from '#/components/Link' export function DesktopFeeds() { const t = useTheme() const {_} = useLingui() - const {data: pinnedFeedInfos} = usePinnedFeedsInfos() + const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos() const selectedFeed = useSelectedFeed() const setSelectedFeed = useSetSelectedFeed() const navigation = useNavigation() @@ -25,7 +25,34 @@ export function DesktopFeeds() { return getCurrentRoute(state) }) - if (!pinnedFeedInfos) { + if (isLoading) { + return ( + + {Array(5) + .fill(0) + .map((_, i) => ( + + ))} + + ) + } + + if (error || !pinnedFeedInfos) { return null } From 28d83146e6a5de208c50af5f997893f29f597654 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 17:49:41 -0600 Subject: [PATCH 47/55] Limit # in sidebar, tweak spacing --- src/view/shell/desktop/SidebarTrendingTopics.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index a50ea9719d..fdf0cf4eef 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -6,10 +6,7 @@ import { useTrendingSettings, useTrendingSettingsApi, } from '#/state/preferences/trending' -import { - DEFAULT_LIMIT as TRENDING_TOPICS_COUNT, - useTrendingTopics, -} from '#/state/queries/trending/useTrendingTopics' +import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics' import {useTrendingConfig} from '#/state/trending-config' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' @@ -24,6 +21,8 @@ import { } from '#/components/TrendingTopics' import {Text} from '#/components/Typography' +const TRENDING_LIMIT = 6 + export function SidebarTrendingTopics() { const {enabled} = useTrendingConfig() const {trendingDisabled} = useTrendingSettings() @@ -40,7 +39,7 @@ function Inner() { return error || noTopics ? null : ( <> - + {isLoading ? ( - Array(TRENDING_TOPICS_COUNT) + Array(TRENDING_LIMIT) .fill(0) .map((_n, i) => ( )) ) : !trending?.topics ? null : ( <> - {trending.topics.map(topic => ( + {trending.topics.slice(0, TRENDING_LIMIT).map(topic => ( {({hovered}) => ( Date: Tue, 17 Dec 2024 18:27:14 -0600 Subject: [PATCH 48/55] Tweak gaps --- src/components/interstitials/Trending.tsx | 2 +- src/screens/Search/components/ExploreTrendingTopics.tsx | 8 +++++++- src/view/shell/desktop/SidebarTrendingTopics.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index 7809365e5c..b326777d2c 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -73,7 +73,7 @@ export function Inner() { - + {isLoading ? ( Array(TRENDING_TOPICS_COUNT) .fill(0) diff --git a/src/screens/Search/components/ExploreTrendingTopics.tsx b/src/screens/Search/components/ExploreTrendingTopics.tsx index 8831d39658..15e8d24752 100644 --- a/src/screens/Search/components/ExploreTrendingTopics.tsx +++ b/src/screens/Search/components/ExploreTrendingTopics.tsx @@ -65,7 +65,13 @@ function Inner() { + style={[ + a.flex_row, + a.justify_start, + a.flex_wrap, + {gap: '8px 6px'}, + gutters, + ]}> {isLoading ? ( Array(TRENDING_TOPICS_COUNT) .fill(0) diff --git a/src/view/shell/desktop/SidebarTrendingTopics.tsx b/src/view/shell/desktop/SidebarTrendingTopics.tsx index fdf0cf4eef..ef8ac3ada5 100644 --- a/src/view/shell/desktop/SidebarTrendingTopics.tsx +++ b/src/view/shell/desktop/SidebarTrendingTopics.tsx @@ -62,7 +62,7 @@ function Inner() { - + {isLoading ? ( Array(TRENDING_LIMIT) .fill(0) From e9fce7ac838ed81d9eab67232f11a7f3daf9f0c1 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 17 Dec 2024 18:53:48 -0600 Subject: [PATCH 49/55] Handle hide/show of sidebar --- src/view/shell/desktop/RightNav.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 1732245e38..f9f1805270 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,6 +1,8 @@ +import React from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/core' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' @@ -15,6 +17,24 @@ import {InlineLinkText} from '#/components/Link' import {ProgressGuideList} from '#/components/ProgressGuide/List' import {Text} from '#/components/Typography' +function useWebQueryParams() { + const navigation = useNavigation() + const [params, setParams] = React.useState>({}) + + React.useEffect(() => { + return navigation.addListener('state', e => { + try { + const {state} = e.data + const lastRoute = state.routes[state.routes.length - 1] + const {params} = lastRoute + setParams(params) + } catch (e) {} + }) + }, [navigation, setParams]) + + return params +} + export function DesktopRightNav({routeName}: {routeName: string}) { const t = useTheme() const {_} = useLingui() @@ -22,6 +42,8 @@ export function DesktopRightNav({routeName}: {routeName: string}) { const kawaii = useKawaiiMode() const gutters = useGutters(['base', 0, 'base', 'wide']) const isSearchScreen = routeName === 'Search' + const {q: searchQuery} = useWebQueryParams() + const showTrending = !isSearchScreen || (isSearchScreen && !!searchQuery) const {isTablet} = useWebMediaQueries() if (isTablet) { @@ -57,7 +79,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { )} - {!isSearchScreen && } + {showTrending && } {hasSession && ( From 34a1575b9c53b05d987c212ffd32d3c3dd7fae85 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 18 Dec 2024 01:14:03 +0000 Subject: [PATCH 50/55] Simplify messages --- src/components/interstitials/Trending.tsx | 6 ++---- src/view/shell/desktop/SidebarTrendingTopics.tsx | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/interstitials/Trending.tsx b/src/components/interstitials/Trending.tsx index b326777d2c..f001e97a47 100644 --- a/src/components/interstitials/Trending.tsx +++ b/src/components/interstitials/Trending.tsx @@ -63,7 +63,7 @@ export function Inner() { - + {isLoading ? ( Array(TRENDING_TOPICS_COUNT) .fill(0) diff --git a/src/screens/Search/components/ExploreTrendingTopics.tsx b/src/screens/Search/components/ExploreTrendingTopics.tsx index 15e8d24752..e9be248cbf 100644 --- a/src/screens/Search/components/ExploreTrendingTopics.tsx +++ b/src/screens/Search/components/ExploreTrendingTopics.tsx @@ -69,7 +69,7 @@ function Inner() { a.flex_row, a.justify_start, a.flex_wrap, - {gap: '8px 6px'}, + {rowGap: 8, columnGap: 6}, gutters, ]}> {isLoading ? ( From 56f086a6094df73206a88231d1632c29fdef5295 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 17 Dec 2024 18:53:20 -0800 Subject: [PATCH 55/55] Add explore page recommendations --- .../components/ExploreRecommendations.tsx | 95 +++++++++++++++++++ .../components/ExploreTrendingTopics.tsx | 2 +- src/view/screens/Search/Explore.tsx | 13 +++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/screens/Search/components/ExploreRecommendations.tsx diff --git a/src/screens/Search/components/ExploreRecommendations.tsx b/src/screens/Search/components/ExploreRecommendations.tsx new file mode 100644 index 0000000000..e253cfb5ab --- /dev/null +++ b/src/screens/Search/components/ExploreRecommendations.tsx @@ -0,0 +1,95 @@ +import {View} from 'react-native' +import {Trans} from '@lingui/macro' + +import {isWeb} from '#/platform/detection' +import {useTrendingSettings} from '#/state/preferences/trending' +import { + DEFAULT_LIMIT as RECOMMENDATIONS_COUNT, + useTrendingTopics, +} from '#/state/queries/trending/useTrendingTopics' +import {useTrendingConfig} from '#/state/trending-config' +import {atoms as a, useGutters, useTheme} from '#/alf' +import {Hashtag_Stroke2_Corner0_Rounded} from '#/components/icons/Hashtag' +import { + TrendingTopic, + TrendingTopicLink, + TrendingTopicSkeleton, +} from '#/components/TrendingTopics' +import {Text} from '#/components/Typography' + +export function ExploreRecommendations() { + const {enabled} = useTrendingConfig() + const {trendingDisabled} = useTrendingSettings() + return enabled && !trendingDisabled ? : null +} + +function Inner() { + const t = useTheme() + const gutters = useGutters([0, 'compact']) + const {data: trending, error, isLoading} = useTrendingTopics() + const noRecs = !isLoading && !error && !trending?.suggested?.length + + return error || noRecs ? null : ( + <> + + + + + + Recommended + + + + Feeds we think you might like. + + + + + + + {isLoading ? ( + Array(RECOMMENDATIONS_COUNT) + .fill(0) + .map((_, i) => ) + ) : !trending?.suggested ? null : ( + <> + {trending.suggested.map(topic => ( + + {({hovered}) => ( + + )} + + ))} + + )} + + + + ) +} diff --git a/src/screens/Search/components/ExploreTrendingTopics.tsx b/src/screens/Search/components/ExploreTrendingTopics.tsx index e9be248cbf..be347dcd4d 100644 --- a/src/screens/Search/components/ExploreTrendingTopics.tsx +++ b/src/screens/Search/components/ExploreTrendingTopics.tsx @@ -58,7 +58,7 @@ function Inner() { - What people are posting about now + What people are posting about. diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index c1ebc10146..378ea59a4d 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -24,6 +24,7 @@ import { ProfileCardFeedLoadingPlaceholder, } from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' +import {ExploreRecommendations} from '#/screens/Search/components/ExploreRecommendations' import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics' import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {Button} from '#/components/Button' @@ -244,6 +245,10 @@ type ExploreScreenItems = type: 'trendingTopics' key: string } + | { + type: 'recommendations' + key: string + } | { type: 'profile' key: string @@ -337,6 +342,11 @@ export function Explore() { key: `trending-topics`, }) + i.push({ + type: 'recommendations', + key: `recommendations`, + }) + i.push({ type: 'header', key: 'suggested-follows-header', @@ -503,6 +513,9 @@ export function Explore() { case 'trendingTopics': { return } + case 'recommendations': { + return + } case 'profile': { return (