From f51ad2802556d91ba5036f015c47dd586c3eb958 Mon Sep 17 00:00:00 2001 From: Paul Frazee <pfrazee@gmail.com> Date: Thu, 26 Jan 2023 19:49:16 -0600 Subject: [PATCH] Add right column of web shell and tweak left column --- .../com/discover/LiteSuggestedFollows.tsx | 194 ++++++++++++++++++ src/view/shell/web/index.tsx | 24 +-- src/view/shell/web/left-column.tsx | 15 +- src/view/shell/web/right-column.tsx | 43 +++- 4 files changed, 246 insertions(+), 30 deletions(-) create mode 100644 src/view/com/discover/LiteSuggestedFollows.tsx diff --git a/src/view/com/discover/LiteSuggestedFollows.tsx b/src/view/com/discover/LiteSuggestedFollows.tsx new file mode 100644 index 00000000..ce01af7c --- /dev/null +++ b/src/view/com/discover/LiteSuggestedFollows.tsx @@ -0,0 +1,194 @@ +import React, {useEffect, useState} from 'react' +import { + ActivityIndicator, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {observer} from 'mobx-react-lite' +import _omit from 'lodash.omit' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Link} from '../util/Link' +import {Text} from '../util/text/Text' +import {UserAvatar} from '../util/UserAvatar' +import * as Toast from '../util/Toast' +import {useStores} from '../../../state' +import * as apilib from '../../../state/lib/api' +import { + SuggestedActorsViewModel, + SuggestedActor, +} from '../../../state/models/suggested-actors-view' +import {s, gradients} from '../../lib/styles' +import {usePalette} from '../../lib/hooks/usePalette' + +export const LiteSuggestedFollows = observer(() => { + const store = useStores() + const [suggestions, setSuggestions] = useState<SuggestedActor[] | undefined>( + undefined, + ) + const [follows, setFollows] = useState<Record<string, string>>({}) + + useEffect(() => { + const view = new SuggestedActorsViewModel(store) + view.loadMore().then( + () => { + setSuggestions(view.suggestions.slice().sort(randomize).slice(0, 3)) + }, + (err: any) => { + setSuggestions([]) + store.log.error('Failed to fetch suggestions', err) + }, + ) + }, [store, store.log]) + + const onPressFollow = async (item: SuggestedActor) => { + try { + const res = await apilib.follow(store, item.did, item.declaration.cid) + setFollows({[item.did]: res.uri, ...follows}) + } catch (e: any) { + store.log.error('Failed fo create follow', e) + Toast.show('An issue occurred, please try again.') + } + } + const onPressUnfollow = async (item: SuggestedActor) => { + try { + await apilib.unfollow(store, follows[item.did]) + setFollows(_omit(follows, [item.did])) + } catch (e: any) { + store.log.error('Failed fo delete follow', e) + Toast.show('An issue occurred, please try again.') + } + } + + return ( + <View> + {!suggestions ? ( + <View> + <ActivityIndicator /> + </View> + ) : ( + <View> + {suggestions.map(item => ( + <Link + key={item.did} + href={`/profile/${item.handle}`} + title={item.displayName || item.handle}> + <User + item={item} + follow={follows[item.did]} + onPressFollow={onPressFollow} + onPressUnfollow={onPressUnfollow} + /> + </Link> + ))} + </View> + )} + </View> + ) +}) + +const User = ({ + item, + follow, + onPressFollow, + onPressUnfollow, +}: { + item: SuggestedActor + follow: string | undefined + onPressFollow: (item: SuggestedActor) => void + onPressUnfollow: (item: SuggestedActor) => void +}) => { + const pal = usePalette('default') + return ( + <View style={[styles.actor]}> + <View style={styles.actorMeta}> + <View style={styles.actorAvi}> + <UserAvatar + size={40} + displayName={item.displayName} + handle={item.handle} + avatar={item.avatar} + /> + </View> + <View style={styles.actorContent}> + <Text type="lg-medium" style={pal.text} numberOfLines={1}> + {item.displayName || item.handle} + </Text> + <Text type="sm" style={pal.textLight} numberOfLines={1}> + @{item.handle} + </Text> + </View> + <View style={styles.actorBtn}> + {follow ? ( + <TouchableOpacity onPress={() => onPressUnfollow(item)}> + <View style={[styles.btn, styles.secondaryBtn, pal.btn]}> + <Text type="button" style={pal.text}> + Unfollow + </Text> + </View> + </TouchableOpacity> + ) : ( + <TouchableOpacity onPress={() => onPressFollow(item)}> + <LinearGradient + colors={[gradients.blueLight.start, gradients.blueLight.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn, styles.gradientBtn]}> + <Text type="sm-medium" style={s.white}> + Follow + </Text> + </LinearGradient> + </TouchableOpacity> + )} + </View> + </View> + </View> + ) +} + +function randomize() { + return Math.random() > 0.5 ? 1 : -1 +} + +const styles = StyleSheet.create({ + footer: { + height: 200, + paddingTop: 20, + }, + + actor: {}, + actorMeta: { + flexDirection: 'row', + }, + actorAvi: { + width: 50, + paddingTop: 10, + paddingBottom: 10, + }, + actorContent: { + flex: 1, + paddingRight: 10, + paddingTop: 10, + }, + actorBtn: { + paddingRight: 10, + paddingTop: 10, + }, + + gradientBtn: { + paddingHorizontal: 14, + paddingVertical: 6, + }, + secondaryBtn: { + paddingHorizontal: 8, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 7, + borderRadius: 50, + marginLeft: 6, + }, +}) diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx index 93ae9282..fedc9c3d 100644 --- a/src/view/shell/web/index.tsx +++ b/src/view/shell/web/index.tsx @@ -1,10 +1,10 @@ import React from 'react' import {observer} from 'mobx-react-lite' -import {View, StyleSheet, Text} from 'react-native' +import {View, StyleSheet} from 'react-native' import {useStores} from '../../../state' import {match, MatchResult} from '../../routes' import {DesktopLeftColumn} from './left-column' -// import {DesktopRightColumn} from './right-column' +import {DesktopRightColumn} from './right-column' import {Login} from '../../screens/Login' import {ErrorBoundary} from '../../com/util/ErrorBoundary' import {usePalette} from '../../lib/hooks/usePalette' @@ -35,6 +35,7 @@ export const WebShell: React.FC = observer(() => { </View> ))} <DesktopLeftColumn /> + <DesktopRightColumn /> </View> ) // TODO @@ -48,25 +49,6 @@ export const WebShell: React.FC = observer(() => { // imagesOpen={store.shell.composerOpts?.imagesOpen} // onPost={store.shell.composerOpts?.onPost} // /> - // return ( - // <View style={styles.outerContainer}> - // {store.session.hasSession ? ( - // <> - // <DesktopLeftColumn /> - // <View style={styles.innerContainer}> - // <Text>Hello, world! (Logged in)</Text> - // {children} - // </View> - // <DesktopRightColumn /> - // </> - // ) : ( - // <View style={styles.innerContainer}> - // <Text>Hello, world! (Logged out)</Text> - // {children} - // </View> - // )} - // </View> - // ) }) /** diff --git a/src/view/shell/web/left-column.tsx b/src/view/shell/web/left-column.tsx index b7309d9c..411b4674 100644 --- a/src/view/shell/web/left-column.tsx +++ b/src/view/shell/web/left-column.tsx @@ -42,7 +42,9 @@ export const NavItem = observer( </Text> )} </View> - <Text type={isCurrent ? 'xl-bold' : 'xl-medium'}>{label}</Text> + <Text type={isCurrent ? 'xl-bold' : 'xl'} style={styles.navItemLabel}> + {label} + </Text> </Link> </Pressable> ) @@ -86,10 +88,11 @@ export const DesktopLeftColumn = observer(() => { const styles = StyleSheet.create({ container: { position: 'absolute', - left: 'calc(50vw - 500px)', - width: '200px', + left: 'calc(50vw - 530px)', + width: '230px', height: '100%', borderRightWidth: 1, + paddingTop: 20, }, navItem: { padding: '1rem', @@ -109,7 +112,11 @@ const styles = StyleSheet.create({ backgroundColor: colors.red3, color: colors.white, fontSize: 12, + fontWeight: 'bold', paddingHorizontal: 4, - borderRadius: 4, + borderRadius: 6, + }, + navItemLabel: { + fontSize: 19, }, }) diff --git a/src/view/shell/web/right-column.tsx b/src/view/shell/web/right-column.tsx index 5fe65cac..2daa16f6 100644 --- a/src/view/shell/web/right-column.tsx +++ b/src/view/shell/web/right-column.tsx @@ -1,10 +1,28 @@ import React from 'react' -import {Text, View, StyleSheet} from 'react-native' +import {View, StyleSheet} from 'react-native' +import {Link} from '../../com/util/Link' +import {Text} from '../../com/util/text/Text' +import {usePalette} from '../../lib/hooks/usePalette' +import {MagnifyingGlassIcon} from '../../lib/icons' +import {LiteSuggestedFollows} from '../../com/discover/LiteSuggestedFollows' +import {s} from '../../lib/styles' export const DesktopRightColumn: React.FC = () => { + const pal = usePalette('default') return ( - <View style={styles.container}> - <Text>Right Column</Text> + <View style={[styles.container, pal.border]}> + <Link href="/search" style={[pal.btn, styles.searchContainer]}> + <View style={styles.searchIcon}> + <MagnifyingGlassIcon style={pal.textLight} /> + </View> + <Text type="lg" style={pal.textLight}> + Search + </Text> + </Link> + <Text type="xl-bold" style={s.mb10}> + Suggested Follows + </Text> + <LiteSuggestedFollows /> </View> ) } @@ -12,8 +30,23 @@ export const DesktopRightColumn: React.FC = () => { const styles = StyleSheet.create({ container: { position: 'absolute', - right: 'calc(50vw - 500px)', - width: '200px', + right: 'calc(50vw - 650px)', + width: '350px', height: '100%', + borderLeftWidth: 1, + overscrollBehavior: 'auto', + paddingLeft: 30, + paddingTop: 10, + }, + searchContainer: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 14, + paddingVertical: 10, + borderRadius: 20, + marginBottom: 20, + }, + searchIcon: { + marginRight: 5, }, })