MM-36721 [GEKIDOU] Porting Channel Nav Bar (#5550)
* MM_36721 : Added testscript for AppVersion component + Corrected imported Type definition * Added CompassIcon component * Adding TextProps to FormattedText component * Added status bar component * Added User status component * Added ProfilePicture, did_update hook and sorted imports * Added ChannelIcon component * Added ChannelTitle component * Added Channel Nav Bar component * Added channel screen * Added withSafeAreaInsets HOC and Added font compassIcon to Xcode * Fix Android crashes as it is looking for MainSidebar and SettingsSidebar * Revert "Fix Android crashes as it is looking for MainSidebar and SettingsSidebar" This reverts commit 62ea11ae691e83bbe3c5e81c243b8ed89fa96083. * Channel Icon clean up * Updated assets/compass-icons files * Updated channel title component * ProfilePicture - Code clean up * UserStatus component - cleaned * Channel screen fix * Fix TS issue * Update index.tsx * Removed ProfilePicture component To be added when needed * Removed UserStatus component * Added IS_LANDSCAPE constant * Code review correction * Fix ts issue * Added channel.displayName to reinforce security for findAndObserve on potential null teammate profile Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Fix observation on array vs single element * Refactored ChannelTitle component Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Refactored ChannelGuestLabel * Refactored ChannelDisplayName * ChannelTitle cleaned up * Fix roles check * Removing unused user utils * Minor clean up * Fix TS issue * Code Refactored. * Fix render bug in channel_display_name * Added logout button * refactored code Co-authored-by: Avinash Lingaloo <> Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
324dbbd054
commit
64c11580fc
27 changed files with 988 additions and 100 deletions
Binary file not shown.
|
|
@ -6,7 +6,8 @@ import {getTimeZone} from 'react-native-localize';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import {queryUserById} from '@queries/servers/user';
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export const isTimezoneEnabled = (config: Partial<ClientConfig>) => {
|
||||
return config?.ExperimentalTimezone === 'true';
|
||||
|
|
@ -33,13 +34,13 @@ export const autoUpdateTimezone = async (serverUrl: string, {deviceTimezone, use
|
|||
|
||||
if (currentTimezone.useAutomaticTimezone && newTimezoneExists) {
|
||||
const timezone = {useAutomaticTimezone: 'true', automaticTimezone: deviceTimezone, manualTimezone: currentTimezone.manualTimezone};
|
||||
const updatedUser = {...currentUser, timezone} as User;
|
||||
const updatedUser = {...currentUser, timezone} as UserModel;
|
||||
await updateMe(serverUrl, updatedUser);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getUserTimezone = (currentUser: User) => {
|
||||
export const getUserTimezone = (currentUser: UserModel) => {
|
||||
if (currentUser?.timezone) {
|
||||
return {
|
||||
...currentUser?.timezone,
|
||||
|
|
|
|||
26
app/components/app_version/__snapshots__/index.test.tsx.snap
Normal file
26
app/components/app_version/__snapshots__/index.test.tsx.snap
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`@components/app_version should match snapshot 1`] = `
|
||||
<View
|
||||
pointerEvents="none"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
App Version: 0.0.0 (Build 0)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
15
app/components/app_version/index.test.tsx
Normal file
15
app/components/app_version/index.test.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntl} from '@test/intl-test-helper';
|
||||
|
||||
import AppVersion from './index';
|
||||
|
||||
describe('@components/app_version', () => {
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = renderWithIntl(<AppVersion/>);
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
186
app/components/channel_icon/index.tsx
Normal file
186
app/components/channel_icon/index.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, Text, View, ViewStyle} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import General from '@constants/general';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type ChannelIconProps = {
|
||||
hasDraft?: boolean;
|
||||
isActive?: boolean;
|
||||
isArchived?: boolean;
|
||||
isInfo?: boolean;
|
||||
isUnread?: boolean;
|
||||
membersCount?: number;
|
||||
shared: boolean;
|
||||
size?: number;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
testID?: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
},
|
||||
iconActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
},
|
||||
iconUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
},
|
||||
iconInfo: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
groupBox: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.16),
|
||||
borderRadius: 4,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
groupBoxActive: {
|
||||
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
|
||||
},
|
||||
groupBoxUnread: {
|
||||
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
|
||||
},
|
||||
groupBoxInfo: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
},
|
||||
group: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
},
|
||||
groupActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
},
|
||||
groupUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
},
|
||||
groupInfo: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ChannelIcon = ({
|
||||
hasDraft = false,
|
||||
isActive = false,
|
||||
isArchived = false,
|
||||
isInfo = false,
|
||||
isUnread = false,
|
||||
membersCount = 0,
|
||||
shared,
|
||||
size = 12,
|
||||
style,
|
||||
testID,
|
||||
type,
|
||||
}: ChannelIconProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let activeIcon;
|
||||
let unreadIcon;
|
||||
let activeGroupBox;
|
||||
let unreadGroupBox;
|
||||
let activeGroup;
|
||||
let unreadGroup;
|
||||
|
||||
if (isUnread) {
|
||||
unreadIcon = styles.iconUnread;
|
||||
unreadGroupBox = styles.groupBoxUnread;
|
||||
unreadGroup = styles.groupUnread;
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
activeIcon = styles.iconActive;
|
||||
activeGroupBox = styles.groupBoxActive;
|
||||
activeGroup = styles.groupActive;
|
||||
}
|
||||
|
||||
if (isInfo) {
|
||||
activeIcon = styles.iconInfo;
|
||||
activeGroupBox = styles.groupBoxInfo;
|
||||
activeGroup = styles.groupInfo;
|
||||
}
|
||||
|
||||
let icon;
|
||||
if (isArchived) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 1}]}
|
||||
testID={`${testID}.archive`}
|
||||
/>
|
||||
);
|
||||
} else if (hasDraft) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 2}]}
|
||||
testID={`${testID}.draft`}
|
||||
/>
|
||||
);
|
||||
} else if (shared) {
|
||||
const iconName = type === General.PRIVATE_CHANNEL ? 'circle-multiple-outline-lock' : 'circle-multiple-outline';
|
||||
const sharedTestID = type === General.PRIVATE_CHANNEL ? 'channel_icon.shared_private' : 'channel_icon.shared_open';
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
testID={sharedTestID}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='globe'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 1}]}
|
||||
testID={`${testID}.public`}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.PRIVATE_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='lock-outline'
|
||||
style={[styles.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
testID={`${testID}.private`}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.GM_CHANNEL) {
|
||||
const fontSize = size - 12;
|
||||
const boxSize = size - 4;
|
||||
icon = (
|
||||
<View
|
||||
style={[styles.groupBox, unreadGroupBox, activeGroupBox, {width: boxSize, height: boxSize}]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.group, unreadGroup, activeGroup, {fontSize}]}
|
||||
testID={`${testID}.gm_member_count`}
|
||||
>
|
||||
{membersCount}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
//todo: Implement ProfilePicture component
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, {width: size, height: size}, style]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelIcon;
|
||||
8
app/components/compass_icon/index.tsx
Normal file
8
app/components/compass_icon/index.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createIconSetFromFontello} from 'react-native-vector-icons';
|
||||
|
||||
import fontelloConfig from '@assets/compass-icons.json';
|
||||
|
||||
export default createIconSetFromFontello(fontelloConfig, 'compass-icons', 'compass-icons.ttf');
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import {render} from '@testing-library/react-native';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
|
||||
import ErrorText from './index';
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createElement, isValidElement} from 'react';
|
||||
import {StyleProp, Text, TextStyle, ViewStyle} from 'react-native';
|
||||
import {StyleProp, Text, TextProps, TextStyle, ViewStyle} from 'react-native';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
type FormattedTextProps = {
|
||||
type FormattedTextProps = TextProps & {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
values?: Record<string, any>;
|
||||
|
|
|
|||
23
app/components/status_bar/index.tsx
Normal file
23
app/components/status_bar/index.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, StatusBar as NativeStatusBar, StatusBarStyle} from 'react-native';
|
||||
import tinyColor from 'tinycolor2';
|
||||
|
||||
type StatusBarProps = {
|
||||
theme: Theme;
|
||||
headerColor?: string;
|
||||
};
|
||||
|
||||
const StatusBar = ({theme, headerColor}: StatusBarProps) => {
|
||||
const headerBarStyle = tinyColor(headerColor ?? theme.sidebarHeaderBg);
|
||||
let barStyle: StatusBarStyle = 'light-content';
|
||||
if (headerBarStyle.isLight() && Platform.OS === 'ios') {
|
||||
barStyle = 'dark-content';
|
||||
}
|
||||
|
||||
return <NativeStatusBar barStyle={barStyle}/>;
|
||||
};
|
||||
|
||||
export default StatusBar;
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import {FileSystem} from 'react-native-unimodules';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import keyMirror from '@utils/key_mirror';
|
||||
|
||||
const device = keyMirror({
|
||||
|
|
@ -17,10 +19,11 @@ export default {
|
|||
...device,
|
||||
DOCUMENTS_PATH: `${FileSystem.cacheDirectory}/Documents`,
|
||||
IMAGES_PATH: `${FileSystem.cacheDirectory}/Images`,
|
||||
IS_IPHONE_WITH_INSETS: Platform.OS === 'ios' && DeviceInfo.hasNotch(),
|
||||
IS_TABLET: DeviceInfo.isTablet(),
|
||||
VIDEOS_PATH: `${FileSystem.cacheDirectory}/Videos`,
|
||||
PERMANENT_SIDEBAR_SETTINGS: '@PERMANENT_SIDEBAR_SETTINGS',
|
||||
TABLET_WIDTH: 250,
|
||||
PUSH_NOTIFY_APPLE_REACT_NATIVE: 'apple_rn',
|
||||
PUSH_NOTIFY_ANDROID_REACT_NATIVE: 'android_rn',
|
||||
PUSH_NOTIFY_APPLE_REACT_NATIVE: 'apple_rn',
|
||||
TABLET_WIDTH: 250,
|
||||
VIDEOS_PATH: `${FileSystem.cacheDirectory}/Videos`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ const ViewTypes = keyMirror({
|
|||
LANDSCAPE: null,
|
||||
|
||||
INDICATOR_BAR_VISIBLE: null,
|
||||
|
||||
CHANNEL_NAV_BAR_CHANGED: null,
|
||||
|
||||
});
|
||||
|
||||
const RequiredServer = {
|
||||
|
|
|
|||
18
app/hooks/did_update.ts
Normal file
18
app/hooks/did_update.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useRef, useEffect, EffectCallback, DependencyList} from 'react';
|
||||
|
||||
function useDidUpdate(callback: EffectCallback, deps?: DependencyList) {
|
||||
const hasMount = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMount.current) {
|
||||
callback();
|
||||
} else {
|
||||
hasMount.current = true;
|
||||
}
|
||||
}, deps);
|
||||
}
|
||||
|
||||
export default useDidUpdate;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {General} from '@constants';
|
||||
import {t} from '@utils/i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type ChannelDisplayNameProps = {
|
||||
channelType: string;
|
||||
currentUserId: string;
|
||||
displayName: string;
|
||||
teammateId: string;
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
const ChannelDisplayName = ({channelType, currentUserId, displayName, teammateId, theme}: ChannelDisplayNameProps) => {
|
||||
const style = getStyle(theme);
|
||||
let isSelfDMChannel = false;
|
||||
if (channelType === General.DM_CHANNEL && teammateId) {
|
||||
isSelfDMChannel = currentUserId === teammateId;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.text}
|
||||
testID='channel.nav_bar.title'
|
||||
>
|
||||
{isSelfDMChannel ? (
|
||||
<FormattedText
|
||||
id={t('channel_header.directchannel.you')}
|
||||
defaultMessage={'{displayname} (you)'}
|
||||
values={{displayname: displayName}}
|
||||
/>) : displayName
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
text: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
flex: 0,
|
||||
flexShrink: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default ChannelDisplayName;
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {General} from '@constants';
|
||||
import {t} from '@utils/i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type ChannelGuestLabelProps = {
|
||||
channelType: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const ChannelGuestLabel = ({channelType, theme}: ChannelGuestLabelProps) => {
|
||||
const style = getStyle(theme);
|
||||
|
||||
let messageId;
|
||||
let defaultMessage;
|
||||
|
||||
switch (channelType) {
|
||||
case General.DM_CHANNEL: {
|
||||
messageId = t('channel.isGuest');
|
||||
defaultMessage = 'This person is a guest';
|
||||
break;
|
||||
}
|
||||
case General.GM_CHANNEL: {
|
||||
messageId = t('channel.hasGuests');
|
||||
defaultMessage = 'This group message has guests';
|
||||
break;
|
||||
}
|
||||
default : {
|
||||
messageId = t('channel.channelHasGuests');
|
||||
defaultMessage = 'This channel has guests';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.guestsWrapper}>
|
||||
<FormattedText
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
id={messageId}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.guestsText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
guestsWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
width: '90%',
|
||||
},
|
||||
guestsText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 14,
|
||||
opacity: 0.6,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default ChannelGuestLabel;
|
||||
190
app/screens/channel/channel_nav_bar/channel_title/index.tsx
Normal file
190
app/screens/channel/channel_nav_bar/channel_title/index.tsx
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {General} from '@constants';
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {isGuest as isTeammateGuest} from '@utils/user';
|
||||
import React from 'react';
|
||||
import {TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import ChannelDisplayName from './channel_display_name';
|
||||
import ChannelGuestLabel from './channel_guest_label';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
|
||||
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type ChannelTitleInputProps = {
|
||||
canHaveSubtitle: boolean;
|
||||
channel: ChannelModel;
|
||||
currentUserId: string;
|
||||
teammateId?: string;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
type ChannelTitleProps = ChannelTitleInputProps & {
|
||||
channelInfo: ChannelInfoModel;
|
||||
channelSettings: MyChannelSettingsModel;
|
||||
database: Database;
|
||||
teammate?: UserModel;
|
||||
teammateId: string;
|
||||
};
|
||||
|
||||
const ConnectedChannelTitle = ({
|
||||
canHaveSubtitle,
|
||||
channel,
|
||||
channelInfo,
|
||||
channelSettings,
|
||||
currentUserId,
|
||||
onPress,
|
||||
teammate,
|
||||
teammateId,
|
||||
}: ChannelTitleProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const style = getStyle(theme);
|
||||
const channelType = channel.type;
|
||||
const isArchived = channel.deleteAt !== 0;
|
||||
const isChannelMuted = channelSettings.notifyProps?.mark_unread === 'mention';
|
||||
const isChannelShared = false; // todo: Read this value from ChannelModel when implemented
|
||||
|
||||
const hasGuests = channelInfo.guestCount > 0;
|
||||
const teammateRoles = teammate?.roles ?? '';
|
||||
const isGuest = channelType === General.DM_CHANNEL && isTeammateGuest(teammateRoles);
|
||||
|
||||
const showGuestLabel = (canHaveSubtitle || (isGuest && hasGuests) || (channelType === General.DM_CHANNEL && isGuest));
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID={'channel.title.button'}
|
||||
style={style.container}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
{isArchived && (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[style.archiveIcon]}
|
||||
/>
|
||||
)}
|
||||
<ChannelDisplayName
|
||||
channelType={channelType}
|
||||
currentUserId={currentUserId}
|
||||
displayName={channel.displayName}
|
||||
teammateId={teammateId}
|
||||
theme={theme}
|
||||
/>
|
||||
{isChannelShared && (
|
||||
<ChannelIcon
|
||||
isActive={true}
|
||||
isArchived={false}
|
||||
size={18}
|
||||
shared={isChannelShared}
|
||||
style={style.channelIconContainer}
|
||||
type={channelType}
|
||||
/>
|
||||
)}
|
||||
<CompassIcon
|
||||
style={style.icon}
|
||||
size={24}
|
||||
name='chevron-down'
|
||||
/>
|
||||
{isChannelMuted && (
|
||||
<CompassIcon
|
||||
style={[style.icon, style.muted]}
|
||||
size={24}
|
||||
name='bell-off-outline'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{showGuestLabel && (
|
||||
<ChannelGuestLabel
|
||||
channelType={channelType}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
width: '90%',
|
||||
},
|
||||
icon: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
marginHorizontal: 1,
|
||||
},
|
||||
emoji: {
|
||||
marginHorizontal: 5,
|
||||
},
|
||||
text: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
flex: 0,
|
||||
flexShrink: 1,
|
||||
},
|
||||
channelIconContainer: {
|
||||
marginLeft: 3,
|
||||
marginRight: 0,
|
||||
},
|
||||
muted: {
|
||||
marginTop: 1,
|
||||
opacity: 0.6,
|
||||
marginLeft: 0,
|
||||
},
|
||||
archiveIcon: {
|
||||
fontSize: 17,
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
paddingRight: 7,
|
||||
},
|
||||
guestsWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
width: '90%',
|
||||
},
|
||||
guestsText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 14,
|
||||
opacity: 0.6,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ChannelTitle: React.FunctionComponent<ChannelTitleInputProps> =
|
||||
withDatabase(
|
||||
withObservables(['channel', 'teammateId'], ({channel, teammateId, database}: { channel: ChannelModel; teammateId: string; database: Database }) => {
|
||||
return {
|
||||
channelInfo: database.collections.get(MM_TABLES.SERVER.CHANNEL_INFO).findAndObserve(channel.id),
|
||||
channelSettings: database.collections.get(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS).findAndObserve(channel.id),
|
||||
...(teammateId && channel.displayName && {teammate: database.collections.get(MM_TABLES.SERVER.USER).findAndObserve(teammateId)}),
|
||||
};
|
||||
},
|
||||
)(ConnectedChannelTitle),
|
||||
);
|
||||
|
||||
export default ChannelTitle;
|
||||
108
app/screens/channel/channel_nav_bar/index.tsx
Normal file
108
app/screens/channel/channel_nav_bar/index.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {DeviceEventEmitter, LayoutChangeEvent, Platform, useWindowDimensions, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import VIEWS from '@constants/view';
|
||||
import DEVICE from '@constants/device';
|
||||
import {General} from '@constants';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import ChannelTitle from './channel_title';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
type ChannelNavBar = {
|
||||
channel: ChannelModel;
|
||||
currentUserId: string;
|
||||
onPress: () => void;
|
||||
config: ClientConfig;
|
||||
}
|
||||
|
||||
const ChannelNavBar = ({currentUserId, channel, onPress}: ChannelNavBar) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const dimensions = useWindowDimensions();
|
||||
const isLandscape = dimensions.width > dimensions.height;
|
||||
|
||||
let height = 0;
|
||||
let canHaveSubtitle = true;
|
||||
|
||||
const onLayout = ({nativeEvent}: LayoutChangeEvent) => {
|
||||
const {height: layoutHeight} = nativeEvent.layout;
|
||||
if (height !== layoutHeight && Platform.OS === 'ios') {
|
||||
height = layoutHeight;
|
||||
}
|
||||
|
||||
DeviceEventEmitter.emit(VIEWS.CHANNEL_NAV_BAR_CHANGED, layoutHeight);
|
||||
};
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'android':
|
||||
height = VIEWS.ANDROID_TOP_PORTRAIT;
|
||||
if (DEVICE.IS_TABLET) {
|
||||
height = VIEWS.ANDROID_TOP_LANDSCAPE;
|
||||
}
|
||||
break;
|
||||
case 'ios':
|
||||
height = VIEWS.IOS_TOP_PORTRAIT - VIEWS.STATUS_BAR_HEIGHT;
|
||||
if (DEVICE.IS_TABLET && isLandscape) {
|
||||
height -= 1;
|
||||
} else if (isLandscape) {
|
||||
height = VIEWS.IOS_TOP_LANDSCAPE;
|
||||
canHaveSubtitle = false;
|
||||
}
|
||||
|
||||
if (DEVICE.IS_IPHONE_WITH_INSETS && isLandscape) {
|
||||
canHaveSubtitle = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let teammateId: string | undefined;
|
||||
if (channel?.type === General.DM_CHANNEL) {
|
||||
teammateId = getUserIdFromChannelName(currentUserId, channel.name);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={onLayout}
|
||||
style={[style.header, {height: height + insets.top, paddingTop: insets.top, paddingLeft: insets.left, paddingRight: insets.right}]}
|
||||
>
|
||||
<ChannelTitle
|
||||
currentUserId={currentUserId}
|
||||
channel={channel}
|
||||
onPress={onPress}
|
||||
canHaveSubtitle={canHaveSubtitle}
|
||||
teammateId={teammateId}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
header: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 10,
|
||||
},
|
||||
ios: {
|
||||
zIndex: 10,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default ChannelNavBar;
|
||||
|
|
@ -1,28 +1,50 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, View} from 'react-native';
|
||||
import {
|
||||
Colors,
|
||||
DebugInstructions,
|
||||
Header,
|
||||
LearnMoreLinks,
|
||||
ReloadInstructions,
|
||||
} from 'react-native/Libraries/NewAppScreen';
|
||||
|
||||
import type {LaunchProps} from '@typings/launch';
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React, {useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {logout} from '@actions/remote/general';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import ViewTypes from '@constants/view';
|
||||
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import {useServerUrl} from '@context/server_url';
|
||||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {unsupportedServer} from '@utils/supported_server/supported_server';
|
||||
import {isSystemAdmin as isUserSystemAdmin} from '@utils/user';
|
||||
import {Colors} from 'react-native/Libraries/NewAppScreen';
|
||||
|
||||
import ChannelNavBar from './channel_nav_bar';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type SystemModel from '@typings/database/models/servers/system';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {LaunchType} from '@typings/launch';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
type ChannelProps = LaunchProps;
|
||||
const {SERVER: {CHANNEL, SYSTEM, USER}} = MM_TABLES;
|
||||
|
||||
const Channel = (props: ChannelProps) => {
|
||||
type WithDatabaseArgs = { database: Database }
|
||||
type WithChannelAndThemeArgs = WithDatabaseArgs & {
|
||||
currentChannelId: SystemModel;
|
||||
currentUserId: SystemModel;
|
||||
}
|
||||
type ChannelProps = WithDatabaseArgs & {
|
||||
channel: ChannelModel;
|
||||
config: SystemModel;
|
||||
launchType: LaunchType;
|
||||
user: UserModel;
|
||||
currentUserId: SystemModel;
|
||||
};
|
||||
|
||||
const Channel = ({channel, user, config, currentUserId}: ChannelProps) => {
|
||||
// TODO: If we have LaunchProps, ensure we load the correct channel/post/modal.
|
||||
const {launchType} = props;
|
||||
console.log(launchType); // eslint-disable-line no-console
|
||||
// TODO: If LaunchProps.error is true, use the LaunchProps.launchType to determine which
|
||||
// error message to display. For example:
|
||||
// if (props.launchError) {
|
||||
|
|
@ -33,69 +55,64 @@ const Channel = (props: ChannelProps) => {
|
|||
// errorMessage = intl.formatMessage({id: 'mobile.launchError.notification', defaultMessage: 'Did not find a server for this notification'});
|
||||
// }
|
||||
// }
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
//todo: https://mattermost.atlassian.net/browse/MM-37266
|
||||
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
useEffect(() => {
|
||||
const serverVersion = (config.value?.Version) || '';
|
||||
|
||||
const isSystemAdmin = isUserSystemAdmin(user.roles);
|
||||
|
||||
if (serverVersion) {
|
||||
const {RequiredServer: {MAJOR_VERSION, MIN_VERSION, PATCH_VERSION}} = ViewTypes;
|
||||
const isSupportedServer = isMinimumServerVersion(serverVersion, MAJOR_VERSION, MIN_VERSION, PATCH_VERSION);
|
||||
|
||||
if (!isSupportedServer) {
|
||||
// Only display the Alert if the TOS does not need to show first
|
||||
unsupportedServer(isSystemAdmin, intl.formatMessage);
|
||||
}
|
||||
}
|
||||
}, [config.value?.Version, intl.formatMessage, user.roles]);
|
||||
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const doLogout = () => {
|
||||
logout(serverUrl!);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar barStyle='dark-content'/>
|
||||
<SafeAreaView>
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
style={styles.scrollView}
|
||||
<SafeAreaView
|
||||
style={styles.flex}
|
||||
mode='margin'
|
||||
edges={['left', 'right', 'bottom']}
|
||||
>
|
||||
<StatusBar theme={theme}/>
|
||||
<ChannelNavBar
|
||||
currentUserId={currentUserId.value}
|
||||
channel={channel}
|
||||
onPress={() => null}
|
||||
config={config.value}
|
||||
/>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text
|
||||
onPress={doLogout}
|
||||
style={styles.sectionTitle}
|
||||
>
|
||||
<Header/>
|
||||
<View style={styles.body}>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text
|
||||
onPress={doLogout}
|
||||
style={styles.sectionTitle}
|
||||
>{`Logout from ${serverUrl}`}</Text>
|
||||
<Text style={[styles.sectionDescription, {color: theme.centerChannelColor}]}>
|
||||
{'Edit '}<Text style={[styles.highlight, {color: theme.centerChannelColor}]}>{'screens/channel/index.tsx'}</Text>{' to change this'}
|
||||
{' screen and then come back to see your edits.'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{'See Your Changes'}</Text>
|
||||
<Text style={styles.sectionDescription}>
|
||||
<ReloadInstructions/>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{'Debug'}</Text>
|
||||
<Text style={styles.sectionDescription}>
|
||||
<DebugInstructions/>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{'Learn More'}</Text>
|
||||
<Text style={styles.sectionDescription}>
|
||||
{'Read the docs to discover what to do next:'}
|
||||
</Text>
|
||||
</View>
|
||||
<LearnMoreLinks/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
{`Logout from ${serverUrl}`}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollView: {
|
||||
backgroundColor: Colors.lighter,
|
||||
},
|
||||
engine: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: Colors.white,
|
||||
const getStyleSheet = makeStyleSheetFromTheme(() => ({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
|
|
@ -106,23 +123,17 @@ const styles = StyleSheet.create({
|
|||
fontWeight: '600',
|
||||
color: Colors.black,
|
||||
},
|
||||
sectionDescription: {
|
||||
marginTop: 8,
|
||||
fontSize: 18,
|
||||
fontWeight: '400',
|
||||
color: Colors.dark,
|
||||
},
|
||||
highlight: {
|
||||
fontWeight: '700',
|
||||
},
|
||||
footer: {
|
||||
color: Colors.dark,
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
padding: 4,
|
||||
paddingRight: 12,
|
||||
textAlign: 'right',
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
export default Channel;
|
||||
export const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
currentChannelId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_CHANNEL_ID),
|
||||
currentUserId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
|
||||
config: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
|
||||
}));
|
||||
|
||||
const withChannelAndUser = withObservables(['currentChannelId'], ({currentChannelId, currentUserId, database}: WithChannelAndThemeArgs) => ({
|
||||
channel: database.collections.get(CHANNEL).findAndObserve(currentChannelId.value),
|
||||
user: database.collections.get(USER).findAndObserve(currentUserId.value),
|
||||
}));
|
||||
|
||||
export default withDatabase(withSystemIds(withChannelAndUser(Channel)));
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withManagedConfig} from '@mattermost/react-native-emm';
|
||||
import React from 'react';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import {Navigation, NavigationFunctionComponent} from 'react-native-navigation';
|
||||
import {Platform, StyleProp, ViewStyle} from 'react-native';
|
||||
import {Navigation, NavigationFunctionComponent} from 'react-native-navigation';
|
||||
import {gestureHandlerRootHOC} from 'react-native-gesture-handler';
|
||||
|
||||
import {withManagedConfig} from '@mattermost/react-native-emm';
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
|
||||
|
||||
import {withServerDatabase} from '@database/components';
|
||||
|
||||
// TODO: Remove this and uncomment screens as they get added
|
||||
|
|
@ -38,6 +37,16 @@ const withIntl = (Screen: React.ComponentType) => {
|
|||
}
|
||||
}
|
||||
|
||||
const withSafeAreaInsets = (Screen: React.ComponentType) => {
|
||||
return function SafeAreaInsets(props: any){
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<Screen {...props} />
|
||||
</SafeAreaProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
let screen: any|undefined;
|
||||
let extraStyles: StyleProp<ViewStyle>;
|
||||
|
|
@ -219,6 +228,6 @@ export function registerScreens() {
|
|||
const channelScreen = require('@screens/channel').default;
|
||||
const serverScreen = require('@screens/server').default;
|
||||
|
||||
Navigation.registerComponent(Screens.CHANNEL, () => withIntl(withServerDatabase(withManagedConfig(channelScreen))));
|
||||
Navigation.registerComponent(Screens.CHANNEL, () => withSafeAreaInsets(withIntl(withServerDatabase(withManagedConfig(channelScreen)))));
|
||||
Navigation.registerComponent(Screens.SERVER, () => withIntl(withManagedConfig(serverScreen)));
|
||||
}
|
||||
|
|
|
|||
22
app/utils/supported_server/supported_server.test.ts
Normal file
22
app/utils/supported_server/supported_server.test.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {unsupportedServer} from './supported_server';
|
||||
|
||||
describe('Unsupported Server Alert', () => {
|
||||
const formatMessage = jest.fn();
|
||||
|
||||
it('should show the alert for sysadmin', () => {
|
||||
const alert = jest.spyOn(Alert, 'alert');
|
||||
unsupportedServer(true, formatMessage);
|
||||
expect(alert?.mock?.calls?.[0]?.[2]?.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should show the alert for team admin / user', () => {
|
||||
const alert = jest.spyOn(Alert, 'alert');
|
||||
unsupportedServer(false, formatMessage);
|
||||
expect(alert?.mock?.calls?.[0]?.[2]?.length).toBe(1);
|
||||
});
|
||||
});
|
||||
76
app/utils/supported_server/supported_server.ts
Normal file
76
app/utils/supported_server/supported_server.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert, AlertButton} from 'react-native';
|
||||
|
||||
import ViewTypes from '@constants/view';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
export interface FormatObjectType {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
}
|
||||
|
||||
export interface FormatMessageType {
|
||||
(obj: FormatObjectType, values?: any): string;
|
||||
}
|
||||
|
||||
export function unsupportedServer(isSystemAdmin: boolean, formatMessage: FormatMessageType) {
|
||||
if (isSystemAdmin) {
|
||||
return unsupportedServerAdminAlert(formatMessage);
|
||||
}
|
||||
return unsupportedServerAlert(formatMessage);
|
||||
}
|
||||
|
||||
function unsupportedServerAdminAlert(formatMessage: FormatMessageType) {
|
||||
const title = formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'});
|
||||
|
||||
const message = formatMessage({
|
||||
id: 'mobile.server_upgrade.alert_description',
|
||||
defaultMessage: 'This server version is unsupported and users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {serverVersion} or later is required.',
|
||||
}, {serverVersion: ViewTypes.RequiredServer.FULL_VERSION});
|
||||
|
||||
const cancel: AlertButton = {
|
||||
text: formatMessage({id: 'mobile.server_upgrade.dismiss', defaultMessage: 'Dismiss'}),
|
||||
style: 'default',
|
||||
};
|
||||
|
||||
const learnMore: AlertButton = {
|
||||
text: formatMessage({id: 'mobile.server_upgrade.learn_more', defaultMessage: 'Learn More'}),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
const url = 'https://docs.mattermost.com/administration/release-lifecycle.html';
|
||||
const onError = () => {
|
||||
Alert.alert(
|
||||
formatMessage({id: 'mobile.link.error.title', defaultMessage: 'Error'}),
|
||||
formatMessage({id: 'mobile.link.error.text', defaultMessage: 'Unable to open the link.'}),
|
||||
);
|
||||
};
|
||||
|
||||
tryOpenURL(url, onError);
|
||||
},
|
||||
};
|
||||
const buttons: AlertButton[] = [cancel, learnMore];
|
||||
const options = {cancelable: false};
|
||||
|
||||
Alert.alert(title, message, buttons, options);
|
||||
}
|
||||
|
||||
function unsupportedServerAlert(formatMessage: FormatMessageType) {
|
||||
const title = formatMessage({id: 'mobile.unsupported_server.title', defaultMessage: 'Unsupported server version'});
|
||||
|
||||
const message = formatMessage({
|
||||
id: 'mobile.unsupported_server.message',
|
||||
defaultMessage: 'Attachments, link previews, reactions and embed data may not be displayed correctly. If this issue persists contact your System Administrator to upgrade your Mattermost server.',
|
||||
});
|
||||
|
||||
const okButton: AlertButton = {
|
||||
text: formatMessage({id: 'mobile.unsupported_server.ok', defaultMessage: 'OK'}),
|
||||
style: 'default',
|
||||
};
|
||||
|
||||
const buttons: AlertButton[] = [okButton];
|
||||
const options = {cancelable: false};
|
||||
|
||||
Alert.alert(title, message, buttons, options);
|
||||
}
|
||||
25
app/utils/user/index.ts
Normal file
25
app/utils/user/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {General} from '@constants';
|
||||
|
||||
export function getUserIdFromChannelName(userId: string, channelName: string): string {
|
||||
const ids = channelName.split('__');
|
||||
if (ids[0] === userId) {
|
||||
return ids[1];
|
||||
}
|
||||
return ids[0];
|
||||
}
|
||||
|
||||
export function isRoleInRoles(roles: string, role: string): boolean {
|
||||
const rolesArray = roles.split(' ');
|
||||
return rolesArray.includes(role);
|
||||
}
|
||||
|
||||
export function isGuest(roles: string): boolean {
|
||||
return isRoleInRoles(roles, General.SYSTEM_GUEST_ROLE);
|
||||
}
|
||||
|
||||
export function isSystemAdmin(roles: string): boolean {
|
||||
return isRoleInRoles(roles, General.SYSTEM_ADMIN_ROLE);
|
||||
}
|
||||
|
|
@ -454,6 +454,34 @@
|
|||
"send-outline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "4dae60c666dc2fac9f861d9e70166d25",
|
||||
"css": "circle-multiple-outline-lock",
|
||||
"code": 59422,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M916.7 666.7V604.2C916.7 523.6 851.4 458.3 770.8 458.3S625 523.6 625 604.2V666.7C602 666.7 583.3 685.3 583.3 708.3V916.7C583.3 939.7 602 958.3 625 958.3H916.7C939.7 958.3 958.3 939.7 958.3 916.7V708.3C958.3 685.3 939.7 666.7 916.7 666.7ZM687.5 604.2C687.5 558.2 724.9 520.8 770.8 520.8S854.2 558.2 854.2 604.2V666.7H687.5V604.2ZM464.4 214.3C503 184.4 551.5 166.7 604.2 166.7 726.7 166.7 826.4 262.8 832.7 383.7 863.2 392.3 891.2 406.9 915.1 426.3 916.1 416.3 916.7 406.1 916.7 395.8 916.7 223.2 776.7 83.3 604.2 83.3 500.9 83.3 409.3 133.5 352.4 210.7 366.7 209.1 381.2 208.3 395.8 208.3 419.2 208.3 442.1 210.4 464.4 214.3ZM500 807.9C468.7 824 433.4 833.3 395.8 833.3 269.3 833.3 166.7 730.8 166.7 604.2S269.3 375 395.8 375C417.7 375 438.8 378 458.8 383.8 510 398.3 553.4 430.5 583.2 473 599.4 449.9 619.6 429.8 643 414.1 630.4 397.8 616.8 382.3 601.3 368.7 546.4 320.8 474.5 291.7 395.8 291.7 223.2 291.7 83.3 431.6 83.3 604.2S223.2 916.7 395.8 916.7C432.4 916.7 467.4 910.1 500 898.5V807.9ZM413.8 523.5C400.6 503.9 390.4 482.1 383.8 458.8 354.5 461.2 327.7 472.2 305.9 489.4 317.2 525.5 334.9 558.8 357.6 587.9 394.3 635 444.4 670.7 501.9 690.6 506.1 661.3 520 635.2 540.8 616.1 538.3 615.4 535.7 614.6 533.2 613.8 484 597.8 442.1 565.6 413.8 523.5Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"circle-multiple-outline-lock"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "a0d8bdcf347c632caf032dc70c78bb2a",
|
||||
"css": "circle-multiple-outline",
|
||||
"code": 984725,
|
||||
"src": "custom_icons",
|
||||
"selected": true,
|
||||
"svg": {
|
||||
"path": "M604.2 708.3C776.7 708.3 916.7 568.4 916.7 395.8 916.7 223.2 776.7 83.3 604.2 83.3 500.9 83.3 409.3 133.5 352.4 210.7 366.7 209.1 381.2 208.3 395.8 208.3 419.2 208.3 442.1 210.4 464.4 214.3 503 184.4 551.5 166.7 604.2 166.7 730.8 166.7 833.3 269.3 833.3 395.8 833.3 522.4 730.8 625 604.2 625 582.3 625 561.2 622 541.2 616.2 538.5 615.5 535.8 614.7 533.2 613.8 484 597.8 442.1 565.6 413.8 523.5 400.6 503.9 390.4 482.1 383.8 458.8 354.5 461.2 327.7 472.2 305.9 489.4 317.2 525.5 334.9 558.8 357.6 587.9 396.2 637.4 449.3 675 510.6 694.1 540.2 703.3 571.6 708.3 604.2 708.3ZM604.2 791.7C618.8 791.7 633.3 790.9 647.6 789.3 590.7 866.5 499.1 916.7 395.8 916.7 223.2 916.7 83.3 776.7 83.3 604.2 83.3 431.6 223.2 291.7 395.8 291.7 474.5 291.7 546.4 320.8 601.3 368.7L601.3 368.7C644.2 406.1 676.7 455 694.1 510.6 672.3 527.8 645.5 538.8 616.2 541.2 594.6 465.3 534.8 405.4 458.8 383.8V383.8C438.8 378 417.7 375 395.8 375 269.3 375 166.7 477.6 166.7 604.2 166.7 730.8 269.3 833.3 395.8 833.3 448.5 833.3 497 815.6 535.7 785.8 557.9 789.6 580.8 791.7 604.2 791.7Z",
|
||||
"width": 1000
|
||||
},
|
||||
"search": [
|
||||
"circle-multiple-outline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "3f2ee509f7e283a723125cb00de0b0ec",
|
||||
"css": "circle-outline",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -28,6 +28,7 @@ module.exports = {
|
|||
'@context': './app/context',
|
||||
'@database': './app/database',
|
||||
'@helpers': './app/helpers',
|
||||
'@hooks': './app/hooks',
|
||||
'@i18n': './app/i18n',
|
||||
'@init': './app/init',
|
||||
'@notifications': './app/notifications',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
279A77DD26A6F1BE00B515F1 /* compass-icons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 279A77DC26A6F1BE00B515F1 /* compass-icons.ttf */; };
|
||||
2D5296A8926B4D7FBAF2D6E2 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */; };
|
||||
49415295267A91230039D64E /* libDatabaseHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49415294267A911C0039D64E /* libDatabaseHelper.a */; };
|
||||
49415296267A91290039D64E /* libDatabaseHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49415294267A911C0039D64E /* libDatabaseHelper.a */; };
|
||||
|
|
@ -163,6 +164,7 @@
|
|||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = "<group>"; };
|
||||
182D203F539AF68F1647EFAF /* Pods-Mattermost-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-Mattermost-MattermostTests/Pods-Mattermost-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = "<group>"; };
|
||||
279A77DC26A6F1BE00B515F1 /* compass-icons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "compass-icons.ttf"; path = "../assets/fonts/compass-icons.ttf"; sourceTree = "<group>"; };
|
||||
297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = "<group>"; };
|
||||
34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Light.ttf"; path = "../assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; };
|
||||
|
|
@ -456,6 +458,7 @@
|
|||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
279A77DC26A6F1BE00B515F1 /* compass-icons.ttf */,
|
||||
4953BF5F2368AE8600593328 /* SwimeProxy.swift */,
|
||||
13B07FAE1A68108700A75B9A /* Mattermost */,
|
||||
7F240A1A220D3A2300637665 /* MattermostShare */,
|
||||
|
|
@ -658,6 +661,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
279A77DD26A6F1BE00B515F1 /* compass-icons.ttf in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
7F0F4B0A24BA173900E14C60 /* LaunchScreen.storyboard in Resources */,
|
||||
7F292AA71E8ABB1100A450A3 /* splash.png in Resources */,
|
||||
|
|
|
|||
|
|
@ -706,7 +706,7 @@ SPEC CHECKSUMS:
|
|||
EXFileSystem: 0a04aba8da751b9ac954065911bcf166503f8267
|
||||
ExpoModulesCore: 2734852616127a6c1fc23012197890a6f3763dc7
|
||||
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
|
||||
FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b
|
||||
FBReactNativeSpec: d35931295aacfe996e833c01a3701d4aa7a80cb4
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
jail-monkey: feb2bdedc4d67312cd41a455c22661d804bba985
|
||||
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
"@context/*": ["app/context/*"],
|
||||
"@database/*": ["app/database/*"],
|
||||
"@helpers/*": ["app/helpers/*"],
|
||||
"@hooks/*": ["app/hooks/*"],
|
||||
"@i18n": ["app/i18n/index"],
|
||||
"@init/*": ["app/init/*"],
|
||||
"@notifications": ["app/notifications/index"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue