Merge branch 'main' into hailey/onboarding-profile

This commit is contained in:
Hailey
2024-02-13 10:28:25 -08:00
19 changed files with 4324 additions and 109 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ module.exports = function () {
* iOS build number. Must be incremented for each TestFlight version.
* WARNING: Always leave this variable on line 24! If it is moved, you need to update ./scripts/bumpIosBuildNumber.sh
*/
const IOS_BUILD_NUMBER = '5'
const IOS_BUILD_NUMBER = '7'
/**
* Android build number. Must be incremented for each release.
+1
View File
@@ -13,6 +13,7 @@ module.exports = {
'uk',
'ca',
'zh-CN',
'it',
],
catalogs: [
{
@@ -30,8 +30,8 @@ export function IconCircle({
a.align_center,
a.rounded_full,
{
width: 64,
height: 64,
width: size === 'lg' ? 52 : 64,
height: size === 'lg' ? 52 : 64,
backgroundColor:
t.name === 'light' ? t.palette.primary_50 : t.palette.primary_950,
},
+2
View File
@@ -140,6 +140,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
return AppLanguage.ca
case 'zh-CN':
return AppLanguage.zh_CN
case 'it':
return AppLanguage.it
default:
continue
}
+6
View File
@@ -14,6 +14,7 @@ import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages'
import {messages as messagesUk} from '#/locale/locales/uk/messages'
import {messages as messagesCa} from '#/locale/locales/ca/messages'
import {messages as messagesZh_CN} from '#/locale/locales/zh-CN/messages'
import {messages as messagesIt} from '#/locale/locales/it/messages'
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {AppLanguage} from '#/locale/languages'
@@ -65,6 +66,11 @@ export async function dynamicActivate(locale: AppLanguage) {
}
case AppLanguage.zh_CN: {
i18n.loadAndActivate({locale, messages: messagesZh_CN})
break
}
case AppLanguage.it: {
i18n.loadAndActivate({locale, messages: messagesIt})
break
}
default: {
i18n.loadAndActivate({locale, messages: messagesEn})
+4
View File
@@ -56,6 +56,10 @@ export async function dynamicActivate(locale: AppLanguage) {
mod = await import(`./locales/zh-CN/messages`)
break
}
case AppLanguage.it: {
mod = await import(`./locales/it/messages`)
break
}
default: {
mod = await import(`./locales/en/messages`)
break
+2
View File
@@ -17,6 +17,7 @@ export enum AppLanguage {
uk = 'uk',
ca = 'ca',
zh_CN = 'zh-CN',
it = 'it',
}
interface AppLanguageConfig {
@@ -37,6 +38,7 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
{code2: AppLanguage.uk, name: 'Українська Ukrainian'},
{code2: AppLanguage.ca, name: 'Català Catalan'},
{code2: AppLanguage.zh_CN, name: '简体中文(中国) Chinese (Simplified)'},
{code2: AppLanguage.it, name: 'Italiano - Italian'},
]
export const LANGUAGES: Language[] = [
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -17,7 +17,7 @@ import {
flatten,
TextStyleProp,
} from '#/alf'
import {H2, P, leading} from '#/components/Typography'
import {P, leading, Text} from '#/components/Typography'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {Button, ButtonIcon} from '#/components/Button'
import {ScrollView} from '#/view/com/util/Views'
@@ -209,16 +209,18 @@ export function Title({
style,
}: React.PropsWithChildren<TextStyleProp>) {
return (
<H2
<Text
style={[
a.pb_sm,
a.text_4xl,
a.font_bold,
{
lineHeight: leading(a.text_4xl, a.leading_tight),
},
flatten(style),
]}>
{children}
</H2>
</Text>
)
}
@@ -20,7 +20,7 @@ import {
OnboardingControls,
} from '#/screens/Onboarding/Layout'
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
export type FeedConfig = {
default: boolean
+1 -1
View File
@@ -23,7 +23,7 @@ import {
Description,
OnboardingControls,
} from '#/screens/Onboarding/Layout'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
import {
bulkWriteFollows,
sortPrimaryAlgorithmFeeds,
+1 -1
View File
@@ -22,7 +22,7 @@ import {
usePreferencesQuery,
useSetFeedViewPreferencesMutation,
} from 'state/queries/preferences'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
export function StepFollowingFeed() {
const {_} = useLingui()
@@ -26,7 +26,7 @@ import {
OnboardingControls,
} from '#/screens/Onboarding/Layout'
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
export function StepInterests() {
const {_} = useLingui()
@@ -26,7 +26,7 @@ import {
import {ModerationOption} from '#/screens/Onboarding/StepModeration/ModerationOption'
import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/AdultContentEnabledPref'
import {Context} from '#/screens/Onboarding/state'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
function AnimatedDivider() {
return (
@@ -27,7 +27,7 @@ import {
SuggestedAccountCardPlaceholder,
} from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard'
import {aggregateInterestItems} from '#/screens/Onboarding/util'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
export function Inner({
profiles,
+1 -1
View File
@@ -21,7 +21,7 @@ import {
} from '#/screens/Onboarding/Layout'
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
import {aggregateInterestItems} from '#/screens/Onboarding/util'
import {IconCircle} from '#/screens/Onboarding/IconCircle'
import {IconCircle} from '#/components/IconCircle'
export function StepTopicalFeeds() {
const {_} = useLingui()
+78 -1
View File
@@ -1,5 +1,6 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Text} from 'view/com/util/text/Text'
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
import {s, colors} from 'lib/styles'
@@ -9,6 +10,14 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
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'
export const SplashScreen = ({
onPressSignin,
@@ -20,6 +29,22 @@ export const SplashScreen = ({
const pal = usePalette('default')
const {_} = useLingui()
const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()
const insets = useSafeAreaInsets()
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 (
<CenteredView style={[styles.container, pal.view]}>
<ErrorBoundary>
@@ -58,6 +83,51 @@ export const SplashScreen = ({
</Text>
</TouchableOpacity>
</View>
<View style={styles.footer}>
<View style={{position: '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: pal.textLight.color,
fontSize: 16,
paddingRight: 10 + 4,
},
inputIOS: {
color: pal.text.color,
fontSize: 16,
paddingRight: 10 + 4,
},
}}
/>
<View
style={{
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
pointerEvents: 'none',
alignItems: 'center',
justifyContent: 'center',
}}>
<FontAwesomeIcon
icon="chevron-down"
size={10}
style={pal.textLight as FontAwesomeIconStyle}
/>
</View>
</View>
</View>
<View style={{height: insets.bottom}} />
</ErrorBoundary>
</CenteredView>
)
@@ -73,7 +143,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
btns: {
paddingBottom: 40,
paddingBottom: 0,
},
title: {
textAlign: 'center',
@@ -95,4 +165,11 @@ const styles = StyleSheet.create({
textAlign: 'center',
fontSize: 21,
},
footer: {
paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 24,
justifyContent: 'center',
alignItems: 'center',
},
})
+72 -7
View File
@@ -9,9 +9,13 @@ import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from '../util/Views'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans} from '@lingui/macro'
import {Trans, msg} from '@lingui/macro'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
import {useLingui} from '@lingui/react'
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {APP_LANGUAGES} from '#/locale/languages'
export const SplashScreen = ({
onDismiss,
@@ -98,24 +102,84 @@ export const SplashScreen = ({
function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
const pal = usePalette('default')
const {_} = useLingui()
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={[styles.footer, pal.view, pal.border]}>
<TextLink
href="https://bsky.social"
text="Business"
text={_(msg`Business`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
href="https://bsky.social/about/blog"
text="Blog"
text={_(msg`Blog`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
href="https://bsky.social/about/join"
text="Jobs"
text={_(msg`Jobs`)}
style={[styles.footerLink, pal.link]}
/>
<View style={styles.footerDivider} />
<View
style={{
flexDirection: 'row',
gap: 8,
alignItems: 'center',
flexShrink: 1,
}}>
<Text aria-hidden={true} style={[pal.textLight]}>
{APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name}
</Text>
<FontAwesomeIcon
icon="chevron-down"
size={12}
style={[pal.textLight, {flexShrink: 0}]}
/>
<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>
</View>
)
}
@@ -188,9 +252,10 @@ const useStyles = () => {
padding: 20,
borderTopWidth: 1,
flexDirection: 'row',
flexWrap: 'wrap',
gap: 20,
},
footerLink: {
marginRight: 20,
},
footerDivider: {flexGrow: 1},
footerLink: {},
})
}
+127 -88
View File
@@ -16,6 +16,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ComposeIcon2, CogIcon, MagnifyingGlassIcon2} from 'lib/icons'
import {s} from 'lib/styles'
import {atoms as a, useTheme} from '#/alf'
import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {
@@ -41,8 +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'>
@@ -215,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(
@@ -400,46 +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>
<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>
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>
</View>
)
}
return <View />
)}
{preferences?.feeds?.saved?.length !== 0 && <FeedsSavedHeader />}
</>
)
} else if (item.type === 'savedFeedNoResults') {
return (
<View
@@ -457,47 +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
ref={searchInputRef}
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
ref={searchInputRef}
query={query}
onChangeQuery={onChangeQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
setIsInputFocused={onChangeSearchFocus}
/>
</View>
)}
</>
)
} else if (item.type === 'popularFeedsLoading') {
@@ -529,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,
],
)
@@ -552,8 +528,6 @@ export function FeedsScreen(_props: Props) {
/>
)}
{preferences ? <View /> : <ActivityIndicator />}
<List
ref={listRef}
style={[!isTabletOrDesktop && s.flex1, styles.list]}
@@ -660,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,