forked from Ivasoft/mattermost-mobile
feat: Channel notification preferences (#7160)
* feat: Channel notification preferences * feedback review * use button color for the icon
This commit is contained in:
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type {MessageDescriptor} from '@formatjs/intl/src/types';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginBottom: 30,
|
||||
},
|
||||
header: {
|
||||
marginHorizontal: 15,
|
||||
marginBottom: 10,
|
||||
fontSize: 13,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
footer: {
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export type SectionText = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
values?: MessageDescriptor;
|
||||
}
|
||||
|
||||
export type BlockProps = {
|
||||
children: React.ReactNode;
|
||||
disableFooter?: boolean;
|
||||
disableHeader?: boolean;
|
||||
footerText?: SectionText;
|
||||
headerText?: SectionText;
|
||||
containerStyles?: StyleProp<ViewStyle>;
|
||||
headerStyles?: StyleProp<TextStyle>;
|
||||
footerStyles?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
const Block = ({
|
||||
children,
|
||||
containerStyles,
|
||||
disableFooter,
|
||||
disableHeader,
|
||||
footerText,
|
||||
headerStyles,
|
||||
headerText,
|
||||
footerStyles,
|
||||
}: BlockProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{(headerText && !disableHeader) &&
|
||||
<FormattedText
|
||||
defaultMessage={headerText.defaultMessage}
|
||||
id={headerText.id}
|
||||
values={headerText.values}
|
||||
style={[styles.header, headerStyles]}
|
||||
/>
|
||||
}
|
||||
<View style={containerStyles}>
|
||||
{children}
|
||||
</View>
|
||||
{(footerText && !disableFooter) &&
|
||||
<FormattedText
|
||||
defaultMessage={footerText.defaultMessage}
|
||||
id={footerText.id}
|
||||
style={[styles.footer, footerStyles]}
|
||||
values={footerText.values}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Block;
|
||||
@@ -2,12 +2,15 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {StyleProp, Text, TextStyle, ViewStyle} from 'react-native';
|
||||
import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native';
|
||||
import RNButton from 'react-native-button';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
|
||||
type Props = {
|
||||
type ConditionalProps = | {iconName: string; iconSize: number} | {iconName?: never; iconSize?: never}
|
||||
|
||||
type Props = ConditionalProps & {
|
||||
theme: Theme;
|
||||
backgroundStyle?: StyleProp<ViewStyle>;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
@@ -20,6 +23,11 @@ type Props = {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {flexDirection: 'row'},
|
||||
icon: {marginRight: 7},
|
||||
});
|
||||
|
||||
const Button = ({
|
||||
theme,
|
||||
backgroundStyle,
|
||||
@@ -31,6 +39,8 @@ const Button = ({
|
||||
onPress,
|
||||
text,
|
||||
testID,
|
||||
iconName,
|
||||
iconSize,
|
||||
}: Props) => {
|
||||
const bgStyle = useMemo(() => [
|
||||
buttonBackgroundStyle(theme, size, emphasis, buttonType, buttonState),
|
||||
@@ -48,12 +58,22 @@ const Button = ({
|
||||
onPress={onPress}
|
||||
testID={testID}
|
||||
>
|
||||
<Text
|
||||
style={txtStyle}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
<View style={styles.container}>
|
||||
{Boolean(iconName) &&
|
||||
<CompassIcon
|
||||
name={iconName!}
|
||||
size={iconSize}
|
||||
color={StyleSheet.flatten(txtStyle).color}
|
||||
style={styles.icon}
|
||||
/>
|
||||
}
|
||||
<Text
|
||||
style={txtStyle}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
</RNButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
import {observeChannelsWithCalls} from '@calls/state';
|
||||
import {General} from '@constants';
|
||||
import {withServerUrl} from '@context/server';
|
||||
import {observeChannelSettings, observeMyChannel, queryChannelMembers} from '@queries/servers/channel';
|
||||
import {observeIsMutedSetting, observeMyChannel, queryChannelMembers} from '@queries/servers/channel';
|
||||
import {queryDraft} from '@queries/servers/drafts';
|
||||
import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
@@ -19,7 +19,6 @@ import ChannelItem from './channel_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
channel: ChannelModel;
|
||||
@@ -27,8 +26,6 @@ type EnhanceProps = WithDatabaseArgs & {
|
||||
serverUrl?: string;
|
||||
}
|
||||
|
||||
const observeIsMutedSetting = (mc: MyChannelModel) => observeChannelSettings(mc.database, mc.id).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION)));
|
||||
|
||||
const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
channel,
|
||||
database,
|
||||
@@ -53,7 +50,7 @@ const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
if (!mc) {
|
||||
return of$(false);
|
||||
}
|
||||
return observeIsMutedSetting(mc);
|
||||
return observeIsMutedSetting(database, mc.id);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
91
app/components/settings/block.tsx
Normal file
91
app/components/settings/block.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
|
||||
type SectionText = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
values?: MessageDescriptor;
|
||||
}
|
||||
|
||||
type SettingBlockProps = {
|
||||
children: React.ReactNode;
|
||||
containerStyles?: StyleProp<ViewStyle>;
|
||||
disableFooter?: boolean;
|
||||
disableHeader?: boolean;
|
||||
footerStyles?: StyleProp<TextStyle>;
|
||||
footerText?: SectionText;
|
||||
headerStyles?: StyleProp<TextStyle>;
|
||||
headerText?: SectionText;
|
||||
onLayout?: (event: LayoutChangeEvent) => void;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginBottom: 30,
|
||||
},
|
||||
contentContainerStyle: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
header: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 300, 'SemiBold'),
|
||||
marginBottom: 8,
|
||||
marginLeft: 20,
|
||||
marginTop: 12,
|
||||
marginRight: 15,
|
||||
},
|
||||
footer: {
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SettingBlock = ({
|
||||
children, containerStyles, disableFooter, disableHeader,
|
||||
footerStyles, footerText, headerStyles, headerText, onLayout,
|
||||
}: SettingBlockProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
{(headerText && !disableHeader) &&
|
||||
<FormattedText
|
||||
defaultMessage={headerText.defaultMessage}
|
||||
id={headerText.id}
|
||||
values={headerText.values}
|
||||
style={[styles.header, headerStyles]}
|
||||
/>
|
||||
}
|
||||
<View style={[styles.contentContainerStyle, containerStyles]}>
|
||||
{children}
|
||||
</View>
|
||||
{(footerText && !disableFooter) &&
|
||||
<FormattedText
|
||||
defaultMessage={footerText.defaultMessage}
|
||||
id={footerText.id}
|
||||
style={[styles.footer, footerStyles]}
|
||||
values={footerText.values}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingBlock;
|
||||
@@ -7,11 +7,12 @@ import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem, {OptionItemProps} from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from './config';
|
||||
import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from '../../screens/settings/config';
|
||||
|
||||
import SettingSeparator from './separator';
|
||||
|
||||
type SettingsConfig = keyof typeof SettingOptionConfig | keyof typeof NotificationsOptionConfig| keyof typeof DisplayOptionConfig
|
||||
type SettingOptionProps = {
|
||||
@@ -10,7 +10,7 @@ export const CALL = 'Call';
|
||||
export const CHANNEL = 'Channel';
|
||||
export const CHANNEL_ADD_PEOPLE = 'ChannelAddPeople';
|
||||
export const CHANNEL_INFO = 'ChannelInfo';
|
||||
export const CHANNEL_MENTION = 'ChannelMention';
|
||||
export const CHANNEL_NOTIFICATION_PREFERENCES = 'ChannelNotificationPreferences';
|
||||
export const CODE = 'Code';
|
||||
export const CREATE_DIRECT_MESSAGE = 'CreateDirectMessage';
|
||||
export const CREATE_OR_EDIT_CHANNEL = 'CreateOrEditChannel';
|
||||
@@ -79,7 +79,7 @@ export default {
|
||||
CHANNEL,
|
||||
CHANNEL_ADD_PEOPLE,
|
||||
CHANNEL_INFO,
|
||||
CHANNEL_MENTION,
|
||||
CHANNEL_NOTIFICATION_PREFERENCES,
|
||||
CODE,
|
||||
CREATE_DIRECT_MESSAGE,
|
||||
CREATE_OR_EDIT_CHANNEL,
|
||||
@@ -172,6 +172,5 @@ export const SCREENS_AS_BOTTOM_SHEET = new Set<string>([
|
||||
|
||||
export const NOT_READY = [
|
||||
CHANNEL_ADD_PEOPLE,
|
||||
CHANNEL_MENTION,
|
||||
CREATE_TEAM,
|
||||
];
|
||||
|
||||
@@ -628,6 +628,10 @@ export const observeChannelSettings = (database: Database, channelId: string) =>
|
||||
);
|
||||
};
|
||||
|
||||
export const observeIsMutedSetting = (database: Database, channelId: string) => {
|
||||
return observeChannelSettings(database, channelId).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION)));
|
||||
};
|
||||
|
||||
export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[], excludeIds?: string[]) => {
|
||||
const ids = myChannels.map((c) => c.id);
|
||||
const idsStr = `'${ids.join("','")}'`;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {isTypeDMorGM} from '@utils/channel';
|
||||
import EditChannel from './edit_channel';
|
||||
import IgnoreMentions from './ignore_mentions';
|
||||
import Members from './members';
|
||||
import NotificationPreference from './notification_preference';
|
||||
import PinnedMessages from './pinned_messages';
|
||||
|
||||
type Props = {
|
||||
@@ -26,7 +27,7 @@ const Options = ({channelId, type, callsEnabled}: Props) => {
|
||||
{type !== General.DM_CHANNEL &&
|
||||
<IgnoreMentions channelId={channelId}/>
|
||||
}
|
||||
{/*<NotificationPreference channelId={channelId}/>*/}
|
||||
<NotificationPreference channelId={channelId}/>
|
||||
<PinnedMessages channelId={channelId}/>
|
||||
{type !== General.DM_CHANNEL &&
|
||||
<Members channelId={channelId}/>
|
||||
|
||||
@@ -6,7 +6,9 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeChannelSettings} from '@queries/servers/channel';
|
||||
import {observeChannel, observeChannelSettings} from '@queries/servers/channel';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import NotificationPreference from './notification_preference';
|
||||
|
||||
@@ -17,13 +19,17 @@ type Props = WithDatabaseArgs & {
|
||||
}
|
||||
|
||||
const enhanced = withObservables(['channelId'], ({channelId, database}: Props) => {
|
||||
const displayName = observeChannel(database, channelId).pipe(switchMap((c) => of$(c?.displayName)));
|
||||
const settings = observeChannelSettings(database, channelId);
|
||||
const userNotifyLevel = observeCurrentUser(database).pipe(switchMap((u) => of$(getNotificationProps(u).push)));
|
||||
const notifyLevel = settings.pipe(
|
||||
switchMap((s) => of$(s?.notifyProps.push)),
|
||||
);
|
||||
|
||||
return {
|
||||
displayName,
|
||||
notifyLevel,
|
||||
userNotifyLevel,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -7,54 +7,85 @@ import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {NotificationLevel, Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import type {Options} from 'react-native-navigation';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
displayName: string;
|
||||
notifyLevel: NotificationLevel;
|
||||
userNotifyLevel: NotificationLevel;
|
||||
}
|
||||
|
||||
const NotificationPreference = ({channelId, notifyLevel}: Props) => {
|
||||
const notificationLevel = (notifyLevel: NotificationLevel) => {
|
||||
let id = '';
|
||||
let defaultMessage = '';
|
||||
switch (notifyLevel) {
|
||||
case NotificationLevel.ALL: {
|
||||
id = t('channel_info.notification.all');
|
||||
defaultMessage = 'All';
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.MENTION: {
|
||||
id = t('channel_info.notification.mention');
|
||||
defaultMessage = 'Mentions';
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.NONE: {
|
||||
id = t('channel_info.notification.none');
|
||||
defaultMessage = 'Never';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
id = t('channel_info.notification.default');
|
||||
defaultMessage = 'Default';
|
||||
break;
|
||||
}
|
||||
|
||||
return {id, defaultMessage};
|
||||
};
|
||||
|
||||
const NotificationPreference = ({channelId, displayName, notifyLevel, userNotifyLevel}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
const title = formatMessage({id: 'channel_info.mobile_notifications', defaultMessage: 'Mobile Notifications'});
|
||||
|
||||
const goToMentions = preventDoubleTap(() => {
|
||||
goToScreen(Screens.CHANNEL_MENTION, title, {channelId});
|
||||
const goToChannelNotificationPreferences = preventDoubleTap(() => {
|
||||
const options: Options = {
|
||||
topBar: {
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
text: displayName,
|
||||
},
|
||||
backButton: {
|
||||
popStackOnPress: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
goToScreen(Screens.CHANNEL_NOTIFICATION_PREFERENCES, title, {channelId}, options);
|
||||
});
|
||||
|
||||
const notificationLevelToText = () => {
|
||||
let id = '';
|
||||
let defaultMessage = '';
|
||||
switch (notifyLevel) {
|
||||
case NotificationLevel.ALL: {
|
||||
id = t('channel_info.notification.all');
|
||||
defaultMessage = 'All';
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.MENTION: {
|
||||
id = t('channel_info.notification.mention');
|
||||
defaultMessage = 'Mentions';
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.NONE: {
|
||||
id = t('channel_info.notification.none');
|
||||
defaultMessage = 'Never';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
id = t('channel_info.notification.default');
|
||||
defaultMessage = 'Default';
|
||||
break;
|
||||
if (notifyLevel === NotificationLevel.DEFAULT) {
|
||||
const userLevel = notificationLevel(userNotifyLevel);
|
||||
return formatMessage(userLevel);
|
||||
}
|
||||
|
||||
return formatMessage({id, defaultMessage});
|
||||
const channelLevel = notificationLevel(notifyLevel);
|
||||
return formatMessage(channelLevel);
|
||||
};
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
action={goToMentions}
|
||||
action={goToChannelNotificationPreferences}
|
||||
label={title}
|
||||
icon='cellphone'
|
||||
type={Platform.select({ios: 'arrow', default: 'default'})}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {LayoutAnimation} from 'react-native';
|
||||
import {useSharedValue} from 'react-native-reanimated';
|
||||
|
||||
import {updateChannelNotifyProps} from '@actions/remote/channel';
|
||||
import SettingsContainer from '@components/settings/container';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import useBackNavigation from '@hooks/navigate_back';
|
||||
|
||||
import {popTopScreen} from '../navigation';
|
||||
|
||||
import MutedBanner, {MUTED_BANNER_HEIGHT} from './muted_banner';
|
||||
import NotifyAbout, {BLOCK_TITLE_HEIGHT} from './notify_about';
|
||||
import ResetToDefault from './reset';
|
||||
import ThreadReplies from './thread_replies';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
componentId: AvailableScreens;
|
||||
defaultLevel: NotificationLevel;
|
||||
defaultThreadReplies: 'all' | 'mention';
|
||||
isCRTEnabled: boolean;
|
||||
isMuted: boolean;
|
||||
notifyLevel?: NotificationLevel;
|
||||
notifyThreadReplies?: 'all' | 'mention';
|
||||
}
|
||||
|
||||
const ChannelNotificationPreferences = ({channelId, componentId, defaultLevel, defaultThreadReplies, isCRTEnabled, isMuted, notifyLevel, notifyThreadReplies}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const defaultNotificationReplies = defaultThreadReplies === 'all';
|
||||
const diffNotificationLevel = notifyLevel !== 'default' && notifyLevel !== defaultLevel;
|
||||
const notifyTitleTop = useSharedValue((isMuted ? MUTED_BANNER_HEIGHT : 0) + BLOCK_TITLE_HEIGHT);
|
||||
const [notifyAbout, setNotifyAbout] = useState<NotificationLevel>((notifyLevel === undefined || notifyLevel === 'default') ? defaultLevel : notifyLevel);
|
||||
const [threadReplies, setThreadReplies] = useState<boolean>((notifyThreadReplies || defaultThreadReplies) === 'all');
|
||||
const [resetDefaultVisible, setResetDefaultVisible] = useState(diffNotificationLevel || defaultNotificationReplies !== threadReplies);
|
||||
|
||||
useDidUpdate(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
}, [isMuted]);
|
||||
|
||||
const onResetPressed = useCallback(() => {
|
||||
setResetDefaultVisible(false);
|
||||
setNotifyAbout(defaultLevel);
|
||||
setThreadReplies(defaultNotificationReplies);
|
||||
}, [defaultLevel, defaultNotificationReplies]);
|
||||
|
||||
const onNotificationLevel = useCallback((level: NotificationLevel) => {
|
||||
setNotifyAbout(level);
|
||||
setResetDefaultVisible(level !== defaultLevel || defaultNotificationReplies !== threadReplies);
|
||||
}, [defaultLevel, defaultNotificationReplies, threadReplies]);
|
||||
|
||||
const onSetThreadReplies = useCallback((value: boolean) => {
|
||||
setThreadReplies(value);
|
||||
setResetDefaultVisible(defaultNotificationReplies !== value || notifyAbout !== defaultLevel);
|
||||
}, [defaultLevel, defaultNotificationReplies, notifyAbout]);
|
||||
|
||||
const save = useCallback(() => {
|
||||
const pushThreads = threadReplies ? 'all' : 'mention';
|
||||
|
||||
if (notifyLevel !== notifyAbout || (isCRTEnabled && pushThreads !== notifyThreadReplies)) {
|
||||
const props: Partial<ChannelNotifyProps> = {push: notifyAbout};
|
||||
if (isCRTEnabled) {
|
||||
props.push_threads = pushThreads;
|
||||
}
|
||||
|
||||
updateChannelNotifyProps(serverUrl, channelId, props);
|
||||
}
|
||||
popTopScreen(componentId);
|
||||
}, [channelId, componentId, isCRTEnabled, notifyAbout, notifyLevel, notifyThreadReplies, serverUrl, threadReplies]);
|
||||
|
||||
useBackNavigation(save);
|
||||
useAndroidHardwareBackHandler(componentId, save);
|
||||
|
||||
return (
|
||||
<SettingsContainer testID='push_notification_settings'>
|
||||
{isMuted && <MutedBanner channelId={channelId}/>}
|
||||
{resetDefaultVisible &&
|
||||
<ResetToDefault
|
||||
onPress={onResetPressed}
|
||||
topPosition={notifyTitleTop}
|
||||
/>
|
||||
}
|
||||
<NotifyAbout
|
||||
defaultLevel={defaultLevel}
|
||||
isMuted={isMuted}
|
||||
notifyLevel={notifyAbout}
|
||||
notifyTitleTop={notifyTitleTop}
|
||||
onPress={onNotificationLevel}
|
||||
/>
|
||||
{isCRTEnabled &&
|
||||
<ThreadReplies
|
||||
isSelected={threadReplies}
|
||||
onPress={onSetThreadReplies}
|
||||
notifyLevel={notifyAbout}
|
||||
/>
|
||||
}
|
||||
</SettingsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelNotificationPreferences;
|
||||
53
app/screens/channel_notification_preferences/index.ts
Normal file
53
app/screens/channel_notification_preferences/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeChannelSettings, observeIsMutedSetting} from '@queries/servers/channel';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import ChannelNotificationPreferences from './channel_notification_preferences';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type EnhancedProps = WithDatabaseArgs & {
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
const enhanced = withObservables([], ({channelId, database}: EnhancedProps) => {
|
||||
const settings = observeChannelSettings(database, channelId);
|
||||
const isCRTEnabled = observeIsCRTEnabled(database);
|
||||
const isMuted = observeIsMutedSetting(database, channelId);
|
||||
const notifyProps = observeCurrentUser(database).pipe(switchMap((u) => of$(getNotificationProps(u))));
|
||||
|
||||
const notifyLevel = settings.pipe(
|
||||
switchMap((s) => of$(s?.notifyProps.push)),
|
||||
);
|
||||
|
||||
const notifyThreadReplies = settings.pipe(
|
||||
switchMap((s) => of$(s?.notifyProps.push_threads)),
|
||||
);
|
||||
|
||||
const defaultLevel = notifyProps.pipe(
|
||||
switchMap((n) => of$(n?.push)),
|
||||
);
|
||||
const defaultThreadReplies = notifyProps.pipe(
|
||||
switchMap((n) => of$(n?.push_threads)),
|
||||
);
|
||||
|
||||
return {
|
||||
isCRTEnabled,
|
||||
isMuted,
|
||||
notifyLevel,
|
||||
notifyThreadReplies,
|
||||
defaultLevel,
|
||||
defaultThreadReplies,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ChannelNotificationPreferences));
|
||||
102
app/screens/channel_notification_preferences/muted_banner.tsx
Normal file
102
app/screens/channel_notification_preferences/muted_banner.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import Animated, {FlipOutXUp} from 'react-native-reanimated';
|
||||
|
||||
import {toggleMuteChannel} from '@actions/remote/channel';
|
||||
import Button from '@app/components/button';
|
||||
import CompassIcon from '@app/components/compass_icon';
|
||||
import FormattedText from '@app/components/formatted_text';
|
||||
import {useServerUrl} from '@app/context/server';
|
||||
import {useTheme} from '@app/context/theme';
|
||||
import {preventDoubleTap} from '@app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
export const MUTED_BANNER_HEIGHT = 200;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
button: {width: '55%'},
|
||||
container: {
|
||||
backgroundColor: changeOpacity(theme.sidebarTextActiveBorder, 0.16),
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 20,
|
||||
marginVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
height: MUTED_BANNER_HEIGHT,
|
||||
},
|
||||
contentText: {
|
||||
...typography('Body', 200),
|
||||
color: theme.centerChannelColor,
|
||||
marginTop: 12,
|
||||
marginBottom: 16,
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
marginTop: 16,
|
||||
},
|
||||
title: {
|
||||
...typography('Heading', 200),
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 10,
|
||||
paddingTop: 5,
|
||||
},
|
||||
}));
|
||||
|
||||
const MutedBanner = ({channelId}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
toggleMuteChannel(serverUrl, channelId, false);
|
||||
}), [channelId, serverUrl]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
exiting={FlipOutXUp}
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.titleContainer}>
|
||||
<CompassIcon
|
||||
name='bell-off-outline'
|
||||
size={24}
|
||||
color={theme.linkColor}
|
||||
/>
|
||||
<FormattedText
|
||||
id='channel_notification_preferences.muted_title'
|
||||
defaultMessage='This channel is muted'
|
||||
style={styles.title}
|
||||
/>
|
||||
</View>
|
||||
<FormattedText
|
||||
id='channel_notification_preferences.muted_content'
|
||||
defaultMessage='You can change the notification settings, but you will not receive notifications until the channel is unmuted.'
|
||||
style={styles.contentText}
|
||||
/>
|
||||
<Button
|
||||
buttonType='default'
|
||||
onPress={onPress}
|
||||
text={formatMessage({
|
||||
id: 'channel_notification_preferences.unmute_content',
|
||||
defaultMessage: 'Unmute channel',
|
||||
})}
|
||||
theme={theme}
|
||||
backgroundStyle={styles.button}
|
||||
iconName='bell-outline'
|
||||
iconSize={18}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default MutedBanner;
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {LayoutChangeEvent, View} from 'react-native';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {NotificationLevel} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import type {SharedValue} from 'react-native-reanimated';
|
||||
|
||||
type Props = {
|
||||
isMuted: boolean;
|
||||
defaultLevel: NotificationLevel;
|
||||
notifyLevel: NotificationLevel;
|
||||
notifyTitleTop: SharedValue<number>;
|
||||
onPress: (level: NotificationLevel) => void;
|
||||
}
|
||||
|
||||
type NotifPrefOptions = {
|
||||
defaultMessage: string;
|
||||
id: string;
|
||||
testID: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const BLOCK_TITLE_HEIGHT = 13;
|
||||
|
||||
const NOTIFY_ABOUT = {id: t('channel_notification_preferences.notify_about'), defaultMessage: 'Notify me about...'};
|
||||
|
||||
const NOTIFY_OPTIONS: Record<string, NotifPrefOptions> = {
|
||||
[NotificationLevel.ALL]: {
|
||||
defaultMessage: 'All new messages',
|
||||
id: t('channel_notification_preferences.notification.all'),
|
||||
testID: 'channel_notification_preferences.notification.all',
|
||||
value: NotificationLevel.ALL,
|
||||
},
|
||||
[NotificationLevel.MENTION]: {
|
||||
defaultMessage: 'Mentions, direct messages only',
|
||||
id: t('channel_notification_preferences.notification.mention'),
|
||||
testID: 'channel_notification_preferences.notification.mention',
|
||||
value: NotificationLevel.MENTION,
|
||||
},
|
||||
[NotificationLevel.NONE]: {
|
||||
defaultMessage: 'Nothing',
|
||||
id: t('channel_notification_preferences.notification.none'),
|
||||
testID: 'channel_notification_preferences.notification.none',
|
||||
value: NotificationLevel.NONE,
|
||||
},
|
||||
};
|
||||
|
||||
const NotifyAbout = ({defaultLevel, isMuted, notifyLevel, notifyTitleTop, onPress}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const onLayout = useCallback((e: LayoutChangeEvent) => {
|
||||
const {y} = e.nativeEvent.layout;
|
||||
|
||||
notifyTitleTop.value = y > 0 ? y + 10 : BLOCK_TITLE_HEIGHT;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
headerText={NOTIFY_ABOUT}
|
||||
headerStyles={{marginTop: isMuted ? 8 : 12}}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
{Object.keys(NOTIFY_OPTIONS).map((key) => {
|
||||
const {id, defaultMessage, value, testID} = NOTIFY_OPTIONS[key];
|
||||
const defaultOption = key === defaultLevel ? formatMessage({id: 'channel_notification_preferences.default', defaultMessage: '(default)'}) : '';
|
||||
const label = `${formatMessage({id, defaultMessage})} ${defaultOption}`;
|
||||
|
||||
return (
|
||||
<View key={`notif_pref_option${key}`}>
|
||||
<SettingOption
|
||||
action={onPress}
|
||||
label={label}
|
||||
selected={notifyLevel === key}
|
||||
testID={testID}
|
||||
type='select'
|
||||
value={value}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotifyAbout;
|
||||
62
app/screens/channel_notification_preferences/reset.tsx
Normal file
62
app/screens/channel_notification_preferences/reset.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
import Animated, {SharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
onPress: () => void;
|
||||
topPosition: SharedValue<number>;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
zIndex: 1,
|
||||
},
|
||||
row: {flexDirection: 'row'},
|
||||
text: {
|
||||
color: theme.linkColor,
|
||||
marginLeft: 7,
|
||||
...typography('Heading', 100),
|
||||
},
|
||||
}));
|
||||
|
||||
const ResetToDefault = ({onPress, topPosition}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
top: withTiming(topPosition.value, {duration: 100}),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, animatedStyle]}>
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
style={styles.row}
|
||||
>
|
||||
<CompassIcon
|
||||
name='refresh'
|
||||
size={18}
|
||||
color={theme.linkColor}
|
||||
/>
|
||||
<FormattedText
|
||||
id='channel_notification_preferences.reset_default'
|
||||
defaultMessage='Reset to default'
|
||||
style={styles.text}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetToDefault;
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {NotificationLevel} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
type Props = {
|
||||
isSelected: boolean;
|
||||
notifyLevel: NotificationLevel;
|
||||
onPress: (selected: boolean) => void;
|
||||
}
|
||||
|
||||
type NotifPrefOptions = {
|
||||
defaultMessage: string;
|
||||
id: string;
|
||||
testID: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const THREAD_REPLIES = {id: t('channel_notification_preferences.thread_replies'), defaultMessage: 'Thread replies'};
|
||||
const NOTIFY_OPTIONS_THREAD: Record<string, NotifPrefOptions> = {
|
||||
THREAD_REPLIES: {
|
||||
defaultMessage: 'Notify me about replies to threads I’m following in this channel',
|
||||
id: t('channel_notification_preferences.notification.thread_replies'),
|
||||
testID: 'channel_notification_preferences.notification.thread_replies',
|
||||
value: 'thread_replies',
|
||||
},
|
||||
};
|
||||
|
||||
const NotifyAbout = ({isSelected, notifyLevel, onPress}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
if ([NotificationLevel.NONE, NotificationLevel.ALL].includes(notifyLevel)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingBlock headerText={THREAD_REPLIES}>
|
||||
<SettingOption
|
||||
action={onPress}
|
||||
label={formatMessage({id: NOTIFY_OPTIONS_THREAD.THREAD_REPLIES.id, defaultMessage: NOTIFY_OPTIONS_THREAD.THREAD_REPLIES.defaultMessage})}
|
||||
testID={NOTIFY_OPTIONS_THREAD.THREAD_REPLIES.testID}
|
||||
type='toggle'
|
||||
selected={isSelected}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotifyAbout;
|
||||
@@ -79,6 +79,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
case Screens.CHANNEL:
|
||||
screen = withServerDatabase(require('@screens/channel').default);
|
||||
break;
|
||||
case Screens.CHANNEL_NOTIFICATION_PREFERENCES:
|
||||
screen = withServerDatabase(require('@screens/channel_notification_preferences').default);
|
||||
break;
|
||||
case Screens.CHANNEL_INFO:
|
||||
screen = withServerDatabase(require('@screens/channel_info').default);
|
||||
break;
|
||||
|
||||
@@ -9,13 +9,13 @@ import DeviceInfo from 'react-native-device-info';
|
||||
import Config from '@assets/config.json';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import AboutLinks from '@constants/about_links';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import SettingContainer from '@screens/settings/setting_container';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
@@ -5,18 +5,17 @@ import React, {useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, TouchableOpacity} from 'react-native';
|
||||
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {deleteFileCache, getAllFilesInCachesDirectory, getFormattedFileSize} from '@utils/file';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
import type {ReadDirItem} from 'react-native-fs';
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React, {useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingItem from '@components/settings/item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
@@ -13,9 +15,6 @@ import {gotoSettingsScreen} from '@screens/settings/config';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {getUserTimezoneProps} from '@utils/user';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingItem from '../setting_item';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -5,17 +5,16 @@ import React, {useCallback, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useBackNavigation from '@hooks/navigate_back';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
const CLOCK_TYPE = {
|
||||
|
||||
@@ -5,6 +5,10 @@ import React, {useCallback, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
@@ -12,11 +16,6 @@ import useBackNavigation from '@hooks/navigate_back';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
const crtDescription = {
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useTheme} from '@context/theme';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
|
||||
import SettingOption from '../setting_option';
|
||||
|
||||
const radioItemProps = {checkedBody: true};
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
|
||||
import CustomTheme from './custom_theme';
|
||||
import {ThemeTiles} from './theme_tiles';
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
@@ -14,10 +17,6 @@ import {preventDoubleTap} from '@utils/tap';
|
||||
import {getDeviceTimezone} from '@utils/timezone';
|
||||
import {getTimezoneRegion, getUserTimezoneProps} from '@utils/user';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import React, {useCallback} from 'react';
|
||||
import {TouchableOpacity, View, Text} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useTheme} from '@context/theme';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import {useIntl} from 'react-intl';
|
||||
import {fetchStatusInBatch, updateMe} from '@actions/remote/user';
|
||||
import FloatingTextInput from '@components/floating_text_input_label';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -18,10 +21,6 @@ import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme}
|
||||
import {typography} from '@utils/typography';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ import {Text} from 'react-native';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -18,11 +22,6 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getEmailInterval, getNotificationProps} from '@utils/user';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import {Text} from 'react-native';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import FloatingTextInput from '@components/floating_text_input_label';
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
@@ -18,10 +21,6 @@ import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme}
|
||||
import {typography} from '@utils/typography';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
|
||||
import MentionSettings from './mention_settings';
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
import React, {Dispatch, SetStateAction} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const replyHeaderText = {
|
||||
id: t('notification_settings.mention.reply'),
|
||||
defaultMessage: 'Send reply notifications for',
|
||||
|
||||
@@ -5,15 +5,14 @@ import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useBackNavigation from '@hooks/navigate_back';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
|
||||
import MobileSendPush from './push_send';
|
||||
import MobilePushStatus from './push_status';
|
||||
import MobilePushThread from './push_thread';
|
||||
|
||||
@@ -5,15 +5,14 @@ import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.send_notification.about'),
|
||||
defaultMessage: 'Notify me about...',
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.mobile.trigger_push'),
|
||||
defaultMessage: 'Trigger push notifications when...',
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.push_threads.replies'),
|
||||
defaultMessage: 'Thread replies',
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingItem from '@components/settings/item';
|
||||
import {General, Screens} from '@constants';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {t} from '@i18n';
|
||||
@@ -11,9 +13,6 @@ import {popTopScreen} from '@screens/navigation';
|
||||
import {gotoSettingsScreen} from '@screens/settings/config';
|
||||
import {getEmailInterval, getEmailIntervalTexts, getNotificationProps} from '@utils/user';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingItem from '../setting_item';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -7,11 +7,10 @@ import React from 'react';
|
||||
import {Alert, Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import SettingItem from '@components/settings/item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import SettingItem from '../setting_item';
|
||||
|
||||
type ReportProblemProps = {
|
||||
buildNumber: string;
|
||||
currentTeamId: string;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
|
||||
import Block, {SectionText, BlockProps} from '@components/block';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
blockHeader: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 300, 'SemiBold'),
|
||||
marginBottom: 8,
|
||||
marginLeft: 20,
|
||||
marginTop: 12,
|
||||
},
|
||||
contentContainerStyle: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type SettingBlockProps = {
|
||||
children: React.ReactNode;
|
||||
headerText?: SectionText;
|
||||
} & BlockProps;
|
||||
|
||||
const SettingBlock = ({headerText, ...props}: SettingBlockProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<Block
|
||||
headerText={headerText}
|
||||
headerStyles={styles.blockHeader}
|
||||
containerStyles={styles.contentContainerStyle}
|
||||
{...props}
|
||||
>
|
||||
|
||||
{props.children}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingBlock;
|
||||
@@ -6,19 +6,19 @@ import {useIntl} from 'react-intl';
|
||||
import {Alert, Platform, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import SettingContainer from '@components/settings/container';
|
||||
import SettingItem from '@components/settings/item';
|
||||
import {Screens} from '@constants';
|
||||
import {useServerDisplayName} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {dismissModal, goToScreen, setButtons} from '@screens/navigation';
|
||||
import SettingContainer from '@screens/settings/setting_container';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import ReportProblem from './report_problem';
|
||||
import SettingItem from './setting_item';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ export function filterProfilesMatchingTerm(users: UserProfile[], term: string):
|
||||
});
|
||||
}
|
||||
|
||||
export function getNotificationProps(user: UserModel) {
|
||||
export function getNotificationProps(user?: UserModel) {
|
||||
if (user && user.notifyProps) {
|
||||
return user.notifyProps;
|
||||
}
|
||||
|
||||
@@ -168,6 +168,17 @@
|
||||
"channel_modal.optional": "(optional)",
|
||||
"channel_modal.purpose": "Purpose",
|
||||
"channel_modal.purposeEx": "A channel to file bugs and improvements",
|
||||
"channel_notification_preferences.default": "(default)",
|
||||
"channel_notification_preferences.muted_content": "You can change the notification settings, but you will not receive notifications until the channel is unmuted.",
|
||||
"channel_notification_preferences.muted_title": "This channel is muted",
|
||||
"channel_notification_preferences.notification.all": "All new messages",
|
||||
"channel_notification_preferences.notification.mention": "Mentions, direct messages only",
|
||||
"channel_notification_preferences.notification.none": "Nothing",
|
||||
"channel_notification_preferences.notification.thread_replies": "Notify me about replies to threads I’m following in this channel",
|
||||
"channel_notification_preferences.notify_about": "Notify me about...",
|
||||
"channel_notification_preferences.reset_default": "Reset to default",
|
||||
"channel_notification_preferences.thread_replies": "Thread replies",
|
||||
"channel_notification_preferences.unmute_content": "Unmute channel",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} and {lastUser} were **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one_you": "You were **added to the channel** by {actor}.",
|
||||
|
||||
5
index.ts
5
index.ts
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import TurboLogger from '@mattermost/react-native-turbo-log';
|
||||
import {LogBox, Platform} from 'react-native';
|
||||
import {LogBox, Platform, UIManager} from 'react-native';
|
||||
import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes';
|
||||
import {RUNNING_E2E} from 'react-native-dotenv';
|
||||
import 'react-native-gesture-handler';
|
||||
@@ -53,6 +53,9 @@ if (Platform.OS === 'android') {
|
||||
const ShareExtension = require('share_extension/index.tsx').default;
|
||||
const AppRegistry = require('react-native/Libraries/ReactNative/AppRegistry');
|
||||
AppRegistry.registerComponent('MattermostShare', () => ShareExtension);
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
Navigation.events().registerAppLaunchedListener(async () => {
|
||||
|
||||
1
types/api/channels.d.ts
vendored
1
types/api/channels.d.ts
vendored
@@ -16,6 +16,7 @@ type ChannelNotifyProps = {
|
||||
mark_unread: 'all' | 'mention';
|
||||
push: NotificationLevel;
|
||||
ignore_channel_mentions: 'default' | 'off' | 'on';
|
||||
push_threads: 'all' | 'mention';
|
||||
};
|
||||
type Channel = {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user