Skip to content

Commit

Permalink
Add context menu for channel and group options
Browse files Browse the repository at this point in the history
  • Loading branch information
patosullivan committed Jan 15, 2025
1 parent 96f449b commit 9e24383
Show file tree
Hide file tree
Showing 15 changed files with 531 additions and 156 deletions.
4 changes: 2 additions & 2 deletions packages/app/features/top/ChatListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function ChatListScreenView({
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const [personalInviteOpen, setPersonalInviteOpen] = useState(false);
const [screenTitle, setScreenTitle] = useState('Home');
const [inviteSheetGroup, setInviteSheetGroup] = useState<db.Group | null>();
const [inviteSheetGroup, setInviteSheetGroup] = useState<string | null>();
const personalInvite = db.personalInviteLink.useValue();
const viewedPersonalInvite = db.hasViewedPersonalInvite.useValue();
const { isOpen, setIsOpen } = useGlobalSearch();
Expand Down Expand Up @@ -328,7 +328,7 @@ export function ChatListScreenView({
open={inviteSheetGroup !== null}
onOpenChange={handleInviteSheetOpenChange}
onInviteComplete={() => setInviteSheetGroup(null)}
group={inviteSheetGroup ?? undefined}
groupId={inviteSheetGroup ?? undefined}
/>
</View>
</NavigationProvider>
Expand Down
8 changes: 4 additions & 4 deletions packages/app/features/top/GroupChannelsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function GroupChannelsScreenContent({
focusedChannelId?: string;
}) {
const isFocused = useIsFocused();
const [inviteSheetGroup, setInviteSheetGroup] = useState<db.Group | null>(
const [inviteSheetGroup, setInviteSheetGroup] = useState<string | null>(
null
);
const { group } = useGroupContext({ groupId: id, isFocused });
Expand Down Expand Up @@ -78,8 +78,8 @@ export function GroupChannelsScreenContent({

return (
<ChatOptionsProvider
onPressInvite={(group) => {
setInviteSheetGroup(group);
onPressInvite={(groupId) => {
setInviteSheetGroup(groupId);
}}
{...useChatSettingsNavigation()}
>
Expand All @@ -100,7 +100,7 @@ export function GroupChannelsScreenContent({
setInviteSheetGroup(null);
}
}}
group={inviteSheetGroup ?? undefined}
groupId={inviteSheetGroup ?? undefined}
onInviteComplete={() => setInviteSheetGroup(null)}
/>
</ChatOptionsProvider>
Expand Down
28 changes: 22 additions & 6 deletions packages/app/hooks/useChatSettingsNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useIsWindowNarrow } from '@tloncorp/ui';
import { useCallback } from 'react';

import type { RootStackParamList } from '../navigation/types';
import { GroupSettingsStackParamList } from '../navigation/types';
import { useRootNavigation } from '../navigation/utils';

export const useChatSettingsNavigation = () => {
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>();

const { navigateToGroup } = useRootNavigation();
const isWindowNarrow = useIsWindowNarrow();

const navigateToGroupSettings = useCallback(
<T extends keyof GroupSettingsStackParamList>(
async <T extends keyof GroupSettingsStackParamList>(
screen: T,
params: GroupSettingsStackParamList[T]
) => {
navigation.navigate('GroupSettings', {
screen,
params,
} as any);
if (!isWindowNarrow) {
// We need to navigate to the group first to ensure that the group is loaded
await navigateToGroup(params.groupId);
setTimeout(() => {
navigation.navigate('GroupSettings', {
screen,
params,
} as any);
}, 100);
} else {
navigation.navigate('GroupSettings', {
screen,
params,
} as any);
}
},
[navigation]
[navigation, navigateToGroup, isWindowNarrow]
);

const onPressGroupMeta = useCallback(
Expand Down
34 changes: 24 additions & 10 deletions packages/shared/src/store/channelActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,29 +232,43 @@ export async function markChannelVisited(channelId: string) {
await db.updateChannel({ id: channelId, lastViewedAt: Date.now() });
}

export type MarkChannelReadParams = Pick<db.Channel, 'id' | 'groupId' | 'type'>;

export async function markChannelRead(params: MarkChannelReadParams) {
logger.log(`marking channel as read`, params.id);
export async function markChannelRead({
id,
groupId,
}: {
id: string;
groupId?: string;
}) {
logger.log(`marking channel as read`, id);
// optimistic update
const existingUnread = await db.getChannelUnread({ channelId: params.id });
const existingUnread = await db.getChannelUnread({ channelId: id });
if (existingUnread) {
await db.clearChannelUnread(params.id);
await db.clearChannelUnread(id);
}

const existingCount = existingUnread?.count ?? 0;
if (params.groupId && existingCount > 0) {
if (groupId && existingCount > 0) {
// optimitically update group unread count
await db.updateGroupUnreadCount({
groupId: params.groupId,
groupId,
decrement: existingCount,
});
}

const existingChannel = await db.getChannel({ id });

if (!existingChannel) {
throw new Error('Channel not found');
}

if (existingChannel.isPendingChannel) {
return;
}

try {
await api.readChannel(params);
await api.readChannel(existingChannel);
} catch (e) {
console.error('Failed to read channel', params, e);
console.error('Failed to read channel', {id, groupId}, e);
// rollback optimistic update
if (existingUnread) {
await db.insertChannelUnreads([existingUnread]);
Expand Down
17 changes: 9 additions & 8 deletions packages/shared/src/store/groupActions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import isEqual from 'lodash/isEqual';

import * as api from '../api';
import * as db from '../db';
import { GroupPrivacy } from '../db/schema';
import { createDevLogger } from '../debug';
import { getRandomId } from '../logic';
import { createSectionId } from '../urbit';
import { createChannel } from './channelActions';
import isEqual from 'lodash/isEqual';

const logger = createDevLogger('groupActions', false);

Expand Down Expand Up @@ -449,12 +450,7 @@ export async function addChannelToNavSection({
channelId: string;
navSectionId: string;
}) {
logger.log(
'adding channel to nav section',
groupId,
channelId,
navSectionId
);
logger.log('adding channel to nav section', groupId, channelId, navSectionId);

const existingGroup = await db.getGroup({ id: groupId });

Expand Down Expand Up @@ -1068,7 +1064,12 @@ export async function leaveGroup(groupId: string) {
}
}

export async function markGroupRead(group: db.Group, deep: boolean = false) {
export async function markGroupRead(groupId: string, deep: boolean = false) {
const group = await db.getGroup({ id: groupId });
if (!group) {
logger.error('Group not found', groupId);
return;
}
// optimistic update
const existingUnread = await db.getGroupUnread({ groupId: group.id });
if (existingUnread) {
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/src/components/ActionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,11 @@ const ActionSheetScrollableContent = ({

const useContentStyle = () => {
const insets = useSafeAreaInsets();
const isWindowNarrow = useIsWindowNarrow();
return {
paddingBottom: insets.bottom + getTokenValue('$2xl', 'size'),
paddingBottom: isWindowNarrow
? insets.bottom + getTokenValue('$2xl', 'size')
: 0,
};
};

Expand Down Expand Up @@ -366,6 +369,7 @@ const ActionSheetActionFrame = styled(ListItem, {
pressStyle: {
backgroundColor: '$secondaryBackground',
},
cursor: 'pointer',
variants: {
type: {
positive: {
Expand Down Expand Up @@ -432,12 +436,14 @@ const ActionSheetMainContent = styled(YStack, {

function ActionSheetAction({ action }: { action: Action }) {
const accent = useContext(ActionSheetActionGroupContext).accent;
const isWindowNarrow = useIsWindowNarrow();
return action.render ? (
action.render({ action })
) : (
<ActionSheetActionFrame
type={action.accent ?? (accent as Accent)}
onPress={accent !== 'disabled' ? action.action : undefined}
height={isWindowNarrow ? undefined : '$4xl'}
>
{action.startIcon &&
resolveIcon(action.startIcon, action.accent ?? accent)}
Expand Down
29 changes: 23 additions & 6 deletions packages/ui/src/components/Channel/ChannelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from 'react';

import { useChatOptions } from '../../contexts';
import useIsWindowNarrow from '../../hooks/useIsWindowNarrow';
import { ChatOptionsSheet } from '../ChatOptionsSheet';
import Pressable from '../Pressable';
import { ScreenHeader } from '../ScreenHeader';
import { BaubleHeader } from './BaubleHeader';
Expand Down Expand Up @@ -100,12 +102,14 @@ export function ChannelHeader({
post?: db.Post;
}) {
const chatOptions = useChatOptions();
const [openChatOptions, setOpenChatOptions] = useState(false);

const handlePressOverflowMenu = useCallback(() => {
chatOptions.open(channel.id, 'channel');
}, [channel.id, chatOptions]);

const contextItems = useContext(ChannelHeaderItemsContext)?.items ?? [];
const isWindowNarrow = useIsWindowNarrow();

if (mode === 'next') {
return <BaubleHeader channel={channel} group={group} />;
Expand Down Expand Up @@ -141,12 +145,25 @@ export function ChannelHeader({
<ScreenHeader.IconButton type="Search" onPress={goToSearch} />
)}
{contextItems}
{showMenuButton && (
<ScreenHeader.IconButton
type="Overflow"
onPress={handlePressOverflowMenu}
/>
)}
{showMenuButton ? (
isWindowNarrow ? (
<ScreenHeader.IconButton
type="Overflow"
onPress={handlePressOverflowMenu}
/>
) : (
<ChatOptionsSheet
open={openChatOptions}
onOpenChange={setOpenChatOptions}
chat={{ type: 'channel', id: channel.id }}
trigger={
<ScreenHeader.IconButton
type="Overflow"
/>
}
/>
)
) : null}
{showEditButton && (
<ScreenHeader.TextButton onPress={goToEdit}>
Edit
Expand Down
Loading

0 comments on commit 9e24383

Please sign in to comment.