diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx
index c60cab2bd4..8bf201dffb 100644
--- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx
+++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx
@@ -9,7 +9,7 @@ import { useUpdatePresentedNotifications } from '@tloncorp/app/lib/notifications
import { RootStack } from '@tloncorp/app/navigation/RootStack';
import { AppDataProvider } from '@tloncorp/app/provider/AppDataProvider';
import { sync } from '@tloncorp/shared';
-import { ZStack } from '@tloncorp/ui';
+import { PortalProvider, ZStack } from '@tloncorp/ui';
import { useCallback, useEffect } from 'react';
import { AppStateStatus } from 'react-native';
@@ -58,7 +58,13 @@ function AuthenticatedApp() {
export default function ConnectedAuthenticatedApp() {
return (
-
+ {/*
+ This portal provider overrides the root portal provider
+ to ensure that sheets have access to `AppDataContext`
+ */}
+
+
+
);
}
diff --git a/apps/tlon-mobile/src/hooks/useNotificationListener.ts b/apps/tlon-mobile/src/hooks/useNotificationListener.ts
index 5f0af562e9..4b49dc5a57 100644
--- a/apps/tlon-mobile/src/hooks/useNotificationListener.ts
+++ b/apps/tlon-mobile/src/hooks/useNotificationListener.ts
@@ -6,6 +6,7 @@ import { connectNotifications } from '@tloncorp/app/lib/notifications';
import { RootStackParamList } from '@tloncorp/app/navigation/types';
import {
createTypedReset,
+ getMainGroupRoute,
screenNameFromChannelId,
} from '@tloncorp/app/navigation/utils';
import * as posthog from '@tloncorp/app/utils/posthog';
@@ -154,17 +155,18 @@ export default function useNotificationListener() {
}
const routeStack: RouteStack = [{ name: 'ChatList' }];
- if (channel.groupId && !channelSwitcherEnabled) {
+ if (channel.groupId) {
+ const mainGroupRoute = await getMainGroupRoute(channel.groupId);
+ routeStack.push(mainGroupRoute);
+ }
+ // Only push the channel if it wasn't already handled by the main group stack
+ if (routeStack[routeStack.length - 1].name !== 'Channel') {
+ const screenName = screenNameFromChannelId(channelId);
routeStack.push({
- name: 'GroupChannels',
- params: { groupId: channel.groupId },
+ name: screenName,
+ params: { channelId: channel.id },
});
}
- const screenName = screenNameFromChannelId(channelId);
- routeStack.push({
- name: screenName,
- params: { channelId: channel.id },
- });
// if we have a post id, try to navigate to the thread
if (postInfo) {
diff --git a/packages/app/features/channels/ChannelMembersScreen.tsx b/packages/app/features/channels/ChannelMembersScreen.tsx
index baef4a6fbd..7910155d57 100644
--- a/packages/app/features/channels/ChannelMembersScreen.tsx
+++ b/packages/app/features/channels/ChannelMembersScreen.tsx
@@ -8,7 +8,7 @@ type Props = NativeStackScreenProps;
export function ChannelMembersScreen(props: Props) {
const { channelId } = props.route.params;
- const channelQuery = store.useChannelWithRelations({
+ const channelQuery = store.useChannel({
id: channelId,
});
diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx
index c5e49946b7..148237ba04 100644
--- a/packages/app/features/top/ChannelScreen.tsx
+++ b/packages/app/features/top/ChannelScreen.tsx
@@ -319,16 +319,6 @@ export default function ChannelScreen(props: Props) {
const canUpload = useCanUpload();
- const isFocused = useIsFocused();
-
- const { data: pins } = store.usePins({
- enabled: isFocused,
- });
-
- const pinnedItems = useMemo(() => {
- return pins ?? [];
- }, [pins]);
-
const chatOptionsNavProps = useChatSettingsNavigation();
const handleGoToUserProfile = useCallback(
@@ -350,8 +340,6 @@ export default function ChannelScreen(props: Props) {
return (
{
setInviteSheetGroup(group);
diff --git a/packages/app/features/top/ChannelSearchScreen.tsx b/packages/app/features/top/ChannelSearchScreen.tsx
index 32c2bf4d1b..3e8d89ce52 100644
--- a/packages/app/features/top/ChannelSearchScreen.tsx
+++ b/packages/app/features/top/ChannelSearchScreen.tsx
@@ -1,5 +1,5 @@
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
-import { useChannelSearch, useChannelWithRelations } from '@tloncorp/shared';
+import { useChannel, useChannelSearch } from '@tloncorp/shared';
import type * as db from '@tloncorp/shared/db';
import { Button, SearchBar, SearchResults, XStack, YStack } from '@tloncorp/ui';
import { useCallback, useState } from 'react';
@@ -13,7 +13,7 @@ type Props = NativeStackScreenProps;
export default function ChannelSearchScreen(props: Props) {
const channelId = props.route.params.channelId;
const groupId = props.route.params.groupId;
- const channelQuery = useChannelWithRelations({
+ const channelQuery = useChannel({
id: channelId,
});
diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx
index de48a4cca7..dabd2a5873 100644
--- a/packages/app/features/top/ChatListScreen.tsx
+++ b/packages/app/features/top/ChatListScreen.tsx
@@ -6,14 +6,11 @@ import {
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { createDevLogger } from '@tloncorp/shared';
import * as db from '@tloncorp/shared/db';
-import * as logic from '@tloncorp/shared/logic';
import * as store from '@tloncorp/shared/store';
import {
AddGroupSheet,
ChatList,
ChatOptionsProvider,
- ChatOptionsSheet,
- ChatOptionsSheetMethods,
GroupPreviewAction,
GroupPreviewSheet,
InviteUsersSheet,
@@ -29,9 +26,11 @@ import { TLON_EMPLOYEE_GROUP } from '../../constants';
import { useChatSettingsNavigation } from '../../hooks/useChatSettingsNavigation';
import { useCurrentUserId } from '../../hooks/useCurrentUser';
import { useGroupActions } from '../../hooks/useGroupActions';
-import { useFeatureFlag } from '../../lib/featureFlags';
import type { RootStackParamList } from '../../navigation/types';
-import { screenNameFromChannelId } from '../../navigation/utils';
+import {
+ screenNameFromChannelId,
+ useNavigateToGroup,
+} from '../../navigation/utils';
import { identifyTlonEmployee } from '../../utils/posthog';
import { isSplashDismissed, setSplashDismissed } from '../../utils/splash';
@@ -53,19 +52,6 @@ export function ChatListScreenView({
const [addGroupOpen, setAddGroupOpen] = useState(false);
const [screenTitle, setScreenTitle] = useState('Home');
const [inviteSheetGroup, setInviteSheetGroup] = useState();
- const chatOptionsSheetRef = useRef(null);
- const [longPressedChat, setLongPressedChat] = useState(null);
- const chatOptionsGroupId = useMemo(() => {
- return longPressedChat?.type === 'group'
- ? longPressedChat.group.id
- : undefined;
- }, [longPressedChat]);
-
- const chatOptionsChannelId = useMemo(() => {
- return longPressedChat?.type === 'channel'
- ? longPressedChat.channel.id
- : undefined;
- }, [longPressedChat]);
const [activeTab, setActiveTab] = useState<'all' | 'groups' | 'messages'>(
'all'
@@ -77,10 +63,6 @@ export function ChatListScreenView({
const [showSearchInput, setShowSearchInput] = useState(false);
const isFocused = useIsFocused();
- const { data: pins } = store.usePins({
- enabled: isFocused,
- });
- const pinned = useMemo(() => pins ?? [], [pins]);
const { data: chats } = store.useCurrentChats({
enabled: isFocused,
@@ -172,22 +154,16 @@ export function ChatListScreenView({
}
}, []);
- const [isChannelSwitcherEnabled] = useFeatureFlag('channelSwitcher');
+ const navigateToGroup = useNavigateToGroup();
const onPressChat = useCallback(
- (item: db.Chat) => {
- if (item.type === 'group' && item.isPending) {
- setSelectedGroupId(item.id);
- } else if (item.type === 'group' && !isChannelSwitcherEnabled) {
- navigation.navigate('GroupChannels', { groupId: item.group.id });
- } else if (item.type === 'group') {
- if (!item.group.channels?.length) {
- throw new Error('cant open group with no channels');
+ async (item: db.Chat) => {
+ if (item.type === 'group') {
+ if (item.isPending) {
+ setSelectedGroupId(item.id);
+ } else {
+ navigateToGroup(item.group.id);
}
- navigation.navigate('Channel', {
- channelId: item.group.channels[0].id,
- groupId: item.group.id,
- });
} else {
const screenName = screenNameFromChannelId(item.id);
navigation.navigate(screenName, {
@@ -195,21 +171,9 @@ export function ChatListScreenView({
});
}
},
- [isChannelSwitcherEnabled, navigation]
+ [navigateToGroup, navigation]
);
- const onLongPressChat = useCallback((item: db.Chat) => {
- if (item.isPending) {
- return;
- }
- setLongPressedChat(item);
- chatOptionsSheetRef.current?.open(
- item.id,
- item.type === 'channel' ? item.channel.type : 'group',
- item.unreadCount
- );
- }, []);
-
const handleGroupPreviewSheetOpenChange = useCallback((open: boolean) => {
if (!open) {
setSelectedGroupId(null);
@@ -298,9 +262,6 @@ export function ChatListScreenView({
useGroup={store.useGroupPreview}
>
{
setInviteSheetGroup(group);
@@ -329,7 +290,6 @@ export function ChatListScreenView({
pinned={resolvedChats.pinned}
unpinned={resolvedChats.unpinned}
pending={resolvedChats.pending}
- onLongPressItem={onLongPressChat}
onPressItem={onPressChat}
onSectionChange={handleSectionChange}
showSearchInput={showSearchInput}
@@ -343,7 +303,6 @@ export function ChatListScreenView({
open={splashVisible}
onOpenChange={handleWelcomeOpenChange}
/>
-
>();
-
const isFocused = useIsFocused();
- const { data: pins } = store.usePins({
- enabled: isFocused,
- });
const [inviteSheetGroup, setInviteSheetGroup] = useState(
null
);
@@ -43,10 +39,6 @@ export function GroupChannelsScreenContent({
group?.id ?? ''
);
- const pinnedItems = useMemo(() => {
- return pins ?? [];
- }, [pins]);
-
const handleChannelSelected = useCallback(
(channel: db.Channel) => {
navigation.navigate('Channel', {
@@ -79,9 +71,6 @@ export function GroupChannelsScreenContent({
return (
{
setInviteSheetGroup(group);
}}
diff --git a/packages/app/features/top/PostScreen.tsx b/packages/app/features/top/PostScreen.tsx
index 51e5659fdd..178151584a 100644
--- a/packages/app/features/top/PostScreen.tsx
+++ b/packages/app/features/top/PostScreen.tsx
@@ -3,10 +3,15 @@ import { useChannelContext } from '@tloncorp/shared';
import * as db from '@tloncorp/shared/db';
import * as store from '@tloncorp/shared/store';
import * as urbit from '@tloncorp/shared/urbit';
-import { PostScreenView, useCurrentUserId } from '@tloncorp/ui';
+import {
+ ChatOptionsProvider,
+ PostScreenView,
+ useCurrentUserId,
+} from '@tloncorp/ui';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useChannelNavigation } from '../../hooks/useChannelNavigation';
+import { useChatSettingsNavigation } from '../../hooks/useChatSettingsNavigation';
import { useGroupActions } from '../../hooks/useGroupActions';
import { useFeatureFlag } from '../../lib/featureFlags';
import type { RootStackParamList } from '../../navigation/types';
@@ -134,34 +139,38 @@ export default function PostScreen(props: Props) {
[props.navigation]
);
+ const chatOptionsNavProps = useChatSettingsNavigation();
+
return currentUserId && channel && post ? (
-
+
+
+
) : null;
}
diff --git a/packages/app/hooks/useChannelNavigation.ts b/packages/app/hooks/useChannelNavigation.ts
index 455b385c89..ca761f2db1 100644
--- a/packages/app/hooks/useChannelNavigation.ts
+++ b/packages/app/hooks/useChannelNavigation.ts
@@ -7,8 +7,7 @@ import { useCallback } from 'react';
import { RootStackParamList } from '../navigation/types';
export const useChannelNavigation = ({ channelId }: { channelId: string }) => {
- // Model context
- const channelQuery = store.useChannelWithRelations({
+ const channelQuery = store.useChannel({
id: channelId,
});
diff --git a/packages/app/hooks/useGroupActions.tsx b/packages/app/hooks/useGroupActions.tsx
index f00ae8945a..506c3c0cb4 100644
--- a/packages/app/hooks/useGroupActions.tsx
+++ b/packages/app/hooks/useGroupActions.tsx
@@ -5,19 +5,19 @@ import { useCallback } from 'react';
import { useGroupNavigation } from './useGroupNavigation';
export const useGroupActions = () => {
- const { goToHome, goToGroupChannels } = useGroupNavigation();
+ const { goToHome, goToGroup } = useGroupNavigation();
const performGroupAction = useCallback(
async (action: GroupPreviewAction, updatedGroup: db.Group) => {
if (action === 'goTo') {
- goToGroupChannels(updatedGroup.id);
+ goToGroup(updatedGroup.id);
}
if (action === 'joined') {
goToHome();
}
},
- [goToGroupChannels, goToHome]
+ [goToGroup, goToHome]
);
return {
diff --git a/packages/app/hooks/useGroupNavigation.ts b/packages/app/hooks/useGroupNavigation.ts
index e62d7998c4..df7597c72f 100644
--- a/packages/app/hooks/useGroupNavigation.ts
+++ b/packages/app/hooks/useGroupNavigation.ts
@@ -26,7 +26,7 @@ export const useGroupNavigation = () => {
[navigation]
);
- const goToGroupChannels = useCallback(
+ const goToGroup = useCallback(
async (groupId: string) => {
resetToGroup(groupId);
},
@@ -48,6 +48,6 @@ export const useGroupNavigation = () => {
goToChannel,
goToHome,
goToContactHostedGroups,
- goToGroupChannels,
+ goToGroup,
};
};
diff --git a/packages/app/navigation/types.ts b/packages/app/navigation/types.ts
index 2340cc3917..73bbc1aa16 100644
--- a/packages/app/navigation/types.ts
+++ b/packages/app/navigation/types.ts
@@ -1,4 +1,7 @@
-import type { NavigatorScreenParams } from '@react-navigation/native';
+import type {
+ NavigationProp,
+ NavigatorScreenParams,
+} from '@react-navigation/native';
export type RootStackParamList = {
Contacts: undefined;
@@ -66,6 +69,8 @@ export type RootStackParamList = {
};
};
+export type RootStackNavigationProp = NavigationProp;
+
export type RootDrawerParamList = {
Home: NavigatorScreenParams;
} & Pick;
diff --git a/packages/app/navigation/utils.ts b/packages/app/navigation/utils.ts
index 13348316aa..e6f0c493e0 100644
--- a/packages/app/navigation/utils.ts
+++ b/packages/app/navigation/utils.ts
@@ -3,10 +3,13 @@ import {
NavigationProp,
useNavigation,
} from '@react-navigation/native';
+import * as db from '@tloncorp/shared/db';
import * as logic from '@tloncorp/shared/logic';
import * as store from '@tloncorp/shared/store';
+import { useCallback } from 'react';
-import { RootStackParamList } from './types';
+import { useFeatureFlagStore } from '../lib/featureFlags';
+import { RootStackNavigationProp, RootStackParamList } from './types';
type ResetRouteConfig> = {
name: Extract;
@@ -83,14 +86,43 @@ export function useResetToDm() {
export function useResetToGroup() {
const reset = useTypedReset();
- return function resetToGroup(groupId: string) {
- reset([
- { name: 'ChatList' },
- { name: 'GroupChannels', params: { groupId } },
- ]);
+ return async function resetToGroup(groupId: string) {
+ reset([{ name: 'ChatList' }, await getMainGroupRoute(groupId)]);
};
}
+export function useNavigateToGroup() {
+ const navigation = useNavigation();
+ const navigationRef = logic.useMutableRef(navigation);
+ return useCallback(
+ async (groupId: string) => {
+ navigationRef.current.navigate(await getMainGroupRoute(groupId));
+ },
+ [navigationRef]
+ );
+}
+
+export async function getMainGroupRoute(groupId: string) {
+ const group = await db.getGroup({ id: groupId });
+ const channelSwitcherEnabled =
+ useFeatureFlagStore.getState().flags.channelSwitcher;
+ if (
+ group &&
+ group.channels &&
+ (group.channels.length === 1 || channelSwitcherEnabled)
+ ) {
+ return {
+ name: 'Channel',
+ params: { channelId: group.channels[0].id, groupId },
+ } as const;
+ } else {
+ return {
+ name: 'GroupChannels',
+ params: { groupId },
+ } as const;
+ }
+}
+
export function screenNameFromChannelId(channelId: string) {
return logic.isDmChannelId(channelId)
? 'DM'
diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts
index 2ca38c6472..77da3c9889 100644
--- a/packages/shared/src/db/keyValue.ts
+++ b/packages/shared/src/db/keyValue.ts
@@ -30,26 +30,6 @@ export const BASE_VOLUME_SETTING_QUERY_KEY = ['volume', 'base'];
export const SHOW_BENEFITS_SHEET_QUERY_KEY = ['showBenefitsSheet'];
export const THEME_STORAGE_KEY = '@user_theme';
-export type ChannelSortPreference = 'recency' | 'arranged';
-export async function storeChannelSortPreference(
- sortPreference: ChannelSortPreference
-) {
- try {
- await AsyncStorage.setItem('channelSortPreference', sortPreference);
- } catch (error) {
- logger.error('storeChannelSortPreference', error);
- }
-}
-
-export async function getChannelSortPreference() {
- try {
- const value = await AsyncStorage.getItem('channelSortPreference');
- return (value ?? 'recency') as ChannelSortPreference;
- } catch (error) {
- logger.error('getChannelSortPreference', error);
- }
-}
-
export async function getActivitySeenMarker() {
const marker = await AsyncStorage.getItem('activitySeenMarker');
return Number(marker) ?? 1;
@@ -312,3 +292,10 @@ export const themeSettings = createStorageItem({
key: THEME_STORAGE_KEY,
defaultValue: null,
});
+
+export type ChannelSortPreference = 'recency' | 'arranged';
+
+export const channelSortPreference = createStorageItem({
+ key: 'channelSortPreference',
+ defaultValue: 'recency',
+});
diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts
index cce64ed748..4a7f93516a 100644
--- a/packages/shared/src/db/queries.ts
+++ b/packages/shared/src/db/queries.ts
@@ -1429,16 +1429,9 @@ export const getAllSingleDms = createReadQuery(
[]
);
-export interface GetChannelWithRelations {
- id: string;
-}
-
export const getChannelWithRelations = createReadQuery(
'getChannelWithRelations',
- async (
- { id }: GetChannelWithRelations,
- ctx: QueryCtx
- ): Promise => {
+ async ({ id }: { id: string }, ctx: QueryCtx): Promise => {
const result = await ctx.db.query.channels.findFirst({
where: eq($channels.id, id),
with: {
@@ -2609,6 +2602,7 @@ export const getGroup = createReadQuery(
.findFirst({
where: (groups, { eq }) => eq(groups.id, id),
with: {
+ unread: true,
pin: true,
channels: {
where: (channels, { eq }) => eq(channels.currentUserIsMember, true),
diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts
index e357e5f33b..a42e8659d4 100644
--- a/packages/shared/src/store/dbHooks.ts
+++ b/packages/shared/src/store/dbHooks.ts
@@ -312,18 +312,15 @@ export const useGroups = (options: db.GetGroupsOptions) => {
});
};
-export const useGroup = (options: { id?: string }) => {
+export const useGroup = ({ id }: { id?: string }) => {
return useQuery({
- enabled: !!options.id,
- queryKey: [['group', options], useKeyFromQueryDeps(db.getGroup, options)],
+ enabled: !!id,
+ queryKey: [['group', { id }], useKeyFromQueryDeps(db.getGroup, { id })],
queryFn: () => {
- if (!options.id) {
- // This should never actually get thrown as the query is disabled if id
- // is missing
- throw new Error('missing id');
+ if (!id) {
+ throw new Error('missing group id');
}
- const enabledOptions = options as { id: string };
- return db.getGroup(enabledOptions);
+ return db.getGroup({ id });
},
});
};
@@ -444,26 +441,24 @@ export const useChannelSearchResults = (
});
};
-export const useChannelWithRelations = (
- options: db.GetChannelWithRelations
-) => {
- const tableDeps = useKeyFromQueryDeps(db.getChannelWithRelations);
+export const useChannel = (options: { id?: string }) => {
+ const { id } = options;
return useQuery({
- queryKey: ['channelWithRelations', tableDeps, options],
- queryFn: async () => {
- const channel = await db.getChannelWithRelations(options);
- return channel ?? null;
+ enabled: !!id,
+ queryKey: [
+ 'channelWithRelations',
+ useKeyFromQueryDeps(db.getChannelWithRelations),
+ options,
+ ],
+ queryFn: () => {
+ if (!id) {
+ throw new Error('missing channel id');
+ }
+ return db.getChannelWithRelations({ id });
},
});
};
-export const useChannel = (options: { id: string }) => {
- return useQuery({
- queryKey: [['channel', options]],
- queryFn: () => db.getChannelWithRelations(options),
- });
-};
-
export const usePostWithThreadUnreads = (options: { id: string }) => {
const tableDeps = useKeyFromQueryDeps(db.getPostWithRelations);
return useQuery({
diff --git a/packages/shared/src/store/useChannelContext.ts b/packages/shared/src/store/useChannelContext.ts
index 20444f21db..45d4424865 100644
--- a/packages/shared/src/store/useChannelContext.ts
+++ b/packages/shared/src/store/useChannelContext.ts
@@ -19,12 +19,10 @@ export const useChannelContext = ({
// need to populate this from feature flags :(
isChannelSwitcherEnabled: boolean;
}) => {
- // const storage = useStorageUnsafelyUnwrapped();
-
- // Model context
- const channelQuery = dbHooks.useChannelWithRelations({
+ const channelQuery = dbHooks.useChannel({
id: channelId,
});
+
const groupQuery = dbHooks.useGroup({
id: channelQuery.data?.groupId ?? '',
});
diff --git a/packages/ui/src/components/Channel/BaubleHeader.tsx b/packages/ui/src/components/Channel/BaubleHeader.tsx
index 317dde5ea6..bfd260208e 100644
--- a/packages/ui/src/components/Channel/BaubleHeader.tsx
+++ b/packages/ui/src/components/Channel/BaubleHeader.tsx
@@ -1,7 +1,7 @@
import { LinearGradient } from '@tamagui/linear-gradient';
import * as db from '@tloncorp/shared/db';
import { BlurView } from 'expo-blur';
-import { useCallback, useRef } from 'react';
+import { useCallback } from 'react';
import { OpaqueColorValue } from 'react-native';
import Animated, {
Easing,
@@ -21,7 +21,6 @@ import { Spinner, Text, View } from 'tamagui';
import { useChatOptions } from '../../contexts/chatOptions';
import { useScrollContext } from '../../contexts/scroll';
import { ContactAvatar } from '../Avatar';
-import { ChatOptionsSheet, ChatOptionsSheetMethods } from '../ChatOptionsSheet';
import { Icon } from '../Icon';
import { Image } from '../Image';
import Pressable from '../Pressable';
@@ -37,12 +36,10 @@ export function BaubleHeader({
showSpinner?: boolean;
group?: db.Group | null;
}) {
+ const chatOptions = useChatOptions();
const [scrollValue] = useScrollContext();
const insets = useSafeAreaInsets();
const frame = useSafeAreaFrame();
- const groupOptions = useChatOptions();
- const isGroupContext = !!group && !!groupOptions;
- const chatOptionsSheetRef = useRef(null);
const easedValue = useDerivedValue(
() => Easing.ease(scrollValue.value),
@@ -67,12 +64,12 @@ export function BaubleHeader({
}, [easedValue, insets.top]);
const handlePress = useCallback(() => {
- if (group && groupOptions) {
- chatOptionsSheetRef.current?.open(group.id, 'group');
+ if (group) {
+ chatOptions.open(group.id, 'group');
} else {
- chatOptionsSheetRef.current?.open(channel.id, channel.type);
+ chatOptions.open(channel.id, 'channel');
}
- }, [channel.id, channel.type, group, groupOptions]);
+ }, [channel.id, group, chatOptions]);
return (
)}
- {isGroupContext && groupOptions && (
-
- )}
);
}
diff --git a/packages/ui/src/components/Channel/ChannelHeader.tsx b/packages/ui/src/components/Channel/ChannelHeader.tsx
index 5c8cc1c846..1b36626f18 100644
--- a/packages/ui/src/components/Channel/ChannelHeader.tsx
+++ b/packages/ui/src/components/Channel/ChannelHeader.tsx
@@ -4,12 +4,11 @@ import {
useCallback,
useContext,
useEffect,
- useRef,
useState,
} from 'react';
-import useIsWindowNarrow from '../../hooks/useIsWindowNarrow';
-import { ChatOptionsSheet, ChatOptionsSheetMethods } from '../ChatOptionsSheet';
+import { useChatOptions } from '../../contexts';
+import Pressable from '../Pressable';
import { ScreenHeader } from '../ScreenHeader';
import { BaubleHeader } from './BaubleHeader';
@@ -80,6 +79,7 @@ export function ChannelHeader({
goBack,
goToSearch,
goToEdit,
+ goToChannels,
showSpinner,
showSearchButton = true,
showMenuButton = false,
@@ -92,17 +92,18 @@ export function ChannelHeader({
goBack?: () => void;
goToSearch?: () => void;
goToEdit?: () => void;
+ goToChannels?: () => void;
showSpinner?: boolean;
showSearchButton?: boolean;
showMenuButton?: boolean;
showEditButton?: boolean;
post?: db.Post;
}) {
- const chatOptionsSheetRef = useRef(null);
+ const chatOptions = useChatOptions();
const handlePressOverflowMenu = useCallback(() => {
- chatOptionsSheetRef.current?.open(channel.id, channel.type);
- }, [channel.id, channel.type]);
+ chatOptions.open(channel.id, 'channel');
+ }, [channel.id, chatOptions]);
const contextItems = useContext(ChannelHeaderItemsContext)?.items ?? [];
@@ -125,7 +126,11 @@ export function ChannelHeader({
return (
<>
+ {title}
+
+ }
titleWidth={titleWidth()}
showSessionStatus
isLoading={showSpinner}
@@ -150,7 +155,6 @@ export function ChannelHeader({
>
}
/>
-
>
);
}
diff --git a/packages/ui/src/components/Channel/index.tsx b/packages/ui/src/components/Channel/index.tsx
index ebc496fb47..aa4b3bf12c 100644
--- a/packages/ui/src/components/Channel/index.tsx
+++ b/packages/ui/src/components/Channel/index.tsx
@@ -337,6 +337,7 @@ export function Channel({
}
showSearchButton={isChatChannel}
goToSearch={goToSearch}
+ goToChannels={goToChannels}
showSpinner={isLoadingPosts}
showMenuButton={true}
/>
diff --git a/packages/ui/src/components/ChannelSwitcherSheet.tsx b/packages/ui/src/components/ChannelSwitcherSheet.tsx
index 5f0147a325..f61989ed03 100644
--- a/packages/ui/src/components/ChannelSwitcherSheet.tsx
+++ b/packages/ui/src/components/ChannelSwitcherSheet.tsx
@@ -4,8 +4,10 @@ import { TouchableOpacity } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { SizableText, Text, XStack } from 'tamagui';
+import { useChatOptions } from '../contexts';
import ChannelNavSections from './ChannelNavSections';
import { Icon } from './Icon';
+import Pressable from './Pressable';
import { Sheet } from './Sheet';
interface Props {
@@ -24,29 +26,23 @@ export function ChannelSwitcherSheet({
onSelect,
}: Props) {
const [hasOpened, setHasOpened] = useState(open);
- const [sortBy, setSortBy] = useState('recency');
+ const chatOptions = useChatOptions();
+ const sortBy = db.channelSortPreference.useValue();
const { bottom } = useSafeAreaInsets();
useEffect(() => {
setHasOpened(open);
}, [open]);
- useEffect(() => {
- const getSortByPreference = async () => {
- const preference = await db.getChannelSortPreference();
- setSortBy(preference ?? 'recency');
- };
-
- getSortByPreference();
- }, [setSortBy]);
+ const handleSortByToggled = useCallback(() => {
+ const newSortBy = sortBy === 'recency' ? 'arranged' : 'recency';
+ db.channelSortPreference.setValue(newSortBy);
+ }, [sortBy]);
- const handleSortByChanged = useCallback(
- (newSortBy: 'recency' | 'arranged') => {
- setSortBy(newSortBy);
- db.storeChannelSortPreference(newSortBy);
- },
- []
- );
+ const handlePressSettings = useCallback(() => {
+ onOpenChange(false);
+ chatOptions.open(group.id, 'group');
+ }, [chatOptions, group.id, onOpenChange]);
return (
{group?.title}
- {
- const newSortBy = sortBy === 'recency' ? 'arranged' : 'recency';
- handleSortByChanged(newSortBy);
- }}
- >
+
+
+
+
{hasOpened && (
void;
- onLongPressItem?: (chat: db.Chat) => void;
onSectionChange?: (title: string) => void;
activeTab: TabName;
setActiveTab: (tab: TabName) => void;
@@ -72,6 +70,14 @@ export const ChatList = React.memo(function ChatListComponent({
[displayData]
);
+ const chatOptions = useChatOptions();
+ const handleLongPress = useCallback(
+ (item: db.Chat) => {
+ chatOptions.open(item.id, item.type);
+ },
+ [chatOptions]
+ );
+
// removed the use of useStyle here because it was causing FlashList to
// peg the CPU and freeze the app on web
// see: https://github.com/Shopify/flash-list/pull/852
@@ -93,7 +99,7 @@ export const ChatList = React.memo(function ChatListComponent({
);
} else {
@@ -101,12 +107,12 @@ export const ChatList = React.memo(function ChatListComponent({
);
}
},
- [onPressItem, onLongPressItem]
+ [onPressItem, handleLongPress]
);
const handlePressTryAll = useCallback(() => {
diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx
index caf158f489..4d017b52b5 100644
--- a/packages/ui/src/components/ChatOptionsSheet.tsx
+++ b/packages/ui/src/components/ChatOptionsSheet.tsx
@@ -8,7 +8,6 @@ import React, {
ReactElement,
useCallback,
useEffect,
- useImperativeHandle,
useMemo,
useState,
} from 'react';
@@ -23,42 +22,20 @@ import { Action, ActionGroup, ActionSheet } from './ActionSheet';
import { IconButton } from './IconButton';
import { ListItem } from './ListItem';
-export type ChatType = 'group' | db.ChannelType;
-
-export type ChatOptionsSheetMethods = {
- open: (chatId: string, chatType: ChatType, unreadCount?: number) => void;
-};
-
-export type ChatOptionsSheetRef = React.Ref;
-
type ChatOptionsSheetProps = {
- // We pass in setSortBy from GroupChannelsScreenView to live-update the sort
- // preference in the channel list.
- setSortBy?: (sortBy: db.ChannelSortPreference) => void;
-};
-
-const ChatOptionsSheetComponent = React.forwardRef<
- ChatOptionsSheetMethods,
- ChatOptionsSheetProps
->(function ChatOptionsSheetImpl(props, ref) {
- const [open, setOpen] = useState(false);
- const [chat, setChat] = useState<{
- type: ChatType;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ chat?: {
+ type: 'group' | 'channel';
id: string;
- unreadCount?: number;
- } | null>(null);
-
- useImperativeHandle(
- ref,
- () => ({
- open: (chatId, chatType, unreadCount) => {
- setOpen(true);
- setChat({ id: chatId, type: chatType, unreadCount });
- },
- }),
- []
- );
+ } | null;
+};
+export const ChatOptionsSheet = React.memo(function ChatOptionsSheet({
+ open,
+ onOpenChange,
+ chat,
+}: ChatOptionsSheetProps) {
if (!chat || !open) {
return null;
}
@@ -68,9 +45,7 @@ const ChatOptionsSheetComponent = React.forwardRef<
);
}
@@ -79,25 +54,19 @@ const ChatOptionsSheetComponent = React.forwardRef<
);
});
-export const ChatOptionsSheet = React.memo(ChatOptionsSheetComponent);
-
export function GroupOptionsSheetLoader({
groupId,
open,
onOpenChange,
- setSortBy,
- unreadCount,
}: {
groupId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
- setSortBy?: (sortBy: db.ChannelSortPreference) => void;
- unreadCount?: number;
}) {
const groupQuery = store.useGroup({ id: groupId });
const [pane, setPane] = useState<
@@ -119,9 +88,8 @@ export function GroupOptionsSheetLoader({
group={groupQuery.data}
pane={pane}
setPane={setPane}
- setSortBy={setSortBy}
onOpenChange={onOpenChange}
- unreadCount={unreadCount}
+ unreadCount={groupQuery.data.unread?.count}
/>
) : null;
@@ -131,16 +99,14 @@ export function GroupOptions({
group,
pane,
setPane,
- setSortBy,
onOpenChange,
unreadCount,
}: {
group: db.Group;
pane: 'initial' | 'edit' | 'notifications' | 'sort';
setPane: (pane: 'initial' | 'edit' | 'notifications' | 'sort') => void;
- setSortBy?: (sortBy: db.ChannelSortPreference) => void;
onOpenChange: (open: boolean) => void;
- unreadCount?: number;
+ unreadCount?: number | null;
}) {
const currentUser = useCurrentUserId();
const { data: currentVolumeLevel } = store.useGroupVolumeLevel(group.id);
@@ -419,7 +385,6 @@ export function GroupOptions({
title: 'Sort by recency',
action: () => {
onSelectSort?.('recency');
- setSortBy?.('recency');
onOpenChange(false);
},
},
@@ -427,14 +392,13 @@ export function GroupOptions({
title: 'Sort by arrangement',
action: () => {
onSelectSort?.('arranged');
- setSortBy?.('arranged');
onOpenChange(false);
},
},
],
},
];
- }, [onSelectSort, setSortBy, onOpenChange]);
+ }, [onSelectSort, onOpenChange]);
const memberCount = group?.members?.length
? group.members.length.toLocaleString()
@@ -504,7 +468,7 @@ export function ChannelOptionsSheetLoader({
onOpenChange: (open: boolean) => void;
}) {
const [pane, setPane] = useState<'initial' | 'notifications'>('initial');
- const channelQuery = store.useChannelWithRelations({
+ const channelQuery = store.useChannel({
id: channelId,
});
@@ -551,7 +515,6 @@ export function ChannelOptions({
onPressChannelMeta,
onPressManageChannels,
onPressInvite,
- onPressLeave,
} = useChatOptions() ?? {};
const currentUserIsHost = useMemo(
diff --git a/packages/ui/src/components/GroupChannelsScreenView.tsx b/packages/ui/src/components/GroupChannelsScreenView.tsx
index 596b91fbdc..7b61f2102a 100644
--- a/packages/ui/src/components/GroupChannelsScreenView.tsx
+++ b/packages/ui/src/components/GroupChannelsScreenView.tsx
@@ -1,20 +1,12 @@
import * as db from '@tloncorp/shared/db';
-import { useCallback, useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import {
- Button,
- ScrollView,
- View,
- YStack,
- getVariableValue,
- useTheme,
-} from 'tamagui';
+import { ScrollView, View, YStack, getVariableValue, useTheme } from 'tamagui';
-import { useCurrentUserId } from '../contexts';
+import { useChatOptions, useCurrentUserId } from '../contexts';
import { useIsAdmin } from '../utils/channelUtils';
import { Badge } from './Badge';
import ChannelNavSections from './ChannelNavSections';
-import { ChatOptionsSheet, ChatOptionsSheetMethods } from './ChatOptionsSheet';
import { ChannelListItem } from './ListItem/ChannelListItem';
import { LoadingSpinner } from './LoadingSpinner';
import { CreateChannelSheet } from './ManageChannels/CreateChannelSheet';
@@ -38,39 +30,30 @@ export function GroupChannelsScreenView({
onBackPressed,
enableCustomChannels = false,
}: GroupChannelsScreenViewProps) {
- const chatOptionsSheetRef = useRef(null);
const [showCreateChannel, setShowCreateChannel] = useState(false);
- const [sortBy, setSortBy] = useState('recency');
+ const sortBy = db.channelSortPreference.useValue();
const insets = useSafeAreaInsets();
const userId = useCurrentUserId();
const isGroupAdmin = useIsAdmin(group?.id ?? '', userId);
- useEffect(() => {
- const getSortByPreference = async () => {
- const preference = await db.getChannelSortPreference();
- setSortBy(preference ?? 'recency');
- };
-
- getSortByPreference();
- }, [setSortBy]);
-
+ const chatOptions = useChatOptions();
const handlePressOverflowButton = useCallback(() => {
if (group) {
- chatOptionsSheetRef.current?.open(group.id, 'group');
+ chatOptions.open(group.id, 'group');
}
- }, [group]);
-
- const title = group ? group?.title ?? 'Untitled' : '';
+ }, [group, chatOptions]);
const handleOpenChannelOptions = useCallback(
(channel: db.Channel) => {
if (group) {
- chatOptionsSheetRef.current?.open(channel.id, channel.type);
+ chatOptions.open(channel.id, 'channel');
}
},
- [group]
+ [group, chatOptions]
);
+ const title = group ? group?.title ?? 'Untitled' : '';
+
const titleWidth = useCallback(() => {
if (isGroupAdmin) {
return 55;
@@ -165,7 +148,6 @@ export function GroupChannelsScreenView({
enableCustomChannels={enableCustomChannels}
/>
)}
-
);
}
diff --git a/packages/ui/src/components/Pressable.tsx b/packages/ui/src/components/Pressable.tsx
index c2386575fd..54ccf64cf8 100644
--- a/packages/ui/src/components/Pressable.tsx
+++ b/packages/ui/src/components/Pressable.tsx
@@ -23,7 +23,7 @@ const StackComponent = ({
return (
void;
onPressGroupMembers: (groupId: string) => void;
@@ -24,19 +26,22 @@ export type ChatOptionsContextValue = {
onTogglePinned: () => void;
onPressLeave: () => Promise;
onSelectSort?: (sortBy: 'recency' | 'arranged') => void;
+ open: (chatId: string, chatType: 'group' | 'channel') => void;
} | null;
const ChatOptionsContext = createContext(null);
export const useChatOptions = () => {
- return useContext(ChatOptionsContext);
+ const value = useContext(ChatOptionsContext);
+ if (!value) {
+ throw new Error('useChatOptions used outside of ChatOptions context');
+ }
+ return value;
};
type ChatOptionsProviderProps = {
children: ReactNode;
- groupId?: string;
- channelId?: string;
- pinned: db.Pin[];
+ useChannel?: typeof store.useChannel;
useGroup?: typeof store.useGroup;
onPressGroupMeta: (groupId: string) => void;
onPressGroupMembers: (groupId: string) => void;
@@ -47,13 +52,12 @@ type ChatOptionsProviderProps = {
onPressChannelMeta: (channelId: string) => void;
onPressRoles: (groupId: string) => void;
onSelectSort?: (sortBy: 'recency' | 'arranged') => void;
- navigateOnLeave?: () => void;
+ onLeaveGroup?: () => void;
};
export const ChatOptionsProvider = ({
children,
- groupId,
- pinned = [],
+ useChannel = store.useChannel,
useGroup = store.useGroup,
onPressGroupMeta,
onPressGroupMembers,
@@ -63,17 +67,30 @@ export const ChatOptionsProvider = ({
onPressChannelMembers,
onPressChannelMeta,
onPressRoles,
- navigateOnLeave,
+ onLeaveGroup: navigateOnLeave,
}: ChatOptionsProviderProps) => {
- const groupQuery = useGroup({ id: groupId ?? '' });
- const group = groupId ? groupQuery.data ?? null : null;
+ const [sheetOpen, setSheetOpen] = useState(false);
+ const [chat, setChat] = useState<{
+ id: string;
+ type: 'group' | 'channel';
+ } | null>(null);
+
+ const isChannel = chat?.type === 'channel';
+ const isGroup = chat?.type === 'group';
+
+ const { data: channel } = useChannel({
+ id: isChannel ? chat.id : undefined,
+ });
+ const { data: group } = useGroup({
+ id: isGroup ? chat.id : channel?.groupId ?? undefined,
+ });
const groupChannels = useMemo(() => {
return group?.channels ?? [];
}, [group?.channels]);
const onTogglePinned = useCallback(() => {
- if (group && group.channels[0]) {
+ if (group && group.channels?.[0]) {
group.pin ? store.unpinItem(group.pin) : store.pinGroup(group);
}
}, [group]);
@@ -86,30 +103,62 @@ export const ChatOptionsProvider = ({
}, [group, navigateOnLeave]);
const onSelectSort = useCallback((sortBy: 'recency' | 'arranged') => {
- db.storeChannelSortPreference(sortBy);
+ db.channelSortPreference.setValue(sortBy);
}, []);
- const contextValue: ChatOptionsContextValue = {
- pinned,
- useGroup,
- group,
- groupChannels,
- onPressGroupMeta,
- onPressGroupMembers,
- onPressManageChannels,
- onPressInvite,
- onPressGroupPrivacy,
- onPressRoles,
- onPressLeave,
- onTogglePinned,
- onPressChannelMembers,
- onPressChannelMeta,
- onSelectSort,
- };
+ const open = useCallback((chatId: string, chatType: 'group' | 'channel') => {
+ setChat({
+ id: chatId,
+ type: chatType,
+ });
+ setSheetOpen(true);
+ }, []);
+
+ const contextValue: ChatOptionsContextValue = useMemo(
+ () => ({
+ useGroup,
+ group,
+ groupChannels,
+ onPressGroupMeta,
+ onPressGroupMembers,
+ onPressManageChannels,
+ onPressInvite,
+ onPressGroupPrivacy,
+ onPressRoles,
+ onPressLeave,
+ onTogglePinned,
+ onPressChannelMembers,
+ onPressChannelMeta,
+ onSelectSort,
+ open,
+ }),
+ [
+ group,
+ groupChannels,
+ onPressChannelMembers,
+ onPressChannelMeta,
+ onPressGroupMembers,
+ onPressGroupMeta,
+ onPressGroupPrivacy,
+ onPressInvite,
+ onPressLeave,
+ onPressManageChannels,
+ onPressRoles,
+ onSelectSort,
+ onTogglePinned,
+ open,
+ useGroup,
+ ]
+ );
return (
{children}
+
);
};
diff --git a/packages/ui/src/utils/channelUtils.tsx b/packages/ui/src/utils/channelUtils.tsx
index dc72b2bbab..811758c391 100644
--- a/packages/ui/src/utils/channelUtils.tsx
+++ b/packages/ui/src/utils/channelUtils.tsx
@@ -4,7 +4,7 @@ import { useMemberRoles } from '@tloncorp/shared/store';
import { useMemo } from 'react';
import type { IconType } from '../components/Icon';
-import { useCalm } from '../contexts';
+import { useCalm } from '../contexts/appDataContext';
export function getChannelMemberName(
member: db.ChatMember,