Skip to content

Commit

Permalink
PWI improvements (#3489)
Browse files Browse the repository at this point in the history
* Enable home and feeds on the PWI

* Add global SigninDialog to drive useRequireAuth()

* Tweak desktop styles

* Make the logo in leftnav PWI a clickable home link

* Add label

* Improve dialog on web

* Fix query key

* Go to home after signout from settings screen

* Filter out feeds from the discover listing for logged out users which are known to break without auth

* Update profile header follow/subscribe to give signin prompt

* Show profile feeds tabs on pwi

* Add language selector to account creation footer and pwi left nav desktop

---------

Co-authored-by: dan <[email protected]>
  • Loading branch information
pfrazee and gaearon authored Apr 12, 2024
1 parent 44039c6 commit ec5c492
Show file tree
Hide file tree
Showing 23 changed files with 518 additions and 477 deletions.
18 changes: 5 additions & 13 deletions src/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="ProfileFeed"
getComponent={() => ProfileFeedScreen}
options={{title: title(msg`Feed`), requireAuth: true}}
options={{title: title(msg`Feed`)}}
/>
<Stack.Screen
name="ProfileFeedLikedBy"
Expand Down Expand Up @@ -331,11 +331,7 @@ function HomeTabNavigator() {
animationDuration: 250,
contentStyle: pal.view,
}}>
<HomeTab.Screen
name="Home"
getComponent={() => HomeScreen}
options={{requireAuth: true}}
/>
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
{commonScreens(HomeTab)}
</HomeTab.Navigator>
)
Expand Down Expand Up @@ -371,11 +367,7 @@ function FeedsTabNavigator() {
animationDuration: 250,
contentStyle: pal.view,
}}>
<FeedsTab.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{requireAuth: true}}
/>
<FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} />
{commonScreens(FeedsTab as typeof HomeTab)}
</FeedsTab.Navigator>
)
Expand Down Expand Up @@ -451,7 +443,7 @@ const FlatNavigator = () => {
<Flat.Screen
name="Home"
getComponent={() => HomeScreen}
options={{title: title(msg`Home`), requireAuth: true}}
options={{title: title(msg`Home`)}}
/>
<Flat.Screen
name="Search"
Expand All @@ -461,7 +453,7 @@ const FlatNavigator = () => {
<Flat.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{title: title(msg`Feeds`), requireAuth: true}}
options={{title: title(msg`Feeds`)}}
/>
<Flat.Screen
name="Notifications"
Expand Down
67 changes: 67 additions & 0 deletions src/components/AppLanguageDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react'
import {View} from 'react-native'
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'

import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {APP_LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {atoms as a, useTheme} from '#/alf'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'

export function AppLanguageDropdown() {
const t = useTheme()

const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)

const onChangeAppLanguage = React.useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
if (!value) return
if (sanitizedLang !== value) {
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
}
},
[sanitizedLang, setLangPrefs],
)

return (
<View style={a.relative}>
<RNPickerSelect
placeholder={{}}
value={sanitizedLang}
onValueChange={onChangeAppLanguage}
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
label: l.name,
value: l.code2,
key: l.code2,
}))}
useNativeAndroidPickerStyle={false}
style={{
inputAndroid: {
color: t.atoms.text_contrast_medium.color,
fontSize: 16,
paddingRight: 12 + 4,
},
inputIOS: {
color: t.atoms.text.color,
fontSize: 16,
paddingRight: 12 + 4,
},
}}
/>

<View
style={[
a.absolute,
a.inset_0,
{left: 'auto'},
{pointerEvents: 'none'},
a.align_center,
a.justify_center,
]}>
<ChevronDown fill={t.atoms.text.color} size="xs" />
</View>
</View>
)
}
62 changes: 62 additions & 0 deletions src/components/AppLanguageDropdown.web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'
import {View} from 'react-native'

import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {APP_LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {atoms as a, useTheme} from '#/alf'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'

export function AppLanguageDropdown() {
const t = useTheme()

const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()

const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)

const onChangeAppLanguage = React.useCallback(
(ev: React.ChangeEvent<HTMLSelectElement>) => {
const value = ev.target.value

if (!value) return
if (sanitizedLang !== value) {
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
}
},
[sanitizedLang, setLangPrefs],
)

return (
<View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_shrink]}>
<Text aria-hidden={true} style={t.atoms.text_contrast_medium}>
{APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name}
</Text>
<ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_shrink} />

<select
value={sanitizedLang}
onChange={onChangeAppLanguage}
style={{
cursor: 'pointer',
MozAppearance: 'none',
WebkitAppearance: 'none',
appearance: 'none',
position: 'absolute',
inset: 0,
width: '100%',
color: 'transparent',
background: 'transparent',
border: 0,
padding: 0,
}}>
{APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => (
<option key={l.code2} value={l.code2}>
{l.name}
</option>
))}
</select>
</View>
)
}
7 changes: 5 additions & 2 deletions src/components/dialogs/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ type Control = Dialog.DialogOuterProps['control']

type ControlsContext = {
mutedWordsDialogControl: Control
signinDialogControl: Control
}

const ControlsContext = React.createContext({
mutedWordsDialogControl: {} as Control,
signinDialogControl: {} as Control,
})

export function useGlobalDialogsControlContext() {
Expand All @@ -18,9 +20,10 @@ export function useGlobalDialogsControlContext() {

export function Provider({children}: React.PropsWithChildren<{}>) {
const mutedWordsDialogControl = Dialog.useDialogControl()
const signinDialogControl = Dialog.useDialogControl()
const ctx = React.useMemo<ControlsContext>(
() => ({mutedWordsDialogControl}),
[mutedWordsDialogControl],
() => ({mutedWordsDialogControl, signinDialogControl}),
[mutedWordsDialogControl, signinDialogControl],
)

return (
Expand Down
99 changes: 99 additions & 0 deletions src/components/dialogs/Signin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {isNative} from '#/platform/detection'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import {Text} from '#/components/Typography'

export function SigninDialog() {
const {signinDialogControl: control} = useGlobalDialogsControlContext()
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<SigninDialogInner control={control} />
</Dialog.Outer>
)
}

function SigninDialogInner({}: {control: Dialog.DialogOuterProps['control']}) {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()

const showSignIn = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount, closeAllActiveElements])

const showCreateAccount = React.useCallback(() => {
closeAllActiveElements()
requestSwitchToAccount({requestedAccount: 'new'})
}, [requestSwitchToAccount, closeAllActiveElements])

return (
<Dialog.ScrollableInner
label={_(msg`Sign into Bluesky or create a new account`)}
style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}>
<View>
<View
style={[
a.flex_row,
a.align_center,
a.justify_center,
a.gap_sm,
a.pb_lg,
]}>
<Logo width={36} />
<View style={{paddingTop: 6}}>
<Logotype width={120} fill={t.atoms.text.color} />
</View>
</View>

<Text style={[a.text_lg, a.text_center, t.atoms.text, a.pb_2xl]}>
<Trans>
Sign in or create your account to join the conversation!
</Trans>
</Text>

<View style={[a.flex_col, a.gap_md]}>
<Button
variant="solid"
color="primary"
size="large"
onPress={showCreateAccount}
label={_(msg`Create an account`)}>
<ButtonText>
<Trans>Create an account</Trans>
</ButtonText>
</Button>

<Button
variant="solid"
color="secondary"
size="large"
onPress={showSignIn}
label={_(msg`Sign in`)}>
<ButtonText>
<Trans>Sign in</Trans>
</ButtonText>
</Button>
</View>

{isNative && <View style={{height: 10}} />}
</View>

<Dialog.Close />
</Dialog.ScrollableInner>
)
}
51 changes: 28 additions & 23 deletions src/screens/Profile/Header/ProfileHeaderLabeler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {useModalControls} from '#/state/modals'
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {useRequireAuth, useSession} from '#/state/session'
import {useAnalytics} from 'lib/analytics/analytics'
import {useHaptics} from 'lib/haptics'
import {useProfileShadow} from 'state/cache/profile-shadow'
Expand Down Expand Up @@ -64,6 +64,7 @@ let ProfileHeaderLabeler = ({
const {currentAccount, hasSession} = useSession()
const {openModal} = useModalControls()
const {track} = useAnalytics()
const requireAuth = useRequireAuth()
const playHaptic = useHaptics()
const cantSubscribePrompt = Prompt.usePromptControl()
const isSelf = currentAccount?.did === profile.did
Expand Down Expand Up @@ -125,27 +126,32 @@ let ProfileHeaderLabeler = ({
})
}, [track, openModal, profile])

const onPressSubscribe = React.useCallback(async () => {
if (!canSubscribe) {
cantSubscribePrompt.open()
return
}
try {
await toggleSubscription({
did: profile.did,
subscribe: !isSubscribed,
})
} catch (e: any) {
// setSubscriptionError(e.message)
logger.error(`Failed to subscribe to labeler`, {message: e.message})
}
}, [
toggleSubscription,
isSubscribed,
profile,
canSubscribe,
cantSubscribePrompt,
])
const onPressSubscribe = React.useCallback(
() =>
requireAuth(async () => {
if (!canSubscribe) {
cantSubscribePrompt.open()
return
}
try {
await toggleSubscription({
did: profile.did,
subscribe: !isSubscribed,
})
} catch (e: any) {
// setSubscriptionError(e.message)
logger.error(`Failed to subscribe to labeler`, {message: e.message})
}
}),
[
requireAuth,
toggleSubscription,
isSubscribed,
profile,
canSubscribe,
cantSubscribePrompt,
],
)

const isMe = React.useMemo(
() => currentAccount?.did === profile.did,
Expand Down Expand Up @@ -184,7 +190,6 @@ let ProfileHeaderLabeler = ({
? _(msg`Unsubscribe from this labeler`)
: _(msg`Subscribe to this labeler`)
}
disabled={!hasSession}
onPress={onPressSubscribe}>
{state => (
<View
Expand Down
1 change: 0 additions & 1 deletion src/screens/Profile/Header/ProfileHeaderStandard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ let ProfileHeaderStandard = ({
? _(msg`Unfollow ${profile.handle}`)
: _(msg`Follow ${profile.handle}`)
}
disabled={!hasSession}
onPress={
profile.viewer?.following ? onPressUnfollow : onPressFollow
}
Expand Down
Loading

0 comments on commit ec5c492

Please sign in to comment.