diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index f6951fc069..e908c558f1 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -6,6 +6,7 @@ import {DeviceEventEmitter} from 'react-native'; import {General, Navigation as NavigationConstants, Preferences, Screens} from '@constants'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; +import {getDefaultThemeByAppearance} from '@context/theme'; import DatabaseManager from '@database/manager'; import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import {extractChannelDisplayName} from '@helpers/database'; @@ -41,6 +42,8 @@ export async function switchToChannel(serverUrl: string, channelId: string, team const system = await getCommonSystemValues(database); const member = await getMyChannel(database, channelId); + EphemeralStore.addSwitchingToChannel(channelId); + if (member) { const channel = await member.channel.fetch(); if (channel) { @@ -93,7 +96,7 @@ export async function switchToChannel(serverUrl: string, channelId: string, team // causing the goToScreen to use the Appearance theme instead and that causes the screen background color to potentially // not match the theme const themes = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME, toTeamId).fetch(); - let theme = Preferences.THEMES.denim; + let theme = getDefaultThemeByAppearance(); if (themes.length) { theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme); } @@ -231,7 +234,7 @@ export async function markChannelAsUnread(serverUrl: string, channelId: string, member.prepareUpdate((m) => { m.viewedAt = lastViewed - 1; - m.lastViewedAt = lastViewed; + m.lastViewedAt = lastViewed - 1; m.messageCount = messageCount; m.mentionsCount = mentionsCount; m.manuallyUnread = true; diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index fde3b4b03c..a9cf1feb96 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -16,6 +16,7 @@ import {getIsCRTEnabled, getThreadById, prepareThreadsFromReceivedPosts, queryTh import {getCurrentUser} from '@queries/servers/user'; import {dismissAllModalsAndPopToRoot, goToScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; import {changeOpacity, setThemeDefaults, updateThemeIfNeeded} from '@utils/theme'; @@ -155,7 +156,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo if (isFromNotification) { await dismissAllModalsAndPopToRoot(); - await EphemeralStore.waitUntilScreenIsTop(Screens.HOME); + await NavigationStore.waitUntilScreenIsTop(Screens.HOME); if (switchingTeams && isTabletDevice) { DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_THREADS); } diff --git a/app/actions/remote/entry/notification.ts b/app/actions/remote/entry/notification.ts index e66d1ee17a..0bdfe0d492 100644 --- a/app/actions/remote/entry/notification.ts +++ b/app/actions/remote/entry/notification.ts @@ -10,7 +10,7 @@ import {getCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnected, s import {getMyTeamById} from '@queries/servers/team'; import {getIsCRTEnabled} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; import {emitNotificationError} from '@utils/notification'; @@ -51,7 +51,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not const isCRTEnabled = await getIsCRTEnabled(database); const isThreadNotification = isCRTEnabled && Boolean(rootId); - await EphemeralStore.waitUntilScreenHasLoaded(Screens.HOME); + await NavigationStore.waitUntilScreenHasLoaded(Screens.HOME); let switchedToScreen = false; let switchedToChannel = false; diff --git a/app/actions/websocket/channel.ts b/app/actions/websocket/channel.ts index 5553f22978..7b7be40f9e 100644 --- a/app/actions/websocket/channel.ts +++ b/app/actions/websocket/channel.ts @@ -130,8 +130,7 @@ export async function handleChannelViewedEvent(serverUrl: string, msg: any) { const activeServerUrl = await DatabaseManager.getActiveServerUrl(); const currentChannelId = await getCurrentChannelId(database); - const viewingChannel = currentChannelId === channelId && EphemeralStore.getNavigationComponents().includes(Screens.CHANNEL); - if (activeServerUrl !== serverUrl || currentChannelId !== channelId || !viewingChannel) { + if (activeServerUrl !== serverUrl || (currentChannelId !== channelId && !EphemeralStore.isSwitchingToChannel(channelId))) { await markChannelAsViewed(serverUrl, channelId, false); } } catch { diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index efcba3ba71..e539361a59 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -15,7 +15,7 @@ import {getCommonSystemValues, getConfig, getWebSocketLastDisconnected, resetWeb import {getCurrentTeam} from '@queries/servers/team'; import {getCurrentUser} from '@queries/servers/user'; import {dismissAllModals, popToRoot} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category'; @@ -117,7 +117,7 @@ async function doReconnect(serverUrl: string) { // if no longer a member of the current team or the current channel if (initialTeamId !== currentTeam?.id || initialChannelId !== currentChannel?.id) { const currentServer = await queryActiveServer(appDatabase); - const isChannelScreenMounted = EphemeralStore.getNavigationComponents().includes(Screens.CHANNEL); + const isChannelScreenMounted = NavigationStore.getNavigationComponents().includes(Screens.CHANNEL); if (serverUrl === currentServer?.url) { if (currentTeam && initialTeamId !== currentTeam.id) { DeviceEventEmitter.emit(Events.LEAVE_TEAM, {displayName: currentTeam.displayName}); diff --git a/app/actions/websocket/posts.ts b/app/actions/websocket/posts.ts index f883f638e2..8b08abc0ac 100644 --- a/app/actions/websocket/posts.ts +++ b/app/actions/websocket/posts.ts @@ -16,7 +16,7 @@ import {getChannelById, getMyChannel} from '@queries/servers/channel'; import {getPostById} from '@queries/servers/post'; import {getCurrentChannelId, getCurrentTeamId, getCurrentUserId} from '@queries/servers/system'; import {getIsCRTEnabled} from '@queries/servers/thread'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; import {isFromWebhook, isSystemMessage, shouldIgnorePost} from '@utils/post'; @@ -141,7 +141,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag // Don't mark as read if we're in global threads screen // the currentChannelId still refers to previously viewed channel - const isChannelScreenMounted = EphemeralStore.getNavigationComponents().includes(Screens.CHANNEL); + const isChannelScreenMounted = NavigationStore.getNavigationComponents().includes(Screens.CHANNEL); const isTabletDevice = await isTablet(); if (isChannelScreenMounted || isTabletDevice) { diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index ac25bac55d..d371362112 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -19,7 +19,7 @@ import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import useDidUpdate from '@hooks/did_update'; import {t} from '@i18n'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {extractFileInfo} from '@utils/file'; import {switchKeyboardForCodeBlocks} from '@utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme} from '@utils/theme'; @@ -219,7 +219,7 @@ export default function PostInput({ }, [addFiles, intl]); const handleHardwareEnterPress = useCallback((keyEvent: {pressedKey: string}) => { - const topScreen = EphemeralStore.getNavigationTopComponentId(); + const topScreen = NavigationStore.getNavigationTopComponentId(); let sourceScreen = Screens.CHANNEL; if (rootId) { sourceScreen = Screens.THREAD; diff --git a/app/init/push_notifications.ts b/app/init/push_notifications.ts index bc6691421b..1b12770431 100644 --- a/app/init/push_notifications.ts +++ b/app/init/push_notifications.ts @@ -27,6 +27,7 @@ import {getCurrentChannelId} from '@queries/servers/system'; import {getIsCRTEnabled} from '@queries/servers/thread'; import {showOverlay} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; import {convertToNotificationData} from '@utils/notification'; @@ -147,10 +148,10 @@ class PushNotifications { } const isDifferentChannel = payload?.channel_id !== channelId; - const isVisibleThread = payload?.root_id === EphemeralStore.getLastViewedThreadId() && EphemeralStore.getNavigationTopComponentId() === Screens.THREAD; - let isChannelScreenVisible = EphemeralStore.getNavigationTopComponentId() === Screens.CHANNEL; + const isVisibleThread = payload?.root_id === EphemeralStore.getLastViewedThreadId() && NavigationStore.getNavigationTopComponentId() === Screens.THREAD; + let isChannelScreenVisible = NavigationStore.getNavigationTopComponentId() === Screens.CHANNEL; if (isTabletDevice) { - isChannelScreenVisible = EphemeralStore.getVisibleTab() === Screens.HOME; + isChannelScreenVisible = NavigationStore.getVisibleTab() === Screens.HOME; } if (isDifferentChannel || (!isChannelScreenVisible && !isVisibleThread)) { diff --git a/app/screens/bottom_sheet/index.tsx b/app/screens/bottom_sheet/index.tsx index 9d3ee68559..20fd0f74bd 100644 --- a/app/screens/bottom_sheet/index.tsx +++ b/app/screens/bottom_sheet/index.tsx @@ -12,7 +12,7 @@ import {Events} from '@constants'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {dismissModal} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {hapticFeedback} from '@utils/general'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -56,7 +56,7 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo useEffect(() => { const listener = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { if (sheetRef.current) { sheetRef.current.snapTo(1); } else { diff --git a/app/screens/channel/channel.tsx b/app/screens/channel/channel.tsx index 7225c93140..24c7afcda2 100644 --- a/app/screens/channel/channel.tsx +++ b/app/screens/channel/channel.tsx @@ -15,6 +15,7 @@ import {useDefaultHeaderHeight} from '@hooks/header'; import {useTeamSwitch} from '@hooks/team_switch'; import {popTopScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import ChannelPostList from './channel_post_list'; import ChannelHeader from './header'; @@ -58,7 +59,7 @@ const Channel = ({channelId, componentId}: ChannelProps) => { let back: NativeEventSubscription|undefined; if (!isTablet && componentId) { back = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { popTopScreen(componentId); return true; } @@ -74,11 +75,20 @@ const Channel = ({channelId, componentId}: ChannelProps) => { useEffect(() => { // This is done so that the header renders // and the screen does not look totally blank - const t = requestAnimationFrame(() => { + const raf = requestAnimationFrame(() => { setShouldRenderPosts(Boolean(channelId)); }); - return () => cancelAnimationFrame(t); + // This is done to give time to the WS event + const t = setTimeout(() => { + EphemeralStore.removeSwitchingToChannel(channelId); + }, 500); + + return () => { + cancelAnimationFrame(raf); + clearTimeout(t); + EphemeralStore.removeSwitchingToChannel(channelId); + }; }, [channelId]); return ( diff --git a/app/screens/custom_status/index.tsx b/app/screens/custom_status/index.tsx index 9a7f917b24..0fae454b18 100644 --- a/app/screens/custom_status/index.tsx +++ b/app/screens/custom_status/index.tsx @@ -23,7 +23,7 @@ import {withTheme} from '@context/theme'; import {observeConfig, observeRecentCustomStatus} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import {dismissModal, goToScreen, showModal} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {getCurrentMomentForTimezone, getRoundedTime, isCustomStatusExpirySupported} from '@utils/helpers'; import {mergeNavigationOptions} from '@utils/navigation'; import {preventDoubleTap} from '@utils/tap'; @@ -166,7 +166,7 @@ class CustomStatusModal extends NavigationComponent { onBackPress = () => { const {componentId} = this.props; - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { if (this.props.isTablet) { DeviceEventEmitter.emit(Events.ACCOUNT_SELECT_TABLET_VIEW, ''); } else { diff --git a/app/screens/custom_status_clear_after/index.tsx b/app/screens/custom_status_clear_after/index.tsx index 443295fada..f448ffed70 100644 --- a/app/screens/custom_status_clear_after/index.tsx +++ b/app/screens/custom_status_clear_after/index.tsx @@ -18,7 +18,7 @@ import { import {CustomStatusDuration} from '@constants/custom_status'; import {observeCurrentUser} from '@queries/servers/user'; import {dismissModal, popTopScreen} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {mergeNavigationOptions} from '@utils/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -113,7 +113,7 @@ class ClearAfterModal extends NavigationComponent { onBackPress = () => { const {componentId} = this.props; - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { if (this.props.isModal) { dismissModal({componentId}); } else { diff --git a/app/screens/edit_profile/edit_profile.tsx b/app/screens/edit_profile/edit_profile.tsx index b4acc1441c..1e11186885 100644 --- a/app/screens/edit_profile/edit_profile.tsx +++ b/app/screens/edit_profile/edit_profile.tsx @@ -16,7 +16,7 @@ import {Events} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {dismissModal, popTopScreen, setButtons} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {preventDoubleTap} from '@utils/tap'; import ProfileForm from './components/form'; @@ -106,7 +106,7 @@ const EditProfile = ({ useEffect(() => { const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { close(); return true; } diff --git a/app/screens/home/channel_list/channel_list.tsx b/app/screens/home/channel_list/channel_list.tsx index f4186b297d..ca19b8f08b 100644 --- a/app/screens/home/channel_list/channel_list.tsx +++ b/app/screens/home/channel_list/channel_list.tsx @@ -15,7 +15,7 @@ import {Navigation as NavigationConstants, Screens} from '@constants'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {resetToTeams} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import AdditionalTabletView from './additional_tablet_view'; import CategoriesList from './categories_list'; @@ -54,8 +54,8 @@ const ChannelListScreen = (props: ChannelProps) => { const canAddOtherServers = managedConfig?.allowOtherServers !== 'false'; const handleBackPress = useCallback(() => { - const isHomeScreen = EphemeralStore.getNavigationTopComponentId() === Screens.HOME; - const homeTab = EphemeralStore.getVisibleTab() === Screens.HOME; + const isHomeScreen = NavigationStore.getNavigationTopComponentId() === Screens.HOME; + const homeTab = NavigationStore.getVisibleTab() === Screens.HOME; const focused = navigation.isFocused() && isHomeScreen && homeTab; if (!backPressedCount && focused) { backPressedCount++; diff --git a/app/screens/home/index.tsx b/app/screens/home/index.tsx index bc20abad11..fece5f503e 100644 --- a/app/screens/home/index.tsx +++ b/app/screens/home/index.tsx @@ -12,7 +12,7 @@ import {enableFreeze, enableScreens} from 'react-native-screens'; import {Events, Screens} from '@constants'; import {useTheme} from '@context/theme'; import {findChannels} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {alertChannelArchived, alertChannelRemove, alertTeamRemove} from '@utils/navigation'; import {notificationError} from '@utils/notification'; @@ -76,7 +76,7 @@ export default function HomeScreen(props: HomeProps) { useEffect(() => { const listener = HWKeyboardEvent.onHWKeyPressed((keyEvent: {pressedKey: string}) => { - const screen = EphemeralStore.getAllNavigationComponents(); + const screen = NavigationStore.getAllNavigationComponents(); if (!screen.includes(Screens.FIND_CHANNELS) && keyEvent.pressedKey === 'find-channels') { findChannels( intl.formatMessage({id: 'find_channels.title', defaultMessage: 'Find Channels'}), diff --git a/app/screens/home/tab_bar/index.tsx b/app/screens/home/tab_bar/index.tsx index cad40d1e45..6ebfae999a 100644 --- a/app/screens/home/tab_bar/index.tsx +++ b/app/screens/home/tab_bar/index.tsx @@ -8,7 +8,7 @@ import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {Events, Navigation as NavigationConstants, Screens, View as ViewConstants} from '@constants'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import Account from './account'; @@ -77,7 +77,7 @@ function TabBar({state, descriptors, navigation, theme}: BottomTabBarProps & {th useEffect(() => { const listner = DeviceEventEmitter.addListener(NavigationConstants.NAVIGATION_HOME, () => { - EphemeralStore.setVisibleTap(Screens.HOME); + NavigationStore.setVisibleTap(Screens.HOME); navigation.navigate(Screens.HOME); }); @@ -102,7 +102,7 @@ function TabBar({state, descriptors, navigation, theme}: BottomTabBarProps & {th if (!event.defaultPrevented) { // The `merge: true` option makes sure that the params inside the tab screen are preserved navigation.navigate({params: {direction, ...params}, name: route.name, merge: false}); - EphemeralStore.setVisibleTap(route.name); + NavigationStore.setVisibleTap(route.name); } }); @@ -168,7 +168,7 @@ function TabBar({state, descriptors, navigation, theme}: BottomTabBarProps & {th if (!isFocused && !event.defaultPrevented) { // The `merge: true` option makes sure that the params inside the tab screen are preserved navigation.navigate({params: {direction}, name: route.name, merge: false}); - EphemeralStore.setVisibleTap(route.name); + NavigationStore.setVisibleTap(route.name); } }; diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 74ec3839eb..6bea5bd90d 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -14,6 +14,7 @@ import NavigationConstants from '@constants/navigation'; import {NOT_READY} from '@constants/screens'; import {getDefaultThemeByAppearance} from '@context/theme'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {LaunchProps, LaunchType} from '@typings/launch'; import {appearanceControlledScreens, mergeNavigationOptions} from '@utils/navigation'; import {changeOpacity, setNavigatorStyles} from '@utils/theme'; @@ -149,7 +150,7 @@ Navigation.setDefaultOptions({ Appearance.addChangeListener(() => { const theme = getThemeFromState(); - const screens = EphemeralStore.getAllNavigationComponents(); + const screens = NavigationStore.getAllNavigationComponents(); if (screens.includes(Screens.SERVER)) { for (const screen of screens) { @@ -198,7 +199,7 @@ export function resetToHome(passProps: LaunchProps = {launchType: LaunchType.Nor return; } - EphemeralStore.clearNavigationComponents(); + NavigationStore.clearNavigationComponents(); const stack = { children: [{ @@ -240,7 +241,7 @@ export function resetToSelectServer(passProps: LaunchProps) { const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); - EphemeralStore.clearNavigationComponents(); + NavigationStore.clearNavigationComponents(); const children = [{ component: { @@ -288,7 +289,7 @@ export function resetToTeams() { const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); - EphemeralStore.clearNavigationComponents(); + NavigationStore.clearNavigationComponents(); Navigation.setRoot({ root: { @@ -331,7 +332,7 @@ export function goToScreen(name: string, title: string, passProps = {}, options const theme = getThemeFromState(); const isDark = tinyColor(theme.sidebarBg).isDark(); - const componentId = EphemeralStore.getNavigationTopComponentId(); + const componentId = NavigationStore.getNavigationTopComponentId(); DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, false); const defaultOptions: Options = { layout: { @@ -378,13 +379,13 @@ export function popTopScreen(screenId?: string) { if (screenId) { Navigation.pop(screenId); } else { - const componentId = EphemeralStore.getNavigationTopComponentId(); + const componentId = NavigationStore.getNavigationTopComponentId(); Navigation.pop(componentId); } } export async function popToRoot() { - const componentId = EphemeralStore.getNavigationTopComponentId(); + const componentId = NavigationStore.getNavigationTopComponentId(); try { await Navigation.popToRoot(componentId); @@ -411,7 +412,7 @@ export async function dismissAllModalsAndPopToRoot() { */ export async function dismissAllModalsAndPopToScreen(screenId: string, title: string, passProps = {}, options = {}) { await dismissAllModals(); - if (EphemeralStore.getNavigationComponents().includes(screenId)) { + if (NavigationStore.getNavigationComponents().includes(screenId)) { let mergeOptions = options; if (title) { mergeOptions = merge(mergeOptions, { @@ -466,7 +467,7 @@ export function showModal(name: string, title: string, passProps = {}, options = }, }; - EphemeralStore.addNavigationModal(name); + NavigationStore.addNavigationModal(name); Navigation.showModal({ stack: { children: [{ @@ -563,15 +564,15 @@ export function showSearchModal(initialValue = '') { } export async function dismissModal(options?: Options & { componentId: string}) { - if (!EphemeralStore.hasModalsOpened()) { + if (!NavigationStore.hasModalsOpened()) { return; } - const componentId = options?.componentId || EphemeralStore.getNavigationTopModalId(); + const componentId = options?.componentId || NavigationStore.getNavigationTopModalId(); if (componentId) { try { await Navigation.dismissModal(componentId, options); - EphemeralStore.removeNavigationModal(componentId); + NavigationStore.removeNavigationModal(componentId); } catch (error) { // RNN returns a promise rejection if there is no modal to // dismiss. We'll do nothing in this case. @@ -580,14 +581,14 @@ export async function dismissModal(options?: Options & { componentId: string}) { } export async function dismissAllModals() { - if (!EphemeralStore.hasModalsOpened()) { + if (!NavigationStore.hasModalsOpened()) { return; } try { - const modals = [...EphemeralStore.getAllNavigationModals()]; + const modals = [...NavigationStore.getAllNavigationModals()]; for await (const modal of modals) { - EphemeralStore.removeNavigationModal(modal); + NavigationStore.removeNavigationModal(modal); await Navigation.dismissModal(modal, {animations: {dismissModal: {enabled: false}}}); } } catch (error) { @@ -685,7 +686,7 @@ export async function bottomSheet({title, renderContent, snapPoints, initialSnap export async function dismissBottomSheet(alternativeScreen = Screens.BOTTOM_SHEET) { DeviceEventEmitter.emit(Events.CLOSE_BOTTOM_SHEET); - await EphemeralStore.waitUntilScreensIsRemoved(alternativeScreen); + await NavigationStore.waitUntilScreensIsRemoved(alternativeScreen); } type AsBottomSheetArgs = { diff --git a/app/screens/permalink/permalink.tsx b/app/screens/permalink/permalink.tsx index bffb24d54f..bb063ece70 100644 --- a/app/screens/permalink/permalink.tsx +++ b/app/screens/permalink/permalink.tsx @@ -17,7 +17,7 @@ import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {dismissModal} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {closePermalink} from '@utils/permalink'; import {preventDoubleTap} from '@utils/tap'; @@ -152,7 +152,7 @@ function Permalink({channel, isCRTEnabled, postId}: Props) { useEffect(() => { const listener = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === Screens.PERMALINK) { + if (NavigationStore.getNavigationTopComponentId() === Screens.PERMALINK) { handleClose(); return true; } diff --git a/app/screens/pinned_messages/pinned_messages.tsx b/app/screens/pinned_messages/pinned_messages.tsx index 1932bd0b20..b42159769e 100644 --- a/app/screens/pinned_messages/pinned_messages.tsx +++ b/app/screens/pinned_messages/pinned_messages.tsx @@ -13,7 +13,7 @@ import {Events, Screens} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {popTopScreen} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {isDateLine, getDateForDateLine, selectOrderedPosts} from '@utils/post_list'; import EmptyState from './empty'; @@ -80,7 +80,7 @@ function SavedMessages({ useEffect(() => { const listener = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { close(); return true; } diff --git a/app/screens/settings/settings.tsx b/app/screens/settings/settings.tsx index 0cbf5c37e6..8af646bc00 100644 --- a/app/screens/settings/settings.tsx +++ b/app/screens/settings/settings.tsx @@ -12,7 +12,7 @@ import {Screens} from '@constants'; import {useServerDisplayName} from '@context/server'; import {useTheme} from '@context/theme'; import {dismissModal, goToScreen, setButtons} from '@screens/navigation'; -import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -95,7 +95,7 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => { useEffect(() => { const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { - if (EphemeralStore.getNavigationTopComponentId() === componentId) { + if (NavigationStore.getNavigationTopComponentId() === componentId) { close(); return true; } diff --git a/app/store/ephemeral_store.ts b/app/store/ephemeral_store.ts index 92a74fac88..e8043c25a9 100644 --- a/app/store/ephemeral_store.ts +++ b/app/store/ephemeral_store.ts @@ -2,11 +2,7 @@ // See LICENSE.txt for license information. class EphemeralStore { - allNavigationComponentIds: string[] = []; - navigationComponentIdStack: string[] = []; - navigationModalStack: string[] = []; theme: Theme | undefined; - visibleTab = 'Home'; creatingChannel = false; creatingDMorGMTeammates: string[] = []; @@ -21,140 +17,9 @@ class EphemeralStore { private leavingChannels = new Set(); private archivingChannels = new Set(); private convertingChannels = new Set(); + private switchingToChannel = new Set(); private lastViewedThreadId = ''; - addNavigationComponentId = (componentId: string) => { - this.addToNavigationComponentIdStack(componentId); - this.addToAllNavigationComponentIds(componentId); - }; - - addToAllNavigationComponentIds = (componentId: string) => { - if (!this.allNavigationComponentIds.includes(componentId)) { - this.allNavigationComponentIds.unshift(componentId); - } - }; - - addToNavigationComponentIdStack = (componentId: string) => { - const index = this.navigationComponentIdStack.indexOf(componentId); - if (index >= 0) { - this.navigationComponentIdStack.splice(index, 1); - } - - this.navigationComponentIdStack.unshift(componentId); - }; - - addNavigationModal = (componentId: string) => { - this.navigationModalStack.unshift(componentId); - }; - - clearNavigationComponents = () => { - this.navigationComponentIdStack = []; - this.navigationModalStack = []; - this.allNavigationComponentIds = []; - }; - - clearNavigationModals = () => { - this.navigationModalStack = []; - }; - - getAllNavigationComponents = () => this.allNavigationComponentIds; - - getAllNavigationModals = () => this.navigationModalStack; - - getNavigationTopComponentId = () => { - return this.navigationComponentIdStack[0]; - }; - - getNavigationTopModalId = () => { - return this.navigationModalStack[0]; - }; - - getNavigationComponents = () => { - return this.navigationComponentIdStack; - }; - - getVisibleTab = () => this.visibleTab; - - hasModalsOpened = () => this.navigationModalStack.length > 0; - - private removeNavigationComponent = (componentId: string) => { - const index = this.allNavigationComponentIds.indexOf(componentId); - if (index >= 0) { - this.allNavigationComponentIds.splice(index, 1); - } - }; - - removeNavigationComponentId = (componentId: string) => { - this.removeNavigationComponent(componentId); - const index = this.navigationComponentIdStack.indexOf(componentId); - if (index >= 0) { - this.navigationComponentIdStack.splice(index, 1); - } - }; - - removeNavigationModal = (componentId: string) => { - this.removeNavigationComponentId(componentId); - const index = this.navigationModalStack.indexOf(componentId); - - if (index >= 0) { - this.navigationModalStack.splice(index, 1); - } - }; - - setVisibleTap = (tab: string) => { - this.visibleTab = tab; - }; - - /** - * Waits until a screen has been mounted and is part of the stack. - * Use this function only if you know what you are doing - * this function will run until the screen appears in the stack - * and can easily run forever if the screen is never prevesented. - * @param componentId string - */ - waitUntilScreenHasLoaded = async (componentId: string) => { - let found = false; - while (!found) { - // eslint-disable-next-line no-await-in-loop - await (new Promise((r) => requestAnimationFrame(r))); - - found = this.navigationComponentIdStack.includes(componentId); - } - }; - - /** - * Waits until a passed screen is the top screen - * Use this function only if you know what you are doing - * this function will run until the screen is in the top - * @param componentId string - */ - waitUntilScreenIsTop = async (componentId: string) => { - let found = false; - while (!found) { - // eslint-disable-next-line no-await-in-loop - await (new Promise((r) => requestAnimationFrame(r))); - - found = this.getNavigationTopComponentId() === componentId; - } - }; - - /** - * Waits until a screen has been removed as part of the stack. - * Use this function only if you know what you are doing - * this function will run until the screen disappears from the stack - * and can easily run forever if the screen is never removed. - * @param componentId string - */ - waitUntilScreensIsRemoved = async (componentId: string) => { - let found = false; - while (!found) { - // eslint-disable-next-line no-await-in-loop - await (new Promise((r) => requestAnimationFrame(r))); - - found = !this.navigationComponentIdStack.includes(componentId); - } - }; - // Ephemeral control when (un)archiving a channel locally addArchivingChannel = (channelId: string) => { this.archivingChannels.add(channelId); @@ -237,6 +102,19 @@ class EphemeralStore { setLastViewedThreadId = (id: string) => { this.lastViewedThreadId = id; }; + + // Ephemeral control when (un)archiving a channel locally + addSwitchingToChannel = (channelId: string) => { + this.switchingToChannel.add(channelId); + }; + + isSwitchingToChannel = (channelId: string) => { + return this.switchingToChannel.has(channelId); + }; + + removeSwitchingToChannel = (channelId: string) => { + this.switchingToChannel.delete(channelId); + }; } export default new EphemeralStore(); diff --git a/app/store/navigation_store.ts b/app/store/navigation_store.ts new file mode 100644 index 0000000000..384aab28b0 --- /dev/null +++ b/app/store/navigation_store.ts @@ -0,0 +1,143 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class NavigationStore { + allNavigationComponentIds: string[] = []; + navigationComponentIdStack: string[] = []; + navigationModalStack: string[] = []; + visibleTab = 'Home'; + + addNavigationComponentId = (componentId: string) => { + this.addToNavigationComponentIdStack(componentId); + this.addToAllNavigationComponentIds(componentId); + }; + + addToAllNavigationComponentIds = (componentId: string) => { + if (!this.allNavigationComponentIds.includes(componentId)) { + this.allNavigationComponentIds.unshift(componentId); + } + }; + + addToNavigationComponentIdStack = (componentId: string) => { + const index = this.navigationComponentIdStack.indexOf(componentId); + if (index >= 0) { + this.navigationComponentIdStack.splice(index, 1); + } + + this.navigationComponentIdStack.unshift(componentId); + }; + + addNavigationModal = (componentId: string) => { + this.navigationModalStack.unshift(componentId); + }; + + clearNavigationComponents = () => { + this.navigationComponentIdStack = []; + this.navigationModalStack = []; + this.allNavigationComponentIds = []; + }; + + clearNavigationModals = () => { + this.navigationModalStack = []; + }; + + getAllNavigationComponents = () => this.allNavigationComponentIds; + + getAllNavigationModals = () => this.navigationModalStack; + + getNavigationTopComponentId = () => { + return this.navigationComponentIdStack[0]; + }; + + getNavigationTopModalId = () => { + return this.navigationModalStack[0]; + }; + + getNavigationComponents = () => { + return this.navigationComponentIdStack; + }; + + getVisibleTab = () => this.visibleTab; + + hasModalsOpened = () => this.navigationModalStack.length > 0; + + private removeNavigationComponent = (componentId: string) => { + const index = this.allNavigationComponentIds.indexOf(componentId); + if (index >= 0) { + this.allNavigationComponentIds.splice(index, 1); + } + }; + + removeNavigationComponentId = (componentId: string) => { + this.removeNavigationComponent(componentId); + const index = this.navigationComponentIdStack.indexOf(componentId); + if (index >= 0) { + this.navigationComponentIdStack.splice(index, 1); + } + }; + + removeNavigationModal = (componentId: string) => { + this.removeNavigationComponentId(componentId); + const index = this.navigationModalStack.indexOf(componentId); + + if (index >= 0) { + this.navigationModalStack.splice(index, 1); + } + }; + + setVisibleTap = (tab: string) => { + this.visibleTab = tab; + }; + + /** + * Waits until a screen has been mounted and is part of the stack. + * Use this function only if you know what you are doing + * this function will run until the screen appears in the stack + * and can easily run forever if the screen is never prevesented. + * @param componentId string + */ + waitUntilScreenHasLoaded = async (componentId: string) => { + let found = false; + while (!found) { + // eslint-disable-next-line no-await-in-loop + await (new Promise((r) => requestAnimationFrame(r))); + + found = this.navigationComponentIdStack.includes(componentId); + } + }; + + /** + * Waits until a passed screen is the top screen + * Use this function only if you know what you are doing + * this function will run until the screen is in the top + * @param componentId string + */ + waitUntilScreenIsTop = async (componentId: string) => { + let found = false; + while (!found) { + // eslint-disable-next-line no-await-in-loop + await (new Promise((r) => requestAnimationFrame(r))); + + found = this.getNavigationTopComponentId() === componentId; + } + }; + + /** + * Waits until a screen has been removed as part of the stack. + * Use this function only if you know what you are doing + * this function will run until the screen disappears from the stack + * and can easily run forever if the screen is never removed. + * @param componentId string + */ + waitUntilScreensIsRemoved = async (componentId: string) => { + let found = false; + while (!found) { + // eslint-disable-next-line no-await-in-loop + await (new Promise((r) => requestAnimationFrame(r))); + + found = !this.navigationComponentIdStack.includes(componentId); + } + }; +} + +export default new NavigationStore(); diff --git a/app/utils/theme/index.ts b/app/utils/theme/index.ts index 7a6f9fe2f3..9719fcda15 100644 --- a/app/utils/theme/index.ts +++ b/app/utils/theme/index.ts @@ -8,6 +8,7 @@ import tinyColor from 'tinycolor2'; import {Preferences} from '@constants'; import {MODAL_SCREENS_WITHOUT_BACK} from '@constants/screens'; import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; import {appearanceControlledScreens, mergeNavigationOptions} from '@utils/navigation'; import type {Options} from 'react-native-navigation'; @@ -113,7 +114,7 @@ export function setNavigatorStyles(componentId: string, theme: Theme, additional } export function setNavigationStackStyles(theme: Theme) { - EphemeralStore.allNavigationComponentIds.forEach((componentId) => { + NavigationStore.allNavigationComponentIds.forEach((componentId) => { if (!appearanceControlledScreens.has(componentId)) { setNavigatorStyles(componentId, theme); } diff --git a/index.ts b/index.ts index 4435ca34ab..f4cfdbe4d2 100644 --- a/index.ts +++ b/index.ts @@ -16,7 +16,7 @@ import GlobalEventHandler from './app/managers/global_event_handler'; import NetworkManager from './app/managers/network_manager'; import WebsocketManager from './app/managers/websocket_manager'; import {registerScreens} from './app/screens'; -import EphemeralStore from './app/store/ephemeral_store'; +import NavigationStore from './app/store/navigation_store'; import setFontFamily from './app/utils/font_family'; import './app/utils/emoji'; // Imported to ensure it is loaded when used @@ -92,7 +92,7 @@ function screenWillAppear({componentId}: ComponentDidAppearEvent) { function screenDidAppearListener({componentId, passProps, componentType}: ComponentDidAppearEvent) { if (!(passProps as any)?.overlay && componentType === 'Component') { - EphemeralStore.addNavigationComponentId(componentId); + NavigationStore.addNavigationComponentId(componentId); } } @@ -102,25 +102,25 @@ function screenDidDisappearListener({componentId}: ComponentDidDisappearEvent) { DeviceEventEmitter.emit(Events.PAUSE_KEYBOARD_TRACKING_VIEW, false); } - if (EphemeralStore.getNavigationTopComponentId() === Screens.HOME) { + if (NavigationStore.getNavigationTopComponentId() === Screens.HOME) { DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true); } } } function screenPoppedListener({componentId}: ScreenPoppedEvent) { - EphemeralStore.removeNavigationComponentId(componentId); - if (EphemeralStore.getNavigationTopComponentId() === Screens.HOME) { + NavigationStore.removeNavigationComponentId(componentId); + if (NavigationStore.getNavigationTopComponentId() === Screens.HOME) { DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true); } } function modalDismissedListener({componentId}: ModalDismissedEvent) { - const topScreen = EphemeralStore.getNavigationTopComponentId(); - const topModal = EphemeralStore.getNavigationTopModalId(); + const topScreen = NavigationStore.getNavigationTopComponentId(); + const topModal = NavigationStore.getNavigationTopModalId(); const toRemove = topScreen === topModal ? topModal : componentId; - EphemeralStore.removeNavigationModal(toRemove); - if (EphemeralStore.getNavigationTopComponentId() === Screens.HOME) { + NavigationStore.removeNavigationModal(toRemove); + if (NavigationStore.getNavigationTopComponentId() === Screens.HOME) { DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true); } }