diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/util/ViewHeader.web.tsx | 138 | ||||
-rw-r--r-- | src/view/com/util/Views.web.tsx | 13 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 4 | ||||
-rw-r--r-- | src/view/screens/Search.web.tsx | 217 | ||||
-rw-r--r-- | src/view/shell/web/DesktopLeftColumn.tsx | 56 | ||||
-rw-r--r-- | src/view/shell/web/DesktopRightColumn.tsx | 3 |
6 files changed, 288 insertions, 143 deletions
diff --git a/src/view/com/util/ViewHeader.web.tsx b/src/view/com/util/ViewHeader.web.tsx index 5c0869e8b..ef70ecaba 100644 --- a/src/view/com/util/ViewHeader.web.tsx +++ b/src/view/com/util/ViewHeader.web.tsx @@ -1,69 +1,51 @@ import React from 'react' import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {CenteredView} from './Views' import {Text} from './text/Text' -import {useStores} from 'state/index' +import {Link} from './Link' import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import {ComposeIcon, MagnifyingGlassIcon} from 'lib/icons' import {colors} from 'lib/styles' -const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} - export const ViewHeader = observer(function ViewHeader({ title, - subtitle, - canGoBack, }: { title: string - subtitle?: string canGoBack?: boolean }) { - const pal = usePalette('default') const store = useStores() - const onPressBack = () => { - store.nav.tab.goBack() - } - if (typeof canGoBack === 'undefined') { - canGoBack = store.nav.tab.canGoBack - } + const pal = usePalette('default') + const onPressCompose = () => store.shell.openComposer({}) return ( - <CenteredView style={[styles.header, pal.view]}> - {canGoBack ? ( - <> - <TouchableOpacity - testID="viewHeaderBackOrMenuBtn" - onPress={onPressBack} - hitSlop={BACK_HITSLOP} - style={styles.backBtn}> - <FontAwesomeIcon - size={18} - icon="angle-left" - style={[styles.backIcon, pal.text]} - /> - </TouchableOpacity> - <View style={styles.titleContainer} pointerEvents="none"> - <Text type="title" style={[pal.text, styles.title]}> - {title} - </Text> - {subtitle ? ( - <Text - type="title-sm" - style={[styles.subtitle, pal.textLight]} - numberOfLines={1}> - {subtitle} - </Text> - ) : undefined} - </View> - </> - ) : ( - <View style={styles.titleContainer} pointerEvents="none"> - <Text type="title" style={[pal.text, styles.title]}> - Home - </Text> + <View style={[styles.header, pal.borderDark, pal.view]}> + <View style={styles.titleContainer} pointerEvents="none"> + <Text type="title-2xl" style={[pal.text, styles.title]}> + {title} + </Text> + </View> + <TouchableOpacity style={[styles.newPostBtn]} onPress={onPressCompose}> + <View style={styles.newPostBtnIconWrapper}> + <ComposeIcon + size={16} + strokeWidth={1.5} + style={styles.newPostBtnLabel} + /> </View> - )} - </CenteredView> + <Text type="md" style={styles.newPostBtnLabel}> + New Post + </Text> + </TouchableOpacity> + <Link href="/search" style={[pal.view, pal.borderDark, styles.search]}> + <MagnifyingGlassIcon + size={18} + style={[pal.textLight, styles.searchIconWrapper]} + /> + <Text type="md-thin" style={pal.textLight}> + Search + </Text> + </Link> + </View> ) }) @@ -71,44 +53,52 @@ const styles = StyleSheet.create({ header: { flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 12, + paddingTop: 24, + paddingBottom: 18, + paddingLeft: 30, + paddingRight: 40, + marginLeft: 300, + borderBottomWidth: 1, }, titleContainer: { - flexDirection: 'row', - alignItems: 'baseline', marginRight: 'auto', }, title: { fontWeight: 'bold', }, - subtitle: { - marginLeft: 4, - maxWidth: 200, - fontWeight: 'normal', - }, - backBtn: { - width: 30, + search: { + flexDirection: 'row', + alignItems: 'center', + width: 300, + borderRadius: 20, + paddingVertical: 8, + paddingHorizontal: 10, + borderWidth: 1, }, - backIcon: { - position: 'relative', - top: -1, + searchIconWrapper: { + flexDirection: 'row', + width: 30, + justifyContent: 'center', + marginRight: 2, }, - btn: { + + newPostBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - width: 36, - height: 36, - borderRadius: 20, - marginLeft: 4, + borderRadius: 24, + paddingTop: 8, + paddingBottom: 9, + paddingHorizontal: 18, + backgroundColor: colors.blue3, + marginRight: 10, + }, + newPostBtnIconWrapper: { + marginRight: 8, }, - littleXIcon: { - color: colors.red3, - position: 'absolute', - right: 7, - bottom: 7, + newPostBtnLabel: { + color: colors.white, }, }) diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index f00d3c072..3d9abd893 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -22,7 +22,6 @@ import { View, ViewProps, } from 'react-native' -import {useTheme} from 'lib/ThemeContext' import {addStyle, colors} from 'lib/styles' export function CenteredView({ @@ -40,15 +39,10 @@ export const FlatList = React.forwardRef(function <ItemT>( }: React.PropsWithChildren<FlatListProps<ItemT>>, ref: React.Ref<RNFlatList>, ) { - const theme = useTheme() contentContainerStyle = addStyle( contentContainerStyle, styles.containerScroll, ) - contentContainerStyle = addStyle( - contentContainerStyle, - theme.colorScheme === 'dark' ? styles.containerDark : styles.containerLight, - ) return ( <RNFlatList contentContainerStyle={contentContainerStyle} @@ -60,17 +54,12 @@ export const FlatList = React.forwardRef(function <ItemT>( export const ScrollView = React.forwardRef(function ( {contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>, - ref: React.Ref<RNFlatList>, + ref: React.Ref<RNScrollView>, ) { - const theme = useTheme() contentContainerStyle = addStyle( contentContainerStyle, styles.containerScroll, ) - contentContainerStyle = addStyle( - contentContainerStyle, - theme.colorScheme === 'dark' ? styles.containerDark : styles.containerLight, - ) return ( <RNScrollView contentContainerStyle={contentContainerStyle} diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index de7e61ba4..125c57406 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -11,6 +11,7 @@ import {ScreenParams} from '../routes' import {s} from 'lib/styles' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics' +import {isWeb} from 'platform/detection' const HEADER_HEIGHT = 42 @@ -91,6 +92,7 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { return ( <View style={s.h100pct}> + {isWeb && <ViewHeader title="Home Feed" canGoBack={false} />} <Feed testID="homeFeed" key="default" @@ -102,7 +104,7 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { onScroll={onMainScroll} headerOffset={HEADER_HEIGHT} /> - <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll /> + {!isWeb && <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll />} {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( <LoadLatestBtn onPress={onPressLoadLatest} /> )} diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx new file mode 100644 index 000000000..38f7cefb8 --- /dev/null +++ b/src/view/screens/Search.web.tsx @@ -0,0 +1,217 @@ +import React from 'react' +import { + Keyboard, + StyleSheet, + TextInput, + TouchableOpacity, + View, +} from 'react-native' +import {ScrollView} from '../com/util/Views' +import {observer} from 'mobx-react-lite' +import {UserAvatar} from '../com/util/UserAvatar' +import {Text} from '../com/util/text/Text' +import {ScreenParams} from '../routes' +import {useStores} from 'state/index' +import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' +import {s} from 'lib/styles' +import {MagnifyingGlassIcon} from 'lib/icons' +import {ViewHeader} from '../com/util/ViewHeader' +import {WhoToFollow} from '../com/discover/WhoToFollow' +import {SuggestedPosts} from '../com/discover/SuggestedPosts' +import {ProfileCard} from '../com/profile/ProfileCard' +import {usePalette} from 'lib/hooks/usePalette' +import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' +import {useAnalytics} from 'lib/analytics' + +const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} +const FIVE_MIN = 5 * 60 * 1e3 + +export const Search = observer(({navIdx, visible, params}: ScreenParams) => { + const pal = usePalette('default') + const store = useStores() + const {track} = useAnalytics() + const scrollElRef = React.useRef<ScrollView>(null) + const onMainScroll = useOnMainScroll(store) + const textInput = React.useRef<TextInput>(null) + const [lastRenderTime, setRenderTime] = React.useState<number>(Date.now()) // used to trigger reloads + const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false) + const [query, setQuery] = React.useState<string>('') + const autocompleteView = React.useMemo<UserAutocompleteViewModel>( + () => new UserAutocompleteViewModel(store), + [store], + ) + const {name} = params + + const onSoftReset = () => { + scrollElRef.current?.scrollTo({x: 0, y: 0}) + } + + React.useEffect(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) + const cleanup = () => { + softResetSub.remove() + } + + if (visible) { + const now = Date.now() + if (now - lastRenderTime > FIVE_MIN) { + setRenderTime(Date.now()) // trigger reload of suggestions + } + store.shell.setMinimalShellMode(false) + autocompleteView.setup() + store.nav.setTitle(navIdx, 'Search') + } + return cleanup + }, [store, visible, name, navIdx, autocompleteView, lastRenderTime]) + + const onPressMenu = () => { + track('ViewHeader:MenuButtonClicked') + store.shell.setMainMenuOpen(true) + } + + const onChangeQuery = (text: string) => { + setQuery(text) + if (text.length > 0) { + autocompleteView.setActive(true) + autocompleteView.setPrefix(text) + } else { + autocompleteView.setActive(false) + } + } + const onPressCancelSearch = () => { + setQuery('') + autocompleteView.setActive(false) + } + + return ( + <View style={s.h100pct}> + <ViewHeader title="Explore" /> + <ScrollView + ref={scrollElRef} + testID="searchScrollView" + style={[pal.view, styles.container]} + onScroll={onMainScroll} + scrollEventThrottle={100}> + <View style={[pal.view, pal.border, styles.header]}> + <TouchableOpacity + testID="viewHeaderBackOrMenuBtn" + onPress={onPressMenu} + hitSlop={MENU_HITSLOP} + style={styles.headerMenuBtn}> + <UserAvatar + size={30} + handle={store.me.handle} + displayName={store.me.displayName} + avatar={store.me.avatar} + /> + </TouchableOpacity> + <View + style={[ + {backgroundColor: pal.colors.backgroundLight}, + styles.headerSearchContainer, + ]}> + <MagnifyingGlassIcon + style={[pal.icon, styles.headerSearchIcon]} + size={21} + /> + <TextInput + testID="searchTextInput" + ref={textInput} + placeholder="Search" + placeholderTextColor={pal.colors.textLight} + selectTextOnFocus + returnKeyType="search" + value={query} + style={[pal.text, styles.headerSearchInput]} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + onChangeText={onChangeQuery} + /> + </View> + {query ? ( + <View style={styles.headerCancelBtn}> + <TouchableOpacity onPress={onPressCancelSearch}> + <Text>Cancel</Text> + </TouchableOpacity> + </View> + ) : undefined} + </View> + {query && autocompleteView.searchRes.length ? ( + <> + {autocompleteView.searchRes.map(item => ( + <ProfileCard + key={item.did} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + /> + ))} + </> + ) : query && !autocompleteView.searchRes.length ? ( + <View> + <Text style={[pal.textLight, styles.searchPrompt]}> + No results found for {autocompleteView.prefix} + </Text> + </View> + ) : isInputFocused ? ( + <View> + <Text style={[pal.textLight, styles.searchPrompt]}> + Search for users on the network + </Text> + </View> + ) : ( + <ScrollView onScroll={Keyboard.dismiss}> + <WhoToFollow key={`wtf-${lastRenderTime}`} /> + <SuggestedPosts key={`sp-${lastRenderTime}`} /> + <View style={s.footerSpacer} /> + </ScrollView> + )} + <View style={s.footerSpacer} /> + </ScrollView> + </View> + ) +}) + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingTop: 4, + marginBottom: 14, + }, + headerMenuBtn: { + width: 40, + height: 30, + marginLeft: 6, + }, + headerSearchContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + borderRadius: 30, + paddingHorizontal: 12, + paddingVertical: 8, + }, + headerSearchIcon: { + marginRight: 6, + alignSelf: 'center', + }, + headerSearchInput: { + flex: 1, + fontSize: 17, + }, + headerCancelBtn: { + width: 60, + paddingLeft: 10, + }, + + searchPrompt: { + textAlign: 'center', + paddingTop: 10, + }, +}) diff --git a/src/view/shell/web/DesktopLeftColumn.tsx b/src/view/shell/web/DesktopLeftColumn.tsx index 819bcba6d..54e3e93e1 100644 --- a/src/view/shell/web/DesktopLeftColumn.tsx +++ b/src/view/shell/web/DesktopLeftColumn.tsx @@ -70,12 +70,7 @@ export const DesktopLeftColumn = observer(() => { styles.containerBgLight, styles.containerBgDark, ) - const hoverBg = useColorSchemeStyle( - styles.navItemHoverBgLight, - styles.navItemHoverBgDark, - ) const pal = usePalette('default') - const onPressCompose = () => store.shell.openComposer({}) const avi = ( <UserAvatar handle={store.me.handle} @@ -90,15 +85,6 @@ export const DesktopLeftColumn = observer(() => { <Link style={styles.logo} href="/"> <Text type="title-xl">Bluesky</Text> </Link> - <Link href="/search" style={[pal.view, pal.borderDark, styles.search]}> - <MagnifyingGlassIcon - size={18} - style={[pal.textLight, styles.searchIconWrapper]} - /> - <Text type="md-thin" style={pal.textLight}> - Search - </Text> - </Link> <NavItem href="/" label="Home" @@ -124,19 +110,6 @@ export const DesktopLeftColumn = observer(() => { icon={<CogIcon strokeWidth={2} size={21} />} iconFilled={<CogIcon strokeWidth={2.5} size={21} />} /> - <View style={[pal.border, styles.separator]} /> - <Pressable - style={state => [ - // @ts-ignore Pressable state differs for RNW -prf - state.hovered && hoverBg, - ]}> - <TouchableOpacity style={styles.navItem} onPress={onPressCompose}> - <View style={styles.navItemIconWrapper}> - <ComposeIcon size={21} /> - </View> - <Text type="xl-thin">New Post</Text> - </TouchableOpacity> - </Pressable> </View> <View style={[styles.footer, pal.borderDark]}> <NavItem @@ -183,24 +156,8 @@ const styles = StyleSheet.create({ }, logo: { - paddingTop: 6, - paddingBottom: 12, - }, - - search: { - flexDirection: 'row', - alignItems: 'center', - borderRadius: 8, - paddingVertical: 8, - paddingHorizontal: 6, - marginBottom: 10, - borderWidth: 1, - }, - searchIconWrapper: { - flexDirection: 'row', - width: 30, - justifyContent: 'center', - marginRight: 6, + paddingTop: 8, + paddingBottom: 14, }, navItem: { @@ -240,13 +197,4 @@ const styles = StyleSheet.create({ paddingHorizontal: 4, borderRadius: 6, }, - composeBtn: { - marginTop: 20, - marginBottom: 10, - marginLeft: 10, - marginRight: 20, - borderRadius: 30, - paddingHorizontal: 20, - paddingVertical: 12, - }, }) diff --git a/src/view/shell/web/DesktopRightColumn.tsx b/src/view/shell/web/DesktopRightColumn.tsx index b051e6094..2f70a43d2 100644 --- a/src/view/shell/web/DesktopRightColumn.tsx +++ b/src/view/shell/web/DesktopRightColumn.tsx @@ -3,10 +3,8 @@ import {StyleSheet, View} from 'react-native' import {Text} from '../../com/util/text/Text' import {LiteSuggestedFollows} from '../../com/discover/LiteSuggestedFollows' import {s} from 'lib/styles' -import {useStores} from 'state/index' export const DesktopRightColumn: React.FC = () => { - const store = useStores() return ( <View style={styles.container}> <Text type="lg-bold" style={s.mb10}> @@ -21,6 +19,7 @@ const styles = StyleSheet.create({ container: { position: 'absolute', right: 0, + top: 90, width: '400px', paddingHorizontal: 16, paddingRight: 32, |