Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (69 commits)
  Update .po files
  use `showControls` to show/hide live text icon on ios (#2982)
  Fix dim mode unread notif color
  Make dim theme dim (#2966)
  Add handle validation to create account UI (#2959)
  Normalize relative day (#2874)
  increase timeout to 15s (#2958)
  use `useOpenLink` hook for links in ALF (#2975)
  Rename Home Feed Prefs to Following Feed Prefs (#2965)
  Refactor feed header components (#2964)
  patch react-navigation to fix history bug (#2955)
  Use EAS managed build number, run build/submit on GH Actions (#2841)
  Fix `numberOfLines` not updating on iOS 15 (#2956)
  Fix UITextView line height adjustment for DynamicType, always use the max width for the view (#2916)
  Navigate back from a deleted post's route (#2948)
  Add optional close callback to Dialog (#2947)
  Fix flash when pressing into just-created post (#2945)
  Last usage (#2944)
  Update blogpost URL in ExportCarDialog.tsx (#2939)
  Prefer full posts for post thread placeholder (#2943)
  ...
This commit is contained in:
Eric Bailey
2024-02-26 14:50:43 -06:00
143 changed files with 16079 additions and 9867 deletions
+164 -86
View File
@@ -1,5 +1,11 @@
import React from 'react'
import {ActivityIndicator, StyleSheet, View, type FlatList} from 'react-native'
import {
ActivityIndicator,
StyleSheet,
View,
type FlatList,
Pressable,
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {ViewHeader} from 'view/com/util/ViewHeader'
@@ -8,9 +14,10 @@ import {Link} from 'view/com/util/Link'
import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ComposeIcon2, CogIcon} from 'lib/icons'
import {ComposeIcon2, CogIcon, MagnifyingGlassIcon2} from 'lib/icons'
import {s} from 'lib/styles'
import {SearchInput} from 'view/com/util/forms/SearchInput'
import {atoms as a, useTheme} from '#/alf'
import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {
LoadingPlaceholder,
@@ -35,7 +42,11 @@ import {
import {cleanError} from 'lib/strings/errors'
import {useComposerControls} from '#/state/shell/composer'
import {useSession} from '#/state/session'
import {isNative} from '#/platform/detection'
import {isNative, isWeb} from '#/platform/detection'
import {HITSLOP_10} from 'lib/constants'
import {IconCircle} from '#/components/IconCircle'
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
@@ -121,6 +132,7 @@ export function FeedsScreen(_props: Props) {
} = useSearchPopularFeedsMutation()
const {hasSession} = useSession()
const listRef = React.useRef<FlatList>(null)
const searchInputRef = React.useRef<SearchInputRef>(null)
/**
* A search query is present. We may not have search results yet.
@@ -207,12 +219,7 @@ export function FeedsScreen(_props: Props) {
// pendingItems: this.rootStore.preferences.savedFeeds.length || 3,
})
} else {
if (preferences?.feeds?.saved.length === 0) {
slices.push({
key: 'savedFeedNoResults',
type: 'savedFeedNoResults',
})
} else {
if (preferences?.feeds?.saved.length !== 0) {
const {saved, pinned} = preferences.feeds
slices = slices.concat(
@@ -330,14 +337,26 @@ export function FeedsScreen(_props: Props) {
const renderHeaderBtn = React.useCallback(() => {
return (
<Link
href="/settings/saved-feeds"
hitSlop={10}
accessibilityRole="button"
accessibilityLabel={_(msg`Edit Saved Feeds`)}
accessibilityHint={_(msg`Opens screen to edit Saved Feeds`)}>
<CogIcon size={22} strokeWidth={2} style={pal.textLight} />
</Link>
<View style={styles.headerBtnGroup}>
<Pressable
accessibilityRole="button"
hitSlop={HITSLOP_10}
onPress={searchInputRef.current?.focus}>
<MagnifyingGlassIcon2
size={22}
strokeWidth={2}
style={pal.textLight}
/>
</Pressable>
<Link
href="/settings/saved-feeds"
hitSlop={10}
accessibilityRole="button"
accessibilityLabel={_(msg`Edit Saved Feeds`)}
accessibilityHint={_(msg`Opens screen to edit Saved Feeds`)}>
<CogIcon size={22} strokeWidth={2} style={pal.textLight} />
</Link>
</View>
)
}, [pal, _])
@@ -380,34 +399,48 @@ export function FeedsScreen(_props: Props) {
) {
return (
<View style={s.p10}>
<ActivityIndicator />
<ActivityIndicator size="large" />
</View>
)
} else if (item.type === 'savedFeedsHeader') {
if (!isMobile) {
return (
<View
style={[
pal.view,
styles.header,
pal.border,
{
borderBottomWidth: 1,
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
<Trans>My Feeds</Trans>
</Text>
<Link
href="/settings/saved-feeds"
accessibilityLabel={_(msg`Edit My Feeds`)}
accessibilityHint="">
<CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
</Link>
</View>
)
}
return <View />
return (
<>
{!isMobile && (
<View
style={[
pal.view,
styles.header,
pal.border,
{
borderBottomWidth: 1,
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
<Trans>Feeds</Trans>
</Text>
<View style={styles.headerBtnGroup}>
<Pressable
accessibilityRole="button"
hitSlop={HITSLOP_10}
onPress={searchInputRef.current?.focus}>
<MagnifyingGlassIcon2
size={22}
strokeWidth={2}
style={pal.icon}
/>
</Pressable>
<Link
href="/settings/saved-feeds"
accessibilityLabel={_(msg`Edit My Feeds`)}
accessibilityHint="">
<CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
</Link>
</View>
</View>
)}
{preferences?.feeds?.saved?.length !== 0 && <FeedsSavedHeader />}
</>
)
} else if (item.type === 'savedFeedNoResults') {
return (
<View
@@ -425,45 +458,17 @@ export function FeedsScreen(_props: Props) {
} else if (item.type === 'popularFeedsHeader') {
return (
<>
<View
style={[
pal.view,
styles.header,
{
// This is first in the flatlist without a session -esb
marginTop: hasSession ? 16 : 0,
paddingLeft: isMobile ? 12 : undefined,
paddingRight: 10,
paddingBottom: isMobile ? 6 : undefined,
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
<Trans>Discover new feeds</Trans>
</Text>
{!isMobile && (
<SearchInput
query={query}
onChangeQuery={onChangeQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
setIsInputFocused={onChangeSearchFocus}
style={{flex: 1, maxWidth: 250}}
/>
)}
<FeedsAboutHeader />
<View style={{paddingHorizontal: 12, paddingBottom: 12}}>
<SearchInput
ref={searchInputRef}
query={query}
onChangeQuery={onChangeQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
setIsInputFocused={onChangeSearchFocus}
/>
</View>
{isMobile && (
<View style={{paddingHorizontal: 8, paddingBottom: 10}}>
<SearchInput
query={query}
onChangeQuery={onChangeQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
setIsInputFocused={onChangeSearchFocus}
/>
</View>
)}
</>
)
} else if (item.type === 'popularFeedsLoading') {
@@ -495,15 +500,20 @@ export function FeedsScreen(_props: Props) {
return null
},
[
_,
hasSession,
isMobile,
pal,
pal.view,
pal.border,
pal.text,
pal.icon,
pal.textLight,
_,
preferences?.feeds?.saved?.length,
query,
onChangeQuery,
onPressCancelSearch,
onSubmitQuery,
onChangeSearchFocus,
hasSession,
],
)
@@ -518,8 +528,6 @@ export function FeedsScreen(_props: Props) {
/>
)}
{preferences ? <View /> : <ActivityIndicator />}
<List
ref={listRef}
style={[!isTabletOrDesktop && s.flex1, styles.list]}
@@ -626,6 +634,71 @@ function SavedFeedLoadingPlaceholder() {
)
}
function FeedsSavedHeader() {
const t = useTheme()
return (
<View
style={
isWeb
? [
a.flex_row,
a.px_md,
a.py_lg,
a.gap_md,
a.border_b,
t.atoms.border_contrast_low,
]
: [
{flexDirection: 'row-reverse'},
a.p_lg,
a.gap_md,
a.border_b,
t.atoms.border_contrast_low,
]
}>
<IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
<Trans>My Feeds</Trans>
</Text>
<Text style={[t.atoms.text_contrast_high]}>
<Trans>All the feeds you've saved, right in one place.</Trans>
</Text>
</View>
</View>
)
}
function FeedsAboutHeader() {
const t = useTheme()
return (
<View
style={
isWeb
? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md]
: [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md]
}>
<IconCircle
icon={ListMagnifyingGlass_Stroke2_Corner0_Rounded}
size="lg"
/>
<View style={[a.flex_1, a.gap_sm]}>
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
<Trans>Discover New Feeds</Trans>
</Text>
<Text style={[t.atoms.text_contrast_high]}>
<Trans>
Custom feeds built by the community bring you new experiences and
help you find the content you love.
</Trans>
</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
@@ -663,4 +736,9 @@ const styles = StyleSheet.create({
paddingHorizontal: 4,
paddingVertical: 2,
},
headerBtnGroup: {
flexDirection: 'row',
gap: 15,
alignItems: 'center',
},
})
+2 -2
View File
@@ -6,7 +6,7 @@ import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
import {HomeHeader} from '../com/home/HomeHeader'
import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
import {FeedPage} from 'view/com/feeds/FeedPage'
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
@@ -118,7 +118,7 @@ function HomeScreenReady({
const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => {
return (
<FeedsTabBar
<HomeHeader
key="FEEDS_TAB_BAR"
selectedPage={props.selectedPage}
onSelect={props.onSelect}
@@ -78,9 +78,9 @@ function RepliesThresholdInput({
type Props = NativeStackScreenProps<
CommonNavigatorParams,
'PreferencesHomeFeed'
'PreferencesFollowingFeed'
>
export function PreferencesHomeFeed({navigation}: Props) {
export function PreferencesFollowingFeed({navigation}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries()
@@ -101,14 +101,14 @@ export function PreferencesHomeFeed({navigation}: Props) {
styles.container,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title={_(msg`Home Feed Preferences`)} showOnDesktop />
<ViewHeader title={_(msg`Following Feed Preferences`)} showOnDesktop />
<View
style={[
styles.titleSection,
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
]}>
<Text type="xl" style={[pal.textLight, styles.description]}>
<Trans>Fine-tune the content you see on your home screen.</Trans>
<Trans>Fine-tune the content you see on your Following feed.</Trans>
</Text>
</View>
@@ -260,7 +260,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
<Text style={[pal.text, s.pb10]}>
<Trans>
Set this setting to "Yes" to show samples of your saved feeds in
your following feed. This is an experimental feature.
your Following feed. This is an experimental feature.
</Trans>
</Text>
<ToggleButton
+4 -4
View File
@@ -17,7 +17,7 @@ import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
import {RichText} from 'view/com/util/text/RichText'
import {RichText} from '#/components/RichText'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {FAB} from 'view/com/util/fab/FAB'
import {EmptyState} from 'view/com/util/EmptyState'
@@ -59,6 +59,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {truncateAndInvalidate} from '#/state/queries/util'
import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {atoms as a} from '#/alf'
const SECTION_TITLES = ['Posts', 'About']
@@ -575,9 +576,8 @@ function AboutSection({
{feedInfo.description ? (
<RichText
testID="listDescription"
type="lg"
style={pal.text}
richText={feedInfo.description}
style={[a.text_md]}
value={feedInfo.description}
/>
) : (
<Text type="lg" style={[{fontStyle: 'italic'}, pal.textLight]}>
+4 -4
View File
@@ -14,7 +14,7 @@ import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {CenteredView} from 'view/com/util/Views'
import {EmptyState} from 'view/com/util/EmptyState'
import {LoadingScreen} from 'view/com/util/LoadingScreen'
import {RichText} from 'view/com/util/text/RichText'
import {RichText} from '#/components/RichText'
import {Button} from 'view/com/util/forms/Button'
import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
@@ -60,6 +60,7 @@ import {
import {logger} from '#/logger'
import {useAnalytics} from '#/lib/analytics/analytics'
import {listenSoftReset} from '#/state/events'
import {atoms as a} from '#/alf'
const SECTION_TITLES_CURATE = ['Posts', 'About']
const SECTION_TITLES_MOD = ['About']
@@ -742,9 +743,8 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
{descriptionRT ? (
<RichText
testID="listDescription"
type="lg"
style={pal.text}
richText={descriptionRT}
style={[a.text_md]}
value={descriptionRT}
/>
) : (
<Text
@@ -0,0 +1,103 @@
import React from 'react'
import {View} from 'react-native'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {Text, P} from '#/components/Typography'
import {Button, ButtonText} from '#/components/Button'
import {InlineLink, Link} from '#/components/Link'
import {getAgent, useSession} from '#/state/session'
export function ExportCarDialog({
control,
}: {
control: Dialog.DialogOuterProps['control']
}) {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession()
const downloadUrl = React.useMemo(() => {
const agent = getAgent()
if (!currentAccount || !agent.session) {
return '' // shouldnt ever happen
}
// eg: https://bsky.social/xrpc/com.atproto.sync.getRepo?did=did:plc:ewvi7nxzyoun6zhxrhs64oiz
const url = new URL(agent.pdsUrl || agent.service)
url.pathname = '/xrpc/com.atproto.sync.getRepo'
url.searchParams.set('did', agent.session.did)
return url.toString()
}, [currentAccount])
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
accessibilityDescribedBy="dialog-description"
accessibilityLabelledBy="dialog-title">
<View style={[a.relative, a.gap_md, a.w_full]}>
<Text nativeID="dialog-title" style={[a.text_2xl, a.font_bold]}>
<Trans>Export My Data</Trans>
</Text>
<P nativeID="dialog-description" style={[a.text_sm]}>
<Trans>
Your account repository, containing all public data records, can
be downloaded as a "CAR" file. This file does not include media
embeds, such as images, or your private data, which must be
fetched separately.
</Trans>
</P>
<Link
variant="solid"
color="primary"
size="large"
label={_(msg`Download CAR file`)}
to={downloadUrl}
download="repo.car">
<ButtonText>
<Trans>Download CAR file</Trans>
</ButtonText>
</Link>
<P
style={[
a.py_xs,
t.atoms.text_contrast_medium,
a.text_sm,
a.leading_snug,
a.flex_1,
]}>
<Trans>
This feature is in beta. You can read more about repository
exports in{' '}
<InlineLink
to="https://docs.bsky.app/blog/repo-export"
style={[a.text_sm]}>
this blogpost.
</InlineLink>
</Trans>
</P>
<View style={gtMobile && [a.flex_row, a.justify_end]}>
<Button
testID="doneBtn"
variant="outline"
color="primary"
size={gtMobile ? 'small' : 'large'}
onPress={() => control.close()}
label={_(msg`Done`)}>
{_(msg`Done`)}
</Button>
</View>
{!gtMobile && <View style={{height: 40}} />}
</View>
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
@@ -17,14 +17,6 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import * as AppInfo from 'lib/app-info'
import {s, colors} from 'lib/styles'
import {ScrollView} from '../com/util/Views'
import {Link, TextLink} from '../com/util/Link'
import {Text} from '../com/util/text/Text'
import * as Toast from '../com/util/Toast'
import {UserAvatar} from '../com/util/UserAvatar'
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
import {usePalette} from 'lib/hooks/usePalette'
import {useCustomPalette} from 'lib/hooks/useCustomPalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -34,8 +26,6 @@ import {NavigationProp} from 'lib/routes/types'
import {HandIcon, HashtagIcon} from 'lib/icons'
import Clipboard from '@react-native-clipboard/clipboard'
import {makeProfileLink} from 'lib/routes/links'
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile'
import {useModalControls} from '#/state/modals'
import {
@@ -48,22 +38,12 @@ import {
useRequireAltTextEnabled,
useSetRequireAltTextEnabled,
} from '#/state/preferences'
import {
useSession,
useSessionApi,
SessionAccount,
getAgent,
} from '#/state/session'
import {useSession, useSessionApi, SessionAccount} from '#/state/session'
import {useProfileQuery} from '#/state/queries/profile'
import {useClearPreferencesMutation} from '#/state/queries/preferences'
import {useInviteCodesQuery} from '#/state/queries/invites'
// TODO import {useInviteCodesQuery} from '#/state/queries/invites'
import {clear as clearStorage} from '#/state/persisted/store'
import {clearLegacyStorage} from '#/state/persisted/legacy'
// TEMPORARY (APP-700)
// remove after backend testing finishes
// -prf
import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header'
import {STATUS_PAGE_URL} from 'lib/constants'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -75,6 +55,19 @@ import {
useSetInAppBrowser,
} from '#/state/preferences/in-app-browser'
import {isNative} from '#/platform/detection'
import {useDialogControl} from '#/components/Dialog'
import {s, colors} from 'lib/styles'
import {ScrollView} from 'view/com/util/Views'
import {Link, TextLink} from 'view/com/util/Link'
import {Text} from 'view/com/util/text/Text'
import * as Toast from 'view/com/util/Toast'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {ExportCarDialog} from './ExportCarDialog'
function SettingsAccountCard({account}: {account: SessionAccount}) {
const pal = usePalette('default')
@@ -159,23 +152,22 @@ export function SettingsScreen({}: Props) {
const {screen, track} = useAnalytics()
const {openModal} = useModalControls()
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(
getAgent(),
)
const {mutate: clearPreferences} = useClearPreferencesMutation()
const {data: invites} = useInviteCodesQuery()
const invitesAvailable = invites?.available?.length ?? 0
// TODO
// const {data: invites} = useInviteCodesQuery()
// const invitesAvailable = invites?.available?.length ?? 0
const {setShowLoggedOut} = useLoggedOutViewControls()
const closeAllActiveElements = useCloseAllActiveElements()
const exportCarControl = useDialogControl()
const primaryBg = useCustomPalette<ViewStyle>({
light: {backgroundColor: colors.blue0},
dark: {backgroundColor: colors.blue6},
})
const primaryText = useCustomPalette<TextStyle>({
light: {color: colors.blue3},
dark: {color: colors.blue2},
})
// const primaryBg = useCustomPalette<ViewStyle>({
// light: {backgroundColor: colors.blue0},
// dark: {backgroundColor: colors.blue6},
// })
// const primaryText = useCustomPalette<TextStyle>({
// light: {color: colors.blue3},
// dark: {color: colors.blue2},
// })
const dangerBg = useCustomPalette<ViewStyle>({
light: {backgroundColor: colors.red1},
@@ -214,10 +206,16 @@ export function SettingsScreen({}: Props) {
})
}, [track, queryClient, openModal, currentAccount])
const onPressExportRepository = React.useCallback(() => {
exportCarControl.open()
}, [exportCarControl])
/* TODO
const onPressInviteCodes = React.useCallback(() => {
track('Settings:InvitecodesButtonClicked')
openModal({name: 'invite-codes'})
}, [track, openModal])
*/
const onPressLanguageSettings = React.useCallback(() => {
navigation.navigate('LanguageSettings')
@@ -243,8 +241,8 @@ export function SettingsScreen({}: Props) {
Toast.show(_(msg`Copied build version to clipboard`))
}, [_])
const openHomeFeedPreferences = React.useCallback(() => {
navigation.navigate('PreferencesHomeFeed')
const openFollowingFeedPreferences = React.useCallback(() => {
navigation.navigate('PreferencesFollowingFeed')
}, [navigation])
const openThreadsPreferences = React.useCallback(() => {
@@ -282,6 +280,8 @@ export function SettingsScreen({}: Props) {
return (
<View style={s.hContentRegion} testID="settingsScreen">
<ExportCarDialog control={exportCarControl} />
<SimpleViewHeader
showBackButton={isMobile}
style={[
@@ -395,51 +395,57 @@ export function SettingsScreen({}: Props) {
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
<Trans>Invite a Friend</Trans>
</Text>
{/* TODO (
<>
<Text type="xl-bold" style={[pal.text, styles.heading]}>
<Trans>Invite a Friend</Trans>
</Text>
<TouchableOpacity
testID="inviteFriendBtn"
style={[
styles.linkCard,
pal.view,
isSwitchingAccounts && styles.dimmed,
]}
onPress={isSwitchingAccounts ? undefined : onPressInviteCodes}
accessibilityRole="button"
accessibilityLabel={_(msg`Invite`)}
accessibilityHint={_(msg`Opens invite code list`)}
disabled={invites?.disabled}>
<View
style={[
styles.iconContainer,
invitesAvailable > 0 ? primaryBg : pal.btn,
]}>
<FontAwesomeIcon
icon="ticket"
style={
(invitesAvailable > 0
? primaryText
: pal.text) as FontAwesomeIconStyle
}
/>
</View>
<Text type="lg" style={invitesAvailable > 0 ? pal.link : pal.text}>
{invites?.disabled ? (
<Trans>
Your invite codes are hidden when logged in using an App
Password
</Trans>
) : invitesAvailable === 1 ? (
<Trans>{invitesAvailable} invite code available</Trans>
) : (
<Trans>{invitesAvailable} invite codes available</Trans>
)}
</Text>
</TouchableOpacity>
<TouchableOpacity
testID="inviteFriendBtn"
style={[
styles.linkCard,
pal.view,
isSwitchingAccounts && styles.dimmed,
]}
onPress={isSwitchingAccounts ? undefined : onPressInviteCodes}
accessibilityRole="button"
accessibilityLabel={_(msg`Invite`)}
accessibilityHint={_(msg`Opens invite code list`)}
disabled={invites?.disabled}>
<View
style={[
styles.iconContainer,
invitesAvailable > 0 ? primaryBg : pal.btn,
]}>
<FontAwesomeIcon
icon="ticket"
style={
(invitesAvailable > 0
? primaryText
: pal.text) as FontAwesomeIconStyle
}
/>
</View>
<Text
type="lg"
style={invitesAvailable > 0 ? pal.link : pal.text}>
{invites?.disabled ? (
<Trans>
Your invite codes are hidden when logged in using an App
Password
</Trans>
) : invitesAvailable === 1 ? (
<Trans>{invitesAvailable} invite code available</Trans>
) : (
<Trans>{invitesAvailable} invite codes available</Trans>
)}
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<View style={styles.spacer20} />
</>
)*/}
<Text type="xl-bold" style={[pal.text, styles.heading]}>
<Trans>Accessibility</Trans>
@@ -523,7 +529,7 @@ export function SettingsScreen({}: Props) {
pal.view,
isSwitchingAccounts && styles.dimmed,
]}
onPress={openHomeFeedPreferences}
onPress={openFollowingFeedPreferences}
accessibilityRole="button"
accessibilityHint=""
accessibilityLabel={_(msg`Opens the home feed preferences`)}>
@@ -534,7 +540,7 @@ export function SettingsScreen({}: Props) {
/>
</View>
<Text type="lg" style={pal.text}>
<Trans>Home Feed Preferences</Trans>
<Trans>Following Feed Preferences</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -735,6 +741,29 @@ export function SettingsScreen({}: Props) {
<Trans>Change Password</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
testID="exportRepositoryBtn"
style={[
styles.linkCard,
pal.view,
isSwitchingAccounts && styles.dimmed,
]}
onPress={isSwitchingAccounts ? undefined : onPressExportRepository}
accessibilityRole="button"
accessibilityLabel={_(msg`Export my data`)}
accessibilityHint={_(
msg`Download Bluesky account data (repository)`,
)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
icon="download"
style={pal.text as FontAwesomeIconStyle}
/>
</View>
<Text type="lg" style={pal.text} numberOfLines={1}>
<Trans>Export My Data</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[pal.view, styles.linkCard]}
onPress={onPressDeleteAccount}
@@ -756,9 +785,6 @@ export function SettingsScreen({}: Props) {
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
<Trans>Developer Tools</Trans>
</Text>
<TouchableOpacity
style={[pal.view, styles.linkCardNoIcon]}
onPress={onPressSystemLog}
@@ -769,14 +795,6 @@ export function SettingsScreen({}: Props) {
<Trans>System log</Trans>
</Text>
</TouchableOpacity>
{__DEV__ ? (
<ToggleButton
type="default-light"
label="Experiment: Use AppView Proxy"
isSelected={debugHeaderEnabled}
onPress={toggleDebugHeader}
/>
) : null}
{__DEV__ ? (
<>
<TouchableOpacity
+33 -22
View File
@@ -7,14 +7,12 @@ import {H3, P} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import * as Prompt from '#/components/Prompt'
import {useDialogStateControlContext} from '#/state/dialogs'
import {useOpenGlobalDialog} from '#/components/dialogs'
import {ReportDialog} from '#/components/dialogs/ReportDialog'
export function Dialogs() {
const control = Dialog.useDialogControl()
const scrollable = Dialog.useDialogControl()
const basic = Dialog.useDialogControl()
const prompt = Prompt.usePromptControl()
const {closeAllDialogs} = useDialogStateControlContext()
const openDialog = useOpenGlobalDialog()
return (
<View style={[a.gap_md]}>
@@ -23,9 +21,9 @@ export function Dialogs() {
color="secondary"
size="small"
onPress={() => {
control.open()
scrollable.open()
prompt.open()
openDialog(ReportDialog, {type: 'user', did: ''})
basic.open()
}}
label="Open basic dialog">
Open all dialogs
@@ -36,7 +34,18 @@ export function Dialogs() {
color="secondary"
size="small"
onPress={() => {
control.open()
scrollable.open()
}}
label="Open basic dialog">
Open scrollable dialog
</Button>
<Button
variant="outline"
color="secondary"
size="small"
onPress={() => {
basic.open()
}}
label="Open basic dialog">
Open basic dialog
@@ -51,17 +60,6 @@ export function Dialogs() {
Open prompt
</Button>
<Button
variant="solid"
color="primary"
size="small"
onPress={() => {
openDialog(ReportDialog, {type: 'user', did: ''})
}}
label="Open prompt">
Open report dialog
</Button>
<Prompt.Outer control={prompt}>
<Prompt.Title>This is a prompt</Prompt.Title>
<Prompt.Description>
@@ -74,9 +72,18 @@ export function Dialogs() {
</Prompt.Actions>
</Prompt.Outer>
<Dialog.Outer control={basic}>
<Dialog.Handle />
<Dialog.Inner label="test">
<H3 nativeID="dialog-title">Dialog</H3>
<P nativeID="dialog-description">A basic dialog</P>
</Dialog.Inner>
</Dialog.Outer>
<Dialog.Outer
control={control}
nativeOptions={{sheet: {snapPoints: ['90%']}}}>
control={scrollable}
nativeOptions={{sheet: {snapPoints: ['100%']}}}>
<Dialog.Handle />
<Dialog.ScrollableInner
@@ -103,9 +110,13 @@ export function Dialogs() {
variant="outline"
color="primary"
size="small"
onPress={() => control.close()}
onPress={() =>
scrollable.close(() => {
console.log('CLOSED')
})
}
label="Open basic dialog">
Close basic dialog
Close dialog
</Button>
</View>
</View>
+9
View File
@@ -6,6 +6,7 @@ import {H1} from '#/components/Typography'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
import {Loader} from '#/components/Loader'
export function Icons() {
const t = useTheme()
@@ -36,6 +37,14 @@ export function Icons() {
<CalendarDays size="lg" fill={t.atoms.text.color} />
<CalendarDays size="xl" fill={t.atoms.text.color} />
</View>
<View style={[a.flex_row, a.gap_xl]}>
<Loader size="xs" fill={t.atoms.text.color} />
<Loader size="sm" fill={t.atoms.text.color} />
<Loader size="md" fill={t.atoms.text.color} />
<Loader size="lg" fill={t.atoms.text.color} />
<Loader size="xl" fill={t.atoms.text.color} />
</View>
</View>
)
}
+6 -1
View File
@@ -19,9 +19,14 @@ export function Links() {
style={[a.text_md]}>
External
</InlineLink>
<InlineLink to="https://bsky.social" style={[a.text_md]}>
<InlineLink to="https://bsky.social" style={[a.text_md, t.atoms.text]}>
<H3>External with custom children</H3>
</InlineLink>
<InlineLink
to="https://bsky.social"
style={[a.text_md, t.atoms.text_contrast_low]}>
External with custom children
</InlineLink>
<InlineLink
to="https://bsky.social"
warnOnMismatchingTextChild
+15 -88
View File
@@ -2,99 +2,26 @@ import React from 'react'
import {View} from 'react-native'
import * as tokens from '#/alf/tokens'
import {atoms as a} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
export function Palette() {
const t = useTheme()
return (
<View style={[a.gap_md]}>
<View style={[a.flex_row, a.gap_md]}>
<View
style={[a.flex_1, {height: 60, backgroundColor: tokens.color.gray_0}]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_25},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_50},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_100},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_200},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_300},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_400},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_500},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_600},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_700},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_800},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_900},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_975},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_1000},
]}
/>
<View style={[a.flex_1, t.atoms.bg_contrast_25, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_50, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_100, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_200, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_300, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_400, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_500, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_600, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_700, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_800, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_900, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_950, {height: 60}]} />
<View style={[a.flex_1, t.atoms.bg_contrast_975, {height: 60}]} />
</View>
<View style={[a.flex_row, a.gap_md]}>
+4 -1
View File
@@ -8,7 +8,9 @@ import {RichText} from '#/components/RichText'
export function Typography() {
return (
<View style={[a.gap_md]}>
<Text style={[a.text_5xl]}>atoms.text_5xl</Text>
<Text selectable style={[a.text_5xl]}>
atoms.text_5xl
</Text>
<Text style={[a.text_4xl]}>atoms.text_4xl</Text>
<Text style={[a.text_3xl]}>atoms.text_3xl</Text>
<Text style={[a.text_2xl]}>atoms.text_2xl</Text>
@@ -24,6 +26,7 @@ export function Typography() {
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
/>
<RichText
selectable
resolveFacets
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
style={[a.text_xl]}