diff --git a/.github/workflows/build-submit-ios.yml b/.github/workflows/build-submit-ios.yml index 1896de14c0..a124d3997d 100644 --- a/.github/workflows/build-submit-ios.yml +++ b/.github/workflows/build-submit-ios.yml @@ -2,8 +2,6 @@ name: Build and Submit iOS on: - schedule: - - cron: '0 5 * * *' workflow_dispatch: inputs: profile: @@ -12,6 +10,11 @@ on: options: - testflight - production + workflow_call: + inputs: + profile: + required: true + type: string jobs: build: diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index 7174a286bb..224165d6f3 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -2,9 +2,6 @@ name: Bundle and Deploy EAS Update on: - push: - branches: - - hailey/configure-builds-and-bundles workflow_dispatch: inputs: channel: @@ -22,6 +19,8 @@ jobs: bundleDeploy: name: Bundle and Deploy EAS Update runs-on: ubuntu-latest + outputs: + fingerprint-diff: ${{ steps.fingerprint.outputs.fingerprint-diff }} steps: - name: Check for EXPO_TOKEN run: > @@ -29,11 +28,39 @@ jobs: echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" exit 1 fi -# - name: 🧐 Validate version -# run: | -# [[ "${{ inputs.runtimeVersion }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1 + + # Validate the version if one is supplied. This should generally happen if the update is for a production client + - name: 🧐 Validate version + if: ${{ inputs.runtimeVersion }} + run: | + if [ -z "${{ inputs.runtimeVersion }}" ]; then + [[ "${{ inputs.runtimeVersion }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1 + fi + - name: ⬇️ Checkout uses: actions/checkout@v4 + with: + fetch-depth: 100 + + - name: ⬇️ Fetch commits from base branch + run: git fetch origin main:main --depth 100 + + # This should get the current production release's commit's hash to see if the update is compatible + - name: 🕵️ Get the base commit + id: base-commit + run: | + if [ -z "${{ inputs.channel == 'production' }}" ]; then + echo base-commit=$(git show-ref -s ${{ inputs.runtimeVersion }}) >> "$GITHUB_OUTPUT" + else + echo base-commit=$(git log -n 1 --skip 1 main --pretty=format:'%H') >> "$GITHUB_OUTPUT" + fi + + - name: ✓ Make sure we found a base commit + run: | + if [ -z "${{ steps.base-commit.outputs.base-commit }}" ]; then + echo "Could not find a base commit for this release. Exiting." + exit 1 + fi - name: 🔧 Setup Node uses: actions/setup-node@v4 @@ -41,37 +68,122 @@ jobs: node-version-file: .nvmrc cache: yarn + - name: ⚙️ Install Dependencies + run: yarn install + + # Run the fingerprint + - name: 📷 Check fingerprint + id: fingerprint + uses: expo/expo-github-action/fingerprint@main + with: + previous-git-commit: ${{ steps.base-commit.outputs.base-commit }} + + - name: 👀 Debug fingerprint + run: | + echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}" + echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}" + - name: 🔨 Setup EAS uses: expo/expo-github-action@v8 + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} with: expo-version: latest eas-version: latest token: ${{ secrets.EXPO_TOKEN }} - name: ⛏️ Setup Expo + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} run: yarn global add eas-cli-local-build-plugin - name: 🪛 Setup jq + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} uses: dcarbone/install-jq-action@v2 - - name: ⚙️ Install Dependencies - run: yarn install - - name: 🔤 Compile Translations + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} run: yarn intl:build - name: ✏️ Write environment variables + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} run: | export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' echo "${{ secrets.ENV_TOKEN }}" > .env echo "$json" > google-services.json - name: 🏗️ Create Bundle - run: yarn export + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} + run: EXPO_PUBLIC_ENV="${{ inputs.channel || 'testflight' }}" yarn export - name: 📦 Package Bundle and 🚀 Deploy + if: ${{ steps.fingerprint.outputs.fingerprint-diff == '[]' }} run: yarn use-build-number bash scripts/bundleUpdate.sh env: DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }} RUNTIME_VERSION: ${{ inputs.runtimeVersion }} CHANNEL_NAME: ${{ inputs.channel || 'testflight' }} + + # GitHub actions are horrible so let's just copy paste this in + buildIfNecessary: + name: Build and Submit iOS + runs-on: macos-14 + needs: [bundleDeploy] + # Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be + # available here + if: ${{ inputs.channel != 'production' && needs.bundleDeploy.outputs.fingerprint-diff != '[]' }} + steps: + - name: Check for EXPO_TOKEN + run: > + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then + echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" + exit 1 + fi + + - name: ⬇️ Checkout + uses: actions/checkout@v4 + + - name: 🔧 Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + + - name: 🔨 Setup EAS + uses: expo/expo-github-action@v8 + with: + expo-version: latest + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: ⛏️ Setup EAS local builds + run: yarn global add eas-cli-local-build-plugin + + - name: ⚙️ Install dependencies + run: yarn install + + - name: ☕️ Setup Cocoapods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.14.3 + + - name: 💾 Cache Pods + uses: actions/cache@v3 + id: pods-cache + with: + path: ./ios/Pods + # We'll use the yarn.lock for our hash since we don't yet have a Podfile.lock. Pod versions will not + # change unless the yarn version changes as well. + key: ${{ runner.os }}-pods-${{ hashFiles('yarn.lock') }} + + - name: 🔤 Compile translations + run: yarn intl:build + + - name: ✏️ Write environment variables + run: | + echo "${{ secrets.ENV_TOKEN }}" > .env + echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json + + - name: 🏗️ EAS Build + run: yarn use-build-number eas build -p ios --profile testflight --local --output build.ipa --non-interactive + + - name: 🚀 Deploy + run: eas submit -p ios --non-interactive --path build.ipa diff --git a/app.config.js b/app.config.js index 9d4964ded8..a1a3f5e37a 100644 --- a/app.config.js +++ b/app.config.js @@ -130,6 +130,8 @@ module.exports = function (config) { }, updates: { url: 'https://updates.bsky.app/manifest', + // TODO Eventually we want to enable this for all environments, but for now it will only be used for + // TestFlight builds enabled: IS_TESTFLIGHT, fallbackToCacheTimeout: 30000, codeSigningCertificate: './code-signing/certificate.pem', @@ -138,7 +140,6 @@ module.exports = function (config) { alg: 'rsa-v1_5-sha256', }, checkAutomatically: 'NEVER', - // This should be set by the EAS configuration, but to ensure it gets set for now we add it here too channel: UPDATES_CHANNEL, }, assetBundlePatterns: ['**/*'], @@ -171,6 +172,7 @@ module.exports = function (config) { './plugins/withAndroidManifestPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js', './plugins/withAndroidStylesWindowBackgroundPlugin.js', + './plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js', './plugins/shareExtension/withShareExtensions.js', ].filter(Boolean), extra: { diff --git a/patches/@mattermost+react-native-paste-input+0.6.4.patch b/patches/@mattermost+react-native-paste-input+0.6.4.patch index 849cbaa85b..08413846ff 100644 --- a/patches/@mattermost+react-native-paste-input+0.6.4.patch +++ b/patches/@mattermost+react-native-paste-input+0.6.4.patch @@ -3594,3 +3594,19 @@ index 19b61ff..04a9951 100644 PasteInput_compileSdkVersion=30 PasteInput_buildToolsVersion=30.0.2 PasteInput_targetSdkVersion=30 +diff --git a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m +index e916023..0564d97 100644 +--- a/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m ++++ b/node_modules/@mattermost/react-native-paste-input/ios/PasteInputView.m +@@ -22,6 +22,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge + _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _backedTextInputView.textInputDelegate = self; + ++ // Disable inline predictions to prevent jank in the composer ++ if (@available(iOS 17.0, *)) { ++ _backedTextInputView.inlinePredictionType = UITextInlinePredictionTypeNo; ++ } ++ + [self addSubview:_backedTextInputView]; + } + diff --git a/plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js b/plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js new file mode 100644 index 0000000000..704ead054b --- /dev/null +++ b/plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js @@ -0,0 +1,28 @@ +const {withStringsXml, AndroidConfig} = require('@expo/config-plugins') + +module.exports = function withAndroidSplashScreenStatusBarTranslucentPlugin( + appConfig, +) { + return withStringsXml(appConfig, function (decoratedAppConfig) { + try { + decoratedAppConfig.modResults = AndroidConfig.Strings.setStringItem( + [ + { + _: 'true', + $: { + name: 'expo_splash_screen_status_bar_translucent', + translatable: 'false', + }, + }, + ], + decoratedAppConfig.modResults, + ) + } catch (e) { + console.error( + `withAndroidSplashScreenStatusBarTranslucentPlugin failed`, + e, + ) + } + return decoratedAppConfig + }) +} diff --git a/scripts/bundleUpdate.sh b/scripts/bundleUpdate.sh index 5df381cc1b..328abbe6bd 100644 --- a/scripts/bundleUpdate.sh +++ b/scripts/bundleUpdate.sh @@ -15,7 +15,7 @@ fi cd bundleTempDir || exit BUNDLE_VERSION=$(date +%s) -DEPLOYMENT_URL="https://updates.bsky.app/v1/upload?runtime-version=1.74.0&bundle-version=$BUNDLE_VERSION&channel=$CHANNEL_NAME&ios-build-number=$BSKY_IOS_BUILD_NUMBER&android-build-number=$BSKY_ANDROID_VERSION_CODE" +DEPLOYMENT_URL="https://updates.bsky.app/v1/upload?runtime-version=$RUNTIME_VERSION&bundle-version=$BUNDLE_VERSION&channel=$CHANNEL_NAME&ios-build-number=$BSKY_IOS_BUILD_NUMBER&android-build-number=$BSKY_ANDROID_VERSION_CODE" tar czvf bundle.tar.gz ./* diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 83aede722b..ab40ff4220 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,86 +1,86 @@ import * as React from 'react' -import { - NavigationContainer, - createNavigationContainerRef, - CommonActions, - StackActions, - DefaultTheme, - DarkTheme, -} from '@react-navigation/native' +import {JSX} from 'react/jsx-runtime' +import {i18n, MessageDescriptor} from '@lingui/core' +import {msg} from '@lingui/macro' import { BottomTabBarProps, createBottomTabNavigator, } from '@react-navigation/bottom-tabs' import { - HomeTabNavigatorParams, - SearchTabNavigatorParams, + CommonActions, + createNavigationContainerRef, + DarkTheme, + DefaultTheme, + NavigationContainer, + StackActions, +} from '@react-navigation/native' + +import {timeout} from 'lib/async/timeout' +import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {usePalette} from 'lib/hooks/usePalette' +import {buildStateObject} from 'lib/routes/helpers' +import { + AllNavigatorParams, + BottomTabNavigatorParams, FeedsTabNavigatorParams, - NotificationsTabNavigatorParams, FlatNavigatorParams, - AllNavigatorParams, + HomeTabNavigatorParams, MyProfileTabNavigatorParams, - BottomTabNavigatorParams, + NotificationsTabNavigatorParams, + SearchTabNavigatorParams, } from 'lib/routes/types' -import {BottomBar} from './view/shell/bottom-bar/BottomBar' -import {buildStateObject} from 'lib/routes/helpers' -import {State, RouteParams} from 'lib/routes/types' +import {RouteParams, State} from 'lib/routes/types' +import {bskyTitle} from 'lib/strings/headings' import {isAndroid, isNative} from 'platform/detection' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' +import {AppPasswords} from 'view/screens/AppPasswords' +import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' +import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' +import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' +import {PreferencesThreads} from 'view/screens/PreferencesThreads' +import {SavedFeeds} from 'view/screens/SavedFeeds' +import HashtagScreen from '#/screens/Hashtag' +import {ModerationScreen} from '#/screens/Moderation' +import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' +import {init as initAnalytics} from './lib/analytics/analytics' +import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' +import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig' import {router} from './routes' -import {usePalette} from 'lib/hooks/usePalette' -import {bskyTitle} from 'lib/strings/headings' -import {JSX} from 'react/jsx-runtime' -import {timeout} from 'lib/async/timeout' +import {useModalControls} from './state/modals' import {useUnreadNotifications} from './state/queries/notifications/unread' import {useSession} from './state/session' -import {useModalControls} from './state/modals' import { - shouldRequestEmailConfirmation, setEmailConfirmationRequested, + shouldRequestEmailConfirmation, } from './state/shell/reminders' -import {init as initAnalytics} from './lib/analytics/analytics' -import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' - -import {HomeScreen} from './view/screens/Home' -import {SearchScreen} from './view/screens/Search' +import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' +import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' +import {DebugModScreen} from './view/screens/DebugMod' import {FeedsScreen} from './view/screens/Feeds' -import {NotificationsScreen} from './view/screens/Notifications' +import {HomeScreen} from './view/screens/Home' +import {LanguageSettingsScreen} from './view/screens/LanguageSettings' import {ListsScreen} from './view/screens/Lists' -import {ModerationScreen} from '#/screens/Moderation' +import {LogScreen} from './view/screens/Log' import {ModerationModlistsScreen} from './view/screens/ModerationModlists' import {NotFoundScreen} from './view/screens/NotFound' -import {SettingsScreen} from './view/screens/Settings' -import {LanguageSettingsScreen} from './view/screens/LanguageSettings' +import {NotificationsScreen} from './view/screens/Notifications' +import {PostLikedByScreen} from './view/screens/PostLikedBy' +import {PostRepostedByScreen} from './view/screens/PostRepostedBy' +import {PostThreadScreen} from './view/screens/PostThread' +import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' import {ProfileScreen} from './view/screens/Profile' -import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' -import {ProfileFollowsScreen} from './view/screens/ProfileFollows' import {ProfileFeedScreen} from './view/screens/ProfileFeed' import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy' +import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' +import {ProfileFollowsScreen} from './view/screens/ProfileFollows' import {ProfileListScreen} from './view/screens/ProfileList' -import {PostThreadScreen} from './view/screens/PostThread' -import {PostLikedByScreen} from './view/screens/PostLikedBy' -import {PostRepostedByScreen} from './view/screens/PostRepostedBy' +import {SearchScreen} from './view/screens/Search' +import {SettingsScreen} from './view/screens/Settings' import {Storybook} from './view/screens/Storybook' -import {DebugModScreen} from './view/screens/DebugMod' -import {LogScreen} from './view/screens/Log' import {SupportScreen} from './view/screens/Support' -import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' import {TermsOfServiceScreen} from './view/screens/TermsOfService' -import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' -import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' -import {AppPasswords} from 'view/screens/AppPasswords' -import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' -import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' -import {SavedFeeds} from 'view/screens/SavedFeeds' -import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' -import {PreferencesThreads} from 'view/screens/PreferencesThreads' -import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' +import {BottomBar} from './view/shell/bottom-bar/BottomBar' import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' -import {msg} from '@lingui/macro' -import {i18n, MessageDescriptor} from '@lingui/core' -import HashtagScreen from '#/screens/Hashtag' -import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' -import {logEvent, attachRouteToLogEvents} from './lib/statsig/statsig' const navigationRef = createNavigationContainerRef() @@ -554,10 +554,14 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { ref={navigationRef} linking={LINKING} theme={theme} + onStateChange={() => { + logEvent('router:navigate', {}) + }} onReady={() => { attachRouteToLogEvents(getCurrentRouteName) logModuleInitTime() onReady() + logEvent('router:navigate', {}) }}> {children} @@ -693,11 +697,11 @@ function logModuleInitTime() { } export { + FlatNavigator, + handleLink, navigate, - resetToTab, reset, - handleLink, - TabsNavigator, - FlatNavigator, + resetToTab, RoutesContainer, + TabsNavigator, } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 0e22944a33..67c33fa0c6 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,19 +1,21 @@ import React from 'react' import { + AccessibilityProps, Pressable, - Text, PressableProps, + StyleProp, + StyleSheet, + Text, TextProps, - ViewStyle, - AccessibilityProps, - View, TextStyle, - StyleSheet, - StyleProp, + View, + ViewStyle, } from 'react-native' import LinearGradient from 'react-native-linear-gradient' +import {Trans} from '@lingui/macro' -import {useTheme, atoms as a, tokens, android, flatten} from '#/alf' +import {logger} from '#/logger' +import {android, atoms as a, flatten, tokens, useTheme} from '#/alf' import {Props as SVGIconProps} from '#/components/icons/common' import {normalizeTextStyles} from '#/components/Typography' @@ -403,18 +405,51 @@ export function Button({ )} - {typeof children === 'string' ? ( - {children} - ) : typeof children === 'function' ? ( - children(context) - ) : ( - children - )} + + {/* @ts-ignore */} + {typeof children === 'string' || children?.type === Trans ? ( + /* @ts-ignore */ + {children} + ) : typeof children === 'function' ? ( + children(context) + ) : ( + children + )} + ) } +export class ButtonTextErrorBoundary extends React.Component< + React.PropsWithChildren<{}>, + {hasError: boolean; error: Error | undefined} +> { + public state = { + hasError: false, + error: undefined, + } + + public static getDerivedStateFromError(error: Error) { + return {hasError: true, error} + } + + public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + logger.error('ButtonTextErrorBoundary caught an error', { + message: error.message, + errorInfo, + }) + } + + public render() { + if (this.state.hasError) { + return ERROR + } + + return this.props.children + } +} + export function useSharedButtonTextStyles() { const t = useTheme() const {color, variant, disabled, size} = useButtonContext() diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index a85a1c4fd4..07e101f85c 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -1,31 +1,31 @@ import React, {useImperativeHandle} from 'react' -import {View, Dimensions, Keyboard, Pressable} from 'react-native' +import {Dimensions, Pressable, View} from 'react-native' +import Animated, {useAnimatedStyle} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import BottomSheet, { BottomSheetBackdropProps, BottomSheetScrollView, + BottomSheetScrollViewMethods, BottomSheetTextInput, BottomSheetView, useBottomSheet, WINDOW_HEIGHT, -} from '@gorhom/bottom-sheet' -import {useSafeAreaInsets} from 'react-native-safe-area-context' -import Animated, {useAnimatedStyle} from 'react-native-reanimated' +} from '@discord/bottom-sheet/src' -import {useTheme, atoms as a, flatten} from '#/alf' -import {Portal} from '#/components/Portal' -import {createInput} from '#/components/forms/TextField' import {logger} from '#/logger' import {useDialogStateControlContext} from '#/state/dialogs' - +import {isNative} from 'platform/detection' +import {atoms as a, flatten, useTheme} from '#/alf' +import {Context} from '#/components/Dialog/context' import { - DialogOuterProps, DialogControlProps, DialogInnerProps, + DialogOuterProps, } from '#/components/Dialog/types' -import {Context} from '#/components/Dialog/context' -import {isNative} from 'platform/detection' +import {createInput} from '#/components/forms/TextField' +import {Portal} from '#/components/Portal' -export {useDialogControl, useDialogContext} from '#/components/Dialog/context' +export {useDialogContext, useDialogControl} from '#/components/Dialog/context' export * from '#/components/Dialog/types' // @ts-ignore export const Input = createInput(BottomSheetTextInput) @@ -122,7 +122,6 @@ export function Outer({ ) const onCloseInner = React.useCallback(() => { - Keyboard.dismiss() try { closeCallback.current?.() } catch (e: any) { @@ -206,16 +205,14 @@ export function Inner({children, style}: DialogInnerProps) { ) } -export function ScrollableInner({ - children, - keyboardDismissMode, - style, -}: DialogInnerProps) { +export const ScrollableInner = React.forwardRef< + BottomSheetScrollViewMethods, + DialogInnerProps +>(function ScrollableInner({children, style}, ref) { const insets = useSafeAreaInsets() return ( + contentContainerStyle={isNative ? a.pb_4xl : undefined} + ref={ref}> {children} ) -} +}) export function Handle() { const t = useTheme() - const onTouchStart = React.useCallback(() => { - Keyboard.dismiss() - }, []) - return ( - + - + {props.labelers.map(labeler => { return ( {state.activeStep !== SignupStep.CAPTCHA && ( <> {isError ? ( ) : ( )} @@ -204,7 +221,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { - + ) diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts index f185e2d449..86a144368f 100644 --- a/src/screens/Signup/state.ts +++ b/src/screens/Signup/state.ts @@ -207,7 +207,7 @@ export function useSubmitSignup({ }) { const {_} = useLingui() const {createAccount} = useSessionApi() - const {mutate: setBirthDate} = usePreferencesSetBirthDateMutation() + const {mutateAsync: setBirthDate} = usePreferencesSetBirthDateMutation() const {mutate: setSavedFeeds} = useSetSaveFeedsMutation() const onboardingDispatch = useOnboardingDispatch() @@ -264,7 +264,7 @@ export function useSubmitSignup({ inviteCode: state.inviteCode.trim(), verificationCode: verificationCode, }) - setBirthDate({birthDate: state.dateOfBirth}) + await setBirthDate({birthDate: state.dateOfBirth}) if (IS_PROD_SERVICE(state.serviceUrl)) { setSavedFeeds(DEFAULT_PROD_FEEDS) } diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index b6748bfad5..c7dba30892 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -1,26 +1,26 @@ import React from 'react' import { - BskyAgent, AtpPersistSessionHandler, BSKY_LABELER_DID, + BskyAgent, } from '@atproto/api' import {useQueryClient} from '@tanstack/react-query' import {jwtDecode} from 'jwt-decode' -import {IS_DEV} from '#/env' -import {IS_TEST_USER} from '#/lib/constants' -import {isWeb} from '#/platform/detection' +import {track} from '#/lib/analytics/analytics' import {networkRetry} from '#/lib/async/retry' +import {IS_TEST_USER} from '#/lib/constants' +import {logEvent, LogEvents} from '#/lib/statsig/statsig' +import {hasProp} from '#/lib/type-guards' import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' import {PUBLIC_BSKY_AGENT} from '#/state/queries' -import {emitSessionDropped} from '../events' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' -import {track} from '#/lib/analytics/analytics' -import {hasProp} from '#/lib/type-guards' +import {IS_DEV} from '#/env' +import {emitSessionDropped} from '../events' import {readLabelers} from './agent-config' -import {logEvent, LogEvents} from '#/lib/statsig/statsig' let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT @@ -230,6 +230,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { }: any) => { logger.info(`session: creating account`) track('Try Create Account') + logEvent('account:create:begin', {}) const agent = new BskyAgent({service}) @@ -290,6 +291,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { logger.debug(`session: created account`, {}, logger.DebugContext.session) track('Create Account') + logEvent('account:create:success', {}) }, [upsertAccount, queryClient, clearCurrentAccount], ) diff --git a/src/view/com/auth/HomeLoggedOutCTA.tsx b/src/view/com/auth/HomeLoggedOutCTA.tsx index a5b5bf7ba6..4c8c35da73 100644 --- a/src/view/com/auth/HomeLoggedOutCTA.tsx +++ b/src/view/com/auth/HomeLoggedOutCTA.tsx @@ -1,14 +1,15 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' -import {ScrollView} from '../util/Views' -import {Text} from '../util/text/Text' + import {usePalette} from '#/lib/hooks/usePalette' -import {colors, s} from '#/lib/styles' -import {TextLink} from '../util/Link' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {colors, s} from '#/lib/styles' import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import {TextLink} from '../util/Link' +import {Text} from '../util/text/Text' +import {ScrollView} from '../util/Views' export function HomeLoggedOutCTA() { const pal = usePalette('default') @@ -79,7 +80,7 @@ export function HomeLoggedOutCTA() { styles.btnLabel, isMobile && styles.btnLabelMobile, ]}> - Sign In + Sign in diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx index b22bbb7fe4..c8c81dd771 100644 --- a/src/view/com/auth/LoggedOut.tsx +++ b/src/view/com/auth/LoggedOut.tsx @@ -1,27 +1,28 @@ import React from 'react' -import {View, Pressable} from 'react-native' +import {Pressable, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' import {useNavigation} from '@react-navigation/native' -import {isIOS, isNative} from '#/platform/detection' -import {Login} from '#/screens/Login' -import {Signup} from '#/screens/Signup' -import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' -import {s} from '#/lib/styles' -import {usePalette} from '#/lib/hooks/usePalette' import {useAnalytics} from '#/lib/analytics/analytics' -import {SplashScreen} from './SplashScreen' -import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' +import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {logEvent} from '#/lib/statsig/statsig' +import {s} from '#/lib/styles' +import {isIOS, isNative} from '#/platform/detection' +import {useSession} from '#/state/session' import { useLoggedOutView, useLoggedOutViewControls, } from '#/state/shell/logged-out' -import {useSession} from '#/state/session' -import {Text} from '#/view/com/util/text/Text' +import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' import {NavigationProp} from 'lib/routes/types' +import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' +import {Text} from '#/view/com/util/text/Text' +import {Login} from '#/screens/Login' +import {Signup} from '#/screens/Signup' +import {SplashScreen} from './SplashScreen' enum ScreenState { S_LoginOrCreateAccount, @@ -133,10 +134,14 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) { {screenState === ScreenState.S_LoginOrCreateAccount ? ( setScreenState(ScreenState.S_Login)} - onPressCreateAccount={() => + onPressSignin={() => { + setScreenState(ScreenState.S_Login) + logEvent('splash:signInPressed', {}) + }} + onPressCreateAccount={() => { setScreenState(ScreenState.S_CreateAccount) - } + logEvent('splash:createAccountPressed', {}) + }} /> ) : undefined} {screenState === ScreenState.S_Login ? ( diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx index fe0d4ee49a..763b01dfa1 100644 --- a/src/view/com/auth/SplashScreen.tsx +++ b/src/view/com/auth/SplashScreen.tsx @@ -1,21 +1,21 @@ import React from 'react' import {View} from 'react-native' +import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {useSafeAreaInsets} from 'react-native-safe-area-context' - -import {ErrorBoundary} from 'view/com/util/ErrorBoundary' -import {CenteredView} from '../util/Views' -import {Trans, msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Logo} from '#/view/icons/Logo' -import {Logotype} from '#/view/icons/Logotype' -import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' + import {sanitizeAppLanguageSetting} from '#/locale/helpers' -import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {atoms as a, useTheme} from '#/alf' -import {Text} from '#/components/Typography' import {Button, ButtonText} from '#/components/Button' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {Text} from '#/components/Typography' +import {CenteredView} from '../util/Views' export const SplashScreen = ({ onPressSignin, @@ -87,7 +87,7 @@ export const SplashScreen = ({ variant="solid" color="secondary"> - Sign In + Sign in diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index e8f312491f..cdb72cc041 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -1,21 +1,22 @@ import React from 'react' -import {View, Pressable} from 'react-native' +import {Pressable, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {ErrorBoundary} from 'view/com/util/ErrorBoundary' -import {CenteredView} from '../util/Views' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {Trans, msg} from '@lingui/macro' -import {Logo} from '#/view/icons/Logo' -import {Logotype} from '#/view/icons/Logotype' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' + import {sanitizeAppLanguageSetting} from '#/locale/helpers' -import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' -import {Text} from '#/components/Typography' import {InlineLink} from '#/components/Link' +import {Text} from '#/components/Typography' +import {CenteredView} from '../util/Views' export const SplashScreen = ({ onDismiss, @@ -115,7 +116,7 @@ export const SplashScreen = ({ variant="solid" color="secondary"> - Sign In + Sign in diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index ddb01a8fa6..ac6fe6fed4 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1,5 +1,4 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {observer} from 'mobx-react-lite' import { ActivityIndicator, BackHandler, @@ -12,60 +11,62 @@ import { TouchableOpacity, View, } from 'react-native' -import {useSafeAreaInsets} from 'react-native-safe-area-context' import LinearGradient from 'react-native-linear-gradient' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import {RichText} from '@atproto/api' -import {useAnalytics} from 'lib/analytics/analytics' -import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible' -import {ExternalEmbed} from './ExternalEmbed' -import {Text} from '../util/text/Text' -import * as Toast from '../util/Toast' -// TODO: Prevent naming components that coincide with RN primitives -// due to linting false positives -import {TextInput, TextInputRef} from './text-input/TextInput' -import {CharProgress} from './char-progress/CharProgress' -import {UserAvatar} from '../util/UserAvatar' -import * as apilib from 'lib/api/index' -import {ComposerOpts} from 'state/shell/composer' -import {s, colors, gradients} from 'lib/styles' -import {cleanError} from 'lib/strings/errors' -import {shortenLinks} from 'lib/strings/rich-text-manip' -import {toShortUrl} from 'lib/strings/url-helpers' -import {SelectPhotoBtn} from './photos/SelectPhotoBtn' -import {OpenCameraBtn} from './photos/OpenCameraBtn' -import {ThreadgateBtn} from './threadgate/ThreadgateBtn' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useExternalLinkFetch} from './useExternalLinkFetch' -import {isWeb, isNative, isAndroid, isIOS} from 'platform/detection' -import {QuoteEmbed} from '../util/post-embeds/QuoteEmbed' -import {GalleryModel} from 'state/models/media/gallery' -import {Gallery} from './photos/Gallery' -import {MAX_GRAPHEME_LENGTH} from 'lib/constants' -import {LabelsBtn} from './labels/LabelsBtn' -import {SelectLangBtn} from './select-language/SelectLangBtn' -import {SuggestedLanguage} from './select-language/SuggestedLanguage' -import {insertMentionAt} from 'lib/strings/mention-manip' -import {Trans, msg} from '@lingui/macro' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {observer} from 'mobx-react-lite' + +import {logEvent} from '#/lib/statsig/statsig' +import {logger} from '#/logger' +import {emitPostCreated} from '#/state/events' import {useModals} from '#/state/modals' import {useRequireAltTextEnabled} from '#/state/preferences' import { + toPostLanguages, useLanguagePrefs, useLanguagePrefsApi, - toPostLanguages, } from '#/state/preferences/languages' -import {useSession, getAgent} from '#/state/session' import {useProfileQuery} from '#/state/queries/profile' -import {useComposerControls} from '#/state/shell/composer' -import {emitPostCreated} from '#/state/events' import {ThreadgateSetting} from '#/state/queries/threadgate' -import {logger} from '#/logger' +import {getAgent, useSession} from '#/state/session' +import {useComposerControls} from '#/state/shell/composer' +import {useAnalytics} from 'lib/analytics/analytics' +import * as apilib from 'lib/api/index' +import {MAX_GRAPHEME_LENGTH} from 'lib/constants' +import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {cleanError} from 'lib/strings/errors' +import {insertMentionAt} from 'lib/strings/mention-manip' +import {shortenLinks} from 'lib/strings/rich-text-manip' +import {toShortUrl} from 'lib/strings/url-helpers' +import {colors, gradients, s} from 'lib/styles' +import {isAndroid, isIOS, isNative, isWeb} from 'platform/detection' +import {useDialogStateControlContext} from 'state/dialogs' +import {GalleryModel} from 'state/models/media/gallery' +import {ComposerOpts} from 'state/shell/composer' import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' import * as Prompt from '#/components/Prompt' -import {useDialogStateControlContext} from 'state/dialogs' -import {logEvent} from '#/lib/statsig/statsig' +import {QuoteEmbed} from '../util/post-embeds/QuoteEmbed' +import {Text} from '../util/text/Text' +import * as Toast from '../util/Toast' +import {UserAvatar} from '../util/UserAvatar' +import {CharProgress} from './char-progress/CharProgress' +import {ExternalEmbed} from './ExternalEmbed' +import {LabelsBtn} from './labels/LabelsBtn' +import {Gallery} from './photos/Gallery' +import {OpenCameraBtn} from './photos/OpenCameraBtn' +import {SelectPhotoBtn} from './photos/SelectPhotoBtn' +import {SelectLangBtn} from './select-language/SelectLangBtn' +import {SuggestedLanguage} from './select-language/SuggestedLanguage' +// TODO: Prevent naming components that coincide with RN primitives +// due to linting false positives +import {TextInput, TextInputRef} from './text-input/TextInput' +import {ThreadgateBtn} from './threadgate/ThreadgateBtn' +import {useExternalLinkFetch} from './useExternalLinkFetch' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -506,7 +507,13 @@ export const ComposePost = observer(function ComposePost({ control={discardPromptControl} title={_(msg`Discard draft?`)} description={_(msg`Are you sure you'd like to discard this draft?`)} - onConfirm={onClose} + onConfirm={() => { + if (isWeb) { + onClose() + } else { + discardPromptControl.close(onClose) + } + }} confirmButtonCta={_(msg`Discard`)} confirmButtonColor="negative" /> diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 238cfc502c..af86f13a3f 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -1,33 +1,33 @@ -import React, {useRef, useEffect} from 'react' +import React, {useEffect, useRef} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' -import BottomSheet from '@gorhom/bottom-sheet' -import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' -import {usePalette} from 'lib/hooks/usePalette' +import BottomSheet from '@discord/bottom-sheet/src' -import {useModals, useModalControls} from '#/state/modals' -import * as EditProfileModal from './EditProfile' -import * as RepostModal from './Repost' -import * as SelfLabelModal from './SelfLabel' -import * as ThreadgateModal from './Threadgate' -import * as CreateOrEditListModal from './CreateOrEditList' -import * as UserAddRemoveListsModal from './UserAddRemoveLists' -import * as ListAddUserModal from './ListAddRemoveUsers' +import {useModalControls, useModals} from '#/state/modals' +import {usePalette} from 'lib/hooks/usePalette' +import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' +import * as AddAppPassword from './AddAppPasswords' import * as AltImageModal from './AltImage' import * as EditImageModal from './AltImage' -import * as DeleteAccountModal from './DeleteAccount' +import * as ChangeEmailModal from './ChangeEmail' import * as ChangeHandleModal from './ChangeHandle' +import * as ChangePasswordModal from './ChangePassword' +import * as CreateOrEditListModal from './CreateOrEditList' +import * as DeleteAccountModal from './DeleteAccount' +import * as EditProfileModal from './EditProfile' +import * as EmbedConsentModal from './EmbedConsent' +import * as InAppBrowserConsentModal from './InAppBrowserConsent' import * as InviteCodesModal from './InviteCodes' -import * as AddAppPassword from './AddAppPasswords' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' -import * as VerifyEmailModal from './VerifyEmail' -import * as ChangeEmailModal from './ChangeEmail' -import * as ChangePasswordModal from './ChangePassword' -import * as SwitchAccountModal from './SwitchAccount' import * as LinkWarningModal from './LinkWarning' -import * as EmbedConsentModal from './EmbedConsent' -import * as InAppBrowserConsentModal from './InAppBrowserConsent' +import * as ListAddUserModal from './ListAddRemoveUsers' +import * as RepostModal from './Repost' +import * as SelfLabelModal from './SelfLabel' +import * as SwitchAccountModal from './SwitchAccount' +import * as ThreadgateModal from './Threadgate' +import * as UserAddRemoveListsModal from './UserAddRemoveLists' +import * as VerifyEmailModal from './VerifyEmail' const DEFAULT_SNAPPOINTS = ['90%'] const HANDLE_HEIGHT = 24 diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index 892b07c9a2..03bef719e9 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -5,22 +5,23 @@ import { TouchableOpacity, View, } from 'react-native' -import {Text} from '../util/text/Text' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' +import {BottomSheetScrollView} from '@discord/bottom-sheet/src' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useProfileQuery} from '#/state/queries/profile' +import {SessionAccount, useSession, useSessionApi} from '#/state/session' +import {useCloseAllActiveElements} from '#/state/util' import {useAnalytics} from 'lib/analytics/analytics' +import {Haptics} from 'lib/haptics' import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' -import {UserAvatar} from '../util/UserAvatar' +import {usePalette} from 'lib/hooks/usePalette' +import {makeProfileLink} from 'lib/routes/links' +import {s} from 'lib/styles' import {AccountDropdownBtn} from '../util/AccountDropdownBtn' import {Link} from '../util/Link' -import {makeProfileLink} from 'lib/routes/links' -import {BottomSheetScrollView} from '@gorhom/bottom-sheet' -import {Haptics} from 'lib/haptics' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSession, useSessionApi, SessionAccount} from '#/state/session' -import {useProfileQuery} from '#/state/queries/profile' -import {useCloseAllActiveElements} from '#/state/util' +import {Text} from '../util/text/Text' +import {UserAvatar} from '../util/UserAvatar' export const snapPoints = ['40%', '90%'] diff --git a/src/view/com/modals/util.tsx b/src/view/com/modals/util.tsx index 06f394ec49..c047a0523c 100644 --- a/src/view/com/modals/util.tsx +++ b/src/view/com/modals/util.tsx @@ -1,4 +1,4 @@ export { BottomSheetScrollView as ScrollView, BottomSheetTextInput as TextInput, -} from '@gorhom/bottom-sheet' +} from '@discord/bottom-sheet/src' diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index ba74ba6d8d..c1159379d6 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -1,36 +1,36 @@ import React, {useEffect, useRef} from 'react' import {StyleSheet, useWindowDimensions, View} from 'react-native' import {AppBskyFeedDefs} from '@atproto/api' -import {Trans, msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {List, ListMethods} from '../util/List' -import {PostThreadItem} from './PostThreadItem' -import {ComposePrompt} from '../composer/Prompt' -import {ViewHeader} from '../util/ViewHeader' -import {Text} from '../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useSetTitle} from 'lib/hooks/useSetTitle' +import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' +import {isAndroid, isNative, isWeb} from '#/platform/detection' import { + sortThread, + ThreadBlocked, ThreadNode, - ThreadPost, ThreadNotFound, - ThreadBlocked, + ThreadPost, usePostThreadQuery, - sortThread, } from '#/state/queries/post-thread' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {sanitizeDisplayName} from 'lib/strings/display-names' import { useModerationOpts, usePreferencesQuery, } from '#/state/queries/preferences' import {useSession} from '#/state/session' -import {isAndroid, isNative, isWeb} from '#/platform/detection' -import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' -import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {usePalette} from 'lib/hooks/usePalette' +import {useSetTitle} from 'lib/hooks/useSetTitle' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {sanitizeDisplayName} from 'lib/strings/display-names' import {cleanError} from 'lib/strings/errors' +import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {ComposePrompt} from '../composer/Prompt' +import {List, ListMethods} from '../util/List' +import {Text} from '../util/text/Text' +import {ViewHeader} from '../util/ViewHeader' +import {PostThreadItem} from './PostThreadItem' // FlatList maintainVisibleContentPosition breaks if too many items // are prepended. This seems to be an optimal number based on *shrug*. @@ -371,7 +371,7 @@ export function PostThread({ return ( <> ) => { const {isMobile, isTabletOrMobile} = useWebMediaQueries() const pal = usePalette('default') @@ -25,7 +30,18 @@ export const LoggedOutLayout = ({ }) if (isMobile) { - return {children} + if (scrollable) { + return ( + + {children} + + ) + } else { + return {children} + } } return ( @@ -50,9 +66,23 @@ export const LoggedOutLayout = ({ {description} - - {children} - + {scrollable ? ( + + + + {children} + + + + ) : ( + + {children} + + )} ) } @@ -74,7 +104,16 @@ const styles = StyleSheet.create({ paddingHorizontal: 40, justifyContent: 'center', }, - + scrollableContent: { + flex: 2, + }, + scrollview: { + flex: 1, + }, + scrollViewContentContainer: { + flex: 1, + paddingHorizontal: 40, + }, leadinText: { fontSize: 36, fontWeight: '800', diff --git a/yarn.lock b/yarn.lock index 286ddfd543..c81f120664 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2814,6 +2814,13 @@ pino "^8.11.0" pino-http "^8.3.3" +"@discord/bottom-sheet@https://github.com/bluesky-social/react-native-bottom-sheet.git#discord-fork-4.6.1": + version "4.6.1" + resolved "https://github.com/bluesky-social/react-native-bottom-sheet.git#54dc2e0e318b0524a2d2d8fb817f6c48101bb0b1" + dependencies: + "@gorhom/portal" "1.0.14" + invariant "^2.2.4" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -3602,14 +3609,6 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@gorhom/bottom-sheet@^4.5.1": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.5.1.tgz#1ac4b234a80e7dff263f0b7ac207f92e41562849" - integrity sha512-4Qy6hzvN32fXu2hDxDXOIS0IBGBT6huST7J7+K1V5bXemZ08KIx5ZffyLgwhCUl+CnyeG2KG6tqk6iYLkIwi7Q== - dependencies: - "@gorhom/portal" "1.0.14" - invariant "^2.2.4" - "@gorhom/portal@1.0.14": version "1.0.14" resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.14.tgz#1953edb76aaba80fb24021dc774550194a18e111" @@ -7629,6 +7628,11 @@ dependencies: "@types/node" "*" +"@types/invariant@^2.2.37": + version "2.2.37" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.37.tgz#1709741e534364d653c87dff22fc76fa94aa7bc0" + integrity sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"