[Gekidou] cleanup fixes (#6376)

* Separate NavigationStore from EphemeralStore

* Prevent WS to mark channel as read while switching to the channel

* Fix mark channel as unread
This commit is contained in:
Elias Nahum
2022-06-13 08:07:24 -04:00
committed by GitHub
parent c365ecea54
commit 8795ca85b5
24 changed files with 241 additions and 204 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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});

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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<Props, State> {
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 {

View File

@@ -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<Props, State> {
onBackPress = () => {
const {componentId} = this.props;
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
if (NavigationStore.getNavigationTopComponentId() === componentId) {
if (this.props.isModal) {
dismissModal({componentId});
} else {

View File

@@ -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;
}

View File

@@ -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++;

View File

@@ -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'}),

View File

@@ -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);
}
};

View File

@@ -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 = {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<string>();
private archivingChannels = new Set<string>();
private convertingChannels = new Set<string>();
private switchingToChannel = new Set<string>();
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();

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);
}
}