[GEKIDOU] Bottom Tab Navigation (#5600)

* Started with bottom tabs layout

* code clean up

* Added animation to bottom tab bar

* returns null if not focused

* code clean up

* Updating layout

* Updated modal screen

* Updated animation

* Updated animation

* Fix SafeArea on Home

* A few clean ups

* code clean up

* Fix issue with navigation on Android

* Use React Navigation in combination of RNN & create bottom tab bar

* Set tab bar line separator height to 0.5

* Fix snapshot tests

* Add home tab mention badge

* Apply new themes

* Home Tab badge

* Remove unused constants

Co-authored-by: Avinash Lingaloo <>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Avinash Lingaloo 2021-08-31 22:58:53 +04:00 committed by GitHub
parent b5c5949b7d
commit 2c193f2133
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 4454 additions and 2674 deletions

View file

@ -0,0 +1,117 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as React from 'react';
import {Animated, StyleProp, StyleSheet, TextStyle} from 'react-native';
import {useTheme} from '@context/theme';
type Props = {
/**
* Whether the badge is visible
*/
visible: boolean;
/**
* Content of the `Badge`.
*/
children?: string | number;
/**
* Size of the `Badge`.
*/
size?: number;
/**
* Style object for the tab bar container.
*/
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
};
export default function Badge({
visible = true,
size = 18,
children,
style,
...rest
}: Props) {
const [opacity] = React.useState(() => new Animated.Value(visible ? 1 : 0));
const [rendered, setRendered] = React.useState(Boolean(visible));
const theme = useTheme();
React.useEffect(() => {
if (!rendered) {
return;
}
Animated.timing(opacity, {
toValue: visible ? 1 : 0,
duration: 150,
useNativeDriver: true,
}).start(({finished}) => {
if (finished && !visible) {
setRendered(false);
}
});
}, [opacity, rendered, visible]);
if (visible && !rendered) {
setRendered(true);
}
if (!visible && !rendered) {
return null;
}
// @ts-expect-error: backgroundColor definitely exists
const {backgroundColor = theme.buttonBg, ...restStyle} =
StyleSheet.flatten(style) || {};
const textColor = theme.buttonColor;
const borderRadius = size / 2;
const fontSize = Math.floor((size * 3) / 4);
return (
<Animated.Text
numberOfLines={1}
style={[
{
opacity,
transform: [
{
scale: opacity.interpolate({
inputRange: [0, 1],
outputRange: [0.5, 1],
}),
},
],
backgroundColor,
color: textColor,
fontSize,
lineHeight: size - 1,
height: size,
minWidth: size,
borderRadius,
},
styles.container,
restStyle,
]}
{...rest}
>
{children}
</Animated.Text>
);
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: -1,
left: 15,
alignSelf: 'flex-end',
textAlign: 'center',
paddingHorizontal: 4,
overflow: 'hidden',
},
});

View file

@ -5,7 +5,7 @@ exports[`ErrorText should match snapshot 1`] = `
style={
Array [
Object {
"color": "#fd5960",
"color": "#d24b4e",
"fontSize": 12,
"marginBottom": 15,
"marginTop": 15,

View file

@ -15,7 +15,7 @@ describe('ErrorText', () => {
fontSize: 14,
marginHorizontal: 15,
},
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
error: 'Username must begin with a letter and contain between 3 and 22 characters including numbers, lowercase letters, and the symbols',
};

View file

@ -13,9 +13,9 @@ import FormattedText from '@components/formatted_text';
import ProgressiveImage from '@components/progressive_image';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {Navigation} from '@constants';
import {Device, Navigation} from '@constants';
import {useServerUrl} from '@context/server_url';
import {usePermanentSidebar, useSplitView} from '@hooks/device';
import {useSplitView} from '@hooks/device';
import {showModalOverCurrentContext} from '@screens/navigation';
import {openGallerWithMockFile} from '@utils/gallery';
import {generateId} from '@utils/general';
@ -55,7 +55,6 @@ const MarkdownImage = ({
const intl = useIntl();
const isSplitView = useSplitView();
const managedConfig = useManagedConfig();
const permanentSidebar = usePermanentSidebar();
const genericFileId = useRef(generateId()).current;
const metadata = imagesMetadata?.[source] || Object.values(imagesMetadata || {})[0];
const [failed, setFailed] = useState(isGifTooLarge(metadata));
@ -87,7 +86,7 @@ const MarkdownImage = ({
height: originalSize.height,
};
const {height, width} = calculateDimensions(fileInfo.height, fileInfo.width, getViewPortWidth(isReplyPost, (permanentSidebar && !isSplitView)));
const {height, width} = calculateDimensions(fileInfo.height, fileInfo.width, getViewPortWidth(isReplyPost, Device.IS_TABLET && !isSplitView));
const handleLinkPress = useCallback(() => {
if (linkDestination) {

View file

@ -0,0 +1,133 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect} from 'react';
import {Platform, StyleProp, View, ViewProps, ViewStyle} from 'react-native';
import FastImage from 'react-native-fast-image';
// import {fetchStatusInBatch} from '@actions/remote/user';
// import UserStatus from '@components/user_status';
import CompassIcon from '@components/compass_icon';
import {useServerUrl} from '@context/server_url';
import {useTheme} from '@context/theme';
import NetworkManager from '@init/network_manager';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {Client} from '@client/rest';
import type UserModel from '@typings/database/models/servers/user';
const STATUS_BUFFER = Platform.select({
ios: 3,
android: 2,
});
type ProfilePictureProps = {
author?: UserModel;
iconSize?: number;
showStatus?: boolean;
size: number;
statusSize?: number;
statusStyle?: StyleProp<ViewProps>;
testID?: string;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 80,
},
icon: {
color: changeOpacity(theme.centerChannelColor, 0.48),
},
statusWrapper: {
position: 'absolute',
bottom: 0,
right: 0,
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.centerChannelBg,
borderWidth: 1,
borderColor: theme.centerChannelBg,
},
status: {
color: theme.centerChannelBg,
},
};
});
const ProfilePicture = ({author, iconSize, showStatus = true, size = 64, statusSize = 14, statusStyle, testID}: ProfilePictureProps) => {
const theme = useTheme();
const serverUrl = useServerUrl();
const style = getStyleSheet(theme);
const buffer = showStatus ? (STATUS_BUFFER || 0) : 0;
let client: Client | undefined;
try {
client = NetworkManager.getClient(serverUrl);
} catch {
// handle below that the client is not set
}
useEffect(() => {
if (author && !author.status && showStatus) {
// fetchStatusInBatch(serverUrl, author.id);
}
}, []);
let statusIcon;
let containerStyle: StyleProp<ViewStyle> = {
width: size + buffer,
height: size + buffer,
};
if (author?.status && !author.isBot && showStatus) {
statusIcon = (
<View style={[style.statusWrapper, statusStyle, {borderRadius: statusSize / 2}]}>
{/* <UserStatus
size={statusSize}
status={author.status}
/> */}
</View>
);
}
let image;
if (author && client) {
const pictureUrl = client.getProfilePictureUrl(author.id, author.lastPictureUpdate);
image = (
<FastImage
key={pictureUrl}
style={{width: size, height: size, borderRadius: (size / 2)}}
source={{uri: `${serverUrl}${pictureUrl}`}}
/>
);
} else {
containerStyle = {
width: size + (buffer - 1),
height: size + (buffer - 1),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
};
image = (
<CompassIcon
name='account-outline'
size={iconSize || size}
style={style.icon}
/>
);
}
return (
<View
style={[style.container, containerStyle]}
testID={`${testID}.${author?.id}`}
>
{image}
{statusIcon}
</View>
);
};
export default ProfilePicture;

View file

@ -21,9 +21,7 @@ export default {
IMAGES_PATH: `${FileSystem.cacheDirectory}/Images`,
IS_IPHONE_WITH_INSETS: Platform.OS === 'ios' && DeviceInfo.hasNotch(),
IS_TABLET: DeviceInfo.isTablet(),
PERMANENT_SIDEBAR_SETTINGS: 'PERMANENT_SIDEBAR_SETTINGS',
PUSH_NOTIFY_ANDROID_REACT_NATIVE: 'android_rn',
PUSH_NOTIFY_APPLE_REACT_NATIVE: 'apple_rn',
TABLET_WIDTH: 250,
VIDEOS_PATH: `${FileSystem.cacheDirectory}/Videos`,
};

View file

@ -9,10 +9,6 @@ const Navigation = keyMirror({
NAVIGATION_ERROR_TEAMS: null,
NAVIGATION_SHOW_OVERLAY: null,
NAVIGATION_DISMISS_AND_POP_TO_ROOT: null,
CLOSE_MAIN_SIDEBAR: null,
MAIN_SIDEBAR_DID_CLOSE: null,
MAIN_SIDEBAR_DID_OPEN: null,
CLOSE_SETTINGS_SIDEBAR: null,
BLUR_POST_DRAFT: null,
});

View file

@ -41,115 +41,142 @@ const Preferences: Record<string, any> = {
CATEGORY_THEME: 'theme',
TEAMS_ORDER: 'teams_order',
THEMES: {
default: {
type: 'Mattermost',
sidebarBg: '#145dbf',
denim: {
type: 'Denim',
sidebarBg: '#1e325c',
sidebarText: '#ffffff',
sidebarUnreadText: '#ffffff',
sidebarTextHoverBg: '#4578bf',
sidebarTextHoverBg: '#28427b',
sidebarTextActiveBorder: '#579eff',
sidebarTextActiveColor: '#ffffff',
sidebarHeaderBg: '#1153ab',
sidebarTextActiveColor: '#5d89ea',
sidebarHeaderBg: '#192a4d',
sidebarHeaderTextColor: '#ffffff',
onlineIndicator: '#06d6a0',
awayIndicator: '#ffbc42',
dndIndicator: '#f74343',
sidebarTeamBarBg: '#14213e',
onlineIndicator: '#3db887',
awayIndicator: '#ffbc1f',
dndIndicator: '#d24b4e',
mentionBg: '#ffffff',
mentionBj: '#ffffff',
mentionColor: '#145dbf',
mentionColor: '#1e325c',
centerChannelBg: '#ffffff',
centerChannelColor: '#3d3c40',
newMessageSeparator: '#ff8800',
linkColor: '#2389d7',
buttonBg: '#166de0',
centerChannelColor: '#3f4350',
newMessageSeparator: '#cc8f00',
linkColor: '#386fe5',
buttonBg: '#1c58d9',
buttonColor: '#ffffff',
errorTextColor: '#fd5960',
mentionHighlightBg: '#ffe577',
mentionHighlightLink: '#166de0',
errorTextColor: '#d24b4e',
mentionHighlightBg: '#ffd470',
mentionHighlightLink: '#1b1d22',
codeTheme: 'github',
} as Theme,
organization: {
type: 'Organization',
sidebarBg: '#2071a7',
},
sapphire: {
type: 'Sapphire',
sidebarBg: '#174ab5',
sidebarText: '#ffffff',
sidebarUnreadText: '#ffffff',
sidebarTextHoverBg: '#136197',
sidebarTextActiveBorder: '#7ab0d6',
sidebarTextHoverBg: '#2a58ba',
sidebarTextActiveBorder: '#57b5f0',
sidebarTextActiveColor: '#ffffff',
sidebarHeaderBg: '#2f81b7',
sidebarHeaderBg: '#1542a2',
sidebarHeaderTextColor: '#ffffff',
onlineIndicator: '#7dbe00',
awayIndicator: '#dcbd4e',
dndIndicator: '#ff6a6a',
mentionBg: '#fbfbfb',
mentionBj: '#fbfbfb',
mentionColor: '#2071f7',
centerChannelBg: '#f2f4f8',
centerChannelColor: '#333333',
newMessageSeparator: '#ff8800',
linkColor: '#2f81b7',
buttonBg: '#1dacfc',
sidebarTeamBarBg: '#133a91',
onlineIndicator: '#3db887',
awayIndicator: '#ffbc1f',
dndIndicator: '#d24b4e',
mentionBg: '#ffffff',
mentionColor: '#174ab5',
centerChannelBg: '#ffffff',
centerChannelColor: '#3f4350',
newMessageSeparator: '#15b7b7',
linkColor: '#1c58d9',
buttonBg: '#1c58d9',
buttonColor: '#ffffff',
errorTextColor: '#a94442',
mentionHighlightBg: '#f3e197',
mentionHighlightLink: '#2f81b7',
errorTextColor: '#d24b4e',
mentionHighlightBg: '#7ff0f0',
mentionHighlightLink: '#0d6e6e',
codeTheme: 'github',
} as Theme,
mattermostDark: {
type: 'Mattermost Dark',
sidebarBg: '#1b2c3e',
},
quartz: {
type: 'Quartz',
sidebarBg: '#f4f4f6',
sidebarText: '#090a0b',
sidebarUnreadText: '#2d3039',
sidebarTextHoverBg: '#ebebed',
sidebarTextActiveBorder: '#32a4ec',
sidebarTextActiveColor: '#2d3039',
sidebarHeaderBg: '#e8e9ed',
sidebarHeaderTextColor: '#2d3039',
sidebarTeamBarBg: '#dddfe4',
onlineIndicator: '#3db887',
awayIndicator: '#f5ab07',
dndIndicator: '#d24b4e',
mentionBg: '#1c58d9',
mentionColor: '#ffffff',
centerChannelBg: '#ffffff',
centerChannelColor: '#3f4350',
newMessageSeparator: '#15b7b7',
linkColor: '#1c58d9',
buttonBg: '#1c58d9',
buttonColor: '#ffffff',
errorTextColor: '#d24b4e',
mentionHighlightBg: '#7ff0f0',
mentionHighlightLink: '#0d6e6e',
codeTheme: 'github',
},
indigo: {
type: 'Indigo',
sidebarBg: '#0f1a2e',
sidebarText: '#ffffff',
sidebarUnreadText: '#ffffff',
sidebarTextHoverBg: '#4a5664',
sidebarTextActiveBorder: '#66b9a7',
sidebarTextHoverBg: '#222c3f',
sidebarTextActiveBorder: '#1279ba',
sidebarTextActiveColor: '#ffffff',
sidebarHeaderBg: '#1b2c3e',
sidebarHeaderTextColor: '#ffffff',
onlineIndicator: '#65dcc8',
awayIndicator: '#c1b966',
dndIndicator: '#e81023',
mentionBg: '#b74a4a',
mentionBj: '#b74a4a',
sidebarHeaderBg: '#152231',
sidebarHeaderTextColor: '#dddfe4',
sidebarTeamBarBg: '#05080f',
onlineIndicator: '#3db887',
awayIndicator: '#f5ab00',
dndIndicator: '#d24b4e',
mentionBg: '#1c58d9',
mentionColor: '#ffffff',
centerChannelBg: '#2f3e4e',
centerChannelColor: '#dddddd',
newMessageSeparator: '#5de5da',
linkColor: '#a4ffeb',
buttonBg: '#4cbba4',
centerChannelBg: '#0a111f',
centerChannelColor: '#dddfe4',
newMessageSeparator: '#81a3ef',
linkColor: '#5d89ea',
buttonBg: '#386fe5',
buttonColor: '#ffffff',
errorTextColor: '#ff6461',
mentionHighlightBg: '#984063',
mentionHighlightLink: '#a4ffeb',
codeTheme: 'solarized-dark',
} as Theme,
windows10: {
type: 'Windows Dark',
sidebarBg: '#171717',
errorTextColor: '#d24b4e',
mentionHighlightBg: '#133a91',
mentionHighlightLink: '#a4f4f4',
codeTheme: 'github',
},
onyx: {
type: 'Onyx',
sidebarBg: '#121317',
sidebarText: '#ffffff',
sidebarUnreadText: '#ffffff',
sidebarTextHoverBg: '#302e30',
sidebarTextActiveBorder: '#196caf',
sidebarTextHoverBg: '#25262a',
sidebarTextActiveBorder: '#1592e0',
sidebarTextActiveColor: '#ffffff',
sidebarHeaderBg: '#1f1f1f',
sidebarHeaderTextColor: '#ffffff',
onlineIndicator: '#399fff',
awayIndicator: '#c1b966',
dndIndicator: '#e81023',
mentionBg: '#0177e7',
mentionBj: '#0177e7',
sidebarHeaderBg: '#1b1d22',
sidebarHeaderTextColor: '#dddfe4',
sidebarTeamBarBg: '#000000',
onlineIndicator: '#3db887',
awayIndicator: '#f5ab00',
dndIndicator: '#d24b4e',
mentionBg: '#1c58d9',
mentionColor: '#ffffff',
centerChannelBg: '#1f1f1f',
centerChannelColor: '#dddddd',
newMessageSeparator: '#cc992d',
linkColor: '#0d93ff',
buttonBg: '#0177e7',
centerChannelBg: '#090a0b',
centerChannelColor: '#dddfe4',
newMessageSeparator: '#1adbdb',
linkColor: '#5d89ea',
buttonBg: '#386fe5',
buttonColor: '#ffffff',
errorTextColor: '#ff6461',
mentionHighlightBg: '#784098',
mentionHighlightLink: '#a4ffeb',
errorTextColor: '#da6c6e',
mentionHighlightBg: '#0d6e6e',
mentionHighlightLink: '#a4f4f4',
codeTheme: 'monokai',
} as Theme,
},
},
} as Record<ThemeKey, Theme>,
};
export default Preferences;

View file

@ -2,8 +2,10 @@
// See LICENSE.txt for license information.
export const ABOUT = 'About';
export const BOTTOM_SHEET = 'BottomSheet';
export const CHANNEL = 'Channel';
export const FORGOT_PASSWORD = 'ForgotPassword';
export const HOME = 'Home';
export const LOGIN = 'Login';
export const LOGIN_OPTIONS = 'LoginOptions';
export const MAIN_SIDEBAR = 'MainSidebar';
@ -15,13 +17,16 @@ export const THREAD = 'Thread';
export default {
ABOUT,
BOTTOM_SHEET,
CHANNEL,
FORGOT_PASSWORD,
HOME,
LOGIN,
LOGIN_OPTIONS,
MAIN_SIDEBAR,
MFA,
SERVER,
SETTINGS_SIDEBAR,
SSO,
THREAD,
};

View file

@ -43,6 +43,10 @@ export const MAX_BADGE_RIGHT_POSITION = -13;
export const LARGE_BADGE_RIGHT_POSITION = -11;
export const SMALL_BADGE_RIGHT_POSITION = -9;
export const TABLET = {
SIDEBAR_WIDTH: 320,
};
const ViewTypes = keyMirror({
DATA_CLEANUP: null,
SERVER_URL_CHANGED: null,
@ -132,4 +136,5 @@ export default {
SidebarSectionTypes,
IOS_HORIZONTAL_LANDSCAPE: 44,
INDICATOR_BAR_HEIGHT,
TABLET,
};

View file

@ -28,9 +28,9 @@ const {SERVER: {PREFERENCE, SYSTEM}} = MM_TABLES;
function getDefaultThemeByAppearance(): Theme {
if (Appearance.getColorScheme() === 'dark') {
return Preferences.THEMES.windows10;
return Preferences.THEMES.onyx;
}
return Preferences.THEMES.default;
return Preferences.THEMES.denim;
}
const ThemeContext = createContext(getDefaultThemeByAppearance());

View file

@ -9,7 +9,8 @@ import {MM_TABLES} from '@constants/database';
import ServerUrlProvider from '@context/server_url';
import ThemeProvider from '@context/theme';
import DatabaseManager from '@database/manager';
import Servers from '@database/models/app/servers';
import type ServersModel from '@typings/database/models/app/servers';
type State = {
database: Database;
@ -25,7 +26,7 @@ export function withServerDatabase<T>(
const [state, setState] = useState<State|undefined>();
const db = DatabaseManager.appDatabase?.database;
const observer = async (servers: Servers[]) => {
const observer = (servers: ServersModel[]) => {
const server = servers.reduce((a, b) => (b.lastActiveAt > a.lastActiveAt ? b : a));
const serverDatabase = DatabaseManager.serverDatabases[server?.url]?.database;

View file

@ -57,6 +57,9 @@ export default class ChannelModel extends Model {
/** A CHANNEL can contain multiple POST (relationship is 1:N) */
[POST]: {type: 'has_many', foreignKey: 'channel_id'},
/** A CHANNEL can contain multiple POST (relationship is 1:N) */
[MY_CHANNEL]: {type: 'has_many', foreignKey: 'channel_id'},
/** A TEAM can be associated to CHANNEL (relationship is 1:N) */
[TEAM]: {type: 'belongs_to', key: 'team_id'},

View file

@ -3,7 +3,7 @@
import {Relation} from '@nozbe/watermelondb';
import {field, immutableRelation} from '@nozbe/watermelondb/decorators';
import Model from '@nozbe/watermelondb/Model';
import Model, {Associations} from '@nozbe/watermelondb/Model';
import {MM_TABLES} from '@constants/database';
@ -18,6 +18,10 @@ export default class MyChannelModel extends Model {
/** table (name) : MyChannel */
static table = MY_CHANNEL;
static associations: Associations = {
[CHANNEL]: {type: 'belongs_to', key: 'id'},
};
/** last_post_at : The timestamp for any last post on this channel */
@field('last_post_at') lastPostAt!: number;

View file

@ -2,47 +2,13 @@
// See LICENSE.txt for license information.
import {useEffect, useState} from 'react';
import {DeviceEventEmitter, NativeModules, useWindowDimensions} from 'react-native';
import {NativeModules, useWindowDimensions} from 'react-native';
import {Device} from '@constants';
import {MM_TABLES} from '@constants/database';
import DatabaseManager from '@database/manager';
import type GlobalModel from '@typings/database/models/app/global';
const {MattermostManaged} = NativeModules;
const isRunningInSplitView = MattermostManaged.isRunningInSplitView;
export function usePermanentSidebar() {
const [permanentSidebar, setPermanentSidebar] = useState(Device.IS_TABLET);
useEffect(() => {
const handlePermanentSidebar = async () => {
if (Device.IS_TABLET) {
const database = DatabaseManager.appDatabase?.database;
if (database) {
try {
const enabled = await database.get(MM_TABLES.APP.GLOBAL).find(Device.PERMANENT_SIDEBAR_SETTINGS) as GlobalModel;
setPermanentSidebar(enabled.value === 'true');
} catch {
setPermanentSidebar(false);
}
}
}
};
handlePermanentSidebar();
const listener = DeviceEventEmitter.addListener(Device.PERMANENT_SIDEBAR_SETTINGS, handlePermanentSidebar);
return () => {
listener.remove();
};
}, []);
return permanentSidebar;
}
export function useSplitView() {
const [isSplitView, setIsSplitView] = useState(false);
const dimensions = useWindowDimensions();

View file

@ -1,15 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AsyncStorage from '@react-native-community/async-storage';
import {Device} from 'app/constants';
if (Device.IS_TABLET) {
// TODO: Use the default database to set this property
AsyncStorage.getItem(Device.PERMANENT_SIDEBAR_SETTINGS).then((value) => {
if (!value) {
AsyncStorage.setItem(Device.PERMANENT_SIDEBAR_SETTINGS, 'true');
}
});
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Linking} from 'react-native';

View file

@ -10,6 +10,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {useTheme} from '@context/theme';
import {Device, View as ViewConstants} from '@constants';
import {MM_TABLES} from '@constants/database';
import {useSplitView} from '@hooks/device';
import {makeStyleSheetFromTheme} from '@utils/theme';
import ChannelTitle from './channel_title';
@ -25,12 +26,14 @@ type ChannelNavBar = {
const ChannelNavBar = ({channel, onPress}: ChannelNavBar) => {
const insets = useSafeAreaInsets();
const theme = useTheme();
const isSplitView = useSplitView();
const style = getStyleFromTheme(theme);
const dimensions = useWindowDimensions();
const isLandscape = dimensions.width > dimensions.height;
let height = 0;
let paddingTop = 0;
let canHaveSubtitle = true;
const onLayout = ({nativeEvent}: LayoutChangeEvent) => {
@ -64,10 +67,15 @@ const ChannelNavBar = ({channel, onPress}: ChannelNavBar) => {
break;
}
if (!Device.IS_TABLET || (Device.IS_TABLET && isSplitView)) {
height += insets.top;
paddingTop = insets.top;
}
return (
<View
onLayout={onLayout}
style={[style.header, {height: height + insets.top, paddingTop: insets.top, paddingLeft: insets.left, paddingRight: insets.right}]}
style={[style.header, {height, paddingTop, paddingLeft: insets.left, paddingRight: insets.right}]}
>
<ChannelTitle
channel={channel}

View file

@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Text, View} from 'react-native';
import {makeStyleSheetFromTheme} from '@utils/theme';
type ChannelProps = {
theme: Theme;
}
const ChannelList = ({theme}: ChannelProps) => {
const styles = getStyleSheet(theme);
return (
<View
testID='channel.screen'
style={styles.container}
>
<Text style={styles.screenTitle}>{' Channel Screen '}</Text>
</View>
);
};
const getStyleSheet = makeStyleSheetFromTheme(() => ({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ffffff',
},
tabContainer: {
position: 'absolute',
bottom: 0,
backgroundColor: '#ffffff',
},
textContainer: {
marginTop: 30,
alignItems: 'center',
},
screenTitle: {
fontSize: 30,
alignSelf: 'center',
},
}));
export default ChannelList;

View file

@ -46,7 +46,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
style={
Array [
Object {
"color": "#fd5960",
"color": "#d24b4e",
"fontSize": 12,
"marginBottom": 15,
"marginTop": 15,
@ -66,7 +66,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
style={
Array [
Object {
"color": "rgba(61,60,64,0.6)",
"color": "rgba(63,67,80,0.6)",
"fontSize": 16,
"fontWeight": "300",
"lineHeight": 22,
@ -90,7 +90,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
keyboardType="email-address"
onChangeText={[Function]}
placeholder="Email"
placeholderTextColor="rgba(61,60,64,0.5)"
placeholderTextColor="rgba(63,67,80,0.5)"
rejectResponderTermination={true}
style={
Object {
@ -98,7 +98,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
"borderColor": "gainsboro",
"borderRadius": 3,
"borderWidth": 1,
"color": "#3d3c40",
"color": "#3f4350",
"fontSize": 16,
"height": 45,
"marginBottom": 5,
@ -124,7 +124,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
Object {
"alignItems": "center",
"alignSelf": "stretch",
"borderColor": "#166de0",
"borderColor": "#1c58d9",
"borderRadius": 3,
"borderWidth": 1,
"marginTop": 10,
@ -137,7 +137,7 @@ exports[`ForgotPassword should match snapshot 1`] = `
<Text
style={
Object {
"color": "#166de0",
"color": "#1c58d9",
"fontSize": 17,
"textAlign": "center",
}

View file

@ -14,7 +14,7 @@ describe('ForgotPassword', () => {
const baseProps = {
componentId: 'ForgotPassword',
serverUrl: 'https://community.mattermost.com',
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
};
test('should match snapshot', () => {

View file

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useRoute} from '@react-navigation/native';
import React from 'react';
import {Text} from 'react-native';
import Animated, {AnimatedLayout, FadeInLeft, FadeInRight} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
const AccountScreen = () => {
const route = useRoute();
const params = route.params! as {direction: string};
const entering = params.direction === 'left' ? FadeInLeft : FadeInRight;
return (
<SafeAreaView
style={{flex: 1, backgroundColor: 'green'}}
>
<AnimatedLayout style={{flex: 1}}>
<Animated.View
entering={entering.duration(150)}
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}
>
<Text style={{fontSize: 20, color: '#fff'}}>{'Account Screen'}</Text>
</Animated.View>
</AnimatedLayout>
</SafeAreaView>
);
};
export default AccountScreen;

View file

@ -0,0 +1,97 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useIsFocused, useRoute} from '@react-navigation/native';
import React from 'react';
import {Text, View} from 'react-native';
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
import {Device, View as ViewConstants} from '@constants';
import {useTheme} from '@context/theme';
import {useSplitView} from '@hooks/device';
import Channel from '@screens/channel';
import {goToScreen} from '@screens/navigation';
import {makeStyleSheetFromTheme} from '@utils/theme';
import type {LaunchProps} from '@typings/launch';
type ChannelProps = LaunchProps & {
time?: number;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
flex: {
flex: 1,
},
content: {
flex: 1,
flexDirection: 'row',
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: theme.centerChannelColor,
},
}));
const ChannelListScreen = (props: ChannelProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const isSplitView = useSplitView();
const showTabletLayout = Device.IS_TABLET && !isSplitView;
const route = useRoute();
const isFocused = useIsFocused();
const params = route.params as {direction: string};
let tabletSidebarStyle;
if (showTabletLayout) {
const {TABLET} = ViewConstants;
tabletSidebarStyle = {maxWidth: TABLET.SIDEBAR_WIDTH};
}
const animated = useAnimatedStyle(() => {
if (!isFocused) {
let initial = 0;
if (params?.direction) {
initial = -25;
}
return {
opacity: withTiming(0, {duration: 150}),
transform: [{translateX: withTiming(initial, {duration: 150})}],
};
}
return {
opacity: withTiming(1, {duration: 150}),
transform: [{translateX: withTiming(0, {duration: 150})}],
};
}, [isFocused, params]);
return (
<SafeAreaView
style={styles.flex}
>
<Animated.View
style={[styles.content, animated]}
>
<View style={[styles.flex, {alignItems: 'center', justifyContent: 'center'}, tabletSidebarStyle]}>
<Text
onPress={() => goToScreen('Channel', '', undefined, {topBar: {visible: false}})}
style={{fontSize: 20, color: '#fff'}}
>
{'Channel List'}
</Text>
</View>
{showTabletLayout &&
<Channel {...props}/>
}
</Animated.View>
</SafeAreaView>
);
};
export default ChannelListScreen;

View file

@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {NavigationContainer} from '@react-navigation/native';
import {createBottomTabNavigator, BottomTabBarProps} from '@react-navigation/bottom-tabs';
import React from 'react';
import {Platform} from 'react-native';
import {enableScreens} from 'react-native-screens';
import {useTheme} from '@context/theme';
import Account from './account';
import ChannelList from './channel_list';
import RecentMentions from './recent_mentions';
import Search from './search';
import TabBar from './tab_bar';
import type {LaunchProps} from '@typings/launch';
if (Platform.OS === 'ios') {
// We do this on iOS to avoid conflicts betwen ReactNavigation & Wix ReactNativeNavigation
enableScreens(false);
}
type HomeProps = LaunchProps & {
time?: number;
};
const Tab = createBottomTabNavigator();
export default function HomeScreen(props: HomeProps) {
const theme = useTheme();
return (
<NavigationContainer
theme={{
dark: false,
colors: {
primary: theme.centerChannelColor,
background: theme.centerChannelBg,
card: theme.centerChannelBg,
text: theme.centerChannelColor,
border: 'white',
notification: theme.mentionHighlightBg,
},
}}
>
<Tab.Navigator
screenOptions={{headerShown: false, lazy: true, unmountOnBlur: true}}
tabBar={(tabProps: BottomTabBarProps) => (
<TabBar
{...tabProps}
theme={theme}
/>)}
>
<Tab.Screen
name='Home'
options={{title: 'Channel', unmountOnBlur: false}}
>
{() => <ChannelList {...props}/>}
</Tab.Screen>
<Tab.Screen
name='Search'
component={Search}
options={{unmountOnBlur: false}}
/>
<Tab.Screen
name='Mentions'
component={RecentMentions}
/>
<Tab.Screen
name='Account'
component={Account}
/>
</Tab.Navigator>
</NavigationContainer>
);
}

View file

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useRoute} from '@react-navigation/native';
import React from 'react';
import {Text} from 'react-native';
import Animated, {AnimatedLayout, FadeInLeft, FadeInRight} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
const RecentMentionsScreen = () => {
const route = useRoute();
const params = route.params! as {direction: string};
const entering = params.direction === 'left' ? FadeInLeft : FadeInRight;
return (
<SafeAreaView
style={{flex: 1, backgroundColor: 'brown'}}
>
<AnimatedLayout style={{flex: 1}}>
<Animated.View
entering={entering.duration(150)}
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}
>
<Text style={{fontSize: 20, color: '#fff'}}>{'Recent Mentions Screen'}</Text>
</Animated.View>
</AnimatedLayout>
</SafeAreaView>
);
};
export default RecentMentionsScreen;

View file

@ -0,0 +1,45 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useIsFocused, useNavigation} from '@react-navigation/native';
import React from 'react';
import {Text} from 'react-native';
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
const SearchScreen = () => {
const nav = useNavigation();
const isFocused = useIsFocused();
const searchScreenIndex = 1;
const stateIndex = nav.getState().index;
const animated = useAnimatedStyle(() => {
if (isFocused) {
return {
opacity: withTiming(1, {duration: 150}),
transform: [{translateX: withTiming(0, {duration: 150})}],
};
}
return {
opacity: withTiming(0, {duration: 150}),
transform: [{translateX: withTiming(stateIndex < searchScreenIndex ? 25 : -25, {duration: 150})}],
};
}, [isFocused, stateIndex]);
return (
<SafeAreaView
style={{flex: 1, backgroundColor: 'blue'}}
>
<Animated.View
style={[{flex: 1, justifyContent: 'center', alignItems: 'center'}, animated]}
>
{isFocused &&
<Text style={{fontSize: 20, color: '#fff'}}>{'Search Screen'}</Text>
}
</Animated.View>
</SafeAreaView>
);
};
export default SearchScreen;

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
import {View} from 'react-native';
import {switchMap} from 'rxjs/operators';
import ProfilePicture from '@components/profile_picture';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@app/constants/database';
import {makeStyleSheetFromTheme} from '@utils/theme';
import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
type Props = {
currentUser: UserModel;
isFocused: boolean;
theme: Theme;
}
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
selected: {
borderWidth: 2,
borderColor: theme.buttonBg,
borderRadius: 20,
},
}));
const Account = ({currentUser, isFocused, theme}: Props) => {
const style = getStyleSheet(theme);
return (
<View style={isFocused ? style.selected : undefined}>
<ProfilePicture
author={currentUser}
showStatus={false}
size={28}
/>
</View>
);
};
const withCurrentUser = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUser: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((id: SystemModel) => database.get(USER).findAndObserve(id.value)),
),
}));
export default withDatabase(withCurrentUser(Account));

View file

@ -0,0 +1,162 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import React, {useEffect, useState} from 'react';
import {Platform, View} from 'react-native';
import {MM_TABLES} from '@constants/database';
import Badge from '@components/badge';
import CompassIcon from '@components/compass_icon';
import DatabaseManager from '@database/manager';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {Subscription} from 'rxjs';
import type ServersModel from '@typings/database/models/app/servers';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
type Props = {
isFocused: boolean;
theme: Theme;
}
type UnreadMessages = {
mentions: number;
messages: number;
};
type UnreadSubscription = UnreadMessages & {
subscription?: Subscription;
}
const {SERVERS} = MM_TABLES.APP;
const {CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
const subscriptions: Map<string, UnreadSubscription> = new Map();
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
unread: {
left: 16,
top: 1,
borderColor: theme.centerChannelBg,
borderWidth: 2,
paddingHorizontal: 0,
},
mentions: {
fontSize: 10,
fontWeight: 'bold',
fontFamily: 'Open Sans',
lineHeight: Platform.select({android: 15, ios: 12.6}),
borderColor: theme.centerChannelBg,
borderWidth: 2,
minWidth: 18,
height: 16,
},
}));
const Home = ({isFocused, theme}: Props) => {
const db = DatabaseManager.appDatabase?.database;
const [total, setTotal] = useState<UnreadMessages>({mentions: 0, messages: 0});
const style = getStyleSheet(theme);
const updateTotal = () => {
let messages = 0;
let mentions = 0;
subscriptions.forEach((value) => {
messages += value.messages;
mentions += value.mentions;
});
setTotal({mentions, messages});
};
const unreadsSubscription = (serverUrl: string, myChannels: MyChannelModel[]) => {
const unreads = subscriptions.get(serverUrl);
if (unreads) {
let mentions = 0;
let messages = 0;
myChannels.forEach((myChannel) => {
mentions += myChannel.mentionsCount;
messages += myChannel.messageCount;
});
unreads.mentions = mentions;
unreads.messages = messages;
subscriptions.set(serverUrl, unreads);
updateTotal();
}
};
const serversObserver = async (servers: ServersModel[]) => {
servers.forEach((server) => {
const serverUrl = server.url;
if (server.lastActiveAt) {
const sdb = DatabaseManager.serverDatabases[serverUrl];
if (sdb?.database) {
const subscription = sdb.database.
get(MY_CHANNEL).
query(Q.on(CHANNEL, Q.where('delete_at', Q.eq(0)))).
observeWithColumns(['mentions_count', 'message_count']).
subscribe(unreadsSubscription.bind(undefined, serverUrl));
if (!subscriptions.has(serverUrl)) {
const unreads: UnreadSubscription = {
mentions: 0,
messages: 0,
subscription,
};
subscriptions.set(serverUrl, unreads);
}
}
// subscribe and listen for unreads and mentions
} else if (subscriptions.has(serverUrl)) {
// logout from server, remove the subscription
subscriptions.delete(serverUrl);
}
});
};
useEffect(() => {
const subscription = db?.
get(SERVERS).
query().
observeWithColumns(['last_active_at']).
subscribe(serversObserver);
return () => {
subscription?.unsubscribe();
subscriptions.forEach((unreads) => {
unreads.subscription?.unsubscribe();
});
};
}, []);
let unreadStyle;
let text: string | number = '';
let size = 16;
if (total.mentions) {
text = total.mentions > 99 ? '99+' : total.mentions;
unreadStyle = style.mentions;
} else if (total.messages) {
unreadStyle = style.unread;
size = 12;
}
return (
<View>
<CompassIcon
size={28}
name='home-variant-outline'
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
/>
<Badge
style={[unreadStyle]}
visible={!isFocused && Boolean(unreadStyle)}
size={size}
>
{text}
</Badge>
</View>
);
};
export default Home;

View file

@ -0,0 +1,172 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react';
import {DeviceEventEmitter, View, TouchableOpacity, useWindowDimensions} from 'react-native';
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
import {Shadow} from 'react-native-neomorph-shadows';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Account from './account';
import Home from './home';
import Mentions from './mentions';
import Search from './search';
import type {BottomTabBarProps} from '@react-navigation/bottom-tabs';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
backgroundColor: theme.centerChannelBg,
alignContent: 'center',
flexDirection: 'row',
height: 52,
justifyContent: 'center',
},
item: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
separator: {
borderTopColor: changeOpacity(theme.centerChannelColor, 0.08),
borderTopWidth: 0.5,
},
slider: {
backgroundColor: theme.buttonBg,
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
width: 48,
height: 4,
},
sliderContainer: {
height: 4,
position: 'absolute',
top: 0,
left: 10,
alignItems: 'center',
},
}));
const TabComponents: Record<string, any> = {
Account,
Home,
Mentions,
Search,
};
function TabBar({state, descriptors, navigation, theme}: BottomTabBarProps & {theme: Theme}) {
const [visible, setVisible] = useState<boolean|undefined>();
const {width} = useWindowDimensions();
const tabWidth = width / state.routes.length;
const style = getStyleSheet(theme);
const safeareaInsets = useSafeAreaInsets();
useEffect(() => {
const event = DeviceEventEmitter.addListener('tabBarVisible', (show) => {
setVisible(show);
});
return () => event.remove();
}, []);
const transform = useAnimatedStyle(() => {
const translateX = withTiming(state.index * tabWidth, {duration: 150});
return {
transform: [{translateX}],
};
}, [state.index]);
const animatedStyle = useAnimatedStyle(() => {
if (visible === undefined) {
return {transform: [{translateY: -safeareaInsets.bottom}]};
}
const height = visible ? withTiming(-safeareaInsets.bottom, {duration: 200}) : withTiming(52 + safeareaInsets.bottom, {duration: 150});
return {
transform: [{translateY: height}],
};
}, [visible]);
return (
<Animated.View style={[style.container, style.separator, animatedStyle]}>
<Shadow
style={{
position: 'absolute',
height: 90,
width,
shadowColor: 'rgba(61, 60, 64, 0.08)',
shadowOffset: {width: 0, height: -0.5},
shadowOpacity: 1,
shadowRadius: 6,
backgroundColor: theme.centerChannelBg,
}}
/>
<Animated.View
style={[
style.sliderContainer,
{width: tabWidth - 20},
transform,
]}
>
<View style={style.slider}/>
</Animated.View>
{state.routes.map((route, index) => {
const {options} = descriptors[route.key];
const isFocused = state.index === index;
const onPress = () => {
const lastTab = state.history[state.history.length - 1];
const lastIndex = state.routes.findIndex((r) => r.key === lastTab.key);
const direction = lastIndex < index ? 'right' : 'left';
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
// The `merge: true` option makes sure that the params inside the tab screen are preserved
navigation.navigate({params: {...route.params, direction}, name: route.name, merge: true});
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const renderOption = () => {
const Component = TabComponents[route.name];
const props = {isFocused, theme};
if (Component) {
return <Component {...props}/>;
}
return null;
};
return (
<TouchableOpacity
key={route.name}
accessibilityRole='button'
accessibilityState={isFocused ? {selected: true} : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
style={style.item}
>
{renderOption()}
</TouchableOpacity>
);
})}
</Animated.View>
);
}
export default TabBar;

View file

@ -0,0 +1,27 @@
// 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 CompassIcon from '@components/compass_icon';
import {changeOpacity} from '@utils/theme';
type Props = {
isFocused: boolean;
theme: Theme;
}
const Mentions = ({isFocused, theme}: Props) => {
return (
<View>
<CompassIcon
size={28}
name='at'
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
/>
</View>
);
};
export default Mentions;

View file

@ -0,0 +1,27 @@
// 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 CompassIcon from '@components/compass_icon';
import {changeOpacity} from '@utils/theme';
type Props = {
isFocused: boolean;
theme: Theme;
}
const Search = ({isFocused, theme}: Props) => {
return (
<View>
<CompassIcon
size={28}
name='magnify'
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
/>
</View>
);
};
export default Search;

View file

@ -51,7 +51,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
let screen: any|undefined;
let extraStyles: StyleProp<ViewStyle>;
switch (screenName) {
case 'About':
case Screens.ABOUT:
screen = withServerDatabase(require('@screens/about').default);
break;
// case 'AddReaction':
@ -60,9 +60,12 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// case 'AdvancedSettings':
// screen = require('@screens/settings/advanced_settings').default;
// break;
case 'BottomSheet':
case Screens.BOTTOM_SHEET:
screen = require('@screens/bottom_sheet').default;
break;
case Screens.CHANNEL:
screen = withServerDatabase(require('@screens/channel').default);
break;
// case 'ChannelAddMembers':
// screen = require('@screens/channel_add_members').default;
// break;
@ -108,7 +111,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// case 'FlaggedPosts':
// screen = require('@screens/flagged_posts').default;
// break;
case 'ForgotPassword':
case Screens.FORGOT_PASSWORD:
screen = require('@screens/forgot_password').default;
break;
// case 'Gallery':
@ -117,10 +120,10 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// case 'InteractiveDialog':
// screen = require('@screens/interactive_dialog').default;
// break;
case 'Login':
case Screens.LOGIN:
screen = require('@screens/login').default;
break;
case 'LoginOptions':
case Screens.LOGIN_OPTIONS:
screen = require('@screens/login_options').default;
break;
// case 'LongPost':
@ -129,7 +132,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// case 'MainSidebar':
// screen = require('app/components/sidebars/main').default;
// break;
case 'MFA':
case Screens.MFA:
screen = require('@screens/mfa').default;
break;
// case 'MoreChannels':
@ -196,9 +199,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// case 'SidebarSettings':
// screen = require('@screens/settings/sidebar').default;
// break;
case 'SSO':
screen = require('@screens/sso').default;
break;
case Screens.SSO:
screen = require('@screens/sso').default;
break;
// case 'Table':
// screen = require('@screens/table').default;
// break;
@ -225,9 +228,8 @@ Navigation.setLazyComponentRegistrator((screenName) => {
});
export function registerScreens() {
const channelScreen = require('@screens/channel').default;
const homeScreen = require('@screens/home').default;
const serverScreen = require('@screens/server').default;
Navigation.registerComponent(Screens.CHANNEL, () => withSafeAreaInsets(withGestures(withIntl(withServerDatabase(withManagedConfig(channelScreen))), undefined)));
Navigation.registerComponent(Screens.SERVER, () => withIntl(withManagedConfig(serverScreen)));
Navigation.registerComponent(Screens.HOME, () => withSafeAreaInsets(withGestures(withIntl(withServerDatabase(withManagedConfig(homeScreen))), undefined)));
}

View file

@ -86,7 +86,7 @@ exports[`Login Login screen should match snapshot 1`] = `
onChangeText={[Function]}
onSubmitEditing={[Function]}
placeholder="Email or Username"
placeholderTextColor="rgba(61,60,64,0.5)"
placeholderTextColor="rgba(63,67,80,0.5)"
rejectResponderTermination={true}
returnKeyType="next"
style={
@ -95,7 +95,7 @@ exports[`Login Login screen should match snapshot 1`] = `
"borderColor": "gainsboro",
"borderRadius": 3,
"borderWidth": 1,
"color": "#3d3c40",
"color": "#3f4350",
"fontSize": 16,
"height": 45,
"marginBottom": 5,
@ -115,7 +115,7 @@ exports[`Login Login screen should match snapshot 1`] = `
onChangeText={[Function]}
onSubmitEditing={[Function]}
placeholder="Password"
placeholderTextColor="rgba(61,60,64,0.5)"
placeholderTextColor="rgba(63,67,80,0.5)"
rejectResponderTermination={true}
returnKeyType="go"
secureTextEntry={true}
@ -125,7 +125,7 @@ exports[`Login Login screen should match snapshot 1`] = `
"borderColor": "gainsboro",
"borderRadius": 3,
"borderWidth": 1,
"color": "#3d3c40",
"color": "#3f4350",
"fontSize": 16,
"height": 45,
"marginBottom": 5,
@ -152,7 +152,7 @@ exports[`Login Login screen should match snapshot 1`] = `
Object {
"alignItems": "center",
"alignSelf": "stretch",
"borderColor": "#166de0",
"borderColor": "#1c58d9",
"borderRadius": 3,
"borderWidth": 1,
"marginTop": 10,
@ -166,7 +166,7 @@ exports[`Login Login screen should match snapshot 1`] = `
style={
Array [
Object {
"color": "#166de0",
"color": "#1c58d9",
"fontSize": 17,
"textAlign": "center",
},
@ -199,7 +199,7 @@ exports[`Login Login screen should match snapshot 1`] = `
<Text
style={
Object {
"color": "#2389d7",
"color": "#386fe5",
}
}
testID="login.forgot"

View file

@ -460,6 +460,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
fontSize: 17,
},
header: {
color: theme.centerChannelColor,
textAlign: 'center',
marginTop: 15,
marginBottom: 15,

View file

@ -33,7 +33,7 @@ describe('Login', () => {
license: {
IsLicensed: 'false',
},
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
serverUrl: 'https://locahost:8065',
};

View file

@ -36,6 +36,7 @@ const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
flex: 1,
},
header: {
color: theme.centerChannelColor,
textAlign: 'center',
marginTop: 15,
marginBottom: 15,

View file

@ -65,6 +65,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
style={
Array [
Object {
"color": "#3f4350",
"fontSize": 32,
"fontWeight": "600",
"marginBottom": 15,
@ -72,7 +73,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
"textAlign": "center",
},
Object {
"color": "rgba(61,60,64,0.6)",
"color": "rgba(63,67,80,0.6)",
"fontSize": 20,
"fontWeight": "400",
},
@ -86,7 +87,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
style={
Array [
Object {
"color": "#fd5960",
"color": "#d24b4e",
"fontSize": 12,
"marginBottom": 15,
"marginTop": 15,
@ -108,7 +109,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
onChangeText={[Function]}
onSubmitEditing={[Function]}
placeholder="MFA Token"
placeholderTextColor="rgba(61,60,64,0.5)"
placeholderTextColor="rgba(63,67,80,0.5)"
rejectResponderTermination={true}
returnKeyType="go"
style={
@ -117,7 +118,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
"borderColor": "gainsboro",
"borderRadius": 3,
"borderWidth": 1,
"color": "#3d3c40",
"color": "#3f4350",
"fontSize": 16,
"height": 45,
"marginBottom": 5,
@ -144,7 +145,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
Object {
"alignItems": "center",
"alignSelf": "stretch",
"borderColor": "#166de0",
"borderColor": "#1c58d9",
"borderRadius": 3,
"borderWidth": 1,
"marginTop": 10,
@ -157,7 +158,7 @@ exports[`*** MFA Screen *** MFA screen should match snapshot 1`] = `
<Text
style={
Object {
"color": "#166de0",
"color": "#1c58d9",
"fontSize": 17,
"textAlign": "center",
}

View file

@ -201,6 +201,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
color: theme.centerChannelColor,
},
header: {
color: theme.centerChannelColor,
textAlign: 'center',
marginTop: 15,
marginBottom: 15,

View file

@ -23,7 +23,7 @@ describe('*** MFA Screen ***', () => {
password: 'passwd',
license: {},
serverUrl: 'https://locahost:8065',
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
};
test('MFA screen should match snapshot', () => {

View 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 {Text, View} from 'react-native';
const Modal = () => {
return (
<View style={{backgroundColor: '#ffffff', flex: 1}}>
<Text>{'Modal screen'}</Text>
</View>
);
};
export default Modal;

View file

@ -2,23 +2,30 @@
// See LICENSE.txt for license information.
import {Appearance, DeviceEventEmitter, Keyboard, Platform} from 'react-native';
import {Layout, Navigation, Options, OptionsModalPresentationStyle} from 'react-native-navigation';
import {Navigation, Options, OptionsModalPresentationStyle} from 'react-native-navigation';
import merge from 'deepmerge';
import {Navigation as NavigationConstants, Preferences, Screens} from '@constants';
import {Device, Preferences, Screens} from '@constants';
import NavigationConstants from '@constants/navigation';
import EphemeralStore from '@store/ephemeral_store';
import type {LaunchProps} from '@typings/launch';
Navigation.setDefaultOptions({
layout: {
//@ts-expect-error all not defined in type definition
orientation: [Device.IS_TABLET ? 'all' : 'portrait'],
},
});
function getThemeFromState() {
if (EphemeralStore.theme) {
return EphemeralStore.theme;
}
if (Appearance.getColorScheme() === 'dark') {
return Preferences.THEMES.windows10;
return Preferences.THEMES.onyx;
}
return Preferences.THEMES.default;
return Preferences.THEMES.denim;
}
export function resetToChannel(passProps = {}) {
@ -29,8 +36,8 @@ export function resetToChannel(passProps = {}) {
const stack = {
children: [{
component: {
id: Screens.CHANNEL,
name: Screens.CHANNEL,
id: Screens.HOME,
name: Screens.HOME,
passProps,
options: {
layout: {
@ -55,34 +62,8 @@ export function resetToChannel(passProps = {}) {
}],
};
const platformStack: Layout = {stack};
// if (Platform.OS === 'android') {
// platformStack = {
// sideMenu: {
// left: {
// component: {
// id: Screens.MAIN_SIDEBAR,
// name: Screens.MAIN_SIDEBAR,
// },
// },
// center: {
// stack,
// },
// right: {
// component: {
// id: Screens.SETTINGS_SIDEBAR,
// name: Screens.SETTINGS_SIDEBAR,
// },
// },
// },
// };
// }
Navigation.setRoot({
root: {
...platformStack,
},
root: {stack},
});
}
@ -177,6 +158,7 @@ export function resetToTeams(name: string, title: string, passProps = {}, option
export function goToScreen(name: string, title: string, passProps = {}, options = {}) {
const theme = getThemeFromState();
const componentId = EphemeralStore.getNavigationTopComponentId();
DeviceEventEmitter.emit('tabBarVisible', false);
const defaultOptions = {
layout: {
componentBackgroundColor: theme.centerChannelBg,

View file

@ -6,11 +6,12 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {
ActivityIndicator, EventSubscription, Image, Keyboard, KeyboardAvoidingView,
Platform, StatusBar, StyleSheet, TextInput, TouchableWithoutFeedback, View,
Platform, StatusBar, StatusBarStyle, StyleSheet, TextInput, TouchableWithoutFeedback, View,
} from 'react-native';
import Button from 'react-native-button';
import {Navigation, NavigationFunctionComponent} from 'react-native-navigation';
import {SafeAreaView} from 'react-native-safe-area-context';
import tinyColor from 'tinycolor2';
import {doPing} from '@actions/remote/general';
import {fetchConfigAndLicense} from '@actions/remote/systems';
@ -257,8 +258,12 @@ const Server: NavigationFunctionComponent = ({componentId, extra, launchType, la
);
}
const barStyle = Platform.OS === 'android' ? 'light-content' : 'dark-content';
const statusColor = tinyColor(theme.centerChannelBg);
const inputDisabled = managedConfig.allowOtherServers === 'false' || connecting;
let barStyle: StatusBarStyle = 'light-content';
if (Platform.OS === 'ios' && statusColor.isLight()) {
barStyle = 'dark-content';
}
const inputStyle = [styles.inputBox];
if (inputDisabled) {
@ -333,13 +338,16 @@ const Server: NavigationFunctionComponent = ({componentId, extra, launchType, la
}
</View>
</TouchableWithoutFeedback>
<AppVersion/>
<AppVersion textStyle={styles.appInfo}/>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
appInfo: {
color: theme.centerChannelColor,
},
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
@ -387,6 +395,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
resizeMode: 'contain',
},
header: {
color: theme.centerChannelColor,
textAlign: 'center',
marginTop: 15,
marginBottom: 15,

View file

@ -27,7 +27,7 @@ describe('SSO', () => {
IsLicensed: 'true',
},
ssoType: 'GITLAB',
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
serverUrl: 'https://locahost:8065',
launchType: LaunchType.Normal,
};

View file

@ -23,7 +23,7 @@ describe('SSO with redirect url', () => {
loginUrl: '',
serverUrl: 'http://localhost:8065',
setLoginError: jest.fn(),
theme: Preferences.THEMES.default,
theme: Preferences.THEMES.denim,
};
test('should show message when user navigates to the page', () => {

View file

@ -7,7 +7,9 @@ class EphemeralStore {
navigationModalStack: string[] = [];
theme: Theme | undefined;
getNavigationTopComponentId = () => this.navigationComponentIdStack[0];
getNavigationTopComponentId = () => {
return this.navigationComponentIdStack[0];
}
clearNavigationComponents = () => {
this.navigationComponentIdStack = [];
@ -30,8 +32,8 @@ class EphemeralStore {
addToNavigationComponentIdStack = (componentId: string) => {
const index = this.navigationComponentIdStack.indexOf(componentId);
if (index > 0) {
this.navigationComponentIdStack.slice(index, 1);
if (index >= 0) {
this.navigationComponentIdStack = this.navigationComponentIdStack.slice(index, 1);
}
this.navigationComponentIdStack.unshift(componentId);

View file

@ -3,7 +3,7 @@
import {Dimensions} from 'react-native';
import {Device} from '@constants';
import {View} from '@constants';
import {IMAGE_MAX_HEIGHT, IMAGE_MIN_DIMENSION, MAX_GIF_SIZE, VIEWPORT_IMAGE_OFFSET, VIEWPORT_IMAGE_REPLY_OFFSET} from '@constants/image';
export const calculateDimensions = (height: number, width: number, viewPortWidth = 0, viewPortHeight = 0) => {
@ -45,12 +45,12 @@ export const calculateDimensions = (height: number, width: number, viewPortWidth
};
};
export function getViewPortWidth(isReplyPost: boolean, permanentSidebar = false) {
export function getViewPortWidth(isReplyPost: boolean, tabletOffset = false) {
const {width, height} = Dimensions.get('window');
let portraitPostWidth = Math.min(width, height) - VIEWPORT_IMAGE_OFFSET;
if (permanentSidebar) {
portraitPostWidth -= Device.TABLET_WIDTH;
if (tabletOffset) {
portraitPostWidth -= View.TABLET.SIDEBAR_WIDTH;
}
if (isReplyPost) {

View file

@ -161,7 +161,7 @@ function blendComponent(background: number, foreground: number, opacity: number)
return ((1 - opacity) * background) + (opacity * foreground);
}
export function blendColors(background: string, foreground: string, opacity: number): string {
export function blendColors(background: string, foreground: string, opacity: number, hex = false): string {
const backgroundComponents = getComponents(background);
const foregroundComponents = getComponents(foreground);
@ -186,5 +186,23 @@ export function blendColors(background: string, foreground: string, opacity: num
opacity,
);
if (hex) {
let r = red.toString(16);
let g = green.toString(16);
let b = blue.toString(16);
if (r.length === 1) {
r = '0' + r;
}
if (g.length === 1) {
g = '0' + g;
}
if (b.length === 1) {
b = '0' + b;
}
return `#${r + g + b}`;
}
return `rgba(${red},${green},${blue},${alpha})`;
}

View file

@ -1,41 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Preferences} from '@constants';
import {getKeyboardAppearanceFromTheme} from '@utils/theme';
const themes = Preferences.THEMES;
describe('getKeyboardAppearanceFromTheme', () => {
const themes: Partial<Theme> = [
{
centerChannelBg: '#ffffff', // Mattermost
},
{
centerChannelBg: '#f2f4f8', // Organization
},
{
centerChannelBg: '#2f3e4e', // Mattermost Dark
},
{
centerChannelBg: '#1f1f1f', // Windows Dark
},
];
it('should return "light" keyboard appearance for centerChannelBg="#ffffff"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes.denim);
expect(keyboardAppearance).toBe('light');
});
it('should return "light" keyboard appearance for centerChannelBg="#ffffff"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes[0]);
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes.sapphire);
expect(keyboardAppearance).toBe('light');
});
it('should return "light" keyboard appearance for centerChannelBg="#f2f4f8"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes[1]);
it('should return "dark" keyboard appearance for centerChannelBg="#ffffff"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes.quartz);
expect(keyboardAppearance).toBe('light');
});
it('should return "dark" keyboard appearance for centerChannelBg="#2f3e4e"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes[2]);
it('should return "dark" keyboard appearance for centerChannelBg="#0a111f"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes.indigo);
expect(keyboardAppearance).toBe('dark');
});
it('should return "dark" keyboard appearance for centerChannelBg="#1f1f1f"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes[3]);
it('should return "dark" keyboard appearance for centerChannelBg="#090a0b"', () => {
const keyboardAppearance = getKeyboardAppearanceFromTheme(themes.onyx);
expect(keyboardAppearance).toBe('dark');
});
});

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -5,7 +5,7 @@ import {DeviceEventEmitter, Platform} from 'react-native';
import 'react-native-gesture-handler';
import {ComponentDidAppearEvent, ComponentDidDisappearEvent, Navigation} from 'react-native-navigation';
import {Navigation as NavigationConstants, Screens} from './app/constants';
import {Screens} from './app/constants';
import DatabaseManager from './app/database/manager';
import {getAllServerCredentials} from './app/init/credentials';
import GlobalEventHandler from './app/init/global_event_handler';
@ -65,28 +65,21 @@ Navigation.events().registerAppLaunchedListener(async () => {
const registerNavigationListeners = () => {
Navigation.events().registerComponentDidAppearListener(componentDidAppearListener);
Navigation.events().registerComponentDidDisappearListener(componentDidDisappearListener);
Navigation.events().registerComponentWillAppearListener(componentWillAppear);
};
function componentWillAppear({componentId}: ComponentDidAppearEvent) {
if (componentId === Screens.HOME) {
DeviceEventEmitter.emit('tabBarVisible', true);
}
}
function componentDidAppearListener({componentId}: ComponentDidAppearEvent) {
EphemeralStore.addNavigationComponentId(componentId);
switch (componentId) {
case 'MainSidebar':
DeviceEventEmitter.emit(NavigationConstants.MAIN_SIDEBAR_DID_OPEN);
DeviceEventEmitter.emit(NavigationConstants.BLUR_POST_DRAFT);
break;
case 'SettingsSidebar':
DeviceEventEmitter.emit(NavigationConstants.BLUR_POST_DRAFT);
break;
}
}
function componentDidDisappearListener({componentId}: ComponentDidDisappearEvent) {
if (componentId !== Screens.CHANNEL) {
if (componentId !== Screens.HOME) {
EphemeralStore.removeNavigationComponentId(componentId);
}
if (componentId === 'MainSidebar') {
DeviceEventEmitter.emit(NavigationConstants.MAIN_SIDEBAR_DID_CLOSE);
}
}

View file

@ -334,6 +334,8 @@ PODS:
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-perflogger (= 0.64.2)
- ReactNativeART (1.2.0):
- React
- ReactNativeExceptionHandler (2.10.10):
- React-Core
- ReactNativeKeyboardTrackingView (5.7.0):
@ -375,7 +377,7 @@ PODS:
- React-Core
- RNReactNativeHapticFeedback (1.11.0):
- React-Core
- RNReanimated (2.2.0):
- RNReanimated (2.3.0-alpha.2):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
@ -407,6 +409,9 @@ PODS:
- RNRudderSdk (1.0.0):
- React
- Rudder (>= 1.0.20)
- RNScreens (3.6.0):
- React-Core
- React-RCTImage
- RNSentry (2.6.1):
- React-Core
- Sentry (= 7.1.4)
@ -495,6 +500,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "ReactNativeART (from `../node_modules/@react-native-community/art`)"
- ReactNativeExceptionHandler (from `../node_modules/react-native-exception-handler`)
- ReactNativeKeyboardTrackingView (from `../node_modules/react-native-keyboard-tracking-view`)
- ReactNativeNavigation (from `../node_modules/react-native-navigation`)
@ -512,6 +518,7 @@ DEPENDENCIES:
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- "RNRudderSdk (from `../node_modules/@rudderstack/rudder-sdk-react-native/ios`)"
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNShare (from `../node_modules/react-native-share`)
- RNSVG (from `../node_modules/react-native-svg`)
@ -636,6 +643,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeART:
:path: "../node_modules/@react-native-community/art"
ReactNativeExceptionHandler:
:path: "../node_modules/react-native-exception-handler"
ReactNativeKeyboardTrackingView:
@ -670,6 +679,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-reanimated"
RNRudderSdk:
:path: "../node_modules/@rudderstack/rudder-sdk-react-native/ios"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSentry:
:path: "../node_modules/@sentry/react-native"
RNShare:
@ -752,6 +763,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 24600e3b1aaa77126989bc58b6747509a1ba14f3
React-runtimeexecutor: a9904c6d0218fb9f8b19d6dd88607225927668f9
ReactCommon: 149906e01aa51142707a10665185db879898e966
ReactNativeART: 78edc68dd4a1e675338cd0cd113319cf3a65f2ab
ReactNativeExceptionHandler: b11ff67c78802b2f62eed0e10e75cb1ef7947c60
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
ReactNativeNavigation: 88128fa06b2c349b613e1e0fd3cdff2c58c01c55
@ -767,8 +779,9 @@ SPEC CHECKSUMS:
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
RNPermissions: 7043bacbf928eae25808275cfe73799b8f618911
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
RNReanimated: daebbd404c0cd9df6daa248d63dd940086bea9ff
RNRudderSdk: 9aa881c2bf754dc3b05e392340a5e3663a970f0a
RNScreens: eb0dfb2d6b21d2d7f980ad46b14eb306d2f1062e
RNSentry: af7873b080a0beb74c020644644d247ef4771644
RNShare: 047d42214f875d731bde73a2b67418638af85ad9
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f

330
package-lock.json generated
View file

@ -21,12 +21,15 @@
"@mattermost/react-native-paste-input": "0.1.4",
"@nozbe/watermelondb": "0.23.0",
"@nozbe/with-observables": "1.4.0",
"@react-native-community/art": "1.2.0",
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/cameraroll": "4.0.4",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/masked-view": "0.1.11",
"@react-native-community/netinfo": "6.0.0",
"@react-native-cookies/cookies": "6.0.8",
"@react-navigation/bottom-tabs": "6.0.5",
"@react-navigation/native": "6.0.2",
"@rudderstack/rudder-sdk-react-native": "1.0.12",
"@sentry/react-native": "2.6.1",
"@types/mime-db": "1.43.1",
@ -62,11 +65,13 @@
"react-native-linear-gradient": "2.5.6",
"react-native-localize": "2.1.1",
"react-native-navigation": "7.18.1",
"react-native-neomorph-shadows": "1.1.2",
"react-native-notifications": "4.1.1",
"react-native-permissions": "3.0.5",
"react-native-reanimated": "2.2.0",
"react-native-reanimated": "2.3.0-alpha.2",
"react-native-redash": "16.1.1",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.6.0",
"react-native-share": "6.5.0",
"react-native-slider": "0.11.0",
"react-native-svg": "12.1.1",
@ -4983,6 +4988,20 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"node_modules/@react-native-community/art": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@react-native-community/art/-/art-1.2.0.tgz",
"integrity": "sha512-a+ZcRGl/BzLa89yi33Mbn5SHavsEXqKUMdbfLf3U8MDLElndPqUetoJyGkv63+BcPO49UMWiQRP1YUz6/zfJ+A==",
"dependencies": {
"art": "^0.10.3",
"invariant": "^2.2.4",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@react-native-community/async-storage": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.12.1.tgz",
@ -6104,6 +6123,93 @@
"resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-1.0.0.tgz",
"integrity": "sha512-0jbp4RxjYopTsIdLl+/Fy2TiwVYHy4mgeu07DG4b/LyM0OS/+lPP5c9sbnt/AMlnF6qz2JRZpPpGw1eMNS6A4w=="
},
"node_modules/@react-navigation/bottom-tabs": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.0.5.tgz",
"integrity": "sha512-GytjJUzacHhe3C24HFrPl881Donrw2m+3JBNqMJALxMRjSA8yY72+l16bZR9YFsrywSHVSbjxIfzqtGb8rIVJg==",
"dependencies": {
"@react-navigation/elements": "^1.1.0",
"color": "^3.1.3",
"warn-once": "^0.1.0"
},
"peerDependencies": {
"@react-navigation/native": "^6.0.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 3.0.0",
"react-native-screens": ">= 3.0.0"
}
},
"node_modules/@react-navigation/core": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.0.1.tgz",
"integrity": "sha512-mVdvBDYdz8uzLQHokmVdX/xC4rS7NIkD1FN/yaGdovVzYApAhM+UGd3w1zskjyCSyXaVHHOwV59ZGVew+84xfQ==",
"dependencies": {
"@react-navigation/routers": "^6.0.1",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.23",
"query-string": "^7.0.0",
"react-is": "^16.13.0"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/@react-navigation/core/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-navigation/elements": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.1.0.tgz",
"integrity": "sha512-jZncciZPGuoP6B6f+Wpf6MYSSYy86B2HJDbFTCtT5xZV0w6V9GgCeqvSTOEAxifZrmKl8uDxsr0GrIxgQE8NxA==",
"peerDependencies": {
"@react-navigation/native": "^6.0.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 3.0.0"
}
},
"node_modules/@react-navigation/native": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.0.2.tgz",
"integrity": "sha512-HDqEwgvQ4Cu16vz8jQ55lfyNK9CGbECI1wM9cPOcUa+gkOQEDZ/95VFfFjGGflXZs3ybPvGXlMC4ZAyh1CcO6w==",
"dependencies": {
"@react-navigation/core": "^6.0.1",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.23"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@react-navigation/native/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-navigation/routers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.0.1.tgz",
"integrity": "sha512-5ctB49rmtTRQuTSBVgqMsEzBUjPP2ByUzBjNivA7jmvk+PDCl4oZsiR8KAm/twhxe215GYThfi2vUWXKAg6EEQ==",
"dependencies": {
"nanoid": "^3.1.23"
}
},
"node_modules/@rudderstack/rudder-sdk-react-native": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@rudderstack/rudder-sdk-react-native/-/rudder-sdk-react-native-1.0.12.tgz",
@ -8926,6 +9032,14 @@
"node": ">=8"
}
},
"node_modules/art": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz",
"integrity": "sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ==",
"engines": {
"node": "*"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@ -14239,6 +14353,14 @@
"node": ">=0.10.0"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -24054,8 +24176,6 @@
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true,
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -26072,6 +26192,23 @@
"node": ">=8"
}
},
"node_modules/query-string": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
"dependencies": {
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@ -26535,6 +26672,15 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
},
"node_modules/react-native-neomorph-shadows": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/react-native-neomorph-shadows/-/react-native-neomorph-shadows-1.1.2.tgz",
"integrity": "sha512-HJ7bBEwuze6TNxz/+1RvaHrrHHYMEZ0itE3hPgGxvst7Jz47K9TnEDiFskjTXMaTxXOCqzPDa9UmUfQK2K15tA==",
"dependencies": {
"@react-native-community/art": "^1.2.0",
"prop-types": "^15.7.2"
}
},
"node_modules/react-native-notifications": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-4.1.1.tgz",
@ -26567,12 +26713,13 @@
}
},
"node_modules/react-native-reanimated": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz",
"integrity": "sha512-lOJDd+5w1gY6DHGXG2jD1dsjzQmXQ2699HUc3IztvI2WP4zUT+UAA+zSG+5JiBS5DUnTL8YhhkmUQmr1KNGO5w==",
"version": "2.3.0-alpha.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.3.0-alpha.2.tgz",
"integrity": "sha512-SpzW1rPMjpy7dMEKo30873pmgj0cgczBgUHvrOKKCCPJbaQXR6w6pOGrTW5M6BQtM8zpIPPwSJN+p3G9W49aiA==",
"dependencies": {
"@babel/plugin-transform-object-assign": "^7.10.4",
"fbjs": "^3.0.0",
"invariant": "^2.2.4",
"lodash.isequal": "^4.5.0",
"mockdate": "^3.0.2",
"string-hash-64": "^1.0.3"
},
@ -26607,6 +26754,18 @@
"react-native": "*"
}
},
"node_modules/react-native-screens": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.6.0.tgz",
"integrity": "sha512-emQmSu+B6cOIjJH2OIgpuxd9zCD6xz7/oo5GCetyjsM5qR3sMnVgOxqtK99xPu9XJH/8k7MplXbtJgtk/PHXwA==",
"dependencies": {
"warn-once": "^0.1.0"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-share": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-6.5.0.tgz",
@ -29567,6 +29726,14 @@
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
"integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY="
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"engines": {
"node": ">=6"
}
},
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -29883,6 +30050,14 @@
"node": ">=0.8.0"
}
},
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"engines": {
"node": ">=4"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -31616,6 +31791,11 @@
"makeerror": "1.0.x"
}
},
"node_modules/warn-once": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.0.tgz",
"integrity": "sha512-recZTSvuaH/On5ZU5ywq66y99lImWqzP93+AiUo9LUwG8gXHW+LJjhOd6REJHm7qb0niYqrEQJvbHSQfuJtTqA=="
},
"node_modules/watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@ -36306,6 +36486,16 @@
}
}
},
"@react-native-community/art": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@react-native-community/art/-/art-1.2.0.tgz",
"integrity": "sha512-a+ZcRGl/BzLa89yi33Mbn5SHavsEXqKUMdbfLf3U8MDLElndPqUetoJyGkv63+BcPO49UMWiQRP1YUz6/zfJ+A==",
"requires": {
"art": "^0.10.3",
"invariant": "^2.2.4",
"prop-types": "^15.7.2"
}
},
"@react-native-community/async-storage": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.12.1.tgz",
@ -37194,6 +37384,66 @@
"resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-1.0.0.tgz",
"integrity": "sha512-0jbp4RxjYopTsIdLl+/Fy2TiwVYHy4mgeu07DG4b/LyM0OS/+lPP5c9sbnt/AMlnF6qz2JRZpPpGw1eMNS6A4w=="
},
"@react-navigation/bottom-tabs": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.0.5.tgz",
"integrity": "sha512-GytjJUzacHhe3C24HFrPl881Donrw2m+3JBNqMJALxMRjSA8yY72+l16bZR9YFsrywSHVSbjxIfzqtGb8rIVJg==",
"requires": {
"@react-navigation/elements": "^1.1.0",
"color": "^3.1.3",
"warn-once": "^0.1.0"
}
},
"@react-navigation/core": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.0.1.tgz",
"integrity": "sha512-mVdvBDYdz8uzLQHokmVdX/xC4rS7NIkD1FN/yaGdovVzYApAhM+UGd3w1zskjyCSyXaVHHOwV59ZGVew+84xfQ==",
"requires": {
"@react-navigation/routers": "^6.0.1",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.23",
"query-string": "^7.0.0",
"react-is": "^16.13.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
}
}
},
"@react-navigation/elements": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.1.0.tgz",
"integrity": "sha512-jZncciZPGuoP6B6f+Wpf6MYSSYy86B2HJDbFTCtT5xZV0w6V9GgCeqvSTOEAxifZrmKl8uDxsr0GrIxgQE8NxA==",
"requires": {}
},
"@react-navigation/native": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.0.2.tgz",
"integrity": "sha512-HDqEwgvQ4Cu16vz8jQ55lfyNK9CGbECI1wM9cPOcUa+gkOQEDZ/95VFfFjGGflXZs3ybPvGXlMC4ZAyh1CcO6w==",
"requires": {
"@react-navigation/core": "^6.0.1",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.23"
},
"dependencies": {
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
}
}
},
"@react-navigation/routers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.0.1.tgz",
"integrity": "sha512-5ctB49rmtTRQuTSBVgqMsEzBUjPP2ByUzBjNivA7jmvk+PDCl4oZsiR8KAm/twhxe215GYThfi2vUWXKAg6EEQ==",
"requires": {
"nanoid": "^3.1.23"
}
},
"@rudderstack/rudder-sdk-react-native": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@rudderstack/rudder-sdk-react-native/-/rudder-sdk-react-native-1.0.12.tgz",
@ -39539,6 +39789,11 @@
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="
},
"art": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz",
"integrity": "sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ=="
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@ -43730,6 +43985,11 @@
"to-regex-range": "^2.1.0"
}
},
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -51443,9 +51703,7 @@
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true,
"peer": true
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw=="
},
"nanomatch": {
"version": "1.2.13",
@ -53018,6 +53276,17 @@
"escape-goat": "^2.0.0"
}
},
"query-string": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
"requires": {
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@ -53390,6 +53659,15 @@
}
}
},
"react-native-neomorph-shadows": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/react-native-neomorph-shadows/-/react-native-neomorph-shadows-1.1.2.tgz",
"integrity": "sha512-HJ7bBEwuze6TNxz/+1RvaHrrHHYMEZ0itE3hPgGxvst7Jz47K9TnEDiFskjTXMaTxXOCqzPDa9UmUfQK2K15tA==",
"requires": {
"@react-native-community/art": "^1.2.0",
"prop-types": "^15.7.2"
}
},
"react-native-notifications": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-4.1.1.tgz",
@ -53411,12 +53689,13 @@
}
},
"react-native-reanimated": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz",
"integrity": "sha512-lOJDd+5w1gY6DHGXG2jD1dsjzQmXQ2699HUc3IztvI2WP4zUT+UAA+zSG+5JiBS5DUnTL8YhhkmUQmr1KNGO5w==",
"version": "2.3.0-alpha.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.3.0-alpha.2.tgz",
"integrity": "sha512-SpzW1rPMjpy7dMEKo30873pmgj0cgczBgUHvrOKKCCPJbaQXR6w6pOGrTW5M6BQtM8zpIPPwSJN+p3G9W49aiA==",
"requires": {
"@babel/plugin-transform-object-assign": "^7.10.4",
"fbjs": "^3.0.0",
"invariant": "^2.2.4",
"lodash.isequal": "^4.5.0",
"mockdate": "^3.0.2",
"string-hash-64": "^1.0.3"
}
@ -53437,6 +53716,14 @@
"integrity": "sha512-k2Nty4PwSnrg9HwrYeeE+EYqViYJoOFwEy9LxL5RIRfoqxAq/uQXNGwpUg2/u4gnKpBbEPa9eRh15KKMe/VHkA==",
"requires": {}
},
"react-native-screens": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.6.0.tgz",
"integrity": "sha512-emQmSu+B6cOIjJH2OIgpuxd9zCD6xz7/oo5GCetyjsM5qR3sMnVgOxqtK99xPu9XJH/8k7MplXbtJgtk/PHXwA==",
"requires": {
"warn-once": "^0.1.0"
}
},
"react-native-share": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-6.5.0.tgz",
@ -55862,6 +56149,11 @@
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
"integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY="
},
"split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
},
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -56132,6 +56424,11 @@
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -57477,6 +57774,11 @@
"makeerror": "1.0.x"
}
},
"warn-once": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.0.tgz",
"integrity": "sha512-recZTSvuaH/On5ZU5ywq66y99lImWqzP93+AiUo9LUwG8gXHW+LJjhOd6REJHm7qb0niYqrEQJvbHSQfuJtTqA=="
},
"watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",

View file

@ -19,12 +19,15 @@
"@mattermost/react-native-paste-input": "0.1.4",
"@nozbe/watermelondb": "0.23.0",
"@nozbe/with-observables": "1.4.0",
"@react-native-community/art": "1.2.0",
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/cameraroll": "4.0.4",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/masked-view": "0.1.11",
"@react-native-community/netinfo": "6.0.0",
"@react-native-cookies/cookies": "6.0.8",
"@react-navigation/bottom-tabs": "6.0.5",
"@react-navigation/native": "6.0.2",
"@rudderstack/rudder-sdk-react-native": "1.0.12",
"@sentry/react-native": "2.6.1",
"@types/mime-db": "1.43.1",
@ -60,11 +63,13 @@
"react-native-linear-gradient": "2.5.6",
"react-native-localize": "2.1.1",
"react-native-navigation": "7.18.1",
"react-native-neomorph-shadows": "1.1.2",
"react-native-notifications": "4.1.1",
"react-native-permissions": "3.0.5",
"react-native-reanimated": "2.2.0",
"react-native-reanimated": "2.3.0-alpha.2",
"react-native-redash": "16.1.1",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.6.0",
"react-native-share": "6.5.0",
"react-native-slider": "0.11.0",
"react-native-svg": "12.1.1",

View file

@ -1,7 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
type ThemeKey = 'denim' | 'sapphire' | 'quartz' | 'indigo' | 'onyx';
type ThemeType = 'Denim' | 'Sapphire' | 'Quartz' | 'Indigo' | 'Onyx' | 'custom';
type Theme = {
[key: string]: string | undefined;
type?: string;
sidebarBg: string;
sidebarText: string;
@ -15,7 +20,6 @@ type Theme = {
awayIndicator: string;
dndIndicator: string;
mentionBg: string;
mentionBj: string;
mentionColor: string;
centerChannelBg: string;
centerChannelColor: string;
@ -27,5 +31,4 @@ type Theme = {
mentionHighlightBg: string;
mentionHighlightLink: string;
codeTheme: string;
[key: string]: any;
};