diff --git a/.github/workflows/deploy-canary.yml b/.github/workflows/deploy-canary.yml index 5ec0a1a027..26fd419ed8 100644 --- a/.github/workflows/deploy-canary.yml +++ b/.github/workflows/deploy-canary.yml @@ -69,6 +69,7 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ env.tag }} + ssh-key: ${{ secrets.DEPLOY_KEY }} - uses: actions/download-artifact@v3 with: name: "ui-dist" diff --git a/.github/workflows/sync-dev.yml b/.github/workflows/sync-dev.yml index a8fcc50cf4..b5eed61dcd 100644 --- a/.github/workflows/sync-dev.yml +++ b/.github/workflows/sync-dev.yml @@ -10,11 +10,13 @@ jobs: name: "Syncs the latest staging deploy from staging to develop" steps: - uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} - name: Set Git config run: | git config --local user.email "actions@github.com" git config --local user.name "Github Actions" - - name: Merge Dev to Master + - name: Merge Staging to Develop run: | git fetch --unshallow git checkout staging diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 1b3e3198f9..0a82613b58 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -10,6 +10,8 @@ jobs: name: "Syncs the latest livenet deploy from develop to master" steps: - uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} - name: Set Git config run: | git config --local user.email "actions@github.com" @@ -22,4 +24,4 @@ jobs: git checkout master git pull git merge --no-ff staging - git push \ No newline at end of file + git push diff --git a/apps/tlon-mobile/android/app/build.gradle b/apps/tlon-mobile/android/app/build.gradle index af13d348a5..656bcf5376 100644 --- a/apps/tlon-mobile/android/app/build.gradle +++ b/apps/tlon-mobile/android/app/build.gradle @@ -88,7 +88,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion versionCode 108 - versionName "4.2.4" + versionName "5.0.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 5d30e4e75d..b97ea9f88a 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1427,7 +1427,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1465,7 +1465,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1689,7 +1689,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1732,7 +1732,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx index 8bf201dffb..8c18a82bd4 100644 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx @@ -2,6 +2,7 @@ import { useShip } from '@tloncorp/app/contexts/ship'; import { useAppStatusChange } from '@tloncorp/app/hooks/useAppStatusChange'; import { useConfigureUrbitClient } from '@tloncorp/app/hooks/useConfigureUrbitClient'; import { useCurrentUserId } from '@tloncorp/app/hooks/useCurrentUser'; +import { useFindSuggestedContacts } from '@tloncorp/app/hooks/useFindSuggestedContacts'; import { useNavigationLogging } from '@tloncorp/app/hooks/useNavigationLogger'; import { useNetworkLogger } from '@tloncorp/app/hooks/useNetworkLogger'; import { useTelemetry } from '@tloncorp/app/hooks/useTelemetry'; @@ -29,6 +30,7 @@ function AuthenticatedApp() { useNavigationLogging(); useNetworkLogger(); useCheckAppUpdated(); + useFindSuggestedContacts(); useEffect(() => { configureClient(); diff --git a/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx index 7ad2c25cae..527bb3b992 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx @@ -1,6 +1,5 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useLureMetadata } from '@tloncorp/app/contexts/branch'; -import { useSignupContext } from '.././../lib/signupContext'; import { NodeBootPhase } from '@tloncorp/app/lib/bootHelpers'; import { ArvosDiscussing, @@ -14,6 +13,7 @@ import { } from '@tloncorp/ui'; import { useEffect, useMemo } from 'react'; +import { useSignupContext } from '../../lib/signupContext'; import type { OnboardingStackParamList } from '../../types'; type Props = NativeStackScreenProps; @@ -36,7 +36,7 @@ export const ReserveShipScreen = ({ navigation }: Props) => { signupContext.setOnboardingValues({ didCompleteOnboarding: true }); } signupContext.kickOffBootSequence(); - }, []); + }, [signupContext]); return ( diff --git a/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx index 832c479b1d..c5f6ca2a6d 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx @@ -16,8 +16,8 @@ import { import { useEffect } from 'react'; import { Controller, useForm } from 'react-hook-form'; +import { useSignupContext } from '../../lib/signupContext'; import type { OnboardingStackParamList } from '../../types'; -import { useSignupContext } from '.././../lib/signupContext'; type Props = NativeStackScreenProps; diff --git a/apps/tlon-web-new/src/app.tsx b/apps/tlon-web-new/src/app.tsx index 15f4a51906..c2e2f702ea 100644 --- a/apps/tlon-web-new/src/app.tsx +++ b/apps/tlon-web-new/src/app.tsx @@ -6,6 +6,7 @@ import { } from '@react-navigation/native'; import { useConfigureUrbitClient } from '@tloncorp/app/hooks/useConfigureUrbitClient'; import { useCurrentUserId } from '@tloncorp/app/hooks/useCurrentUser'; +import { useFindSuggestedContacts } from '@tloncorp/app/hooks/useFindSuggestedContacts'; import { useIsDarkMode } from '@tloncorp/app/hooks/useIsDarkMode'; import { checkDb, useMigrations } from '@tloncorp/app/lib/webDb'; import { BasePathNavigator } from '@tloncorp/app/navigation/BasePathNavigator'; @@ -135,6 +136,7 @@ const App = React.memo(function AppComponent() { const [dbIsLoaded, setDbIsLoaded] = useState(false); const [startedSync, setStartedSync] = useState(false); const configureClient = useConfigureUrbitClient(); + useFindSuggestedContacts(); useEffect(() => { handleError(() => { diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 111ab94919..7c5efe7802 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.irfsv.sq9vg.l6b96.rj7vs.74118.glob' 0v4.irfsv.sq9vg.l6b96.rj7vs.74118] + glob-http+['https://bootstrap.urbit.org/glob-0v2.imu38.n1u9d.o2plp.8juvp.5t2ao.glob' 0v2.imu38.n1u9d.o2plp.8juvp.5t2ao] base+'groups' version+[6 5 0] website+'https://tlon.io' diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx index 148237ba04..f954a539d3 100644 --- a/packages/app/features/top/ChannelScreen.tsx +++ b/packages/app/features/top/ChannelScreen.tsx @@ -71,6 +71,14 @@ export default function ChannelScreen(props: Props) { store.syncChannelThreadUnreads(channel.id, { priority: store.SyncPriority.High, }); + if (group) { + // Update the last visited channel in the group so we can return to it + // when we come back to the group + db.updateGroup({ + id: group.id, + lastVisitedChannelId: channel.id, + }); + } } // Mark the channel as visited when we unfocus/leave this screen () => { @@ -78,7 +86,7 @@ export default function ChannelScreen(props: Props) { store.markChannelVisited(channel); } }; - }, [channel]) + }, [channel, group]) ); const [channelNavOpen, setChannelNavOpen] = React.useState(false); diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index dabd2a5873..c85e019909 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -15,6 +15,7 @@ import { GroupPreviewSheet, InviteUsersSheet, NavBarView, + NavigationProvider, RequestsProvider, ScreenHeader, View, @@ -45,8 +46,10 @@ export default function ChatListScreen(props: Props) { export function ChatListScreenView({ previewGroupId, + focusedChannelId, }: { previewGroupId?: string; + focusedChannelId?: string; }) { const navigation = useNavigation>(); const [addGroupOpen, setAddGroupOpen] = useState(false); @@ -267,55 +270,57 @@ export function ChatListScreenView({ setInviteSheetGroup(group); }} > - - - - setAddGroupOpen(true)} - /> - - } - /> - {chats && chats.unpinned.length ? ( - + + + + setAddGroupOpen(true)} + /> + + } /> - ) : null} - - - - setInviteSheetGroup(null)} - group={inviteSheetGroup ?? undefined} - /> - + {chats && chats.unpinned.length ? ( + + ) : null} + + + + setInviteSheetGroup(null)} + group={inviteSheetGroup ?? undefined} + /> + + { navigation.navigate('Contacts'); diff --git a/packages/app/features/top/GroupChannelsScreen.tsx b/packages/app/features/top/GroupChannelsScreen.tsx index 0dfaef0236..f6e2a1ab91 100644 --- a/packages/app/features/top/GroupChannelsScreen.tsx +++ b/packages/app/features/top/GroupChannelsScreen.tsx @@ -10,6 +10,7 @@ import { ChatOptionsProvider, GroupChannelsScreenView, InviteUsersSheet, + NavigationProvider, } from '@tloncorp/ui'; import { useCallback, useState } from 'react'; @@ -26,8 +27,10 @@ export function GroupChannelsScreen({ route }: Props) { export function GroupChannelsScreenContent({ groupId: id, + focusedChannelId, }: { groupId: string; + focusedChannelId?: string; }) { const navigation = useNavigation>(); const isFocused = useIsFocused(); @@ -76,14 +79,16 @@ export function GroupChannelsScreenContent({ }} {...useChatSettingsNavigation()} > - + + + { diff --git a/packages/app/hooks/useFindSuggestedContacts.ts b/packages/app/hooks/useFindSuggestedContacts.ts new file mode 100644 index 0000000000..5bc2a54a1e --- /dev/null +++ b/packages/app/hooks/useFindSuggestedContacts.ts @@ -0,0 +1,10 @@ +import * as store from '@tloncorp/shared/store'; +import { useEffect } from 'react'; + +export function useFindSuggestedContacts() { + const { data: joinedGroupCount } = store.useJoinedGroupsCount(); + + useEffect(() => { + store.findContactSuggestions(); + }, [joinedGroupCount]); +} diff --git a/packages/app/navigation/desktop/HomeNavigator.tsx b/packages/app/navigation/desktop/HomeNavigator.tsx index 1ba05fff08..5884d5dba3 100644 --- a/packages/app/navigation/desktop/HomeNavigator.tsx +++ b/packages/app/navigation/desktop/HomeNavigator.tsx @@ -56,7 +56,19 @@ function DrawerContent(props: DrawerContentComponentProps) { 'groupId' in focusedRoute.params && focusedRoute.params.groupId ) { + if ('channelId' in focusedRoute.params) { + return ( + + ); + } return ; + } else if (focusedRoute.params && 'channelId' in focusedRoute.params) { + return ( + + ); } else { return ; } diff --git a/packages/app/navigation/desktop/TopLevelDrawer.tsx b/packages/app/navigation/desktop/TopLevelDrawer.tsx index 2442c78246..04c4956945 100644 --- a/packages/app/navigation/desktop/TopLevelDrawer.tsx +++ b/packages/app/navigation/desktop/TopLevelDrawer.tsx @@ -34,19 +34,25 @@ const DrawerContent = (props: DrawerContentComponentProps) => { // hasUnreads={(unreadCount?.channels ?? 0) > 0} // intentionally leave undotted for now hasUnreads={false} - onPress={() => props.navigation.navigate('Home')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Home' }] }) + } /> props.navigation.navigate('Activity')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Activity' }] }) + } /> props.navigation.navigate('Contacts')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Contacts' }] }) + } /> {webAppNeedsUpdate && ( (); const navigationRef = logic.useMutableRef(navigation); return useCallback( async (groupId: string) => { - navigationRef.current.navigate(await getMainGroupRoute(groupId)); + navigationRef.current.navigate( + await getMainGroupRoute(groupId, isWindowNarrow) + ); }, - [navigationRef] + [navigationRef, isWindowNarrow] ); } -export async function getMainGroupRoute(groupId: string) { +export async function getMainGroupRoute( + groupId: string, + isWindowNarrow?: boolean +) { const group = await db.getGroup({ id: groupId }); const channelSwitcherEnabled = useFeatureFlagStore.getState().flags.channelSwitcher; if ( group && group.channels && - (group.channels.length === 1 || channelSwitcherEnabled) + (group.channels.length === 1 || channelSwitcherEnabled || !isWindowNarrow) ) { + if (!isWindowNarrow && group.lastVisitedChannelId) { + return { + name: 'Channel', + params: { channelId: group.lastVisitedChannelId, groupId }, + } as const; + } + return { name: 'Channel', params: { channelId: group.channels[0].id, groupId }, diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 3678ebf8d2..6e613f1d0e 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -200,6 +200,7 @@ const createStorageItem = (config: StorageItem) => { deserialize = JSON.parse, } = config; const storage = getStorageMethods(config.isSecure ?? false); + let updateLock = Promise.resolve(); const getValue = async (): Promise => { const value = await storage.getItem(key); @@ -207,24 +208,30 @@ const createStorageItem = (config: StorageItem) => { }; const resetValue = async (): Promise => { - await storage.setItem(key, serialize(defaultValue)); - queryClient.invalidateQueries({ queryKey: [key] }); - logger.log(`reset value ${key}`); + updateLock = updateLock.then(async () => { + await storage.setItem(key, serialize(defaultValue)); + queryClient.invalidateQueries({ queryKey: [key] }); + logger.log(`reset value ${key}`); + }); + await updateLock; return defaultValue; }; const setValue = async (valueInput: T | ((curr: T) => T)): Promise => { - let newValue: T; - if (valueInput instanceof Function) { - const currValue = await getValue(); - newValue = valueInput(currValue); - } else { - newValue = valueInput; - } - - await storage.setItem(key, serialize(newValue)); - queryClient.invalidateQueries({ queryKey: [key] }); - logger.log(`set value ${key}`, newValue); + updateLock = updateLock.then(async () => { + let newValue: T; + if (valueInput instanceof Function) { + const currValue = await getValue(); + newValue = valueInput(currValue); + } else { + newValue = valueInput; + } + + await storage.setItem(key, serialize(newValue)); + queryClient.invalidateQueries({ queryKey: [key] }); + logger.log(`set value ${key}`, newValue); + }); + await updateLock; }; function useValue() { @@ -283,6 +290,11 @@ export const groupsUsedForSuggestions = createStorageItem({ defaultValue: [], }); +export const lastAddedSuggestionsAt = createStorageItem({ + key: 'lastAddedSuggestionsAt', + defaultValue: 0, +}); + export const postDraft = (opts: { key: string; type: 'caption' | 'text' | undefined; // matches GalleryDraftType diff --git a/packages/shared/src/db/migrations/0000_lowly_tinkerer.sql b/packages/shared/src/db/migrations/0000_loving_namora.sql similarity index 99% rename from packages/shared/src/db/migrations/0000_lowly_tinkerer.sql rename to packages/shared/src/db/migrations/0000_loving_namora.sql index 3391562df1..6953459b7d 100644 --- a/packages/shared/src/db/migrations/0000_lowly_tinkerer.sql +++ b/packages/shared/src/db/migrations/0000_loving_namora.sql @@ -226,7 +226,8 @@ CREATE TABLE `groups` ( `is_new` integer, `join_status` text, `last_post_id` text, - `last_post_at` integer + `last_post_at` integer, + `last_visited_channel_id` text ); --> statement-breakpoint CREATE TABLE `pins` ( diff --git a/packages/shared/src/db/migrations/meta/0000_snapshot.json b/packages/shared/src/db/migrations/meta/0000_snapshot.json index 30cf78b3ab..f63b6445e7 100644 --- a/packages/shared/src/db/migrations/meta/0000_snapshot.json +++ b/packages/shared/src/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "2ec6d840-6e53-4d97-bf4c-b4d00a248728", + "id": "74260723-19aa-401e-92d0-c790bcc15a53", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "activity_event_contact_group_pins": { @@ -1520,6 +1520,13 @@ "primaryKey": false, "notNull": false, "autoincrement": false + }, + "last_visited_channel_id": { + "name": "last_visited_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false } }, "indexes": {}, diff --git a/packages/shared/src/db/migrations/meta/_journal.json b/packages/shared/src/db/migrations/meta/_journal.json index 252394b402..ee7b772857 100644 --- a/packages/shared/src/db/migrations/meta/_journal.json +++ b/packages/shared/src/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1732574199336, - "tag": "0000_lowly_tinkerer", + "when": 1733348853492, + "tag": "0000_loving_namora", "breakpoints": true } ] diff --git a/packages/shared/src/db/migrations/migrations.js b/packages/shared/src/db/migrations/migrations.js index 34781b5268..94bde2c6a2 100644 --- a/packages/shared/src/db/migrations/migrations.js +++ b/packages/shared/src/db/migrations/migrations.js @@ -1,7 +1,7 @@ // This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo import journal from './meta/_journal.json'; -import m0000 from './0000_lowly_tinkerer.sql'; +import m0000 from './0000_loving_namora.sql'; export default { journal, diff --git a/packages/shared/src/db/queries.test.ts b/packages/shared/src/db/queries.test.ts index a58e31ea71..7ffaf29abc 100644 --- a/packages/shared/src/db/queries.test.ts +++ b/packages/shared/src/db/queries.test.ts @@ -56,6 +56,12 @@ test('uses init data to get chat list', async () => { '~hansel-ribbur', '~pondus-watbel', ]); + + expect(result.pending.map((r) => r.id)).toEqual([ + '~fabled-faster/new-york', + '~barmyl-sigted/network-being', + '~salfer-biswed/gamers', + ]); }); const refDate = Date.now(); diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index 380d05ff79..37c443cf40 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -153,6 +153,19 @@ export const getGroupPreviews = createReadQuery( ['groups'] ); +export const getJoinedGroupsCount = createReadQuery( + 'getJoinedGroupCount', + async (ctx: QueryCtx) => { + const result = await ctx.db + .select({ count: count() }) + .from($groups) + .where(eq($groups.currentUserIsMember, true)); + + return result[0]?.count ?? 0; + }, + ['groups'] +); + // TODO: inefficient, should optimize export const getGroupsWithMemberThreshold = createReadQuery( 'getGroupsWithMemberThreshold', @@ -311,6 +324,8 @@ export const getChats = createReadQuery( where: or( eq($groups.currentUserIsMember, true), eq($groups.isNew, true), + eq($groups.haveInvite, true), + eq($groups.haveRequestedInvite, true), isNotNull($groups.joinStatus) ), with: { diff --git a/packages/shared/src/db/schema.ts b/packages/shared/src/db/schema.ts index fce95dd775..f7c8550134 100644 --- a/packages/shared/src/db/schema.ts +++ b/packages/shared/src/db/schema.ts @@ -311,6 +311,7 @@ export const groups = sqliteTable('groups', { joinStatus: text('join_status').$type(), lastPostId: text('last_post_id'), lastPostAt: timestamp('last_post_at'), + lastVisitedChannelId: text('last_visited_channel_id'), }); export const groupsRelations = relations(groups, ({ one, many }) => ({ diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index 93a5bf333c..8ec4c905bb 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -98,6 +98,14 @@ export async function findContactSuggestions() { const MAX_SUGGESTIONS = 6; // arbitrary try { + // if we've already added suggestions recently, don't do it again + const lastAddedSuggestionsAt = await db.lastAddedSuggestionsAt.getValue(); + const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000; + if (lastAddedSuggestionsAt > oneDayAgo) { + logger.log('Suggestions added recently, skipping'); + return; + } + // first see if we have any joined groups and seem to be a somewhat // new user const groups = await db.getGroups({ includeUnjoined: false }); @@ -182,10 +190,11 @@ export async function findContactSuggestions() { runContext.suggestions = suggestions.length; logger.crumb(`Found ${suggestions.length} suggestions`); - db.groupsUsedForSuggestions.setValue(groupchats.map((g) => g.id)); if (suggestions.length > 0) { - await addContactSuggestions(suggestions); + addContactSuggestions(suggestions); + db.groupsUsedForSuggestions.setValue(groupchats.map((g) => g.id)); + db.lastAddedSuggestionsAt.setValue(Date.now()); logger.trackEvent('Client Contact Suggestions', { ...runContext, suggestionsFound: true, diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index a42e8659d4..3ceee7aec2 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -325,6 +325,14 @@ export const useGroup = ({ id }: { id?: string }) => { }); }; +export const useJoinedGroupsCount = () => { + const deps = useKeyFromQueryDeps(db.getJoinedGroupsCount); + return useQuery({ + queryKey: ['joinedGroupsCount', deps], + queryFn: () => db.getJoinedGroupsCount(), + }); +}; + export const useGroupByChannel = (channelId: string) => { return useQuery({ queryKey: [['group', channelId]], diff --git a/packages/shared/src/store/sync.ts b/packages/shared/src/store/sync.ts index 034e4ace09..bed4de2db6 100644 --- a/packages/shared/src/store/sync.ts +++ b/packages/shared/src/store/sync.ts @@ -12,7 +12,6 @@ import { INFINITE_ACTIVITY_QUERY_KEY, resetActivityFetchers, } from '../store/useActivityFetchers'; -import { findContactSuggestions } from './contactActions'; import { useLureState } from './lure'; import { getSyncing, updateIsSyncing, updateSession } from './session'; import { SyncCtx, SyncPriority, syncQueue } from './syncQueue'; @@ -1175,10 +1174,6 @@ export const syncStart = async (alreadySubscribed?: boolean) => { }); updateIsSyncing(false); - - // finding contacts is a bit of an outlier here, but it's work we need to do - // that can roughly be batched whenever we sync - findContactSuggestions(); }; export const setupHighPrioritySubscriptions = async (ctx?: SyncCtx) => { diff --git a/packages/shared/src/test/init.json b/packages/shared/src/test/init.json index 4b7b0bd44b..7b08f0903d 100644 --- a/packages/shared/src/test/init.json +++ b/packages/shared/src/test/init.json @@ -11600,7 +11600,10 @@ "~fabled-faster/new-york": { "preview": null, "invite": null, - "claim": null + "claim": { + "progress": "knocking", + "join-all": false + } }, "~barmyl-sigted/network-being": { "preview": { @@ -11615,7 +11618,10 @@ "description": "A group to discuss Heidegger, cybernetics, and the phenomenology of being online. " } }, - "invite": null, + "invite": { + "flag": "~dacler-ricwyt/new-new-new-york", + "ship": "~solfer-magfed" + }, "claim": null }, "~salfer-biswed/gamers": { @@ -11632,7 +11638,10 @@ } }, "invite": null, - "claim": null + "claim": { + "progress": "adding", + "join-all": true + } }, "~dacler-ricwyt/new-new-new-york": { "preview": { diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 497a937a05..357433072c 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -3,6 +3,7 @@ import * as logic from '@tloncorp/shared/logic'; import { useMemo } from 'react'; import { View, isWeb } from 'tamagui'; +import { useNavigation } from '../../contexts'; import * as utils from '../../utils'; import { capitalize } from '../../utils'; import { Badge } from '../Badge'; @@ -57,12 +58,15 @@ export function ChannelListItem({ } }, [model, firstMemberId, memberCount]); + const isFocused = useNavigation().focusedChannelId === model.id; + return ( ) { const swipeableRef = useRef(null); const [currentSwipeDirection, setCurrentSwipeDirection] = useState< @@ -136,12 +137,18 @@ function BaseInteractableChatRow({ model={model} onPress={onPress} onLongPress={onLongPress} + {...props} /> ); } else { return ( - + ); } } diff --git a/packages/ui/src/components/ListItem/ListItem.tsx b/packages/ui/src/components/ListItem/ListItem.tsx index 8edd5989fe..5d3d4bb237 100644 --- a/packages/ui/src/components/ListItem/ListItem.tsx +++ b/packages/ui/src/components/ListItem/ListItem.tsx @@ -29,6 +29,7 @@ export interface BaseListItemProps { onPress?: (model: T) => void; onLongPress?: (model: T) => void; unreadCount?: number; + isFocused?: boolean; } export type ListItemProps = BaseListItemProps & diff --git a/packages/ui/src/components/ListItem/listItemUtils.tsx b/packages/ui/src/components/ListItem/listItemUtils.tsx index 547c8f5624..6d239074ff 100644 --- a/packages/ui/src/components/ListItem/listItemUtils.tsx +++ b/packages/ui/src/components/ListItem/listItemUtils.tsx @@ -28,14 +28,14 @@ export function getGroupStatus(group: db.Group) { const state = isNew ? 'new' - : isRequested - ? 'requested' - : isInvite - ? 'invited' - : isErrored - ? 'errored' - : isJoining - ? 'joining' + : isErrored + ? 'errored' + : isJoining + ? 'joining' + : isRequested + ? 'requested' + : isInvite + ? 'invited' : 'joined'; const labels = { diff --git a/packages/ui/src/contexts/navigation.tsx b/packages/ui/src/contexts/navigation.tsx index 68ab156349..5cef43f3d3 100644 --- a/packages/ui/src/contexts/navigation.tsx +++ b/packages/ui/src/contexts/navigation.tsx @@ -6,6 +6,7 @@ type State = { onPressGroupRef?: (group: db.Group) => void; onPressGoToDm?: (participants: string[]) => void; onGoToUserProfile?: (userId: string) => void; + focusedChannelId?: string; }; type ContextValue = State; @@ -41,16 +42,30 @@ export const NavigationProvider = ({ onPressGroupRef, onPressGoToDm, onGoToUserProfile, + focusedChannelId, }: { children: React.ReactNode; onPressRef?: (channel: db.Channel, post: db.Post) => void; onPressGroupRef?: (group: db.Group) => void; onPressGoToDm?: (participants: string[]) => void; onGoToUserProfile?: (userId: string) => void; + focusedChannelId?: string; }) => { const value = useMemo( - () => ({ onPressRef, onPressGroupRef, onPressGoToDm, onGoToUserProfile }), - [onPressRef, onPressGroupRef, onPressGoToDm, onGoToUserProfile] + () => ({ + onPressRef, + onPressGroupRef, + onPressGoToDm, + onGoToUserProfile, + focusedChannelId, + }), + [ + onPressRef, + onPressGroupRef, + onPressGoToDm, + onGoToUserProfile, + focusedChannelId, + ] ); return {children}; }; diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 79ad23b890..bfacb2664c 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.rkfda.u301l.vk9sa.av06i.1gbmf.glob' 0v1.rkfda.u301l.vk9sa.av06i.1gbmf] + glob-http+['https://bootstrap.urbit.org/glob-0vr4p9n.vrsur.04pus.od75v.hpc8t.glob' 0vr4p9n.vrsur.04pus.od75v.hpc8t] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io'