diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/modals/Confirm.tsx | 95 | ||||
-rw-r--r-- | src/view/com/modals/CreateScene.tsx | 2 | ||||
-rw-r--r-- | src/view/com/modals/InviteToScene.tsx | 238 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 28 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 35 | ||||
-rw-r--r-- | src/view/com/notifications/InviteAccepter.tsx | 96 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 36 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 103 | ||||
-rw-r--r-- | src/view/com/util/Link.tsx | 5 |
9 files changed, 612 insertions, 26 deletions
diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx new file mode 100644 index 000000000..5afe7c087 --- /dev/null +++ b/src/view/com/modals/Confirm.tsx @@ -0,0 +1,95 @@ +import React, {useState} from 'react' +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {useStores} from '../../../state' +import {s, colors, gradients} from '../../lib/styles' +import {ErrorMessage} from '../util/ErrorMessage' + +export const snapPoints = ['50%'] + +export function Component({ + title, + message, + onPressConfirm, +}: { + title: string + message: string | (() => JSX.Element) + onPressConfirm: () => void | Promise<void> +}) { + const store = useStores() + const [isProcessing, setIsProcessing] = useState<boolean>(false) + const [error, setError] = useState<string>('') + const onPress = async () => { + setError('') + setIsProcessing(true) + try { + await onPressConfirm() + store.shell.closeModal() + return + } catch (e: any) { + setError(e.toString()) + setIsProcessing(false) + } + } + return ( + <View style={[s.flex1, s.pl10, s.pr10]}> + <Text style={styles.title}>{title}</Text> + {typeof message === 'string' ? ( + <Text style={styles.description}>{message}</Text> + ) : ( + message() + )} + {error ? ( + <View style={s.mt10}> + <ErrorMessage message={error} /> + </View> + ) : undefined} + {isProcessing ? ( + <View style={[styles.btn, s.mt10]}> + <ActivityIndicator /> + </View> + ) : ( + <TouchableOpacity style={s.mt10} onPress={onPress}> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn]}> + <Text style={[s.white, s.bold, s.f18]}>Confirm</Text> + </LinearGradient> + </TouchableOpacity> + )} + </View> + ) +} + +const styles = StyleSheet.create({ + title: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 24, + marginBottom: 12, + }, + description: { + textAlign: 'center', + fontSize: 17, + paddingHorizontal: 22, + color: colors.gray5, + marginBottom: 10, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + borderRadius: 32, + padding: 14, + backgroundColor: colors.gray1, + }, +}) diff --git a/src/view/com/modals/CreateScene.tsx b/src/view/com/modals/CreateScene.tsx index 16a085d53..5c0a351b2 100644 --- a/src/view/com/modals/CreateScene.tsx +++ b/src/view/com/modals/CreateScene.tsx @@ -53,7 +53,7 @@ export function Component({}: {}) { { subject: { did: createSceneRes.data.did, - declarationCid: createSceneRes.data.declarationCid, + declarationCid: createSceneRes.data.declaration.cid, }, createdAt: new Date().toISOString(), }, diff --git a/src/view/com/modals/InviteToScene.tsx b/src/view/com/modals/InviteToScene.tsx new file mode 100644 index 000000000..f1c9d3386 --- /dev/null +++ b/src/view/com/modals/InviteToScene.tsx @@ -0,0 +1,238 @@ +import React, {useState, useEffect, useMemo} from 'react' +import Toast from '../util/Toast' +import { + ActivityIndicator, + FlatList, + StyleSheet, + Text, + useWindowDimensions, + View, +} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import { + TabView, + SceneMap, + Route, + TabBar, + TabBarProps, +} from 'react-native-tab-view' +import _omit from 'lodash.omit' +import {AtUri} from '../../../third-party/uri' +import {ProfileCard} from '../profile/ProfileCard' +import {ErrorMessage} from '../util/ErrorMessage' +import {useStores} from '../../../state' +import * as apilib from '../../../state/lib/api' +import {ProfileViewModel} from '../../../state/models/profile-view' +import {SceneInviteSuggestions} from '../../../state/models/scene-invite-suggestions' +import {FollowItem} from '../../../state/models/user-follows-view' +import {s, colors} from '../../lib/styles' + +export const snapPoints = ['70%'] + +export function Component({profileView}: {profileView: ProfileViewModel}) { + const store = useStores() + const layout = useWindowDimensions() + const [index, setIndex] = useState(0) + const tabRoutes = [ + {key: 'suggestions', title: 'Suggestions'}, + {key: 'pending', title: 'Pending Invites'}, + ] + const [hasSetup, setHasSetup] = useState<boolean>(false) + const [error, setError] = useState<string>('') + const suggestions = useMemo( + () => new SceneInviteSuggestions(store, {sceneDid: profileView.did}), + [profileView.did], + ) + const [createdInvites, setCreatedInvites] = useState<Record<string, string>>( + {}, + ) + + useEffect(() => { + let aborted = false + if (hasSetup) { + return + } + suggestions.setup().then(() => { + if (aborted) return + setHasSetup(true) + }) + return () => { + aborted = true + } + }, [profileView.did]) + + const onPressInvite = async (follow: FollowItem) => { + setError('') + try { + const assertionUri = await apilib.inviteToScene( + store, + profileView.did, + follow.did, + follow.declaration.cid, + ) + setCreatedInvites({[follow.did]: assertionUri, ...createdInvites}) + Toast.show('Invite sent', { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + }) + } catch (e) { + setError('There was an issue with the invite. Please try again.') + console.error(e) + } + } + const onPressUndo = async (subjectDid: string, assertionUri: string) => { + setError('') + const urip = new AtUri(assertionUri) + try { + await store.api.app.bsky.graph.assertion.delete({ + did: profileView.did, + rkey: urip.rkey, + }) + setCreatedInvites(_omit(createdInvites, [subjectDid])) + } catch (e) { + setError('There was an issue with the invite. Please try again.') + console.error(e) + } + } + + const renderSuggestionItem = ({item}: {item: FollowItem}) => { + const createdInvite = createdInvites[item.did] + return ( + <ProfileCard + did={item.did} + handle={item.handle} + displayName={item.displayName} + renderButton={() => + !createdInvite ? ( + <> + <FontAwesomeIcon icon="user-plus" style={[s.mr5]} size={14} /> + <Text style={[s.fw400, s.f14]}>Invite</Text> + </> + ) : ( + <> + <FontAwesomeIcon icon="x" style={[s.mr5]} size={14} /> + <Text style={[s.fw400, s.f14]}>Undo invite</Text> + </> + ) + } + onPressButton={() => + !createdInvite + ? onPressInvite(item) + : onPressUndo(item.did, createdInvite) + } + /> + ) + } + + const Suggestions = () => ( + <View style={s.flex1}> + {hasSetup ? ( + <View style={s.flex1}> + <View style={styles.todoContainer}> + <Text style={styles.todoLabel}> + User search is still being implemented. For now, you can pick from + your follows below. + </Text> + </View> + {!suggestions.hasContent ? ( + <Text + style={{ + textAlign: 'center', + paddingTop: 10, + paddingHorizontal: 40, + fontWeight: 'bold', + color: colors.gray5, + }}> + {suggestions.myFollowsView.follows.length + ? 'Sorry! You dont follow anybody for us to suggest.' + : 'Sorry! All of the users you follow are members already.'} + </Text> + ) : ( + <FlatList + data={suggestions.suggestions} + keyExtractor={item => item._reactKey} + renderItem={renderSuggestionItem} + style={s.flex1} + /> + )} + </View> + ) : !error ? ( + <ActivityIndicator /> + ) : undefined} + </View> + ) + + const PendingInvites = () => ( + <View> + <View style={styles.todoContainer}> + <Text style={styles.todoLabel}> + Pending invites are still being implemented. Check back soon! + </Text> + </View> + </View> + ) + + const renderScene = SceneMap({ + suggestions: Suggestions, + pending: PendingInvites, + }) + + const renderTabBar = (props: TabBarProps<Route>) => ( + <TabBar + {...props} + style={{backgroundColor: 'white'}} + activeColor="black" + inactiveColor={colors.gray5} + labelStyle={{textTransform: 'none'}} + indicatorStyle={{backgroundColor: colors.purple3}} + /> + ) + + return ( + <View style={s.flex1}> + <Text style={styles.title}> + Invite to {profileView.displayName || profileView.handle} + </Text> + {error !== '' ? ( + <View style={s.p10}> + <ErrorMessage message={error} /> + </View> + ) : undefined} + <TabView + navigationState={{index, routes: tabRoutes}} + renderScene={renderScene} + renderTabBar={renderTabBar} + onIndexChange={setIndex} + initialLayout={{width: layout.width}} + /> + </View> + ) +} + +const styles = StyleSheet.create({ + title: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 18, + marginBottom: 4, + }, + todoContainer: { + backgroundColor: colors.pink1, + margin: 10, + padding: 10, + borderRadius: 6, + }, + todoLabel: { + color: colors.pink5, + textAlign: 'center', + }, + + tabBar: { + flexDirection: 'row', + }, + tabItem: { + alignItems: 'center', + padding: 16, + flex: 1, + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index f79a571d4..f2c61a6ae 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -8,9 +8,11 @@ import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import * as models from '../../../state/models/shell-ui' import * as LinkActionsModal from './LinkActions' +import * as ConfirmModal from './Confirm' import * as SharePostModal from './SharePost.native' -import * as EditProfile from './EditProfile' -import * as CreateScene from './CreateScene' +import * as EditProfileModal from './EditProfile' +import * as CreateSceneModal from './CreateScene' +import * as InviteToSceneModal from './InviteToScene' const CLOSED_SNAPPOINTS = ['10%'] @@ -44,6 +46,13 @@ export const Modal = observer(function Modal() { {...(store.shell.activeModal as models.LinkActionsModel)} /> ) + } else if (store.shell.activeModal?.name === 'confirm') { + snapPoints = ConfirmModal.snapPoints + element = ( + <ConfirmModal.Component + {...(store.shell.activeModal as models.ConfirmModel)} + /> + ) } else if (store.shell.activeModal?.name === 'share-post') { snapPoints = SharePostModal.snapPoints element = ( @@ -52,15 +61,22 @@ export const Modal = observer(function Modal() { /> ) } else if (store.shell.activeModal?.name === 'edit-profile') { - snapPoints = EditProfile.snapPoints + snapPoints = EditProfileModal.snapPoints element = ( - <EditProfile.Component + <EditProfileModal.Component {...(store.shell.activeModal as models.EditProfileModel)} /> ) } else if (store.shell.activeModal?.name === 'create-scene') { - snapPoints = CreateScene.snapPoints - element = <CreateScene.Component /> + snapPoints = CreateSceneModal.snapPoints + element = <CreateSceneModal.Component /> + } else if (store.shell.activeModal?.name === 'invite-to-scene') { + snapPoints = InviteToSceneModal.snapPoints + element = ( + <InviteToSceneModal.Component + {...(store.shell.activeModal as models.InviteToSceneModel)} + /> + ) } else { element = <View /> } diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index c67e6b966..fdc893e78 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -1,6 +1,6 @@ import React, {useMemo} from 'react' import {observer} from 'mobx-react-lite' -import {Image, StyleSheet, Text, View} from 'react-native' +import {StyleSheet, Text, View} from 'react-native' import {AtUri} from '../../../third-party/uri' import {FontAwesomeIcon, Props} from '@fortawesome/react-native-fontawesome' import {NotificationsViewItemModel} from '../../../state/models/notifications-view' @@ -11,6 +11,7 @@ import {UserAvatar} from '../util/UserAvatar' import {PostText} from '../post/PostText' import {Post} from '../post/Post' import {Link} from '../util/Link' +import {InviteAccepter} from './InviteAccepter' const MAX_AUTHORS = 8 @@ -20,10 +21,10 @@ export const FeedItem = observer(function FeedItem({ item: NotificationsViewItemModel }) { const itemHref = useMemo(() => { - if (item.isUpvote || item.isRepost) { + if (item.isUpvote || item.isRepost || item.isTrend) { const urip = new AtUri(item.subjectUri) return `/profile/${urip.host}/post/${urip.rkey}` - } else if (item.isFollow) { + } else if (item.isFollow || item.isAssertion) { return `/profile/${item.author.handle}` } else if (item.isReply) { const urip = new AtUri(item.uri) @@ -34,7 +35,7 @@ export const FeedItem = observer(function FeedItem({ const itemTitle = useMemo(() => { if (item.isUpvote || item.isRepost) { return 'Post' - } else if (item.isFollow) { + } else if (item.isFollow || item.isAssertion) { return item.author.handle } else if (item.isReply) { return 'Post' @@ -66,6 +67,10 @@ export const FeedItem = observer(function FeedItem({ action = 'reposted your post' icon = 'retweet' iconStyle = [s.green3] + } else if (item.isTrend) { + action = 'Your post is trending with' + icon = 'arrow-trend-up' + iconStyle = [s.blue3] } else if (item.isReply) { action = 'replied to your post' icon = ['far', 'comment'] @@ -73,6 +78,10 @@ export const FeedItem = observer(function FeedItem({ action = 'followed you' icon = 'user-plus' iconStyle = [s.blue3] + } else if (item.isInvite) { + icon = 'users' + iconStyle = [s.blue3] + action = 'invited you to join their scene' } else { return <></> } @@ -133,6 +142,9 @@ export const FeedItem = observer(function FeedItem({ ) : undefined} </View> <View style={styles.meta}> + {item.isTrend && ( + <Text style={[styles.metaItem, s.f15]}>{action}</Text> + )} <Link key={authors[0].href} style={styles.metaItem} @@ -150,7 +162,9 @@ export const FeedItem = observer(function FeedItem({ </Text> </> ) : undefined} - <Text style={[styles.metaItem, s.f15]}>{action}</Text> + {!item.isTrend && ( + <Text style={[styles.metaItem, s.f15]}>{action}</Text> + )} <Text style={[styles.metaItem, s.f15, s.gray5]}> {ago(item.indexedAt)} </Text> @@ -162,6 +176,11 @@ export const FeedItem = observer(function FeedItem({ )} </View> </View> + {item.isInvite && ( + <View style={styles.addedContainer}> + <InviteAccepter item={item} /> + </View> + )} {item.isReply ? ( <View style={s.pt5}> <Post uri={item.uri} /> @@ -216,6 +235,7 @@ const styles = StyleSheet.create({ }, meta: { flexDirection: 'row', + flexWrap: 'wrap', paddingTop: 6, paddingBottom: 2, }, @@ -225,4 +245,9 @@ const styles = StyleSheet.create({ postText: { paddingBottom: 5, }, + + addedContainer: { + paddingTop: 4, + paddingLeft: 36, + }, }) diff --git a/src/view/com/notifications/InviteAccepter.tsx b/src/view/com/notifications/InviteAccepter.tsx new file mode 100644 index 000000000..7d735a66b --- /dev/null +++ b/src/view/com/notifications/InviteAccepter.tsx @@ -0,0 +1,96 @@ +import React, {useState} from 'react' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import * as apilib from '../../../state/lib/api' +import {NotificationsViewItemModel} from '../../../state/models/notifications-view' +import {ConfirmModel} from '../../../state/models/shell-ui' +import {useStores} from '../../../state' +import {ProfileCard} from '../profile/ProfileCard' +import Toast from '../util/Toast' +import {s, colors, gradients} from '../../lib/styles' + +export function InviteAccepter({item}: {item: NotificationsViewItemModel}) { + const store = useStores() + const [confirmationUri, setConfirmationUri] = useState<string>('') + const isMember = + confirmationUri !== '' || store.me.memberships?.isMemberOf(item.author.did) + const onPressAccept = async () => { + store.shell.openModal( + new ConfirmModel( + 'Join this scene?', + () => ( + <View> + <View style={styles.profileCardContainer}> + <ProfileCard + did={item.author.did} + handle={item.author.handle} + displayName={item.author.displayName} + /> + </View> + </View> + ), + onPressConfirmAccept, + ), + ) + } + const onPressConfirmAccept = async () => { + const uri = await apilib.acceptSceneInvite(store, { + originator: { + did: item.author.did, + declarationCid: item.author.declaration.cid, + }, + assertion: { + uri: item.uri, + cid: item.cid, + }, + }) + store.me.refreshMemberships() + Toast.show('Invite accepted', { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + }) + setConfirmationUri(uri) + } + return ( + <View style={styles.container}> + {!isMember ? ( + <TouchableOpacity onPress={onPressAccept}> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn]}> + <Text style={[s.white, s.bold, s.f16]}>Accept Invite</Text> + </LinearGradient> + </TouchableOpacity> + ) : ( + <View style={styles.inviteAccepted}> + <FontAwesomeIcon icon="check" size={14} style={s.mr5} /> + <Text style={[s.gray5, s.f15]}>Invite accepted</Text> + </View> + )} + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + }, + btn: { + borderRadius: 32, + paddingHorizontal: 18, + paddingVertical: 8, + backgroundColor: colors.gray1, + }, + profileCardContainer: { + borderWidth: 1, + borderColor: colors.gray3, + borderRadius: 6, + }, + inviteAccepted: { + flexDirection: 'row', + alignItems: 'center', + }, +}) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index cb58aec3f..fc8104937 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {StyleSheet, Text, View} from 'react-native' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' import {Link} from '../util/Link' import {UserAvatar} from '../util/UserAvatar' import {s, colors} from '../../lib/styles' @@ -9,11 +9,15 @@ export function ProfileCard({ handle, displayName, description, + renderButton, + onPressButton, }: { did: string handle: string displayName?: string description?: string + renderButton?: () => JSX.Element + onPressButton?: () => void }) { return ( <Link style={styles.outer} href={`/profile/${handle}`} title={handle}> @@ -22,9 +26,20 @@ export function ProfileCard({ <UserAvatar size={40} displayName={displayName} handle={handle} /> </View> <View style={styles.layoutContent}> - <Text style={[s.f16, s.bold]}>{displayName || handle}</Text> - <Text style={[s.f15, s.gray5]}>@{handle}</Text> + <Text style={[s.f16, s.bold]} numberOfLines={1}> + {displayName || handle} + </Text> + <Text style={[s.f15, s.gray5]} numberOfLines={1}> + @{handle} + </Text> </View> + {renderButton ? ( + <View style={styles.layoutButton}> + <TouchableOpacity onPress={onPressButton} style={styles.btn}> + {renderButton()} + </TouchableOpacity> + </View> + ) : undefined} </View> </Link> ) @@ -34,9 +49,11 @@ const styles = StyleSheet.create({ outer: { marginTop: 1, backgroundColor: colors.white, + borderRadius: 6, }, layout: { flexDirection: 'row', + alignItems: 'center', }, layoutAvi: { width: 60, @@ -56,4 +73,17 @@ const styles = StyleSheet.create({ paddingTop: 12, paddingBottom: 10, }, + layoutButton: { + paddingRight: 10, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 7, + paddingHorizontal: 14, + borderRadius: 50, + backgroundColor: colors.gray1, + marginLeft: 6, + }, }) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index ee4df4fb9..3adffc44c 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useMemo} from 'react' import {observer} from 'mobx-react-lite' import { ActivityIndicator, @@ -9,12 +9,18 @@ import { } from 'react-native' import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {AtUri} from '../../../third-party/uri' import {ProfileViewModel} from '../../../state/models/profile-view' import {useStores} from '../../../state' -import {EditProfileModel} from '../../../state/models/shell-ui' +import { + ConfirmModel, + EditProfileModel, + InviteToSceneModel, +} from '../../../state/models/shell-ui' import {pluralize} from '../../lib/strings' import {s, colors} from '../../lib/styles' import {getGradient} from '../../lib/asset-gen' +import {DropdownBtn, DropdownItem} from '../util/DropdownBtn' import Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {UserBanner} from '../util/UserBanner' @@ -22,10 +28,16 @@ import {UserInfoText} from '../util/UserInfoText' export const ProfileHeader = observer(function ProfileHeader({ view, + onRefreshAll, }: { view: ProfileViewModel + onRefreshAll: () => void }) { const store = useStores() + const isMember = useMemo( + () => view.isScene && view.myState.member, + [view.myState.member], + ) const onPressBack = () => { store.nav.tab.goBack() @@ -49,9 +61,6 @@ export const ProfileHeader = observer(function ProfileHeader({ const onPressEditProfile = () => { store.shell.openModal(new EditProfileModel(view)) } - const onPressMenu = () => { - // TODO - } const onPressFollowers = () => { store.nav.navigate(`/profile/${view.handle}/followers`) } @@ -61,6 +70,31 @@ export const ProfileHeader = observer(function ProfileHeader({ const onPressMembers = () => { store.nav.navigate(`/profile/${view.handle}/members`) } + const onPressInviteMembers = () => { + store.shell.openModal(new InviteToSceneModel(view)) + } + const onPressLeaveScene = () => { + store.shell.openModal( + new ConfirmModel( + 'Leave this scene?', + `You'll be able to come back unless your invite is revoked.`, + onPressConfirmLeaveScene, + ), + ) + } + const onPressConfirmLeaveScene = async () => { + if (view.myState.member) { + await store.api.app.bsky.graph.confirmation.delete({ + did: store.me.did || '', + rkey: new AtUri(view.myState.member).rkey, + }) + Toast.show(`Scene left`, { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + }) + } + onRefreshAll() + } // loading // = @@ -86,6 +120,23 @@ export const ProfileHeader = observer(function ProfileHeader({ // = const gradient = getGradient(view.handle) const isMe = store.me.did === view.did + const isCreator = view.isScene && view.creator === store.me.did + let dropdownItems: DropdownItem[] | undefined + if (isCreator || isMember) { + dropdownItems = [] + if (isCreator) { + dropdownItems.push({ + label: 'Edit Profile', + onPress: () => {}, // TODO + }) + } + if (isMember) { + dropdownItems.push({ + label: 'Leave Scene...', + onPress: onPressLeaveScene, + }) + } + } return ( <View style={styles.outer}> <UserBanner handle={view.handle} /> @@ -136,11 +187,14 @@ export const ProfileHeader = observer(function ProfileHeader({ )} </> )} - <TouchableOpacity - onPress={onPressMenu} - style={[styles.btn, styles.secondaryBtn]}> - <FontAwesomeIcon icon="ellipsis" style={[s.gray5]} /> - </TouchableOpacity> + {view.isScene && + (view.myState.member || view.creator === store.me.did) ? ( + <DropdownBtn + items={dropdownItems} + style={[styles.btn, styles.secondaryBtn]}> + <FontAwesomeIcon icon="ellipsis" style={[s.gray5]} /> + </DropdownBtn> + ) : undefined} </View> <View style={styles.displayNameLine}> <Text style={styles.displayName}> @@ -224,6 +278,24 @@ export const ProfileHeader = observer(function ProfileHeader({ </View> ) : undefined} </View> + {view.isScene && view.creator === store.me.did ? ( + <View style={styles.sceneAdminContainer}> + <TouchableOpacity onPress={onPressInviteMembers}> + <LinearGradient + colors={[gradient[1], gradient[0]]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn, styles.gradientBtn, styles.sceneAdminBtn]}> + <FontAwesomeIcon + icon="user-plus" + style={[s.mr5, s.white]} + size={15} + /> + <Text style={[s.bold, s.f15, s.white]}>Invite Members</Text> + </LinearGradient> + </TouchableOpacity> + </View> + ) : undefined} </View> ) }) @@ -340,4 +412,15 @@ const styles = StyleSheet.create({ alignItems: 'center', marginBottom: 5, }, + + sceneAdminContainer: { + borderColor: colors.gray1, + borderTopWidth: 1, + borderBottomWidth: 1, + paddingVertical: 12, + paddingHorizontal: 12, + }, + sceneAdminBtn: { + paddingVertical: 8, + }, }) diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index 08536b0c3..84060ac01 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -16,7 +16,10 @@ export const Link = observer(function Link({ children?: React.ReactNode }) { const store = useStores() - const onPress = () => store.nav.navigate(href) + const onPress = () => { + store.shell.closeModal() // close any active modals + store.nav.navigate(href) + } const onLongPress = () => { store.shell.openModal(new LinkActionsModel(href, title || href)) } |