diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index 370231841d..bb98cd5dbc 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {getActiveServerUrl} from '@app/init/credentials'; import {Tutorial} from '@constants'; import {GLOBAL_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; @@ -50,3 +51,29 @@ export const storeLastAskForReview = async (prepareRecordsOnly = false) => { export const storeFirstLaunch = async (prepareRecordsOnly = false) => { return storeGlobal(GLOBAL_IDENTIFIERS.FIRST_LAUNCH, Date.now(), prepareRecordsOnly); }; + +export const storeLastViewedChannelIdAndServer = async (channelId: string) => { + const currentServerUrl = await getActiveServerUrl(); + + return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_CHANNEL, { + server_url: currentServerUrl, + channel_id: channelId, + }, false); +}; + +export const storeLastViewedThreadIdAndServer = async (threadId: string) => { + const currentServerUrl = await getActiveServerUrl(); + + return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD, { + server_url: currentServerUrl, + thread_id: threadId, + }, false); +}; + +export const removeLastViewedChannelIdAndServer = async () => { + return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_CHANNEL, null, false); +}; + +export const removeLastViewedThreadIdAndServer = async () => { + return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD, null, false); +}; diff --git a/app/constants/database.ts b/app/constants/database.ts index a40ea02098..f14d22a621 100644 --- a/app/constants/database.ts +++ b/app/constants/database.ts @@ -80,6 +80,8 @@ export const GLOBAL_IDENTIFIERS = { FIRST_LAUNCH: 'firstLaunch', LAST_ASK_FOR_REVIEW: 'lastAskForReview', ONBOARDING: 'onboarding', + LAST_VIEWED_CHANNEL: 'lastViewedChannel', + LAST_VIEWED_THREAD: 'lastViewedThread', }; export enum OperationType { diff --git a/app/init/launch.ts b/app/init/launch.ts index 081c6aeeae..ca46ede8b6 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -5,12 +5,14 @@ import Emm from '@mattermost/react-native-emm'; import {Alert, AppState, DeviceEventEmitter, Linking, Platform} from 'react-native'; import {Notifications} from 'react-native-notifications'; +import {switchToChannelById} from '@actions/remote/channel'; import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/entry'; +import {fetchAndSwitchToThread} from '@actions/remote/thread'; import LocalConfig from '@assets/config.json'; import {DeepLink, Events, Launch, PushNotification} from '@constants'; import DatabaseManager from '@database/manager'; import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from '@init/credentials'; -import {getOnboardingViewed} from '@queries/app/global'; +import {getLastViewedChannelIdAndServer, getOnboardingViewed, getLastViewedThreadIdAndServer} from '@queries/app/global'; import {getThemeForCurrentTeam} from '@queries/servers/preference'; import {getCurrentUserId} from '@queries/servers/system'; import {queryMyTeams} from '@queries/servers/team'; @@ -172,6 +174,15 @@ const launchToHome = async (props: LaunchProps) => { } case Launch.Normal: if (props.coldStart) { + const lastViewedChannel = await getLastViewedChannelIdAndServer(); + const lastViewedThread = await getLastViewedThreadIdAndServer(); + + if (lastViewedThread && lastViewedThread.server_url === props.serverUrl && lastViewedThread.thread_id) { + fetchAndSwitchToThread(props.serverUrl!, lastViewedThread.thread_id); + } else if (lastViewedChannel && lastViewedChannel.server_url === props.serverUrl && lastViewedChannel.channel_id) { + switchToChannelById(props.serverUrl!, lastViewedChannel.channel_id); + } + appEntry(props.serverUrl!); } break; diff --git a/app/queries/app/global.ts b/app/queries/app/global.ts index 311d9b3205..9784c6124d 100644 --- a/app/queries/app/global.ts +++ b/app/queries/app/global.ts @@ -64,6 +64,16 @@ export const getFirstLaunch = async () => { return records[0].value; }; +export const getLastViewedChannelIdAndServer = async () => { + const records = await queryGlobalValue(GLOBAL_IDENTIFIERS.LAST_VIEWED_CHANNEL)?.fetch(); + return records?.[0]?.value; +}; + +export const getLastViewedThreadIdAndServer = async () => { + const records = await queryGlobalValue(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD)?.fetch(); + return records?.[0]?.value; +}; + export const observeTutorialWatched = (tutorial: string) => { const query = queryGlobalValue(tutorial); if (!query) { diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx index 69140bafc2..d65f31a551 100644 --- a/app/screens/channel/channel.tsx +++ b/app/screens/channel/channel.tsx @@ -5,6 +5,7 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {type LayoutChangeEvent, StyleSheet, View} from 'react-native'; import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'; +import {storeLastViewedChannelIdAndServer, removeLastViewedChannelIdAndServer} from '@actions/app/global'; import FloatingCallContainer from '@calls/components/floating_call_container'; import {RoundedHeaderCalls} from '@calls/components/join_call_banner/rounded_header_calls'; import FreezeScreen from '@components/freeze_screen'; @@ -83,9 +84,12 @@ const Channel = ({ EphemeralStore.removeSwitchingToChannel(channelId); }, 500); + storeLastViewedChannelIdAndServer(channelId); + return () => { cancelAnimationFrame(raf); clearTimeout(t); + removeLastViewedChannelIdAndServer(); EphemeralStore.removeSwitchingToChannel(channelId); }; }, [channelId]); diff --git a/app/screens/thread/thread.tsx b/app/screens/thread/thread.tsx index 4bf847f969..542be36126 100644 --- a/app/screens/thread/thread.tsx +++ b/app/screens/thread/thread.tsx @@ -6,6 +6,7 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {type LayoutChangeEvent, StyleSheet, View} from 'react-native'; import {type Edge, SafeAreaView} from 'react-native-safe-area-context'; +import {storeLastViewedThreadIdAndServer, removeLastViewedThreadIdAndServer} from '@actions/app/global'; import FloatingCallContainer from '@calls/components/floating_call_container'; import {RoundedHeaderCalls} from '@calls/components/join_call_banner/rounded_header_calls'; import FreezeScreen from '@components/freeze_screen'; @@ -18,6 +19,7 @@ import useDidUpdate from '@hooks/did_update'; import {useKeyboardTrackingPaused} from '@hooks/keyboard_tracking'; import {popTopScreen, setButtons} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import ThreadPostList from './thread_post_list'; @@ -80,7 +82,17 @@ const Thread = ({ }, [componentId, rootId, isCRTEnabled]); useEffect(() => { + // when opened from notification, first screen in stack is HOME + // if last screen was global thread or thread opened from notification, store the last viewed thread id + const isFromGlobalOrNotification = NavigationStore.getScreensInStack()[1] === Screens.GLOBAL_THREADS || NavigationStore.getScreensInStack()[1] === Screens.HOME; + if (isCRTEnabled && isFromGlobalOrNotification) { + storeLastViewedThreadIdAndServer(rootId); + } + return () => { + if (isCRTEnabled) { + removeLastViewedThreadIdAndServer(); + } if (rootId === EphemeralStore.getCurrentThreadId()) { EphemeralStore.setCurrentThreadId(''); }