-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mobile context menu #122
base: dev
Are you sure you want to change the base?
Mobile context menu #122
Changes from all commits
87b425b
6cb75ff
86afcd2
2e84afb
c9fc0e9
8137c2d
36a2ab8
e71535b
203130f
5cc9d09
d78dd73
000082a
bcd0aca
ecede9e
ca441e7
10e91c9
a7712b4
71e0ff9
ff71c8b
214cf94
5b5d4f2
c58f3da
2353e3f
8af2ce9
0758434
a2864a9
45cd04b
ee888eb
a80358c
8e55708
7f2b9e2
608a202
b5be525
19a7f76
ce86161
55966ab
e9530b3
3b31d72
8b2b3bf
310e5cf
f882b87
393ed6a
47a7581
26521bf
fd98425
e4d0024
35b333a
1baed52
c45be24
8496bf2
60e4e5b
8d3570e
69a642a
e7b9cfe
2875b67
35e1298
df7faa3
259bd5f
623e508
0338074
aac4442
91b41d5
7a84ae4
652cb5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import React, { useState, useEffect } from 'react' | ||
import { Settings, Users, ChevronRight } from 'lucide-react-native' | ||
import { View, Text, TouchableOpacity } from 'react-native' | ||
import { useTranslation } from 'react-i18next' | ||
import { useNavigation } from '@react-navigation/native' | ||
import FastImage from 'react-native-fast-image' | ||
import Avatar from 'components/Avatar' | ||
import { RESP_ADMINISTRATION } from 'store/constants' | ||
// import { DEFAULT_BANNER, DEFAULT_AVATAR } from 'store/models/Group' | ||
import useHasResponsibility from 'hooks/useHasResponsibility' | ||
import { cn } from '../../util' | ||
|
||
export default function GroupMenuHeader ({ group }) { | ||
const { t } = useTranslation() | ||
const navigation = useNavigation() | ||
const avatarUrl = group.avatarUrl | ||
const bannerUrl = group.bannerUrl | ||
const [textColor, setTextColor] = useState('background') | ||
const hasResponsibility = useHasResponsibility({ forCurrentGroup: true, forCurrentUser: true }) | ||
const canAdmin = hasResponsibility(RESP_ADMINISTRATION) | ||
|
||
useEffect(() => { | ||
/* | ||
TODO: Web has a bunch of code that checks the color of the group background | ||
image to select a good constrasting color for the text. | ||
|
||
Its very dependent on the DOM, so need to consider a different option for mobile | ||
*/ | ||
setTextColor('primary-foreground') | ||
}, [bannerUrl]) | ||
|
||
return ( | ||
<View className='relative flex flex-col justify-end h-[140px] shadow-md pb-2' testID='group-header'> | ||
<FastImage | ||
source={{ uri: bannerUrl }} | ||
style={{ | ||
height: 146, | ||
width: '100%', | ||
position: 'absolute', | ||
opacity: 0.8, | ||
}} | ||
/> | ||
|
||
{canAdmin && ( | ||
<View className='absolute top-2 right-2'> | ||
<TouchableOpacity | ||
// TODO redesign: make this actually navigate correctly | ||
onPress={() => navigation.navigate('Group Settings')} | ||
> | ||
<View className='w-6 h-6 drop-shadow-md'> | ||
<Settings color='white' size={24}/> | ||
</View> | ||
</TouchableOpacity> | ||
</View> | ||
)} | ||
|
||
<View className='relative flex flex-row items-center text-background ml-2 mr-2 gap-1'> | ||
<FastImage | ||
source={{ uri: avatarUrl }} | ||
style={{ | ||
height: 36, | ||
width: 36, | ||
marginRight: 6, | ||
borderRadius: 4 | ||
}} | ||
/> | ||
|
||
<View className={cn( | ||
'flex flex-col flex-1', | ||
`text-${textColor} drop-shadow-md` | ||
)}> | ||
<Text className='text-xl font-bold m-0 text-white'> | ||
{group.name} | ||
</Text> | ||
|
||
<TouchableOpacity | ||
onPress={() => navigation.navigate('Members', { groupSlug: group.slug })} | ||
className='flex-row items-center' | ||
> | ||
<View className='w-4 h-4 mr-1 align-bottom' > | ||
<Users color='white' size={16} /> | ||
</View> | ||
<Text className='text-xs align-middle text-white underline'> | ||
{t('{{count}} Members', { count: group.memberCount })} | ||
</Text> | ||
</TouchableOpacity> | ||
</View> | ||
|
||
<TouchableOpacity | ||
onPress={() => navigation.navigate('About', { groupSlug: group.slug })} | ||
hitSlop={6} | ||
> | ||
<View className='cursor-pointer'> | ||
<ChevronRight color='white' size={24} strokeWidth={3}/> | ||
</View> | ||
</TouchableOpacity> | ||
</View> | ||
</View> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import component from './GroupMenuHeader' | ||
|
||
export default component |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,21 @@ import HyloHTML from 'components/HyloHTML' | |
import Icon from 'components/Icon' | ||
import { personUrl } from 'util/navigation' | ||
|
||
const formatEventDate = (startTime) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this go to shared? |
||
if (!startTime) return null | ||
try { | ||
const dateTime = DateTime.fromJSDate(startTime) | ||
if (!dateTime.isValid) return null | ||
return { | ||
month: dateTime.toFormat('MMM'), | ||
day: dateTime.toFormat('D') | ||
} | ||
} catch (error) { | ||
console.error('Error formatting event date:', error) | ||
return null | ||
} | ||
} | ||
|
||
const PostListRow = (props) => { | ||
const { | ||
childPost, | ||
|
@@ -39,7 +54,7 @@ const PostListRow = (props) => { | |
const creatorUrl = personUrl(creator.id, slug) | ||
const numOtherCommentors = commentersTotal - 1 | ||
const unread = false | ||
const start = DateTime.fromJSDate(post.startTime) | ||
const eventDate = formatEventDate(post.startTime) | ||
const isFlagged = post.flaggedGroups && post.flaggedGroups.includes(currentGroupId) | ||
const { t } = useTranslation() | ||
|
||
|
@@ -59,11 +74,11 @@ const PostListRow = (props) => { | |
<Icon name={typeName} /> | ||
</View> | ||
<View style={styles.participants}> | ||
{post.type === 'event' | ||
{post.type === 'event' && eventDate | ||
? ( | ||
<View style={styles.date}> | ||
<Text style={styles.dateText}>{start.toFormat('MMM')}</Text> | ||
<Text style={styles.dateText}>{start.toFormat('D')}</Text> | ||
<Text style={styles.dateText}>{eventDate.month}</Text> | ||
<Text style={styles.dateText}>{eventDate.day}</Text> | ||
</View> | ||
) | ||
: ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import React from 'react' | ||
import FastImage from 'react-native-fast-image' | ||
import Icon from 'components/Icon' | ||
import { Grid3x3 } from 'lucide-react-native' | ||
import Avatar from 'components/Avatar' | ||
import { ViewHelpers, WidgetHelpers } from '@hylo/shared' | ||
const { widgetTypeResolver } = WidgetHelpers | ||
|
||
export function WidgetIconResolver({ widget, style, className }) { | ||
const type = widgetTypeResolver({ widget }) | ||
if (widget.icon) { | ||
return <Icon name={widget.icon} style={style} className={className} /> | ||
} | ||
|
||
if (widget.viewUser) { | ||
return ( | ||
<Avatar | ||
avatarUrl={widget.viewUser.avatarUrl} | ||
name={widget.viewUser.name} | ||
style={style} | ||
className={className} | ||
/> | ||
) | ||
} | ||
|
||
if (widget.viewGroup) { | ||
return ( | ||
<Avatar | ||
avatarUrl={widget.viewGroup.avatarUrl} | ||
name={widget.viewGroup.name} | ||
style={style} | ||
className={className} | ||
/> | ||
) | ||
} | ||
|
||
if (widget.customView?.icon) { | ||
return <Icon name={widget.customView.icon} style={style} className={className} /> | ||
} | ||
|
||
if (widget.context === 'my') { | ||
return null | ||
} | ||
|
||
if (ViewHelpers.COMMON_VIEWS[type]) { | ||
return <Icon name={ViewHelpers.COMMON_VIEWS[type].icon} style={style} className={className} /> | ||
} | ||
|
||
// TODO redesign: - make a iconName method in WidgetHelpers / WidgetPresenter | ||
// the name icon name is shared by web mobile? | ||
// TOM COMMENT: waiting for full shake-out of Lucide transition before cleaning this up | ||
switch (type) { | ||
case 'chats': | ||
return <Icon name='Message' style={style} className={className} /> | ||
case 'setup': | ||
return <Icon name='Settings' style={style} className={className} /> | ||
case 'custom-views': | ||
return <Icon name='Stack' style={style} className={className} /> | ||
case 'viewChat': | ||
return <Icon name='Message' style={style} className={className} /> | ||
case 'chat': | ||
return <Icon name='Message' style={style} className={className} /> | ||
case 'viewPost': | ||
return <Icon name='Posticon' style={style} className={className} /> | ||
case 'about': | ||
return <Icon name='Info' style={style} className={className} /> | ||
case 'all-views': | ||
return <Grid3x3 className='h-[16px]'/> | ||
} | ||
return null | ||
} | ||
|
||
export default WidgetIconResolver |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import component from './WidgetIconResolver' | ||
|
||
export default component |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,33 @@ export default gql` | |
avatarUrl | ||
bannerUrl | ||
description | ||
homeWidget { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this a "fragment ContextWidgetFragment on ContextWidget" and apply it the places it is currently used |
||
id | ||
type | ||
view | ||
highlightNumber | ||
secondaryNumber | ||
customView { | ||
id | ||
groupId | ||
collectionId | ||
externalLink | ||
isActive | ||
icon | ||
name | ||
order | ||
postTypes | ||
topics { | ||
id | ||
name | ||
} | ||
type | ||
} | ||
viewChat { | ||
id | ||
name | ||
} | ||
} | ||
geoShape | ||
location | ||
memberCount | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove