Merge remote-tracking branch 'origin/main' into hailey/verify-email-dialog-alf

This commit is contained in:
Hailey
2024-10-10 11:56:44 -07:00
13 changed files with 116 additions and 59 deletions
+36 -8
View File
@@ -42,7 +42,7 @@ class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDele
let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else { let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else {
return return
} }
switch payload.action { switch payload.action {
case .present: case .present:
guard let url = self.starterPackUrl else { guard let url = self.starterPackUrl else {
@@ -66,23 +66,51 @@ class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDele
guard let url = navigationAction.request.url else { guard let url = navigationAction.request.url else {
return .allow return .allow
} }
// Store the previous one to compare later, but only set starterPackUrl when we find the right one // Store the previous one to compare later, but only set starterPackUrl when we find the right one
prevUrl = url prevUrl = url
// pathComponents starts with "/" as the first component, then each path name. so... // pathComponents starts with "/" as the first component, then each path name. so...
// ["/", "start", "name", "rkey"] // ["/", "start", "name", "rkey"]
if url.pathComponents.count == 4, if isStarterPackUrl(url){
url.pathComponents[1] == "start" {
self.starterPackUrl = url self.starterPackUrl = url
} }
return .allow return .allow
} }
func isStarterPackUrl(_ url: URL) -> Bool {
var host: String?
if #available(iOS 16.0, *) {
host = url.host()
} else {
host = url.host
}
switch host {
case "bsky.app":
if url.pathComponents.count == 4,
(url.pathComponents[1] == "start" || url.pathComponents[1] == "starter-pack") {
return true
}
return false
case "go.bsky.app":
if url.pathComponents.count == 2 {
return true
}
return false
default:
return false
}
}
func handleURL(url: URL) { func handleURL(url: URL) {
let urlString = "\(url.absoluteString)?clip=true" if isStarterPackUrl(url) {
if let url = URL(string: urlString) { let urlString = "\(url.absoluteString)?clip=true"
self.webView?.load(URLRequest(url: url)) if let url = URL(string: urlString) {
self.webView?.load(URLRequest(url: url))
}
} else {
self.webView?.load(URLRequest(url: URL(string: "https://bsky.app/?splash=true&clip=true")!))
} }
} }
+11 -11
View File
@@ -153,18 +153,18 @@ export function Outer({
) )
return ( return (
<Context.Provider value={context}> <BottomSheet
<BottomSheet ref={ref}
ref={ref} cornerRadius={20}
cornerRadius={20} backgroundColor={t.atoms.bg.backgroundColor}
backgroundColor={t.atoms.bg.backgroundColor} {...nativeOptions}
{...nativeOptions} onSnapPointChange={onSnapPointChange}
onSnapPointChange={onSnapPointChange} onStateChange={onStateChange}
onStateChange={onStateChange} disableDrag={disableDrag}>
disableDrag={disableDrag}> <Context.Provider value={context}>
<View testID={testID}>{children}</View> <View testID={testID}>{children}</View>
</BottomSheet> </Context.Provider>
</Context.Provider> </BottomSheet>
) )
} }
-2
View File
@@ -6,7 +6,6 @@ import type {
} from 'react-native' } from 'react-native'
import {ViewStyleProp} from '#/alf' import {ViewStyleProp} from '#/alf'
import {PortalComponent} from '#/components/Portal'
import {BottomSheetViewProps} from '../../../modules/bottom-sheet' import {BottomSheetViewProps} from '../../../modules/bottom-sheet'
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types' import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
@@ -61,7 +60,6 @@ export type DialogOuterProps = {
nativeOptions?: Omit<BottomSheetViewProps, 'children'> nativeOptions?: Omit<BottomSheetViewProps, 'children'>
webOptions?: {} webOptions?: {}
testID?: string testID?: string
Portal?: PortalComponent
} }
type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T
+1 -5
View File
@@ -18,7 +18,6 @@ import {
ItemTextProps, ItemTextProps,
TriggerProps, TriggerProps,
} from '#/components/Menu/types' } from '#/components/Menu/types'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export { export {
@@ -78,11 +77,9 @@ export function Trigger({children, label}: TriggerProps) {
export function Outer({ export function Outer({
children, children,
showCancel, showCancel,
Portal,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
showCancel?: boolean showCancel?: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
Portal?: PortalComponent
}>) { }>) {
const context = React.useContext(Context) const context = React.useContext(Context)
const {_} = useLingui() const {_} = useLingui()
@@ -90,8 +87,7 @@ export function Outer({
return ( return (
<Dialog.Outer <Dialog.Outer
control={context.control} control={context.control}
nativeOptions={{preventExpansion: true}} nativeOptions={{preventExpansion: true}}>
Portal={Portal}>
<Dialog.Handle /> <Dialog.Handle />
{/* Re-wrap with context since Dialogs are portal-ed to root */} {/* Re-wrap with context since Dialogs are portal-ed to root */}
<Context.Provider value={context}> <Context.Provider value={context}>
-2
View File
@@ -12,8 +12,6 @@ type ComponentMap = {
[id: string]: Component [id: string]: Component
} }
export type PortalComponent = ({children}: {children?: React.ReactNode}) => null
export function createPortalGroup() { export function createPortalGroup() {
const Context = React.createContext<ContextType>({ const Context = React.createContext<ContextType>({
outlet: null, outlet: null,
+1 -7
View File
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonColor, ButtonText} from '#/components/Button' import {Button, ButtonColor, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {BottomSheetViewProps} from '../../modules/bottom-sheet' import {BottomSheetViewProps} from '../../modules/bottom-sheet'
@@ -27,12 +26,10 @@ export function Outer({
children, children,
control, control,
testID, testID,
Portal,
nativeOptions, nativeOptions,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
testID?: string testID?: string
Portal?: PortalComponent
nativeOptions?: Omit<BottomSheetViewProps, 'children'> nativeOptions?: Omit<BottomSheetViewProps, 'children'>
}>) { }>) {
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
@@ -48,7 +45,6 @@ export function Outer({
<Dialog.Outer <Dialog.Outer
control={control} control={control}
testID={testID} testID={testID}
Portal={Portal}
nativeOptions={{preventExpansion: true, ...nativeOptions}}> nativeOptions={{preventExpansion: true, ...nativeOptions}}>
<Dialog.Handle /> <Dialog.Handle />
<Context.Provider value={context}> <Context.Provider value={context}>
@@ -190,7 +186,6 @@ export function Basic({
onConfirm, onConfirm,
confirmButtonColor, confirmButtonColor,
showCancel = true, showCancel = true,
Portal,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
control: Dialog.DialogOuterProps['control'] control: Dialog.DialogOuterProps['control']
title: string title: string
@@ -207,10 +202,9 @@ export function Basic({
onConfirm: (e: GestureResponderEvent) => void onConfirm: (e: GestureResponderEvent) => void
confirmButtonColor?: ButtonColor confirmButtonColor?: ButtonColor
showCancel?: boolean showCancel?: boolean
Portal?: PortalComponent
}>) { }>) {
return ( return (
<Outer control={control} testID="confirmModal" Portal={Portal}> <Outer control={control} testID="confirmModal">
<TitleText>{title}</TitleText> <TitleText>{title}</TitleText>
<DescriptionText>{description}</DescriptionText> <DescriptionText>{description}</DescriptionText>
<Actions> <Actions>
+1 -1
View File
@@ -35,7 +35,7 @@ function ReportDialogInner(props: ReportDialogProps) {
isLoading: isLabelerLoading, isLoading: isLabelerLoading,
data: labelers, data: labelers,
error, error,
} = useMyLabelersQuery() } = useMyLabelersQuery({excludeNonConfigurableLabelers: true})
const isLoading = useDelayedLoading(500, isLabelerLoading) const isLoading = useDelayedLoading(500, isLabelerLoading)
const ref = React.useRef<ScrollView>(null) const ref = React.useRef<ScrollView>(null)
@@ -47,7 +47,7 @@ interface AppClipMessage {
jsonToStore?: string jsonToStore?: string
} }
function postAppClipMessage(message: AppClipMessage) { export function postAppClipMessage(message: AppClipMessage) {
// @ts-expect-error safari webview only // @ts-expect-error safari webview only
window.webkit.messageHandlers.onMessage.postMessage(JSON.stringify(message)) window.webkit.messageHandlers.onMessage.postMessage(JSON.stringify(message))
} }
@@ -356,7 +356,7 @@ function LandingScreenLoaded({
) )
} }
function AppClipOverlay({ export function AppClipOverlay({
visible, visible,
setIsVisible, setIsVisible,
}: { }: {
+12 -4
View File
@@ -1,12 +1,13 @@
import React from 'react' import React from 'react'
import { import {
DEFAULT_LABEL_SETTINGS,
BskyAgent, BskyAgent,
DEFAULT_LABEL_SETTINGS,
interpretLabelValueDefinitions, interpretLabelValueDefinitions,
} from '@atproto/api' } from '@atproto/api'
import {usePreferencesQuery} from './index' import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
import {useLabelersDetailedInfoQuery} from '../labeler' import {useLabelersDetailedInfoQuery} from '../labeler'
import {usePreferencesQuery} from './index'
/** /**
* More strict than our default settings for logged in users. * More strict than our default settings for logged in users.
@@ -16,15 +17,22 @@ export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS
Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']), Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']),
) )
export function useMyLabelersQuery() { export function useMyLabelersQuery({
excludeNonConfigurableLabelers = false,
}: {
excludeNonConfigurableLabelers?: boolean
} = {}) {
const prefs = usePreferencesQuery() const prefs = usePreferencesQuery()
const dids = Array.from( let dids = Array.from(
new Set( new Set(
BskyAgent.appLabelers.concat( BskyAgent.appLabelers.concat(
prefs.data?.moderationPrefs.labelers.map(l => l.did) || [], prefs.data?.moderationPrefs.labelers.map(l => l.did) || [],
), ),
), ),
) )
if (excludeNonConfigurableLabelers) {
dids = dids.filter(did => !isNonConfigurableModerationAuthority(did))
}
const labelers = useLabelersDetailedInfoQuery({dids}) const labelers = useLabelersDetailedInfoQuery({dids})
const isLoading = prefs.isLoading || labelers.isLoading const isLoading = prefs.isLoading || labelers.isLoading
const error = prefs.error || labelers.error const error = prefs.error || labelers.error
+20
View File
@@ -9,6 +9,10 @@ import {useKawaiiMode} from '#/state/preferences/kawaii'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype' import {Logotype} from '#/view/icons/Logotype'
import {
AppClipOverlay,
postAppClipMessage,
} from '#/screens/StarterPack/StarterPackLandingScreen'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
@@ -28,6 +32,18 @@ export const SplashScreen = ({
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries() const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries()
const [showClipOverlay, setShowClipOverlay] = React.useState(false)
React.useEffect(() => {
const getParams = new URLSearchParams(window.location.search)
const clip = getParams.get('clip')
if (clip === 'true') {
setShowClipOverlay(true)
postAppClipMessage({
action: 'present',
})
}
}, [])
const kawaii = useKawaiiMode() const kawaii = useKawaiiMode()
@@ -119,6 +135,10 @@ export const SplashScreen = ({
</View> </View>
<Footer /> <Footer />
</CenteredView> </CenteredView>
<AppClipOverlay
visible={showClipOverlay}
setIsVisible={setShowClipOverlay}
/>
</> </>
) )
} }
+1 -4
View File
@@ -30,7 +30,6 @@ import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {PortalComponent} from '#/components/Portal'
import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
@@ -51,7 +50,6 @@ interface UserAvatarProps extends BaseUserAvatarProps {
interface EditableUserAvatarProps extends BaseUserAvatarProps { interface EditableUserAvatarProps extends BaseUserAvatarProps {
onSelectNewAvatar: (img: RNImage | null) => void onSelectNewAvatar: (img: RNImage | null) => void
Portal?: PortalComponent
} }
interface PreviewableUserAvatarProps extends BaseUserAvatarProps { interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
@@ -268,7 +266,6 @@ let EditableUserAvatar = ({
size, size,
avatar, avatar,
onSelectNewAvatar, onSelectNewAvatar,
Portal,
}: EditableUserAvatarProps): React.ReactNode => { }: EditableUserAvatarProps): React.ReactNode => {
const t = useTheme() const t = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
@@ -366,7 +363,7 @@ let EditableUserAvatar = ({
</Pressable> </Pressable>
)} )}
</Menu.Trigger> </Menu.Trigger>
<Menu.Outer showCancel Portal={Portal}> <Menu.Outer showCancel>
<Menu.Group> <Menu.Group>
{isNative && ( {isNative && (
<Menu.Item <Menu.Item
+1 -4
View File
@@ -25,7 +25,6 @@ import {
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {PortalComponent} from '#/components/Portal'
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
export function UserBanner({ export function UserBanner({
@@ -33,13 +32,11 @@ export function UserBanner({
banner, banner,
moderation, moderation,
onSelectNewBanner, onSelectNewBanner,
Portal,
}: { }: {
type?: 'labeler' | 'default' type?: 'labeler' | 'default'
banner?: string | null banner?: string | null
moderation?: ModerationUI moderation?: ModerationUI
onSelectNewBanner?: (img: RNImage | null) => void onSelectNewBanner?: (img: RNImage | null) => void
Portal?: PortalComponent
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
@@ -118,7 +115,7 @@ export function UserBanner({
</Pressable> </Pressable>
)} )}
</Menu.Trigger> </Menu.Trigger>
<Menu.Outer showCancel Portal={Portal}> <Menu.Outer showCancel>
<Menu.Group> <Menu.Group>
{isNative && ( {isNative && (
<Menu.Item <Menu.Item
+30 -9
View File
@@ -4,8 +4,15 @@ import {useFocusEffect} from '@react-navigation/native'
import {PROD_DEFAULT_FEED} from '#/lib/constants' import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
import {useSetTitle} from '#/lib/hooks/useSetTitle' import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
import {
HomeTabNavigatorParams,
NativeStackScreenProps,
} from '#/lib/routes/types'
import {logEvent, LogEvents} from '#/lib/statsig/statsig' import {logEvent, LogEvents} from '#/lib/statsig/statsig'
import {isWeb} from '#/platform/detection'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed' import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
import {FeedParams} from '#/state/queries/post-feed' import {FeedParams} from '#/state/queries/post-feed'
@@ -13,26 +20,34 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' import {FeedPage} from '#/view/com/feeds/FeedPage'
import {useRequestNotificationsPermission} from 'lib/notifications/notifications' import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
import {FeedPage} from 'view/com/feeds/FeedPage' import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
import {HomeHeader} from '../com/home/HomeHeader' import {HomeHeader} from '../com/home/HomeHeader'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'> type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
export function HomeScreen(props: Props) { export function HomeScreen(props: Props) {
const {setShowLoggedOut} = useLoggedOutViewControls()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
usePinnedFeedsInfos() usePinnedFeedsInfos()
React.useEffect(() => { React.useEffect(() => {
if (isWeb && !currentAccount) {
const getParams = new URLSearchParams(window.location.search)
const splash = getParams.get('splash')
if (splash === 'true') {
setShowLoggedOut(true)
return
}
}
const params = props.route.params const params = props.route.params
if ( if (
currentAccount && currentAccount &&
@@ -45,7 +60,13 @@ export function HomeScreen(props: Props) {
name: params.name, name: params.name,
}) })
} }
}, [currentAccount, props.navigation, props.route.name, props.route.params]) }, [
currentAccount,
props.navigation,
props.route.name,
props.route.params,
setShowLoggedOut,
])
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
return ( return (