Gekidou fixes (#5748)

* Use Intl based on user locale

* set PROMPT_IN_APP_PIN_CODE_AFTER to wait for 5 mins

* Observables Improvements

* Fix iPad external keyboard listener

* file model description
This commit is contained in:
Elias Nahum 2021-10-13 14:13:39 -03:00 committed by GitHub
parent 96f3970ddb
commit fcc6394502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 531 additions and 435 deletions

View file

@ -7,8 +7,8 @@ import withObservables from '@nozbe/with-observables';
import React, {ReactNode, useCallback, useState} from 'react';
import {useIntl} from 'react-intl';
import {Text, View} from 'react-native';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {combineLatest} from 'rxjs';
import {map} from 'rxjs/operators';
import CompasIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
@ -26,12 +26,6 @@ import type {WithDatabaseArgs} from '@typings/database/database';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
type AutoCompleteSelectorArgs = {
config: SystemModel;
license: SystemModel;
preferences: PreferenceModel[];
}
type AutoCompleteSelectorProps = {
dataSource?: string;
disabled?: boolean;
@ -245,14 +239,18 @@ const AutoCompleteSelector = ({
);
};
const withPreferences = withObservables([], ({database}: WithDatabaseArgs) => ({
config: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(switchMap((cfg: SystemModel) => cfg.value)),
license: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE),
preferences: database.get(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(),
}));
const withTeammateNameDisplay = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const license = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE);
const preferences = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe();
const teammateNameDisplay = combineLatest([config, license, preferences]).pipe(
map(
([{value: cfg}, {value: lcs}, prefs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs),
),
);
return {
teammateNameDisplay,
};
});
const withTeammateNameDisplay = withObservables(['preferences', 'config', 'license'], ({config, license, preferences}: AutoCompleteSelectorArgs) => ({
teammateNameDisplay: of$(getTeammateNameDisplaySetting(preferences, config.value, license.value)),
}));
export default withDatabase(withPreferences(withTeammateNameDisplay(AutoCompleteSelector)));
export default withDatabase(withTeammateNameDisplay(AutoCompleteSelector));

View file

@ -155,7 +155,7 @@ const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}:
return {
displayTextOnly,
customEmojis: hasEmojiBuiltIn ? of$([]) : database.get(MM_TABLES.SERVER.CUSTOM_EMOJI).query(Q.where('name', emojiName)).observe(),
customEmojis: hasEmojiBuiltIn ? of$([]) : database.get<CustomEmojiModel>(MM_TABLES.SERVER.CUSTOM_EMOJI).query(Q.where('name', emojiName)).observe(),
};
});

View file

@ -6,12 +6,12 @@ import React from 'react';
import {Text, TextProps} from 'react-native';
type FormattedDateProps = TextProps & {
format: string;
format?: string;
timezone?: string | UserTimezone | null;
value: number | string | Date;
}
const FormattedDate = ({format, timezone, value, ...props}: FormattedDateProps) => {
const FormattedDate = ({format = 'ddd, MMM DD, YYYY', timezone, value, ...props}: FormattedDateProps) => {
let formattedDate = moment(value).format(format);
if (timezone) {
let zone = timezone as string;
@ -24,8 +24,4 @@ const FormattedDate = ({format, timezone, value, ...props}: FormattedDateProps)
return <Text {...props}>{formattedDate}</Text>;
};
FormattedDate.defaultProps = {
format: 'ddd, MMM DD, YYYY',
};
export default FormattedDate;

View file

@ -7,7 +7,7 @@ import {StyleProp, Text, TextProps, TextStyle, ViewStyle} from 'react-native';
type FormattedTextProps = TextProps & {
id: string;
defaultMessage: string;
defaultMessage?: string;
values?: Record<string, any>;
testID?: string;
style?: StyleProp<ViewStyle> | StyleProp<TextStyle>;
@ -16,7 +16,7 @@ type FormattedTextProps = TextProps & {
const FormattedText = (props: FormattedTextProps) => {
const intl = useIntl();
const {formatMessage} = intl;
const {id, defaultMessage, values, ...otherProps} = props;
const {id, defaultMessage = '', values, ...otherProps} = props;
const tokenizedValues: Record<string, any> = {};
const elements: Record<string, any> = {};
let tokenDelimiter = '';
@ -81,8 +81,4 @@ const FormattedText = (props: FormattedTextProps) => {
return createElement(Text, otherProps, ...nodes);
};
FormattedText.defaultProps = {
defaultMessage: '',
};
export default FormattedText;

View file

@ -9,7 +9,8 @@ import Clipboard from '@react-native-community/clipboard';
import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {DeviceEventEmitter, GestureResponderEvent, StyleProp, StyleSheet, Text, TextStyle, View} from 'react-native';
import {of as of$} from 'rxjs';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import CompassIcon from '@components/compass_icon';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
@ -44,14 +45,6 @@ type AtMentionProps = {
users: UserModelType[];
}
type AtMentionArgs = {
currentUserId: SystemModel;
config: SystemModel;
license: SystemModel;
preferences: PreferenceModel[];
mentionName: string;
}
const {SERVER: {GROUP, GROUP_MEMBERSHIP, PREFERENCE, SYSTEM, USER}} = MM_TABLES;
const style = StyleSheet.create({
@ -260,34 +253,35 @@ const AtMention = ({
);
};
const withPreferences = withObservables([], ({database}: WithDatabaseArgs) => ({
config: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
currentUserId: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
license: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE),
preferences: database.get(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(),
}));
const withAtMention = withObservables(['mentionName'], ({database, mentionName}: {mentionName: string} & WithDatabaseArgs) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const license = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE);
const preferences = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe();
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => of$(value)),
);
const teammateNameDisplay = combineLatest([config, license, preferences]).pipe(
map(
([{value: cfg}, {value: lcs}, prefs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs),
),
);
const withAtMention = withObservables(
['currentUserId', 'mentionName', 'preferences', 'config', 'license'],
({currentUserId, database, mentionName, preferences, config, license}: WithDatabaseArgs & AtMentionArgs) => {
let mn = mentionName.toLowerCase();
if ((/[._-]$/).test(mn)) {
mn = mn.substring(0, mn.length - 1);
}
let mn = mentionName.toLowerCase();
if ((/[._-]$/).test(mn)) {
mn = mn.substring(0, mn.length - 1);
}
const teammateNameDisplay = of$(getTeammateNameDisplaySetting(preferences, config.value, license.value));
return {
currentUserId,
groups: database.get(GROUP).query(Q.where('delete_at', Q.eq(0))).observe(),
myGroups: database.get(GROUP_MEMBERSHIP).query().observe(),
teammateNameDisplay,
users: database.get(USER).query(
Q.where('username', Q.like(
`%${Q.sanitizeLikeString(mn)}%`,
)),
).observeWithColumns(['username']),
};
});
return {
currentUserId: of$(currentUserId.value),
groups: database.get(GROUP).query(Q.where('delete_at', Q.eq(0))).observe(),
myGroups: database.get(GROUP_MEMBERSHIP).query().observe(),
teammateNameDisplay,
users: database.get(USER).query(
Q.where('username', Q.like(
`%${Q.sanitizeLikeString(mn)}%`,
)),
).observeWithColumns(['username']),
};
});
export default withDatabase(withPreferences(withAtMention(React.memo(AtMention))));
export default withDatabase(withAtMention(React.memo(AtMention)));

View file

@ -7,6 +7,7 @@ import withObservables from '@nozbe/with-observables';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {StyleProp, Text, TextStyle} from 'react-native';
import {map, switchMap} from 'rxjs/operators';
import {switchToChannel} from '@actions/local/channel';
import {joinChannel} from '@actions/remote/channel';
@ -18,24 +19,24 @@ import {alertErrorWithFallback} from '@utils/draft';
import {preventDoubleTap} from '@utils/tap';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModelType from '@typings/database/models/servers/channel';
import type ChannelModel from '@typings/database/models/servers/channel';
import type SystemModel from '@typings/database/models/servers/system';
import type TeamModelType from '@typings/database/models/servers/team';
import type TeamModel from '@typings/database/models/servers/team';
export type ChannelMentions = Record<string, {id?: string; display_name: string; name?: string; team_name: string}>;
type ChannelMentionProps = {
channelMentions?: ChannelMentions;
channelName: string;
channels: ChannelModelType[];
currentTeamId: SystemModel;
currentUserId: SystemModel;
channels: ChannelModel[];
currentTeamId: string;
currentUserId: string;
linkStyle: StyleProp<TextStyle>;
team: TeamModelType;
team: TeamModel;
textStyle: StyleProp<TextStyle>;
}
function getChannelFromChannelName(name: string, channels: ChannelModelType[], channelMentions: ChannelMentions = {}, teamName: string) {
function getChannelFromChannelName(name: string, channels: ChannelModel[], channelMentions: ChannelMentions = {}, teamName: string) {
const channelsByName = channelMentions;
let channelName = name;
@ -64,6 +65,8 @@ function getChannelFromChannelName(name: string, channels: ChannelModelType[], c
return null;
}
const {SERVER: {CHANNEL, SYSTEM, TEAM}} = MM_TABLES;
const ChannelMention = ({
channelMentions, channelName, channels, currentTeamId, currentUserId,
linkStyle, team, textStyle,
@ -76,7 +79,7 @@ const ChannelMention = ({
let c = channel;
if (!c?.id && c?.display_name) {
const result = await joinChannel(serverUrl, currentUserId.value, currentTeamId.value, undefined, channelName);
const result = await joinChannel(serverUrl, currentUserId, currentTeamId, undefined, channelName);
if (result.error || !result.channel) {
const joinFailedMessage = {
id: t('mobile.join_channel.error'),
@ -121,14 +124,22 @@ const ChannelMention = ({
);
};
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
currentTeamId: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID),
currentUserId: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
}));
const withChannelsForTeam = withObservables([], ({database}: WithDatabaseArgs) => {
const currentTeamId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID);
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID);
const channels = currentTeamId.pipe(
switchMap(({value}) => database.get<ChannelModel>(CHANNEL).query(Q.where('team_id', value)).observeWithColumns(['display_name'])),
);
const team = currentTeamId.pipe(
switchMap(({value}) => database.get<TeamModel>(TEAM).findAndObserve(value)),
);
const withChannelsForTeam = withObservables(['currentTeamId'], ({database, currentTeamId}: WithDatabaseArgs & {currentTeamId: SystemModel}) => ({
channels: database.get(MM_TABLES.SERVER.CHANNEL).query(Q.where('team_id', currentTeamId.value)).observeWithColumns(['display_name']),
team: database.get(MM_TABLES.SERVER.TEAM).findAndObserve(currentTeamId.value),
}));
return {
channels,
currentTeamId: currentTeamId.pipe(map((ct) => ct.value)),
currentUserId: currentUserId.pipe(map((cu) => cu.value)),
team,
};
});
export default withDatabase(withSystemIds(withChannelsForTeam(ChannelMention)));
export default withDatabase(withChannelsForTeam(ChannelMention));

View file

@ -9,6 +9,7 @@ import React, {Children, ReactElement, useCallback} from 'react';
import {useIntl} from 'react-intl';
import {Alert, DeviceEventEmitter, StyleSheet, Text, View} from 'react-native';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import urlParse from 'url-parse';
import {switchToChannelByName} from '@actions/local/channel';
@ -168,17 +169,23 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
);
};
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
config: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
}));
type ConfigValue = {
value: ClientConfig;
}
const withConfigValues = withObservables(['config'], ({config}: {config: SystemModel}) => {
const cfg: ClientConfig = config.value;
const withConfigValues = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const experimentalNormalizeMarkdownLinks = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.ExperimentalNormalizeMarkdownLinks)),
);
const siteURL = config.pipe(
switchMap(({value}: ConfigValue) => of$(value.SiteURL)),
);
return {
experimentalNormalizeMarkdownLinks: of$(cfg.ExperimentalNormalizeMarkdownLinks),
siteURL: of$(cfg.SiteURL),
experimentalNormalizeMarkdownLinks,
siteURL,
};
});
export default withDatabase(withSystemIds(withConfigValues(MarkdownLink)));
export default withDatabase(withConfigValues(MarkdownLink));

View file

@ -4,8 +4,8 @@
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {from as from$, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {combineLatest, from as from$, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {Permissions} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@ -19,51 +19,51 @@ import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
type CombinedPostsInput = WithDatabaseArgs & {
currentUser: UserModel;
postId: string;
posts: PostModel[];
}
const {SERVER: {POST, SYSTEM, USER}} = MM_TABLES;
const withPostId = withObservables(['postId'], ({database, postId}: WithDatabaseArgs & {postId: string}) => {
const withCombinedPosts = withObservables(['postId'], ({database, postId}: WithDatabaseArgs & {postId: string}) => {
const currentUserId = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
map(({value}: {value: string}) => value),
);
const currentUser = currentUserId.pipe(
switchMap((value) => database.get<UserModel>(USER).findAndObserve(value)),
);
const postIds = getPostIdsForCombinedUserActivityPost(postId);
const posts = database.get<PostModel>(POST).query(
Q.where('id', Q.oneOf(postIds)),
).observe();
const post = posts.pipe(map((ps) => generateCombinedPost(postId, ps)));
const canDelete = combineLatest([posts, currentUser]).pipe(
switchMap(([ps, u]) => from$(hasPermissionForPost(ps[0], u, Permissions.DELETE_OTHERS_POSTS, false))),
);
return {
currentUser: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId: SystemModel) => database.get(USER).findAndObserve(currentUserId.value)),
const usernamesById = post.pipe(
switchMap(
(p) => database.get<UserModel>(USER).query(
Q.or(
Q.where('id', Q.oneOf(p.props.user_activity.allUserIds)),
Q.where('username', Q.oneOf(p.props.user_activity.allUsernames)),
)).observe().
pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((users) => {
// eslint-disable-next-line max-nested-callbacks
return of$(users.reduce((acc, user) => {
acc[user.id] = user.username;
return acc;
}, {} as Record<string, string>));
}),
),
),
posts: database.get(POST).query(
Q.where('id', Q.oneOf(postIds)),
).observe(),
};
});
const withCombinedPosts = withObservables(['currentUser', 'postId', 'posts'], ({currentUser, database, postId, posts}: CombinedPostsInput) => {
const post = generateCombinedPost(postId, posts);
const canDelete = from$(hasPermissionForPost(posts[0], currentUser, Permissions.DELETE_OTHERS_POSTS, false));
const usernamesById = database.get(USER).query(
Q.or(
Q.where('id', Q.oneOf(post.props.user_activity.allUserIds)),
Q.where('username', Q.oneOf(post.props.user_activity.allUsernames)),
),
).observe().pipe(
switchMap((users: UserModel[]) => {
// eslint-disable-next-line max-nested-callbacks
return of$(users.reduce((acc, user) => {
acc[user.id] = user.username;
return acc;
}, {} as Record<string, string>));
}),
);
return {
canDelete,
currentUserId: of$(currentUser.id),
post: of$(post),
currentUserId,
post,
usernamesById,
};
});
export default withDatabase(withPostId(withCombinedPosts(CombinedUserActivity)));
export default withDatabase(withCombinedPosts(CombinedUserActivity));

View file

@ -227,30 +227,30 @@ const PostList = ({currentTimezone, currentUsername, isTimezoneEnabled, lastView
};
const withPosts = withObservables(['channelId'], ({database, channelId}: {channelId: string} & WithDatabaseArgs) => {
const currentUser = database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId: SystemModel) => database.get(USER).findAndObserve(currentUserId.value)),
const currentUser = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId) => database.get<UserModel>(USER).findAndObserve(currentUserId.value)),
);
return {
currentTimezone: currentUser.pipe((switchMap((user: UserModel) => of$(user.timezone)))),
currentUsername: currentUser.pipe((switchMap((user: UserModel) => of$(user.username)))),
isTimezoneEnabled: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((config: SystemModel) => of$(config.value.ExperimentalTimezone === 'true')),
currentTimezone: currentUser.pipe((switchMap((user) => of$(user.timezone)))),
currentUsername: currentUser.pipe((switchMap((user) => of$(user.username)))),
isTimezoneEnabled: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((config) => of$(config.value.ExperimentalTimezone === 'true')),
),
lastViewedAt: database.get(MY_CHANNEL).findAndObserve(channelId).pipe(
switchMap((myChannel: MyChannelModel) => of$(myChannel.lastViewedAt)),
lastViewedAt: database.get<MyChannelModel>(MY_CHANNEL).findAndObserve(channelId).pipe(
switchMap((myChannel) => of$(myChannel.lastViewedAt)),
),
posts: database.get(POSTS_IN_CHANNEL).query(
posts: database.get<PostsInChannelModel>(POSTS_IN_CHANNEL).query(
Q.where('channel_id', channelId),
Q.experimentalSortBy('latest', Q.desc),
).observe().pipe(
switchMap((postsInChannel: PostsInChannelModel[]) => {
switchMap((postsInChannel) => {
if (!postsInChannel.length) {
return of$([]);
}
const {earliest, latest} = postsInChannel[0];
return database.get(POST).query(
return database.get<PostModel>(POST).query(
Q.and(
Q.where('delete_at', 0),
Q.where('channel_id', channelId),
@ -260,11 +260,11 @@ const withPosts = withObservables(['channelId'], ({database, channelId}: {channe
).observe();
}),
),
shouldShowJoinLeaveMessages: database.get(PREFERENCE).query(
shouldShowJoinLeaveMessages: database.get<PreferenceModel>(PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_ADVANCED_SETTINGS),
Q.where('name', Preferences.ADVANCED_FILTER_JOIN_LEAVE),
).observe().pipe(
switchMap((preferences: PreferenceModel[]) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true))),
switchMap((preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE, true))),
),
};
});

View file

@ -165,17 +165,15 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, isSystemPost, pe
);
};
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
enablePostIconOverride: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg: SystemModel) => of$(cfg.value.EnablePostIconOverride === 'true')),
),
}));
const withPost = withObservables(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
const enablePostIconOverride = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg) => of$(cfg.value.EnablePostIconOverride === 'true')),
);
const withPost = withObservables(['post', 'enablePostIconOverride'], ({post, enablePostIconOverride}: {post: PostModel; enablePostIconOverride: boolean}) => {
return {
author: post.author.observe(),
enablePostIconOverride: of$(enablePostIconOverride),
enablePostIconOverride,
};
});
export default withDatabase(withSystemIds(withPost(React.memo(Avatar))));
export default withDatabase(withPost(React.memo(Avatar)));

View file

@ -224,8 +224,8 @@ const AddMembers = ({channelType, currentUser, post, theme}: AddMembersProps) =>
};
const withChannelType = withObservables(['post'], ({database, post}: WithDatabaseArgs & {post: PostModel}) => ({
currentUser: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId: SystemModel) => database.get(USER).findAndObserve(currentUserId.value)),
currentUser: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get(USER).findAndObserve(value)),
),
channelType: post.channel.observe().pipe(
switchMap(

View file

@ -5,8 +5,7 @@ import withObservables from '@nozbe/with-observables';
import React, {useCallback, useRef} from 'react';
import {useIntl} from 'react-intl';
import Button from 'react-native-button';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {map} from 'rxjs/operators';
import {doAppCall, postEphemeralCallResponseForPost} from '@actions/remote/apps';
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@constants/apps';
@ -136,9 +135,9 @@ const ButtonBinding = ({currentTeamId, binding, post, teamID, theme}: Props) =>
};
const withTeamId = withObservables(['post'], ({post}: {post: PostModel}) => ({
teamID: post.channel.observe().pipe(switchMap((channel: ChannelModel) => of$(channel.teamId))),
currentTeamId: post.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
switchMap((currentTeamId: SystemModel) => of$(currentTeamId.value)),
teamID: post.channel.observe().pipe(map((channel: ChannelModel) => channel.teamId)),
currentTeamId: post.collections.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
map(({value}) => value),
),
}));

View file

@ -4,8 +4,7 @@
import withObservables from '@nozbe/with-observables';
import React, {useCallback, useState} from 'react';
import {useIntl} from 'react-intl';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {map} from 'rxjs/operators';
import {doAppCall, postEphemeralCallResponseForPost} from '@actions/remote/apps';
import AutocompleteSelector from '@components/autocomplete_selector';
@ -109,9 +108,9 @@ const MenuBinding = ({binding, currentTeamId, post, teamID, theme}: Props) => {
};
const withTeamId = withObservables(['post'], ({post}: {post: PostModel}) => ({
teamID: post.channel.observe().pipe(switchMap((channel: ChannelModel) => of$(channel.teamId))),
currentTeamId: post.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
switchMap((currentTeamId: SystemModel) => of$(currentTeamId.value)),
teamID: post.channel.observe().pipe(map((channel: ChannelModel) => channel.teamId)),
currentTeamId: post.collections.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
map(({value}) => value),
),
}));

View file

@ -185,10 +185,9 @@ const YouTube = ({googleDeveloperKey, isReplyPost, metadata}: YouTubeProps) => {
};
const withGoogleKey = withObservables([], ({database}: WithDatabaseArgs) => ({
googleDeveloperKey: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((config: SystemModel) => {
const cfg: ClientConfig = config.value;
return of$(cfg.GoogleDeveloperKey);
googleDeveloperKey: database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap(({value}: {value: ClientConfig}) => {
return of$(value.GoogleDeveloperKey);
}),
),
}));

View file

@ -5,8 +5,8 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {useEffect, useMemo, useRef, useState} from 'react';
import {DeviceEventEmitter, StyleProp, StyleSheet, View, ViewStyle} from 'react-native';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {Device} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@ -188,23 +188,23 @@ const Files = ({authorId, canDownloadFiles, failed, files, isReplyPost, postId,
);
};
const withConfigAndLicense = withObservables([], ({database}: WithDatabaseArgs) => ({
enableMobileFileDownload: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg: SystemModel) => of$(cfg.value.EnableMobileFileDownload !== 'false')),
),
complianceDisabled: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE).pipe(
switchMap((lc: SystemModel) => of$(lc.value.IsLicensed === 'false' || lc.value.Compliance === 'false')),
),
}));
const withCanDownload = withObservables(['post'], ({database, post}: {post: PostModel} & WithDatabaseArgs) => {
const enableMobileFileDownload = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap(({value}: {value: ClientConfig}) => of$(value.EnableMobileFileDownload !== 'false')),
);
const complianceDisabled = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE).pipe(
switchMap(({value}: {value: ClientLicense}) => of$(value.IsLicensed === 'false' || value.Compliance === 'false')),
);
const withCanDownload = withObservables(
['enableMobileFileDownload', 'complianceDisabled', 'post'],
({enableMobileFileDownload, complianceDisabled, post}: {enableMobileFileDownload: boolean; complianceDisabled: boolean; post: PostModel}) => {
return {
authorId: of$(post.userId),
canDownloadFiles: of$(complianceDisabled || enableMobileFileDownload),
postId: of$(post.id),
};
});
const canDownloadFiles = combineLatest([enableMobileFileDownload, complianceDisabled]).pipe(
map(([download, compliance]) => compliance || download),
);
export default withDatabase(withConfigAndLicense(withCanDownload(React.memo(Files))));
return {
authorId: of$(post.userId),
canDownloadFiles,
postId: of$(post.id),
};
});
export default withDatabase(withCanDownload(React.memo(Files)));

View file

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {from as from$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {queryGroupForPosts} from '@helpers/database/groups';
@ -14,29 +14,24 @@ import Message from './message';
import type {WithDatabaseArgs} from '@typings/database/database';
import type PostModel from '@typings/database/models/servers/post';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
const {SERVER: {GROUP, SYSTEM, USER}} = MM_TABLES;
const withPreferences = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUserId: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
groups: database.get(GROUP).query(Q.where('delete_at', Q.eq(0))).observe(), // Needed for when a group is added or removed
}));
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
type MessageInputArgs = {
currentUserId: SystemModel;
post: PostModel;
}
const withMessageInput = withObservables(
['currentUserId', 'post'],
({database, currentUserId, post}: WithDatabaseArgs & MessageInputArgs) => {
const currentUser = database.get(USER).findAndObserve(currentUserId.value);
const groupsForPosts = from$(queryGroupForPosts(post));
const withMessageInput = withObservables(['post'], ({database, post}: WithDatabaseArgs & MessageInputArgs) => {
const currentUser = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get<UserModel>(USER).findAndObserve(value)),
);
const groupsForPosts = from$(queryGroupForPosts(post));
return {
currentUser,
groupsForPosts,
};
});
return {
currentUser,
groupsForPosts,
};
});
export default withDatabase(withPreferences(withMessageInput(Message)));
export default withDatabase(withMessageInput(Message));

View file

@ -3,8 +3,8 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {from as from$, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {combineLatest, from as from$, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {General, Permissions} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@ -13,6 +13,7 @@ import {isSystemAdmin} from '@utils/user';
import Reactions from './reactions';
import type {Relation} from '@nozbe/watermelondb';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
@ -20,38 +21,35 @@ import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
type WithReactionsInput = WithDatabaseArgs & {
experimentalTownSquareIsReadOnly: boolean;
post: PostModel;
currentUser: UserModel;
}
const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUser: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId: SystemModel) =>
database.get(MM_TABLES.SERVER.USER).findAndObserve(currentUserId.value),
),
),
experimentalTownSquareIsReadOnly: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg: SystemModel) => of$(cfg.value.ExperimentalTownSquareIsReadOnly === 'true')),
),
}));
const withReactions = withObservables(['experimentalTownSquareIsReadOnly', 'post', 'currentUser'], ({experimentalTownSquareIsReadOnly, post, currentUser}: WithReactionsInput) => {
const disabled = post.channel.observe().pipe(
switchMap((channel: ChannelModel) => {
return of$(channel.deleteAt > 0 ||
(channel?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(currentUser.roles) && experimentalTownSquareIsReadOnly));
}),
const withReactions = withObservables(['post'], ({database, post}: WithReactionsInput) => {
const currentUserId = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
map(({value}: {value: string}) => value),
);
const currentUser = currentUserId.pipe(
switchMap((id) => database.get<UserModel>(MM_TABLES.SERVER.USER).findAndObserve(id)),
);
const channel = (post.channel as Relation<ChannelModel>).observe();
const experimentalTownSquareIsReadOnly = database.get<SystemModel>(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
map(({value}: {value: ClientConfig}) => value.ExperimentalTownSquareIsReadOnly === 'true'),
);
const disabled = combineLatest([currentUser, channel, experimentalTownSquareIsReadOnly]).pipe(
map(([u, c, readOnly]) => ((c && c.deleteAt > 0) || (c?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(u.roles) && readOnly))),
);
const canAddReaction = currentUser.pipe(switchMap((u) => from$(hasPermissionForPost(post, u, Permissions.ADD_REACTION, true))));
const canRemoveReaction = currentUser.pipe(switchMap((u) => from$(hasPermissionForPost(post, u, Permissions.REMOVE_REACTION, true))));
return {
canAddReaction: from$(hasPermissionForPost(post, currentUser, Permissions.ADD_REACTION, true)),
canRemoveReaction: from$(hasPermissionForPost(post, currentUser, Permissions.REMOVE_REACTION, true)),
currentUserId: of$(currentUser.id),
canAddReaction,
canRemoveReaction,
currentUserId,
disabled,
postId: of$(post.id),
reactions: post.reactions.observe(),
};
});
export default withDatabase(withSystem(withReactions(Reactions)));
export default withDatabase(withReactions(Reactions));

View file

@ -4,8 +4,8 @@
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@ -20,29 +20,27 @@ import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
type HeaderInputProps = {
config: ClientConfig;
differentThreadSequence: boolean;
license: ClientLicense;
preferences: PreferenceModel[];
post: PostModel;
};
const withBaseHeaderProps = withObservables([], ({database}: WithDatabaseArgs & {post: PostModel}) => ({
config: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(switchMap((cfg: SystemModel) => of$(cfg.value))),
license: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE).pipe(switchMap((lcs: SystemModel) => of$(lcs.value))),
preferences: database.get(MM_TABLES.SERVER.PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(),
}));
const {SERVER: {POST, PREFERENCE, SYSTEM}} = MM_TABLES;
const withHeaderProps = withObservables(
['preferences', 'post', 'differentThreadSequence'],
({config, post, license, database, preferences, differentThreadSequence}: WithDatabaseArgs & HeaderInputProps) => {
['post', 'differentThreadSequence'],
({post, database, differentThreadSequence}: WithDatabaseArgs & HeaderInputProps) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(switchMap(({value}) => of$(value as ClientConfig)));
const license = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE).pipe(switchMap(({value}) => of$(value as ClientLicense)));
const preferences = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe();
const author = post.author.observe();
const enablePostUsernameOverride = of$(config.EnablePostUsernameOverride === 'true');
const isTimezoneEnabled = of$(config.ExperimentalTimezone === 'true');
const isMilitaryTime = of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false));
const teammateNameDisplay = of$(getTeammateNameDisplaySetting(preferences, config, license));
const isCustomStatusEnabled = of$(config.EnableCustomUserStatuses === 'true' && isMinimumServerVersion(config.Version, 5, 36));
const commentCount = database.get(MM_TABLES.SERVER.POST).query(
const enablePostUsernameOverride = config.pipe(map((cfg) => cfg.EnablePostUsernameOverride === 'true'));
const isTimezoneEnabled = config.pipe(map((cfg) => cfg.ExperimentalTimezone === 'true'));
const isMilitaryTime = preferences.pipe(map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)));
const isCustomStatusEnabled = config.pipe(map((cfg) => cfg.EnableCustomUserStatuses === 'true' && isMinimumServerVersion(cfg.Version, 5, 36)));
const teammateNameDisplay = combineLatest([preferences, config, license]).pipe(
map(([prefs, cfg, lcs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs)),
);
const commentCount = database.get(POST).query(
Q.and(
Q.where('root_id', (post.rootId || post.id)),
Q.where('delete_at', Q.eq(0)),
@ -68,4 +66,4 @@ const withHeaderProps = withObservables(
};
});
export default withDatabase(withBaseHeaderProps(withHeaderProps(Header)));
export default withDatabase(withHeaderProps(Header));

View file

@ -71,9 +71,11 @@ async function shouldHighlightReplyBar(currentUser: UserModel, post: PostModel,
return false;
}
const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({
featureFlagAppsEnabled: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(switchMap((cfg: SystemModel) => of$(cfg.value.FeatureFlagAppsEnabled))),
currentUser: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId: SystemModel) => database.get(USER).findAndObserve(currentUserId.value)),
featureFlagAppsEnabled: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap((cfg) => of$(cfg.value.FeatureFlagAppsEnabled)),
),
currentUser: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((currentUserId) => database.get<UserModel>(USER).findAndObserve(currentUserId.value)),
),
}));
@ -92,10 +94,10 @@ const withPost = withObservables(
},
));
const isEphemeral = of$(isPostEphemeral(post));
const isFlagged = database.get(PREFERENCE).query(
const isFlagged = database.get<PreferenceModel>(PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_FLAGGED_POST),
Q.where('name', post.id),
).observe().pipe(switchMap((pref: PreferenceModel[]) => of$(Boolean(pref.length))));
).observe().pipe(switchMap((pref) => of$(Boolean(pref.length))));
if (post.props?.add_channel_member && isPostEphemeral(post)) {
isPostAddChannelMember = from$(canManageChannelMembers(post, currentUser));
@ -120,7 +122,8 @@ const withPost = withObservables(
isJumboEmoji = post.collections.get(CUSTOM_EMOJI).query().observe().pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((customEmojis: CustomEmojiModel[]) => of$(hasJumboEmojiOnly(post.message, customEmojis.map((c) => c.name))),
));
),
);
}
const partialConfig: Partial<ClientConfig> = {

View file

@ -5,6 +5,8 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {useEffect} from 'react';
import {useIntl} from 'react-intl';
import {of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {View} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@ -16,22 +18,18 @@ 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 WithUserArgs = WithDatabaseArgs & {
currentUserId: SystemModel;
}
type ServerVersionProps = WithDatabaseArgs & {
config: SystemModel;
user: UserModel;
version?: string;
roles: string;
};
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
const ServerVersion = ({config, user}: ServerVersionProps) => {
const ServerVersion = ({version, roles}: ServerVersionProps) => {
const intl = useIntl();
useEffect(() => {
const serverVersion = (config.value?.Version) || '';
const serverVersion = version || '';
if (serverVersion) {
const {RequiredServer: {MAJOR_VERSION, MIN_VERSION, PATCH_VERSION}} = View;
@ -39,21 +37,26 @@ const ServerVersion = ({config, user}: ServerVersionProps) => {
if (!isSupportedServer) {
// Only display the Alert if the TOS does not need to show first
unsupportedServer(isSystemAdmin(user.roles), intl);
unsupportedServer(isSystemAdmin(roles), intl);
}
}
}, [config.value?.Version, user.roles]);
}, [version, roles]);
return null;
};
const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUserId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
config: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
const enahanced = withObservables([], ({database}: WithDatabaseArgs) => ({
varsion: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(
switchMap(({value}: {value: ClientConfig}) => of$(value.Version)),
),
roles: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(
({value}) => database.get<UserModel>(USER).findAndObserve(value).pipe(
// eslint-disable-next-line max-nested-callbacks
map((user) => user.roles),
),
),
),
}));
const withUser = withObservables(['currentUserId'], ({currentUserId, database}: WithUserArgs) => ({
user: database.collections.get(USER).findAndObserve(currentUserId.value),
}));
export default withDatabase(withSystem(withUser(ServerVersion)));
export default withDatabase(enahanced(ServerVersion));

View file

@ -6,7 +6,7 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
import {View} from 'react-native';
import {of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import FormattedText from '@components/formatted_text';
import FormattedTime from '@components/formatted_time';
@ -21,12 +21,6 @@ import type PreferenceModel from '@typings/database/models/servers/preference';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
type withUserInputProps = {
config: SystemModel;
currentUserId: SystemModel;
preferences: PreferenceModel[];
}
type Props = {
createAt: number | string | Date;
isMilitaryTime: boolean;
@ -35,6 +29,8 @@ type Props = {
user: UserModel;
}
const {SERVER: {PREFERENCE, SYSTEM, USER}} = MM_TABLES;
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
displayName: {
@ -89,24 +85,24 @@ const SystemHeader = ({isMilitaryTime, isTimezoneEnabled, createAt, theme, user}
);
};
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
config: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
currentUserId: database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
preferences: database.get(MM_TABLES.SERVER.PREFERENCE).query(
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
const preferences = database.get<PreferenceModel>(PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS), Q.where('name', 'use_military_time'),
).observe(),
}));
const withUser = withObservables(['currentUserId'], ({config, currentUserId, database, preferences}: WithDatabaseArgs & withUserInputProps) => {
const cfg: ClientConfig = config.value;
const isTimezoneEnabled = of$(cfg.ExperimentalTimezone === 'true');
const isMilitaryTime = of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false));
).observe();
const isTimezoneEnabled = config.pipe(map(({value}: {value: ClientConfig}) => value.ExperimentalTimezone === 'true'));
const isMilitaryTime = preferences.pipe(
map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
);
const user = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get(USER).findAndObserve(value)),
);
return {
isMilitaryTime,
isTimezoneEnabled,
user: database.get(MM_TABLES.SERVER.USER).findAndObserve(currentUserId.value),
user,
};
});
export default withDatabase(withSystemIds(withUser(React.memo(SystemHeader))));
export default withDatabase(enhanced(React.memo(SystemHeader)));

View file

@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import withObservables from '@nozbe/with-observables';
import React, {ComponentType, createContext} from 'react';
import {IntlProvider} from 'react-intl';
import {of as of$} from 'rxjs';
import {catchError, switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import type {SystemModel} from '@database/models/server';
import type Database from '@nozbe/watermelondb/Database';
import type UserModel from '@typings/database/models/servers/user';
type Props = {
locale: string;
children: React.ReactNode;
}
type WithUserLocaleProps = {
locale: string;
}
const {SERVER: {USER, SYSTEM}} = MM_TABLES;
export const UserLocaleContext = createContext(DEFAULT_LOCALE);
const {Consumer, Provider} = UserLocaleContext;
const UserLocaleProvider = ({locale, children}: Props) => {
return (
<Provider value={locale}>
<IntlProvider
locale={locale}
messages={getTranslations(locale)}
>
{children}
</IntlProvider>
</Provider>);
};
export function withUserLocale<T extends WithUserLocaleProps>(Component: ComponentType<T>): ComponentType<T> {
return function UserLocaleComponent(props) {
return (
<Consumer>
{(locale: string) => (
<IntlProvider
locale={locale}
messages={getTranslations(locale)}
>
<Component
{...props}
/>
</IntlProvider>
)}
</Consumer>
);
};
}
export function useUserLocale(): string {
return React.useContext(UserLocaleContext);
}
const enhancedThemeProvider = withObservables([], ({database}: {database: Database}) => ({
locale: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap(({value}) => database.get<UserModel>(USER).findAndObserve(value).pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((user) => of$(user.locale)),
)),
catchError(() => of$(DEFAULT_LOCALE)),
),
}));
export default enhancedThemeProvider(UserLocaleProvider);

View file

@ -8,6 +8,7 @@ import React, {ComponentType, useEffect, useState} from 'react';
import {MM_TABLES} from '@constants/database';
import ServerUrlProvider from '@context/server_url';
import ThemeProvider from '@context/theme';
import UserLocaleProvider from '@context/user_locale';
import DatabaseManager from '@database/manager';
import type ServersModel from '@typings/database/models/app/servers';
@ -56,11 +57,13 @@ export function withServerDatabase<T>(Component: ComponentType<T>): ComponentTyp
return (
<DatabaseProvider database={state.database}>
<ServerUrlProvider url={state.serverUrl}>
<ThemeProvider database={state.database}>
<Component {...props}/>
</ThemeProvider>
</ServerUrlProvider>
<UserLocaleProvider database={state.database}>
<ServerUrlProvider url={state.serverUrl}>
<ThemeProvider database={state.database}>
<Component {...props}/>
</ThemeProvider>
</ServerUrlProvider>
</UserLocaleProvider>
</DatabaseProvider>
);
};

View file

@ -12,7 +12,7 @@ import type PostModel from '@typings/database/models/servers/post';
const {FILE, POST} = MM_TABLES.SERVER;
/**
* The File model works in pair with the Post model. It hosts information about the files shared in a Post
* The File model works in pair with the Post model. It hosts information about the files attached to a Post
*/
export default class FileModel extends Model {
/** table (name) : File */

View file

@ -8,7 +8,7 @@ import {Alert, AlertButton, AppState, AppStateStatus, Platform} from 'react-nati
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
import {getIOSAppGroupDetails} from '@utils/mattermost_managed';
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000;
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 60 * 1000;
class ManagedApp {
backgroundSince = 0;

View file

@ -33,6 +33,91 @@ import type SystemModel from '@typings/database/models/servers/system';
const MATTERMOST_BUNDLE_IDS = ['com.mattermost.rnbeta', 'com.mattermost.rn'];
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
},
scrollViewContent: {
paddingBottom: 30,
},
logoContainer: {
alignItems: 'center',
flex: 1,
height: 200,
paddingVertical: 40,
},
infoContainer: {
flex: 1,
flexDirection: 'column',
paddingHorizontal: 20,
},
titleContainer: {
flex: 1,
marginBottom: 20,
},
title: {
fontSize: 22,
color: theme.centerChannelColor,
},
subtitle: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 19,
marginBottom: 15,
},
info: {
color: theme.centerChannelColor,
fontSize: 16,
lineHeight: 19,
},
licenseContainer: {
flex: 1,
flexDirection: 'row',
marginTop: 20,
},
noticeContainer: {
flex: 1,
flexDirection: 'column',
},
noticeLink: {
color: theme.linkColor,
fontSize: 11,
lineHeight: 13,
},
hashContainer: {
flex: 1,
flexDirection: 'column',
},
footerGroup: {
flex: 1,
},
footerTitleText: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 11,
fontFamily: 'OpenSans-Semibold',
lineHeight: 13,
},
footerText: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 11,
lineHeight: 13,
marginBottom: 10,
},
copyrightText: {
marginBottom: 0,
},
tosPrivacyContainer: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
};
});
type ConnectedAboutProps = {
config: SystemModel;
license: SystemModel;
@ -255,93 +340,9 @@ const ConnectedAbout = ({config, license}: ConnectedAboutProps) => {
);
};
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
},
scrollViewContent: {
paddingBottom: 30,
},
logoContainer: {
alignItems: 'center',
flex: 1,
height: 200,
paddingVertical: 40,
},
infoContainer: {
flex: 1,
flexDirection: 'column',
paddingHorizontal: 20,
},
titleContainer: {
flex: 1,
marginBottom: 20,
},
title: {
fontSize: 22,
color: theme.centerChannelColor,
},
subtitle: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 19,
marginBottom: 15,
},
info: {
color: theme.centerChannelColor,
fontSize: 16,
lineHeight: 19,
},
licenseContainer: {
flex: 1,
flexDirection: 'row',
marginTop: 20,
},
noticeContainer: {
flex: 1,
flexDirection: 'column',
},
noticeLink: {
color: theme.linkColor,
fontSize: 11,
lineHeight: 13,
},
hashContainer: {
flex: 1,
flexDirection: 'column',
},
footerGroup: {
flex: 1,
},
footerTitleText: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 11,
fontFamily: 'OpenSans-Semibold',
lineHeight: 13,
},
footerText: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 11,
lineHeight: 13,
marginBottom: 10,
},
copyrightText: {
marginBottom: 0,
},
tosPrivacyContainer: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
};
});
export default withDatabase(withObservables([], ({database}: WithDatabaseArgs) => ({
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
config: database.collections.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG),
license: database.collections.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE),
}))(ConnectedAbout));
}));
export default withDatabase(enhanced(ConnectedAbout));

View file

@ -5,6 +5,8 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
import {TouchableOpacity, View} from 'react-native';
import {of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import ChannelIcon from '@components/channel_icon';
import CompassIcon from '@components/compass_icon';
@ -24,21 +26,19 @@ import type MyChannelSettingsModel from '@typings/database/models/servers/my_cha
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
const {SERVER: {SYSTEM, USER}} = MM_TABLES;
type WithChannelArgs = WithDatabaseArgs & {
currentUserId: SystemModel;
channel: ChannelModel;
}
type ChannelTitleInputProps = {
type ChannelTitleProps = {
canHaveSubtitle: boolean;
channel: ChannelModel;
currentUserId: string;
onPress: () => void;
};
type ChannelTitleProps = ChannelTitleInputProps & {
channelInfo: ChannelInfoModel;
channelSettings: MyChannelSettingsModel;
onPress: () => void;
teammate?: UserModel;
};
@ -52,7 +52,6 @@ const ChannelTitle = ({
teammate,
}: ChannelTitleProps) => {
const theme = useTheme();
const style = getStyle(theme);
const channelType = channel.type;
const isArchived = channel.deleteAt !== 0;
@ -175,21 +174,29 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
};
});
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUserId: database.collections.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID),
}));
const enhancedChannelTitle = withObservables(['channel', 'currentUserId'], ({channel, currentUserId, database}: WithChannelArgs) => {
let teammateId;
const enhanced = withObservables(['channel'], ({channel, database}: WithChannelArgs) => {
const currentUserId = database.collections.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
map(({value}: {value: string}) => value),
);
let teammate;
if (channel.type === General.DM_CHANNEL && channel.displayName) {
teammateId = getUserIdFromChannelName(currentUserId.value, channel.name);
teammate = currentUserId.pipe(
switchMap((id) => {
const teammateId = getUserIdFromChannelName(id, channel.name);
if (teammateId) {
return database.get<UserModel>(USER).findAndObserve(teammateId);
}
return of$(undefined);
}),
);
}
return {
channelInfo: channel.info.observe(),
channelSettings: channel.settings.observe(),
...(teammateId && {teammate: database.collections.get(MM_TABLES.SERVER.USER).findAndObserve(teammateId)}),
currentUserId,
teammate,
};
});
export default withDatabase(withSystemIds(enhancedChannelTitle(ChannelTitle)));
export default withDatabase(enhanced(ChannelTitle));

View file

@ -7,6 +7,7 @@ import React, {useMemo} from 'react';
import {useIntl} from 'react-intl';
import {Text, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {map} from 'rxjs/operators';
import {logout} from '@actions/remote/session';
import PostList from '@components/post_list';
@ -26,9 +27,9 @@ import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
import type {LaunchProps} from '@typings/launch';
type ChannelProps = WithDatabaseArgs & LaunchProps & {
currentChannelId: SystemModel;
currentTeamId: SystemModel;
type ChannelProps = LaunchProps & {
currentChannelId: string;
currentTeamId: string;
time?: number;
};
@ -81,22 +82,22 @@ const Channel = ({currentChannelId, currentTeamId, time}: ChannelProps) => {
};
const renderComponent = useMemo(() => {
if (!currentTeamId.value) {
if (!currentTeamId) {
return <FailedTeams/>;
}
if (!currentChannelId.value) {
return <FailedChannels teamId={currentTeamId.value}/>;
if (!currentChannelId) {
return <FailedChannels teamId={currentTeamId}/>;
}
return (
<>
<ChannelNavBar
channelId={currentChannelId?.value}
channelId={currentChannelId}
onPress={() => null}
/>
<PostList
channelId={currentChannelId?.value}
channelId={currentChannelId}
testID='channel.post_list'
/>
<View style={styles.sectionContainer}>
@ -117,7 +118,7 @@ const Channel = ({currentChannelId, currentTeamId, time}: ChannelProps) => {
</View>
</>
);
}, [currentTeamId.value, currentChannelId.value, theme]);
}, [currentTeamId, currentChannelId, theme]);
return (
<SafeAreaView
@ -132,9 +133,13 @@ const Channel = ({currentChannelId, currentTeamId, time}: ChannelProps) => {
);
};
const withSystemIds = withObservables([], ({database}: WithDatabaseArgs) => ({
currentChannelId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_CHANNEL_ID),
currentTeamId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID),
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
currentChannelId: database.collections.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_CHANNEL_ID).pipe(
map(({value}: {value: string}) => value),
),
currentTeamId: database.collections.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
map(({value}: {value: string}) => value),
),
}));
export default withDatabase(withSystemIds(Channel));
export default withDatabase(enhanced(Channel));

View file

@ -208,7 +208,7 @@ class ClearAfterModal extends NavigationComponent<Props, State> {
}
}
const enhancedCAM = withObservables([], ({database}: WithDatabaseArgs) => ({
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUser: database.get<SystemModel>(SYSTEM).
findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).
pipe(
@ -216,4 +216,4 @@ const enhancedCAM = withObservables([], ({database}: WithDatabaseArgs) => ({
),
}));
export default withDatabase(enhancedCAM(injectIntl(ClearAfterModal)));
export default withDatabase(enhanced(injectIntl(ClearAfterModal)));

View file

@ -148,7 +148,7 @@ const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpir
);
};
const withUserConfig = withObservables([], ({database}: WithDatabaseArgs) => {
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const config = database.
get<SystemModel>(SYSTEM).
findAndObserve(SYSTEM_IDENTIFIERS.CONFIG);
@ -180,4 +180,4 @@ const withUserConfig = withObservables([], ({database}: WithDatabaseArgs) => {
};
});
export default withDatabase(withUserConfig(AccountScreen));
export default withDatabase(enhanced(AccountScreen));

View file

@ -46,8 +46,8 @@ const Account = ({currentUser, isFocused, theme}: Props) => {
};
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)),
currentUser: database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe(
switchMap((id) => database.get(USER).findAndObserve(id.value)),
),
}));

View file

@ -118,7 +118,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// screen = require('@screens/flagged_posts').default;
// break;
case Screens.FORGOT_PASSWORD:
screen = require('@screens/forgot_password').default;
screen = withIntl(require('@screens/forgot_password').default);
break;
// case 'Gallery':
// screen = require('@screens/gallery').default;
@ -127,10 +127,10 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// screen = require('@screens/interactive_dialog').default;
// break;
case Screens.LOGIN:
screen = require('@screens/login').default;
screen = withIntl(require('@screens/login').default);
break;
case Screens.LOGIN_OPTIONS:
screen = require('@screens/login_options').default;
screen = withIntl(require('@screens/login_options').default);
break;
// case 'LongPost':
// screen = require('@screens/long_post').default;
@ -139,7 +139,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// screen = require('app/components/sidebars/main').default;
// break;
case Screens.MFA:
screen = require('@screens/mfa').default;
screen = withIntl(require('@screens/mfa').default);
break;
// case 'MoreChannels':
// screen = require('@screens/more_channels').default;
@ -206,7 +206,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
// screen = require('@screens/settings/sidebar').default;
// break;
case Screens.SSO:
screen = require('@screens/sso').default;
screen = withIntl(require('@screens/sso').default);
break;
// case 'Table':
// screen = require('@screens/table').default;
@ -229,13 +229,13 @@ Navigation.setLazyComponentRegistrator((screenName) => {
}
if (screen) {
Navigation.registerComponent(screenName, () => withSafeAreaInsets(withGestures(withIntl(withManagedConfig(screen)), extraStyles)));
Navigation.registerComponent(screenName, () => withGestures(withSafeAreaInsets(withManagedConfig(screen)), extraStyles));
}
});
export function registerScreens() {
const homeScreen = require('@screens/home').default;
const serverScreen = require('@screens/server').default;
Navigation.registerComponent(Screens.SERVER, () => withIntl(withManagedConfig(serverScreen)));
Navigation.registerComponent(Screens.HOME, () => withSafeAreaInsets(withGestures(withIntl(withServerDatabase(withManagedConfig(homeScreen))), undefined)));
Navigation.registerComponent(Screens.SERVER, () => withGestures(withIntl(withManagedConfig(serverScreen)), undefined));
Navigation.registerComponent(Screens.HOME, () => withGestures(withSafeAreaInsets(withServerDatabase(withManagedConfig(homeScreen))), undefined));
}

View file

@ -169,15 +169,29 @@ MattermostBucket* bucket = nil;
*/
RNHWKeyboardEvent *hwKeyEvent = nil;
- (NSMutableArray<UIKeyCommand *> *)keyCommands {
NSMutableArray *keys = [NSMutableArray new];
if (hwKeyEvent == nil) {
hwKeyEvent = [[RNHWKeyboardEvent alloc] init];
}
NSMutableArray *commands = [NSMutableArray new];
if ([hwKeyEvent isListening]) {
[keys addObject: [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(sendEnter:)]];
[keys addObject: [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierShift action:@selector(sendShiftEnter:)]];
UIKeyCommand *enter = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(sendEnter:)];
UIKeyCommand *shiftEnter = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierShift action:@selector(sendShiftEnter:)];
if (@available(iOS 13.0, *)) {
[enter setTitle:@"Send message"];
[shiftEnter setTitle:@"Add new line"];
}
if (@available(iOS 15.0, *)) {
[enter setWantsPriorityOverSystemBehavior:YES];
[shiftEnter setWantsPriorityOverSystemBehavior:YES];
}
[commands addObject: enter];
[commands addObject: shiftEnter];
}
return keys;
return commands;
}
- (void)sendEnter:(UIKeyCommand *)sender {