diff options
-rw-r--r-- | src/state/models/onboard.ts | 2 | ||||
-rw-r--r-- | src/view/com/discover/SuggestedFollows.tsx | 177 | ||||
-rw-r--r-- | src/view/com/onboard/Follows.tsx | 160 | ||||
-rw-r--r-- | src/view/lib/icons.tsx | 27 | ||||
-rw-r--r-- | src/view/screens/Search.tsx | 56 | ||||
-rw-r--r-- | src/view/shell/mobile/index.tsx | 17 |
6 files changed, 278 insertions, 161 deletions
diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts index 77a066332..20e6ecda9 100644 --- a/src/state/models/onboard.ts +++ b/src/state/models/onboard.ts @@ -9,7 +9,7 @@ export const OnboardStage = { export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows] export class OnboardModel { - isOnboarding: boolean = true + isOnboarding: boolean = false stage: string = OnboardStageOrder[0] constructor() { diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx new file mode 100644 index 000000000..e73d9a7db --- /dev/null +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -0,0 +1,177 @@ +import React, {useMemo, useEffect} from 'react' +import { + ActivityIndicator, + FlatList, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {observer} from 'mobx-react-lite' +import {ErrorScreen} from '../util/ErrorScreen' +import {UserAvatar} from '../util/UserAvatar' +import {useStores} from '../../../state' +import { + SuggestedActorsViewModel, + SuggestedActor, +} from '../../../state/models/suggested-actors-view' +import {s, colors, gradients} from '../../lib/styles' + +export const SuggestedFollows = observer( + ({onNoSuggestions}: {onNoSuggestions?: () => void}) => { + const store = useStores() + + const view = useMemo<SuggestedActorsViewModel>( + () => new SuggestedActorsViewModel(store), + [], + ) + + useEffect(() => { + console.log('Fetching suggested actors') + view + .setup() + .catch((err: any) => console.error('Failed to fetch suggestions', err)) + }, [view]) + + useEffect(() => { + if (!view.isLoading && !view.hasError && !view.hasContent) { + onNoSuggestions?.() + } + }, [view, view.isLoading, view.hasError, view.hasContent]) + + const onPressTryAgain = () => + view + .setup() + .catch((err: any) => console.error('Failed to fetch suggestions', err)) + + const renderItem = ({item}: {item: SuggestedActor}) => <User item={item} /> + return ( + <View style={styles.container}> + {view.isLoading ? ( + <View> + <ActivityIndicator /> + </View> + ) : view.hasError ? ( + <ErrorScreen + title="Failed to load suggestions" + message="There was an error while trying to load suggested follows." + details={view.error} + onPressTryAgain={onPressTryAgain} + /> + ) : ( + <View style={styles.suggestionsContainer}> + <FlatList + data={view.suggestions} + keyExtractor={item => item._reactKey} + renderItem={renderItem} + style={s.flex1} + /> + </View> + )} + </View> + ) + }, +) + +const User = ({item}: {item: SuggestedActor}) => { + return ( + <View style={styles.actor}> + <View style={styles.actorMeta}> + <View style={styles.actorAvi}> + <UserAvatar + size={40} + displayName={item.displayName} + handle={item.handle} + /> + </View> + <View style={styles.actorContent}> + <Text style={[s.f17, s.bold]} numberOfLines={1}> + {item.displayName} + </Text> + <Text style={[s.f14, s.gray5]} numberOfLines={1}> + @{item.handle} + </Text> + </View> + <View style={styles.actorBtn}> + <TouchableOpacity> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn, styles.gradientBtn]}> + <FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} size={15} /> + <Text style={[s.white, s.fw600, s.f15]}>Follow</Text> + </LinearGradient> + </TouchableOpacity> + </View> + </View> + {item.description ? ( + <View style={styles.actorDetails}> + <Text style={[s.f15]} numberOfLines={4}> + {item.description} + </Text> + </View> + ) : undefined} + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + + suggestionsContainer: { + flex: 1, + backgroundColor: colors.gray1, + }, + + actor: { + backgroundColor: colors.white, + borderRadius: 6, + margin: 2, + marginBottom: 0, + }, + actorMeta: { + flexDirection: 'row', + }, + actorAvi: { + width: 60, + paddingLeft: 10, + paddingTop: 10, + paddingBottom: 10, + }, + actorContent: { + flex: 1, + paddingRight: 10, + paddingTop: 10, + }, + actorBtn: { + paddingRight: 10, + paddingTop: 10, + }, + actorDetails: { + paddingLeft: 60, + paddingRight: 10, + paddingBottom: 10, + }, + + gradientBtn: { + paddingHorizontal: 24, + paddingVertical: 6, + }, + secondaryBtn: { + paddingHorizontal: 14, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 7, + borderRadius: 50, + backgroundColor: colors.gray1, + marginLeft: 6, + }, +}) diff --git a/src/view/com/onboard/Follows.tsx b/src/view/com/onboard/Follows.tsx index c48531522..ab65cd45d 100644 --- a/src/view/com/onboard/Follows.tsx +++ b/src/view/com/onboard/Follows.tsx @@ -1,78 +1,29 @@ -import React, {useMemo, useEffect} from 'react' +import React from 'react' import { - ActivityIndicator, - FlatList, SafeAreaView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' -import {ErrorScreen} from '../util/ErrorScreen' -import {UserAvatar} from '../util/UserAvatar' +import {SuggestedFollows} from '../discover/SuggestedFollows' import {useStores} from '../../../state' -import { - SuggestedActorsViewModel, - SuggestedActor, -} from '../../../state/models/suggested-actors-view' -import {s, colors, gradients} from '../../lib/styles' +import {s} from '../../lib/styles' export const Follows = observer(() => { const store = useStores() - const view = useMemo<SuggestedActorsViewModel>( - () => new SuggestedActorsViewModel(store), - [], - ) - - useEffect(() => { - console.log('Fetching suggested actors') - view - .setup() - .catch((err: any) => console.error('Failed to fetch suggestions', err)) - }, [view]) - - useEffect(() => { - if (!view.isLoading && !view.hasError && !view.hasContent) { - // no suggestions, bounce from this view - store.onboard.next() - } - }, [view, view.isLoading, view.hasError, view.hasContent]) - - const onPressTryAgain = () => - view - .setup() - .catch((err: any) => console.error('Failed to fetch suggestions', err)) + const onNoSuggestions = () => { + // no suggestions, bounce from this view + store.onboard.next() + } const onPressNext = () => store.onboard.next() - const renderItem = ({item}: {item: SuggestedActor}) => <User item={item} /> return ( <SafeAreaView style={styles.container}> <Text style={styles.title}>Suggested follows</Text> - {view.isLoading ? ( - <View> - <ActivityIndicator /> - </View> - ) : view.hasError ? ( - <ErrorScreen - title="Failed to load suggestions" - message="There was an error while trying to load suggested follows." - details={view.error} - onPressTryAgain={onPressTryAgain} - /> - ) : ( - <View style={styles.suggestionsContainer}> - <FlatList - data={view.suggestions} - keyExtractor={item => item._reactKey} - renderItem={renderItem} - style={s.flex1} - /> - </View> - )} + <SuggestedFollows onNoSuggestions={onNoSuggestions} /> <View style={styles.footer}> <TouchableOpacity onPress={onPressNext}> <Text style={[s.blue3, s.f18]}>Skip</Text> @@ -86,49 +37,6 @@ export const Follows = observer(() => { ) }) -const User = ({item}: {item: SuggestedActor}) => { - return ( - <View style={styles.actor}> - <View style={styles.actorMeta}> - <View style={styles.actorAvi}> - <UserAvatar - size={40} - displayName={item.displayName} - handle={item.handle} - /> - </View> - <View style={styles.actorContent}> - <Text style={[s.f17, s.bold]} numberOfLines={1}> - {item.displayName} - </Text> - <Text style={[s.f14, s.gray5]} numberOfLines={1}> - @{item.handle} - </Text> - </View> - <View style={styles.actorBtn}> - <TouchableOpacity> - <LinearGradient - colors={[gradients.primary.start, gradients.primary.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.btn, styles.gradientBtn]}> - <FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} size={15} /> - <Text style={[s.white, s.fw600, s.f15]}>Follow</Text> - </LinearGradient> - </TouchableOpacity> - </View> - </View> - {item.description ? ( - <View style={styles.actorDetails}> - <Text style={[s.f15]} numberOfLines={4}> - {item.description} - </Text> - </View> - ) : undefined} - </View> - ) -} - const styles = StyleSheet.create({ container: { flex: 1, @@ -141,58 +49,6 @@ const styles = StyleSheet.create({ paddingBottom: 12, }, - suggestionsContainer: { - flex: 1, - backgroundColor: colors.gray1, - }, - - actor: { - backgroundColor: colors.white, - borderRadius: 6, - margin: 2, - marginBottom: 0, - }, - actorMeta: { - flexDirection: 'row', - }, - actorAvi: { - width: 60, - paddingLeft: 10, - paddingTop: 10, - paddingBottom: 10, - }, - actorContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - }, - actorBtn: { - paddingRight: 10, - paddingTop: 10, - }, - actorDetails: { - paddingLeft: 60, - paddingRight: 10, - paddingBottom: 10, - }, - - gradientBtn: { - paddingHorizontal: 24, - paddingVertical: 6, - }, - secondaryBtn: { - paddingHorizontal: 14, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 7, - borderRadius: 50, - backgroundColor: colors.gray1, - marginLeft: 6, - }, - footer: { flexDirection: 'row', paddingHorizontal: 32, diff --git a/src/view/lib/icons.tsx b/src/view/lib/icons.tsx index bf4d242f0..b42fc6fc8 100644 --- a/src/view/lib/icons.tsx +++ b/src/view/lib/icons.tsx @@ -53,6 +53,33 @@ export function HomeIcon({ ) } +// Copyright (c) 2020 Refactoring UI Inc. +// https://github.com/tailwindlabs/heroicons/blob/master/LICENSE +export function MangifyingGlassIcon({ + style, + size, +}: { + style?: StyleProp<ViewStyle> + size?: string | number +}) { + return ( + <Svg + fill="none" + viewBox="0 0 24 24" + strokeWidth={2} + stroke="currentColor" + width={size || 24} + height={size || 24} + style={style}> + <Path + strokeLinecap="round" + strokeLinejoin="round" + d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" + /> + </Svg> + ) +} + // https://github.com/Remix-Design/RemixIcon/blob/master/License export function BellIcon({ style, diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index aea54051e..735326025 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -1,11 +1,57 @@ -import React from 'react' -import {Text, View} from 'react-native' +import React, {useEffect} from 'react' +import {StyleSheet, Text, View} from 'react-native' +import {ViewHeader} from '../com/util/ViewHeader' +import {SuggestedFollows} from '../com/discover/SuggestedFollows' import {ScreenParams} from '../routes' +import {useStores} from '../../state' +import {colors} from '../lib/styles' + +export const Search = ({visible, params}: ScreenParams) => { + const store = useStores() + const {name} = params + + useEffect(() => { + if (visible) { + store.nav.setTitle(`Search`) + } + }, [store, visible, name]) -export const Search = ({params}: ScreenParams) => { return ( - <View style={{justifyContent: 'center', alignItems: 'center'}}> - <Text style={{fontSize: 20, fontWeight: 'bold'}}>Search</Text> + <View style={styles.container}> + <ViewHeader title="Search" /> + <View style={styles.todoContainer}> + <Text style={styles.todoLabel}> + Search is still being implemented. Check back soon! + </Text> + </View> + <Text style={styles.heading}>Suggested follows</Text> + <SuggestedFollows /> </View> ) } + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.white, + }, + + todoContainer: { + backgroundColor: colors.pink1, + margin: 10, + padding: 10, + borderRadius: 6, + }, + todoLabel: { + color: colors.pink5, + textAlign: 'center', + }, + + heading: { + fontSize: 16, + fontWeight: 'bold', + paddingTop: 12, + paddingBottom: 6, + paddingHorizontal: 12, + }, +}) diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index b359bdcb3..49b18a481 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -33,7 +33,12 @@ import {MainMenu} from './MainMenu' import {TabsSelector} from './TabsSelector' import {Composer} from './Composer' import {s, colors} from '../../lib/styles' -import {GridIcon, HomeIcon, BellIcon} from '../../lib/icons' +import { + GridIcon, + HomeIcon, + MangifyingGlassIcon, + BellIcon, +} from '../../lib/icons' const SWIPE_GESTURE_DIST_TRIGGER = 0.5 const SWIPE_GESTURE_VEL_TRIGGER = 2500 @@ -45,7 +50,7 @@ const Btn = ({ onPress, onLongPress, }: { - icon: IconProp | 'menu' | 'house' | 'bell' + icon: IconProp | 'menu' | 'house' | 'bell' | 'search' inactive?: boolean notificationCount?: number onPress?: (event: GestureResponderEvent) => void @@ -58,6 +63,11 @@ const Btn = ({ IconEl = GridIcon } else if (icon === 'house') { IconEl = HomeIcon + size = 24 + } else if (icon === 'search') { + IconEl = MangifyingGlassIcon + size = 24 + addedStyles = {position: 'relative', top: -1} as ViewStyle } else if (icon === 'bell') { IconEl = BellIcon size = 24 @@ -114,6 +124,7 @@ export const MobileShell: React.FC = observer(() => { store.nav.navigate('/') } } + const onPressSearch = () => store.nav.navigate('/search') const onPressMenu = () => setMainMenuActive(true) const onPressNotifications = () => store.nav.navigate('/notifications') const onPressTabs = () => setTabsSelectorActive(true) @@ -210,7 +221,7 @@ export const MobileShell: React.FC = observer(() => { </SafeAreaView> <View style={styles.bottomBar}> <Btn icon="house" onPress={onPressHome} /> - <Btn icon="search" inactive={true} onPress={() => {} /* TODO */} /> + <Btn icon="search" onPress={onPressSearch} /> <Btn icon="menu" onPress={onPressMenu} /> <Btn icon="bell" |