From 64c11580fc2306a6843861e28c0afc1e3150e6e5 Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo Date: Thu, 22 Jul 2021 21:41:07 +0400 Subject: [PATCH] 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 * Fix observation on array vs single element * Refactored ChannelTitle component Co-authored-by: Elias Nahum * 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 --- .../src/main/assets/fonts/compass-icons.ttf | Bin 61240 -> 41676 bytes app/actions/local/timezone.ts | 7 +- .../__snapshots__/index.test.tsx.snap | 26 +++ app/components/app_version/index.test.tsx | 15 ++ app/components/channel_icon/index.tsx | 186 +++++++++++++++++ app/components/compass_icon/index.tsx | 8 + app/components/error_text/error_text.test.tsx | 1 + app/components/formatted_text/index.tsx | 4 +- app/components/status_bar/index.tsx | 23 +++ app/constants/device.ts | 9 +- app/constants/view.ts | 3 + app/hooks/did_update.ts | 18 ++ .../channel_display_name/index.tsx | 58 ++++++ .../channel_guest_label/index.tsx | 71 +++++++ .../channel_nav_bar/channel_title/index.tsx | 190 ++++++++++++++++++ app/screens/channel/channel_nav_bar/index.tsx | 108 ++++++++++ app/screens/channel/index.tsx | 183 +++++++++-------- app/screens/index.tsx | 19 +- .../supported_server/supported_server.test.ts | 22 ++ .../supported_server/supported_server.ts | 76 +++++++ app/utils/user/index.ts | 25 +++ assets/base/compass-icons.json | 28 +++ assets/fonts/compass-icons.ttf | Bin 61240 -> 41676 bytes babel.config.js | 1 + ios/Mattermost.xcodeproj/project.pbxproj | 4 + ios/Podfile.lock | 2 +- tsconfig.json | 1 + 27 files changed, 988 insertions(+), 100 deletions(-) create mode 100644 app/components/app_version/__snapshots__/index.test.tsx.snap create mode 100644 app/components/app_version/index.test.tsx create mode 100644 app/components/channel_icon/index.tsx create mode 100644 app/components/compass_icon/index.tsx create mode 100644 app/components/status_bar/index.tsx create mode 100644 app/hooks/did_update.ts create mode 100644 app/screens/channel/channel_nav_bar/channel_title/channel_display_name/index.tsx create mode 100644 app/screens/channel/channel_nav_bar/channel_title/channel_guest_label/index.tsx create mode 100644 app/screens/channel/channel_nav_bar/channel_title/index.tsx create mode 100644 app/screens/channel/channel_nav_bar/index.tsx create mode 100644 app/utils/supported_server/supported_server.test.ts create mode 100644 app/utils/supported_server/supported_server.ts create mode 100644 app/utils/user/index.ts diff --git a/android/app/src/main/assets/fonts/compass-icons.ttf b/android/app/src/main/assets/fonts/compass-icons.ttf index 2a2743fa9a281f7445bc982b106ea1fdf544386a..35a5333af0bef17c7bda1c5b7f23adcae9cd8846 100644 GIT binary patch delta 9171 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 delta 29153 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 diff --git a/app/actions/local/timezone.ts b/app/actions/local/timezone.ts index f234d1dc1..6640c9f3f 100644 --- a/app/actions/local/timezone.ts +++ b/app/actions/local/timezone.ts @@ -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) => { 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, diff --git a/app/components/app_version/__snapshots__/index.test.tsx.snap b/app/components/app_version/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..fe8c0f2d7 --- /dev/null +++ b/app/components/app_version/__snapshots__/index.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`@components/app_version should match snapshot 1`] = ` + + + + App Version: 0.0.0 (Build 0) + + + +`; diff --git a/app/components/app_version/index.test.tsx b/app/components/app_version/index.test.tsx new file mode 100644 index 000000000..d02033558 --- /dev/null +++ b/app/components/app_version/index.test.tsx @@ -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(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/app/components/channel_icon/index.tsx b/app/components/channel_icon/index.tsx new file mode 100644 index 000000000..4efb07631 --- /dev/null +++ b/app/components/channel_icon/index.tsx @@ -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; + 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 = ( + + ); + } else if (hasDraft) { + icon = ( + + ); + } 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 = ( + + ); + } else if (type === General.OPEN_CHANNEL) { + icon = ( + + ); + } else if (type === General.PRIVATE_CHANNEL) { + icon = ( + + ); + } else if (type === General.GM_CHANNEL) { + const fontSize = size - 12; + const boxSize = size - 4; + icon = ( + + + {membersCount} + + + ); + } else if (type === General.DM_CHANNEL) { + //todo: Implement ProfilePicture component + } + + return ( + + {icon} + + ); +}; + +export default ChannelIcon; diff --git a/app/components/compass_icon/index.tsx b/app/components/compass_icon/index.tsx new file mode 100644 index 000000000..fe63c6a66 --- /dev/null +++ b/app/components/compass_icon/index.tsx @@ -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'); diff --git a/app/components/error_text/error_text.test.tsx b/app/components/error_text/error_text.test.tsx index 7c8173ffe..431892ee4 100644 --- a/app/components/error_text/error_text.test.tsx +++ b/app/components/error_text/error_text.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import {render} from '@testing-library/react-native'; + import {Preferences} from '@constants'; import ErrorText from './index'; diff --git a/app/components/formatted_text/index.tsx b/app/components/formatted_text/index.tsx index 291d3a5bc..a9ac28907 100644 --- a/app/components/formatted_text/index.tsx +++ b/app/components/formatted_text/index.tsx @@ -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; diff --git a/app/components/status_bar/index.tsx b/app/components/status_bar/index.tsx new file mode 100644 index 000000000..bda3598ae --- /dev/null +++ b/app/components/status_bar/index.tsx @@ -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 ; +}; + +export default StatusBar; diff --git a/app/constants/device.ts b/app/constants/device.ts index 1719ab8b5..d99480133 100644 --- a/app/constants/device.ts +++ b/app/constants/device.ts @@ -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`, }; diff --git a/app/constants/view.ts b/app/constants/view.ts index fe1e86c75..d7a888159 100644 --- a/app/constants/view.ts +++ b/app/constants/view.ts @@ -98,6 +98,9 @@ const ViewTypes = keyMirror({ LANDSCAPE: null, INDICATOR_BAR_VISIBLE: null, + + CHANNEL_NAV_BAR_CHANGED: null, + }); const RequiredServer = { diff --git a/app/hooks/did_update.ts b/app/hooks/did_update.ts new file mode 100644 index 000000000..e65c88c9c --- /dev/null +++ b/app/hooks/did_update.ts @@ -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; diff --git a/app/screens/channel/channel_nav_bar/channel_title/channel_display_name/index.tsx b/app/screens/channel/channel_nav_bar/channel_title/channel_display_name/index.tsx new file mode 100644 index 000000000..d2a72293e --- /dev/null +++ b/app/screens/channel/channel_nav_bar/channel_title/channel_display_name/index.tsx @@ -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 ( + + {isSelfDMChannel ? ( + ) : displayName + } + + ); +}; + +const getStyle = makeStyleSheetFromTheme((theme) => { + return { + text: { + color: theme.sidebarHeaderTextColor, + fontSize: 18, + fontWeight: 'bold', + textAlign: 'center', + flex: 0, + flexShrink: 1, + }, + }; +}); + +export default ChannelDisplayName; diff --git a/app/screens/channel/channel_nav_bar/channel_title/channel_guest_label/index.tsx b/app/screens/channel/channel_nav_bar/channel_title/channel_guest_label/index.tsx new file mode 100644 index 000000000..a4ebc2b81 --- /dev/null +++ b/app/screens/channel/channel_nav_bar/channel_title/channel_guest_label/index.tsx @@ -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 ( + + + + ); +}; + +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; diff --git a/app/screens/channel/channel_nav_bar/channel_title/index.tsx b/app/screens/channel/channel_nav_bar/channel_title/index.tsx new file mode 100644 index 000000000..026a3afc3 --- /dev/null +++ b/app/screens/channel/channel_nav_bar/channel_title/index.tsx @@ -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 ( + + + {isArchived && ( + + )} + + {isChannelShared && ( + + )} + + {isChannelMuted && ( + + )} + + {showGuestLabel && ( + + )} + + ); +}; + +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 = + 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; diff --git a/app/screens/channel/channel_nav_bar/index.tsx b/app/screens/channel/channel_nav_bar/index.tsx new file mode 100644 index 000000000..b45ecd416 --- /dev/null +++ b/app/screens/channel/channel_nav_bar/index.tsx @@ -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 ( + + + + ); +}; + +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; diff --git a/app/screens/channel/index.tsx b/app/screens/channel/index.tsx index 5d022c22f..ec2b38475 100644 --- a/app/screens/channel/index.tsx +++ b/app/screens/channel/index.tsx @@ -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 ( - <> - - - + + null} + config={config.value} + /> + + -
- - - {`Logout from ${serverUrl}`} - - {'Edit '}{'screens/channel/index.tsx'}{' to change this'} - {' screen and then come back to see your edits.'} - - - - {'See Your Changes'} - - - - - - {'Debug'} - - - - - - {'Learn More'} - - {'Read the docs to discover what to do next:'} - - - - - - - + {`Logout from ${serverUrl}`} + + + + ); }; -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))); diff --git a/app/screens/index.tsx b/app/screens/index.tsx index c022fa0d9..5644bd8ce 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -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 ( + + + + ) + } +} + Navigation.setLazyComponentRegistrator((screenName) => { let screen: any|undefined; let extraStyles: StyleProp; @@ -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))); } diff --git a/app/utils/supported_server/supported_server.test.ts b/app/utils/supported_server/supported_server.test.ts new file mode 100644 index 000000000..8d7e84b58 --- /dev/null +++ b/app/utils/supported_server/supported_server.test.ts @@ -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); + }); +}); diff --git a/app/utils/supported_server/supported_server.ts b/app/utils/supported_server/supported_server.ts new file mode 100644 index 000000000..030fbdff5 --- /dev/null +++ b/app/utils/supported_server/supported_server.ts @@ -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); +} diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts new file mode 100644 index 000000000..89574ba27 --- /dev/null +++ b/app/utils/user/index.ts @@ -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); +} diff --git a/assets/base/compass-icons.json b/assets/base/compass-icons.json index 9e3729b0b..a6e20aa16 100644 --- a/assets/base/compass-icons.json +++ b/assets/base/compass-icons.json @@ -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", diff --git a/assets/fonts/compass-icons.ttf b/assets/fonts/compass-icons.ttf index 2a2743fa9a281f7445bc982b106ea1fdf544386a..35a5333af0bef17c7bda1c5b7f23adcae9cd8846 100644 GIT binary patch delta 9171 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 delta 29153 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 diff --git a/babel.config.js b/babel.config.js index 70c7f6e8c..2b647a29a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -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', diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 01460b382..4ddd8376e 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; + 279A77DC26A6F1BE00B515F1 /* compass-icons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "compass-icons.ttf"; path = "../assets/fonts/compass-icons.ttf"; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; @@ -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 */, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 285cd4f1d..9dcf130ad 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -706,7 +706,7 @@ SPEC CHECKSUMS: EXFileSystem: 0a04aba8da751b9ac954065911bcf166503f8267 ExpoModulesCore: 2734852616127a6c1fc23012197890a6f3763dc7 FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b - FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b + FBReactNativeSpec: d35931295aacfe996e833c01a3701d4aa7a80cb4 glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 jail-monkey: feb2bdedc4d67312cd41a455c22661d804bba985 libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0 diff --git a/tsconfig.json b/tsconfig.json index ecf4461bf..cffc16cc0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"],