Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add right column of web shell and tweak left column

+246 -30
+194
src/view/com/discover/LiteSuggestedFollows.tsx
··· 1 + import React, {useEffect, useState} from 'react' 2 + import { 3 + ActivityIndicator, 4 + StyleSheet, 5 + TouchableOpacity, 6 + View, 7 + } from 'react-native' 8 + import LinearGradient from 'react-native-linear-gradient' 9 + import {observer} from 'mobx-react-lite' 10 + import _omit from 'lodash.omit' 11 + import {ErrorMessage} from '../util/error/ErrorMessage' 12 + import {Link} from '../util/Link' 13 + import {Text} from '../util/text/Text' 14 + import {UserAvatar} from '../util/UserAvatar' 15 + import * as Toast from '../util/Toast' 16 + import {useStores} from '../../../state' 17 + import * as apilib from '../../../state/lib/api' 18 + import { 19 + SuggestedActorsViewModel, 20 + SuggestedActor, 21 + } from '../../../state/models/suggested-actors-view' 22 + import {s, gradients} from '../../lib/styles' 23 + import {usePalette} from '../../lib/hooks/usePalette' 24 + 25 + export const LiteSuggestedFollows = observer(() => { 26 + const store = useStores() 27 + const [suggestions, setSuggestions] = useState<SuggestedActor[] | undefined>( 28 + undefined, 29 + ) 30 + const [follows, setFollows] = useState<Record<string, string>>({}) 31 + 32 + useEffect(() => { 33 + const view = new SuggestedActorsViewModel(store) 34 + view.loadMore().then( 35 + () => { 36 + setSuggestions(view.suggestions.slice().sort(randomize).slice(0, 3)) 37 + }, 38 + (err: any) => { 39 + setSuggestions([]) 40 + store.log.error('Failed to fetch suggestions', err) 41 + }, 42 + ) 43 + }, [store, store.log]) 44 + 45 + const onPressFollow = async (item: SuggestedActor) => { 46 + try { 47 + const res = await apilib.follow(store, item.did, item.declaration.cid) 48 + setFollows({[item.did]: res.uri, ...follows}) 49 + } catch (e: any) { 50 + store.log.error('Failed fo create follow', e) 51 + Toast.show('An issue occurred, please try again.') 52 + } 53 + } 54 + const onPressUnfollow = async (item: SuggestedActor) => { 55 + try { 56 + await apilib.unfollow(store, follows[item.did]) 57 + setFollows(_omit(follows, [item.did])) 58 + } catch (e: any) { 59 + store.log.error('Failed fo delete follow', e) 60 + Toast.show('An issue occurred, please try again.') 61 + } 62 + } 63 + 64 + return ( 65 + <View> 66 + {!suggestions ? ( 67 + <View> 68 + <ActivityIndicator /> 69 + </View> 70 + ) : ( 71 + <View> 72 + {suggestions.map(item => ( 73 + <Link 74 + key={item.did} 75 + href={`/profile/${item.handle}`} 76 + title={item.displayName || item.handle}> 77 + <User 78 + item={item} 79 + follow={follows[item.did]} 80 + onPressFollow={onPressFollow} 81 + onPressUnfollow={onPressUnfollow} 82 + /> 83 + </Link> 84 + ))} 85 + </View> 86 + )} 87 + </View> 88 + ) 89 + }) 90 + 91 + const User = ({ 92 + item, 93 + follow, 94 + onPressFollow, 95 + onPressUnfollow, 96 + }: { 97 + item: SuggestedActor 98 + follow: string | undefined 99 + onPressFollow: (item: SuggestedActor) => void 100 + onPressUnfollow: (item: SuggestedActor) => void 101 + }) => { 102 + const pal = usePalette('default') 103 + return ( 104 + <View style={[styles.actor]}> 105 + <View style={styles.actorMeta}> 106 + <View style={styles.actorAvi}> 107 + <UserAvatar 108 + size={40} 109 + displayName={item.displayName} 110 + handle={item.handle} 111 + avatar={item.avatar} 112 + /> 113 + </View> 114 + <View style={styles.actorContent}> 115 + <Text type="lg-medium" style={pal.text} numberOfLines={1}> 116 + {item.displayName || item.handle} 117 + </Text> 118 + <Text type="sm" style={pal.textLight} numberOfLines={1}> 119 + @{item.handle} 120 + </Text> 121 + </View> 122 + <View style={styles.actorBtn}> 123 + {follow ? ( 124 + <TouchableOpacity onPress={() => onPressUnfollow(item)}> 125 + <View style={[styles.btn, styles.secondaryBtn, pal.btn]}> 126 + <Text type="button" style={pal.text}> 127 + Unfollow 128 + </Text> 129 + </View> 130 + </TouchableOpacity> 131 + ) : ( 132 + <TouchableOpacity onPress={() => onPressFollow(item)}> 133 + <LinearGradient 134 + colors={[gradients.blueLight.start, gradients.blueLight.end]} 135 + start={{x: 0, y: 0}} 136 + end={{x: 1, y: 1}} 137 + style={[styles.btn, styles.gradientBtn]}> 138 + <Text type="sm-medium" style={s.white}> 139 + Follow 140 + </Text> 141 + </LinearGradient> 142 + </TouchableOpacity> 143 + )} 144 + </View> 145 + </View> 146 + </View> 147 + ) 148 + } 149 + 150 + function randomize() { 151 + return Math.random() > 0.5 ? 1 : -1 152 + } 153 + 154 + const styles = StyleSheet.create({ 155 + footer: { 156 + height: 200, 157 + paddingTop: 20, 158 + }, 159 + 160 + actor: {}, 161 + actorMeta: { 162 + flexDirection: 'row', 163 + }, 164 + actorAvi: { 165 + width: 50, 166 + paddingTop: 10, 167 + paddingBottom: 10, 168 + }, 169 + actorContent: { 170 + flex: 1, 171 + paddingRight: 10, 172 + paddingTop: 10, 173 + }, 174 + actorBtn: { 175 + paddingRight: 10, 176 + paddingTop: 10, 177 + }, 178 + 179 + gradientBtn: { 180 + paddingHorizontal: 14, 181 + paddingVertical: 6, 182 + }, 183 + secondaryBtn: { 184 + paddingHorizontal: 8, 185 + }, 186 + btn: { 187 + flexDirection: 'row', 188 + alignItems: 'center', 189 + justifyContent: 'center', 190 + paddingVertical: 7, 191 + borderRadius: 50, 192 + marginLeft: 6, 193 + }, 194 + })
+3 -21
src/view/shell/web/index.tsx
··· 1 1 import React from 'react' 2 2 import {observer} from 'mobx-react-lite' 3 - import {View, StyleSheet, Text} from 'react-native' 3 + import {View, StyleSheet} from 'react-native' 4 4 import {useStores} from '../../../state' 5 5 import {match, MatchResult} from '../../routes' 6 6 import {DesktopLeftColumn} from './left-column' 7 - // import {DesktopRightColumn} from './right-column' 7 + import {DesktopRightColumn} from './right-column' 8 8 import {Login} from '../../screens/Login' 9 9 import {ErrorBoundary} from '../../com/util/ErrorBoundary' 10 10 import {usePalette} from '../../lib/hooks/usePalette' ··· 35 35 </View> 36 36 ))} 37 37 <DesktopLeftColumn /> 38 + <DesktopRightColumn /> 38 39 </View> 39 40 ) 40 41 // TODO ··· 48 49 // imagesOpen={store.shell.composerOpts?.imagesOpen} 49 50 // onPost={store.shell.composerOpts?.onPost} 50 51 // /> 51 - // return ( 52 - // <View style={styles.outerContainer}> 53 - // {store.session.hasSession ? ( 54 - // <> 55 - // <DesktopLeftColumn /> 56 - // <View style={styles.innerContainer}> 57 - // <Text>Hello, world! (Logged in)</Text> 58 - // {children} 59 - // </View> 60 - // <DesktopRightColumn /> 61 - // </> 62 - // ) : ( 63 - // <View style={styles.innerContainer}> 64 - // <Text>Hello, world! (Logged out)</Text> 65 - // {children} 66 - // </View> 67 - // )} 68 - // </View> 69 - // ) 70 52 }) 71 53 72 54 /**
+11 -4
src/view/shell/web/left-column.tsx
··· 42 42 </Text> 43 43 )} 44 44 </View> 45 - <Text type={isCurrent ? 'xl-bold' : 'xl-medium'}>{label}</Text> 45 + <Text type={isCurrent ? 'xl-bold' : 'xl'} style={styles.navItemLabel}> 46 + {label} 47 + </Text> 46 48 </Link> 47 49 </Pressable> 48 50 ) ··· 86 88 const styles = StyleSheet.create({ 87 89 container: { 88 90 position: 'absolute', 89 - left: 'calc(50vw - 500px)', 90 - width: '200px', 91 + left: 'calc(50vw - 530px)', 92 + width: '230px', 91 93 height: '100%', 92 94 borderRightWidth: 1, 95 + paddingTop: 20, 93 96 }, 94 97 navItem: { 95 98 padding: '1rem', ··· 109 112 backgroundColor: colors.red3, 110 113 color: colors.white, 111 114 fontSize: 12, 115 + fontWeight: 'bold', 112 116 paddingHorizontal: 4, 113 - borderRadius: 4, 117 + borderRadius: 6, 118 + }, 119 + navItemLabel: { 120 + fontSize: 19, 114 121 }, 115 122 })
+38 -5
src/view/shell/web/right-column.tsx
··· 1 1 import React from 'react' 2 - import {Text, View, StyleSheet} from 'react-native' 2 + import {View, StyleSheet} from 'react-native' 3 + import {Link} from '../../com/util/Link' 4 + import {Text} from '../../com/util/text/Text' 5 + import {usePalette} from '../../lib/hooks/usePalette' 6 + import {MagnifyingGlassIcon} from '../../lib/icons' 7 + import {LiteSuggestedFollows} from '../../com/discover/LiteSuggestedFollows' 8 + import {s} from '../../lib/styles' 3 9 4 10 export const DesktopRightColumn: React.FC = () => { 11 + const pal = usePalette('default') 5 12 return ( 6 - <View style={styles.container}> 7 - <Text>Right Column</Text> 13 + <View style={[styles.container, pal.border]}> 14 + <Link href="/search" style={[pal.btn, styles.searchContainer]}> 15 + <View style={styles.searchIcon}> 16 + <MagnifyingGlassIcon style={pal.textLight} /> 17 + </View> 18 + <Text type="lg" style={pal.textLight}> 19 + Search 20 + </Text> 21 + </Link> 22 + <Text type="xl-bold" style={s.mb10}> 23 + Suggested Follows 24 + </Text> 25 + <LiteSuggestedFollows /> 8 26 </View> 9 27 ) 10 28 } ··· 12 30 const styles = StyleSheet.create({ 13 31 container: { 14 32 position: 'absolute', 15 - right: 'calc(50vw - 500px)', 16 - width: '200px', 33 + right: 'calc(50vw - 650px)', 34 + width: '350px', 17 35 height: '100%', 36 + borderLeftWidth: 1, 37 + overscrollBehavior: 'auto', 38 + paddingLeft: 30, 39 + paddingTop: 10, 40 + }, 41 + searchContainer: { 42 + flexDirection: 'row', 43 + alignItems: 'center', 44 + paddingHorizontal: 14, 45 + paddingVertical: 10, 46 + borderRadius: 20, 47 + marginBottom: 20, 48 + }, 49 + searchIcon: { 50 + marginRight: 5, 18 51 }, 19 52 })