forked from Ivasoft/mattermost-mobile
[Gekidou] multiple fixes (#6335)
* Fix navigation stack tracking * Fix hardwareBackPress handlers * Use user locale for formattedTime * Show in-app notifications when in a thread but hide when in the same thread * Fix post draft archived & read only safe area * Do not show reply post option in thread screen * Open permalink as full screen modal * Fix crash when using chinese locale * Fix team list and call handle team change
This commit is contained in:
@@ -119,6 +119,7 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
||||
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
||||
}
|
||||
|
||||
EphemeralStore.setLastViewedThreadId(rootId);
|
||||
goToScreen(Screens.THREAD, '', {rootId}, {
|
||||
topBar: {
|
||||
title: {
|
||||
|
||||
@@ -6,6 +6,8 @@ import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, TextProps} from 'react-native';
|
||||
|
||||
import {getLocaleFromLanguage} from '@i18n';
|
||||
|
||||
type FormattedDateProps = TextProps & {
|
||||
format?: string;
|
||||
timezone?: string | UserTimezone | null;
|
||||
@@ -14,7 +16,7 @@ type FormattedDateProps = TextProps & {
|
||||
|
||||
const FormattedDate = ({format = 'MMM DD, YYYY', timezone, value, ...props}: FormattedDateProps) => {
|
||||
const {locale} = useIntl();
|
||||
moment.locale(locale);
|
||||
moment.locale(getLocaleFromLanguage(locale).toLowerCase());
|
||||
let formattedDate = moment(value).format(format);
|
||||
if (timezone) {
|
||||
let zone: string;
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, TextProps} from 'react-native';
|
||||
|
||||
import {getLocaleFromLanguage} from '@i18n';
|
||||
|
||||
type FormattedTimeProps = TextProps & {
|
||||
isMilitaryTime: boolean;
|
||||
timezone: UserTimezone | string;
|
||||
@@ -12,6 +15,8 @@ type FormattedTimeProps = TextProps & {
|
||||
}
|
||||
|
||||
const FormattedTime = ({isMilitaryTime, timezone, value, ...props}: FormattedTimeProps) => {
|
||||
const {locale} = useIntl();
|
||||
moment.locale(getLocaleFromLanguage(locale).toLowerCase());
|
||||
const getFormattedTime = () => {
|
||||
let format = 'H:mm';
|
||||
if (!isMilitaryTime) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {switchToPenultimateChannel} from '@actions/remote/channel';
|
||||
import FormattedMarkdownText from '@components/formatted_markdown_text';
|
||||
@@ -47,6 +47,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const edges: Edge[] = ['bottom'];
|
||||
|
||||
export default function Archived({
|
||||
testID,
|
||||
deactivated,
|
||||
@@ -78,7 +80,8 @@ export default function Archived({
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
testID={testID}
|
||||
style={style.archivedWrapper}
|
||||
>
|
||||
@@ -98,6 +101,6 @@ export default function Archived({
|
||||
style={style.closeButtonText}
|
||||
/>
|
||||
</Button>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
@@ -40,14 +40,14 @@ const getStyle = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const safeAreaEdges = ['bottom' as const];
|
||||
const edges: Edge[] = ['bottom'];
|
||||
|
||||
const ReadOnlyChannnel = ({testID}: ReadOnlyProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyle(theme);
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={safeAreaEdges}
|
||||
edges={edges}
|
||||
style={style.background}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {handleTeamChange} from '@actions/remote/team';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import BottomSheetContent from '@screens/bottom_sheet/content';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
|
||||
@@ -18,15 +20,17 @@ type Props = {
|
||||
|
||||
export default function AddTeamSlideUp({otherTeams, showTitle = true}: Props) {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const onPressCreate = useCallback(() => {
|
||||
//TODO Create team screen https://mattermost.atlassian.net/browse/MM-43622
|
||||
dismissBottomSheet();
|
||||
}, []);
|
||||
|
||||
const onTeamAdded = useCallback(() => {
|
||||
dismissBottomSheet();
|
||||
}, []);
|
||||
const onTeamAdded = useCallback(async (teamId: string) => {
|
||||
await dismissBottomSheet();
|
||||
handleTeamChange(serverUrl, teamId);
|
||||
}, [serverUrl]);
|
||||
|
||||
return (
|
||||
<BottomSheetContent
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function AddTeam({otherTeams}: Props) {
|
||||
|
||||
let height = CONTAINER_HEIGHT;
|
||||
if (otherTeams.length) {
|
||||
height = Math.min(maxHeight, HEADER_HEIGHT + (otherTeams.length * ITEM_HEIGHT));
|
||||
height = Math.min(maxHeight, HEADER_HEIGHT + ((otherTeams.length + 1) * ITEM_HEIGHT));
|
||||
}
|
||||
|
||||
bottomSheet({
|
||||
|
||||
@@ -9,7 +9,7 @@ import TeamIcon from '@components/team_sidebar/team_list/team_item/team_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
@@ -54,8 +54,10 @@ export default function TeamListItem({team, currentUserId, textColor, iconTextCo
|
||||
const styles = getStyleSheet(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
const onPress = useCallback(async () => {
|
||||
await addUserToTeam(serverUrl, team.id, currentUserId);
|
||||
onTeamAdded(team.id);
|
||||
const {error} = await addUserToTeam(serverUrl, team.id, currentUserId);
|
||||
if (!error) {
|
||||
onTeamAdded(team.id);
|
||||
}
|
||||
}, [onTeamAdded]);
|
||||
|
||||
const displayName = 'displayName' in team ? team.displayName : team.display_name;
|
||||
@@ -75,13 +77,13 @@ export default function TeamListItem({team, currentUserId, textColor, iconTextCo
|
||||
displayName={displayName}
|
||||
lastIconUpdate={lastTeamIconUpdateAt}
|
||||
selected={false}
|
||||
textColor={iconTextColor}
|
||||
backgroundColor={iconBackgroundColor}
|
||||
textColor={iconTextColor || theme.centerChannelColor}
|
||||
backgroundColor={iconBackgroundColor || changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
testID={`${teamListItemTestId}.team_icon`}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={[styles.text, {color: textColor}]}
|
||||
style={[styles.text, textColor && {color: textColor}]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{displayName}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
import {getLocales} from 'react-native-localize';
|
||||
|
||||
import en from '@assets/i18n/en.json';
|
||||
@@ -149,7 +149,8 @@ function loadTranslation(locale?: string) {
|
||||
}
|
||||
|
||||
if (momentData && locale) {
|
||||
moment.updateLocale(locale.toLowerCase(), momentData);
|
||||
const lang = getLocaleFromLanguage(locale).toLowerCase();
|
||||
moment.updateLocale(lang, momentData);
|
||||
} else {
|
||||
resetMomentLocale();
|
||||
}
|
||||
@@ -173,10 +174,11 @@ export function getLocaleFromLanguage(lang: string) {
|
||||
}
|
||||
|
||||
export function resetMomentLocale(locale?: string) {
|
||||
moment.locale(locale || DEFAULT_LOCALE.split('-')[0]);
|
||||
moment.locale(locale?.split('-')[0] || DEFAULT_LOCALE.split('-')[0]);
|
||||
}
|
||||
|
||||
export function getTranslations(locale?: string) {
|
||||
export function getTranslations(lang: string) {
|
||||
const locale = getLocaleFromLanguage(lang);
|
||||
return loadTranslation(locale);
|
||||
}
|
||||
|
||||
|
||||
@@ -147,12 +147,13 @@ 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;
|
||||
if (isTabletDevice) {
|
||||
isChannelScreenVisible = EphemeralStore.getVisibleTab() === Screens.HOME;
|
||||
}
|
||||
|
||||
if (isDifferentChannel || !isChannelScreenVisible) {
|
||||
if (isDifferentChannel || (!isChannelScreenVisible && !isVisibleThread)) {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATION_SHOW_OVERLAY);
|
||||
|
||||
const screen = Screens.IN_APP_NOTIFICATION;
|
||||
|
||||
@@ -12,6 +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 {hapticFeedback} from '@utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -55,12 +56,15 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
|
||||
|
||||
useEffect(() => {
|
||||
const listener = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
if (sheetRef.current) {
|
||||
sheetRef.current.snapTo(1);
|
||||
} else {
|
||||
close();
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
if (sheetRef.current) {
|
||||
sheetRef.current.snapTo(1);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
|
||||
@@ -14,6 +14,7 @@ import {useAppState, useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {useTeamSwitch} from '@hooks/team_switch';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import ChannelHeader from './header';
|
||||
@@ -57,8 +58,12 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
|
||||
let back: NativeEventSubscription|undefined;
|
||||
if (!isTablet && componentId) {
|
||||
back = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
popTopScreen(componentId);
|
||||
return true;
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
popTopScreen(componentId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +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 {getCurrentMomentForTimezone, getRoundedTime, isCustomStatusExpirySupported} from '@utils/helpers';
|
||||
import {mergeNavigationOptions} from '@utils/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
@@ -164,12 +165,16 @@ class CustomStatusModal extends NavigationComponent<Props, State> {
|
||||
}
|
||||
|
||||
onBackPress = () => {
|
||||
if (this.props.isTablet) {
|
||||
DeviceEventEmitter.emit(Events.ACCOUNT_SELECT_TABLET_VIEW, '');
|
||||
} else {
|
||||
dismissModal();
|
||||
const {componentId} = this.props;
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
if (this.props.isTablet) {
|
||||
DeviceEventEmitter.emit(Events.ACCOUNT_SELECT_TABLET_VIEW, '');
|
||||
} else {
|
||||
dismissModal({componentId});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
handleSetStatus = async () => {
|
||||
|
||||
@@ -18,6 +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 {mergeNavigationOptions} from '@utils/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
@@ -111,24 +112,28 @@ class ClearAfterModal extends NavigationComponent<Props, State> {
|
||||
}
|
||||
|
||||
onBackPress = () => {
|
||||
if (this.props.isModal) {
|
||||
dismissModal();
|
||||
} else {
|
||||
popTopScreen();
|
||||
}
|
||||
const {componentId} = this.props;
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
if (this.props.isModal) {
|
||||
dismissModal({componentId});
|
||||
} else {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
onDone = () => {
|
||||
const {handleClearAfterClick, isModal} = this.props;
|
||||
const {componentId, handleClearAfterClick, isModal} = this.props;
|
||||
handleClearAfterClick(this.state.duration, this.state.expiresAt);
|
||||
if (isModal) {
|
||||
dismissModal();
|
||||
dismissModal({componentId});
|
||||
return;
|
||||
}
|
||||
|
||||
popTopScreen();
|
||||
popTopScreen(componentId);
|
||||
};
|
||||
|
||||
handleItemClick = (duration: CustomStatusDuration, expiresAt: string) =>
|
||||
|
||||
@@ -16,6 +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 {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import ProfileForm from './components/form';
|
||||
@@ -104,7 +105,14 @@ const EditProfile = ({
|
||||
}, [userInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', close);
|
||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return () => {
|
||||
backHandler.remove();
|
||||
};
|
||||
@@ -127,8 +135,6 @@ const EditProfile = ({
|
||||
} else {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const enableSaveButton = useCallback((value: boolean) => {
|
||||
|
||||
@@ -5,13 +5,13 @@ import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import {useIsFocused, useNavigation, useRoute} from '@react-navigation/native';
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {BackHandler, StyleSheet, ToastAndroid} from 'react-native';
|
||||
import {BackHandler, DeviceEventEmitter, StyleSheet, ToastAndroid} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import TeamSidebar from '@components/team_sidebar';
|
||||
import {Screens} from '@constants';
|
||||
import {Navigation as NavigationConstants, Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {resetToTeams} from '@screens/navigation';
|
||||
@@ -54,7 +54,9 @@ const ChannelListScreen = (props: ChannelProps) => {
|
||||
const canAddOtherServers = managedConfig?.allowOtherServers !== 'false';
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
const focused = navigation.isFocused() && EphemeralStore.getNavigationTopComponentId() === Screens.HOME;
|
||||
const isHomeScreen = EphemeralStore.getNavigationTopComponentId() === Screens.HOME;
|
||||
const homeTab = EphemeralStore.getVisibleTab() === Screens.HOME;
|
||||
const focused = navigation.isFocused() && isHomeScreen && homeTab;
|
||||
if (!backPressedCount && focused) {
|
||||
backPressedCount++;
|
||||
ToastAndroid.show(intl.formatMessage({
|
||||
@@ -70,6 +72,9 @@ const ChannelListScreen = (props: ChannelProps) => {
|
||||
backPressedCount = 0;
|
||||
}, 2000);
|
||||
return true;
|
||||
} else if (isHomeScreen && !homeTab) {
|
||||
DeviceEventEmitter.emit(NavigationConstants.NAVIGATION_HOME);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [intl]);
|
||||
|
||||
@@ -103,6 +103,7 @@ export default function HomeScreen(props: HomeProps) {
|
||||
>
|
||||
<Tab.Navigator
|
||||
screenOptions={{headerShown: false, lazy: true, unmountOnBlur: false}}
|
||||
backBehavior='none'
|
||||
tabBar={(tabProps: BottomTabBarProps) => (
|
||||
<TabBar
|
||||
{...tabProps}
|
||||
|
||||
@@ -32,7 +32,7 @@ const withIntl = (Screen: React.ComponentType) => {
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={DEFAULT_LOCALE}
|
||||
messages={getTranslations()}
|
||||
messages={getTranslations(DEFAULT_LOCALE)}
|
||||
>
|
||||
<Screen {...props}/>
|
||||
</IntlProvider>
|
||||
|
||||
@@ -581,12 +581,11 @@ export async function dismissAllModals() {
|
||||
}
|
||||
|
||||
try {
|
||||
const modals = EphemeralStore.navigationModalStack;
|
||||
const modals = [...EphemeralStore.getAllNavigationModals()];
|
||||
for await (const modal of modals) {
|
||||
EphemeralStore.removeNavigationModal(modal);
|
||||
await Navigation.dismissModal(modal, {animations: {dismissModal: {enabled: false}}});
|
||||
}
|
||||
|
||||
EphemeralStore.clearNavigationModals();
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there are no modals to
|
||||
// dismiss. We'll do nothing in this case.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {BackHandler, Text, TouchableOpacity, View} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {fetchPostsAround} from '@actions/remote/post';
|
||||
@@ -16,17 +16,21 @@ import {Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {dismissModal} from '@screens/navigation';
|
||||
import ChannelModel from '@typings/database/models/servers/channel';
|
||||
import PostModel from '@typings/database/models/servers/post';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {closePermalink} from '@utils/permalink';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
type Props = {
|
||||
postId: PostModel['id'];
|
||||
channel?: ChannelModel;
|
||||
}
|
||||
|
||||
const edges: Edge[] = ['left', 'right', 'top'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
@@ -145,8 +149,12 @@ function Permalink({channel, postId}: Props) {
|
||||
|
||||
useEffect(() => {
|
||||
const listener = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
handleClose();
|
||||
return true;
|
||||
if (EphemeralStore.getNavigationTopComponentId() === Screens.PERMALINK) {
|
||||
handleClose();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -164,6 +172,7 @@ function Permalink({channel, postId}: Props) {
|
||||
<SafeAreaView
|
||||
style={containerStyle}
|
||||
testID='permalink.screen'
|
||||
edges={edges}
|
||||
>
|
||||
<Animated.View style={style.wrapper}>
|
||||
<View style={style.header}>
|
||||
|
||||
@@ -81,7 +81,7 @@ const PostOptions = ({
|
||||
return (
|
||||
<>
|
||||
{canAddReaction && <ReactionBar postId={post.id}/>}
|
||||
{canReply && <ReplyOption post={post}/>}
|
||||
{canReply && sourceScreen !== Screens.THREAD && <ReplyOption post={post}/>}
|
||||
{shouldRenderFollow &&
|
||||
<FollowThreadOption thread={thread}/>
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {Events, Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {dismissModal} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isDateLine, getDateForDateLine, selectOrderedPosts} from '@utils/post_list';
|
||||
|
||||
import EmptyState from './components/empty';
|
||||
@@ -72,10 +73,7 @@ function SavedMessages({
|
||||
const close = () => {
|
||||
if (componentId) {
|
||||
dismissModal({componentId});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -106,7 +104,14 @@ function SavedMessages({
|
||||
useEffect(() => {
|
||||
let listener: EventSubscription|undefined;
|
||||
if (!isTablet && componentId) {
|
||||
listener = BackHandler.addEventListener('hardwareBackPress', close);
|
||||
listener = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return () => listener?.remove();
|
||||
|
||||
@@ -12,6 +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 {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
@@ -84,8 +85,6 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
|
||||
|
||||
const close = useCallback(() => {
|
||||
dismissModal({componentId});
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -95,7 +94,14 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', close);
|
||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return () => {
|
||||
backHandler.remove();
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ class EphemeralStore {
|
||||
private leavingChannels = new Set<string>();
|
||||
private archivingChannels = new Set<string>();
|
||||
private convertingChannels = new Set<string>();
|
||||
private lastViewedThreadId = '';
|
||||
|
||||
addNavigationComponentId = (componentId: string) => {
|
||||
this.addToNavigationComponentIdStack(componentId);
|
||||
@@ -36,7 +37,7 @@ class EphemeralStore {
|
||||
addToNavigationComponentIdStack = (componentId: string) => {
|
||||
const index = this.navigationComponentIdStack.indexOf(componentId);
|
||||
if (index >= 0) {
|
||||
this.navigationComponentIdStack = this.navigationComponentIdStack.slice(index, 1);
|
||||
this.navigationComponentIdStack.splice(index, 1);
|
||||
}
|
||||
|
||||
this.navigationComponentIdStack.unshift(componentId);
|
||||
@@ -58,6 +59,8 @@ class EphemeralStore {
|
||||
|
||||
getAllNavigationComponents = () => this.allNavigationComponentIds;
|
||||
|
||||
getAllNavigationModals = () => this.navigationModalStack;
|
||||
|
||||
getNavigationTopComponentId = () => {
|
||||
return this.navigationComponentIdStack[0];
|
||||
};
|
||||
@@ -90,7 +93,7 @@ class EphemeralStore {
|
||||
};
|
||||
|
||||
removeNavigationModal = (componentId: string) => {
|
||||
this.removeNavigationComponent(componentId);
|
||||
this.removeNavigationComponentId(componentId);
|
||||
const index = this.navigationModalStack.indexOf(componentId);
|
||||
|
||||
if (index >= 0) {
|
||||
@@ -209,6 +212,15 @@ class EphemeralStore {
|
||||
getPushProxyVerificationState = (serverUrl: string) => {
|
||||
return this.pushProxyVerification[serverUrl];
|
||||
};
|
||||
|
||||
// Ephemeral for the last viewed thread
|
||||
getLastViewedThreadId = () => {
|
||||
return this.lastViewedThreadId;
|
||||
};
|
||||
|
||||
setLastViewedThreadId = (id: string) => {
|
||||
this.lastViewedThreadId = id;
|
||||
};
|
||||
}
|
||||
|
||||
export default new EphemeralStore();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Keyboard} from 'react-native';
|
||||
import {OptionsModalPresentationStyle} from 'react-native-navigation';
|
||||
|
||||
import {dismissAllModals, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
@@ -22,6 +23,7 @@ export const displayPermalink = async (teamName: string, postId: string, openAsP
|
||||
};
|
||||
|
||||
const options = {
|
||||
modalPresentationStyle: OptionsModalPresentationStyle.fullScreen,
|
||||
layout: {
|
||||
componentBackgroundColor: changeOpacity('#000', 0.2),
|
||||
},
|
||||
|
||||
37
index.ts
37
index.ts
@@ -4,7 +4,7 @@
|
||||
import {DeviceEventEmitter, LogBox} from 'react-native';
|
||||
import {RUNNING_E2E} from 'react-native-dotenv';
|
||||
import 'react-native-gesture-handler';
|
||||
import {ComponentDidAppearEvent, ComponentDidDisappearEvent, Navigation} from 'react-native-navigation';
|
||||
import {ComponentDidAppearEvent, ComponentDidDisappearEvent, ModalDismissedEvent, Navigation, ScreenPoppedEvent} from 'react-native-navigation';
|
||||
|
||||
import {Events, Screens} from './app/constants';
|
||||
import DatabaseManager from './app/database/manager';
|
||||
@@ -75,12 +75,14 @@ Navigation.events().registerAppLaunchedListener(async () => {
|
||||
});
|
||||
|
||||
const registerNavigationListeners = () => {
|
||||
Navigation.events().registerComponentDidAppearListener(componentDidAppearListener);
|
||||
Navigation.events().registerComponentDidDisappearListener(componentDidDisappearListener);
|
||||
Navigation.events().registerComponentWillAppearListener(componentWillAppear);
|
||||
Navigation.events().registerComponentDidAppearListener(screenDidAppearListener);
|
||||
Navigation.events().registerComponentDidDisappearListener(screenDidDisappearListener);
|
||||
Navigation.events().registerComponentWillAppearListener(screenWillAppear);
|
||||
Navigation.events().registerScreenPoppedListener(screenPoppedListener);
|
||||
Navigation.events().registerModalDismissedListener(modalDismissedListener);
|
||||
};
|
||||
|
||||
function componentWillAppear({componentId}: ComponentDidAppearEvent) {
|
||||
function screenWillAppear({componentId}: ComponentDidAppearEvent) {
|
||||
if (componentId === Screens.HOME) {
|
||||
DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true);
|
||||
} else if ([Screens.EDIT_POST, Screens.THREAD].includes(componentId)) {
|
||||
@@ -88,16 +90,14 @@ function componentWillAppear({componentId}: ComponentDidAppearEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function componentDidAppearListener({componentId, passProps}: ComponentDidAppearEvent) {
|
||||
if (!(passProps as any)?.overlay) {
|
||||
function screenDidAppearListener({componentId, passProps, componentType}: ComponentDidAppearEvent) {
|
||||
if (!(passProps as any)?.overlay && componentType === 'Component') {
|
||||
EphemeralStore.addNavigationComponentId(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
function componentDidDisappearListener({componentId}: ComponentDidDisappearEvent) {
|
||||
function screenDidDisappearListener({componentId}: ComponentDidDisappearEvent) {
|
||||
if (componentId !== Screens.HOME) {
|
||||
EphemeralStore.removeNavigationComponentId(componentId);
|
||||
|
||||
if ([Screens.EDIT_POST, Screens.THREAD].includes(componentId)) {
|
||||
DeviceEventEmitter.emit(Events.PAUSE_KEYBOARD_TRACKING_VIEW, false);
|
||||
}
|
||||
@@ -107,3 +107,20 @@ function componentDidDisappearListener({componentId}: ComponentDidDisappearEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function screenPoppedListener({componentId}: ScreenPoppedEvent) {
|
||||
EphemeralStore.removeNavigationComponentId(componentId);
|
||||
if (EphemeralStore.getNavigationTopComponentId() === Screens.HOME) {
|
||||
DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true);
|
||||
}
|
||||
}
|
||||
|
||||
function modalDismissedListener({componentId}: ModalDismissedEvent) {
|
||||
const topScreen = EphemeralStore.getNavigationTopComponentId();
|
||||
const topModal = EphemeralStore.getNavigationTopModalId();
|
||||
const toRemove = topScreen === topModal ? topModal : componentId;
|
||||
EphemeralStore.removeNavigationModal(toRemove);
|
||||
if (EphemeralStore.getNavigationTopComponentId() === Screens.HOME) {
|
||||
DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user