From fcc6394502c5a7a7784cf450ef60c0ede4561eb9 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 13 Oct 2021 14:13:39 -0300 Subject: [PATCH] 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 --- .../autocomplete_selector/index.tsx | 34 ++-- app/components/emoji/index.tsx | 2 +- app/components/formatted_date/index.tsx | 8 +- app/components/formatted_text/index.tsx | 8 +- app/components/markdown/at_mention/index.tsx | 68 ++++--- .../markdown/channel_mention/index.tsx | 45 +++-- .../markdown/markdown_link/index.tsx | 23 ++- .../post_list/combined_user_activity/index.ts | 74 ++++---- app/components/post_list/index.tsx | 26 +-- .../post_list/post/avatar/index.tsx | 14 +- .../post_list/post/body/add_members/index.tsx | 4 +- .../button_binding/index.tsx | 9 +- .../embedded_bindings/menu_binding/index.tsx | 9 +- .../post/body/content/youtube/index.tsx | 7 +- .../post_list/post/body/files/index.tsx | 40 ++-- .../post_list/post/body/message/index.ts | 33 ++-- .../post_list/post/body/reactions/index.ts | 48 +++-- app/components/post_list/post/header/index.ts | 36 ++-- app/components/post_list/post/index.ts | 15 +- app/components/server_version/index.tsx | 39 ++-- app/components/system_header/index.tsx | 36 ++-- app/context/user_locale/index.tsx | 76 ++++++++ app/database/components/index.tsx | 13 +- app/database/models/server/file.ts | 2 +- app/init/managed_app.ts | 2 +- app/screens/about/index.tsx | 175 +++++++++--------- .../channel_nav_bar/channel_title/index.tsx | 39 ++-- app/screens/channel/index.tsx | 31 ++-- .../custom_status_clear_after/index.tsx | 4 +- app/screens/home/account/index.tsx | 4 +- app/screens/home/tab_bar/account.tsx | 4 +- app/screens/index.tsx | 16 +- ios/Mattermost/AppDelegate.m | 22 ++- 33 files changed, 531 insertions(+), 435 deletions(-) create mode 100644 app/context/user_locale/index.tsx diff --git a/app/components/autocomplete_selector/index.tsx b/app/components/autocomplete_selector/index.tsx index 242552fdb..a4c0a178e 100644 --- a/app/components/autocomplete_selector/index.tsx +++ b/app/components/autocomplete_selector/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG); + const license = database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE); + const preferences = database.get(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)); diff --git a/app/components/emoji/index.tsx b/app/components/emoji/index.tsx index aa45d710b..a28e1b9fd 100644 --- a/app/components/emoji/index.tsx +++ b/app/components/emoji/index.tsx @@ -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(MM_TABLES.SERVER.CUSTOM_EMOJI).query(Q.where('name', emojiName)).observe(), }; }); diff --git a/app/components/formatted_date/index.tsx b/app/components/formatted_date/index.tsx index d65c0279f..5bb97d6ab 100644 --- a/app/components/formatted_date/index.tsx +++ b/app/components/formatted_date/index.tsx @@ -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 {formattedDate}; }; -FormattedDate.defaultProps = { - format: 'ddd, MMM DD, YYYY', -}; - export default FormattedDate; diff --git a/app/components/formatted_text/index.tsx b/app/components/formatted_text/index.tsx index d87ce8987..26db7fd1b 100644 --- a/app/components/formatted_text/index.tsx +++ b/app/components/formatted_text/index.tsx @@ -7,7 +7,7 @@ import {StyleProp, Text, TextProps, TextStyle, ViewStyle} from 'react-native'; type FormattedTextProps = TextProps & { id: string; - defaultMessage: string; + defaultMessage?: string; values?: Record; testID?: string; style?: StyleProp | StyleProp; @@ -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 = {}; const elements: Record = {}; let tokenDelimiter = ''; @@ -81,8 +81,4 @@ const FormattedText = (props: FormattedTextProps) => { return createElement(Text, otherProps, ...nodes); }; -FormattedText.defaultProps = { - defaultMessage: '', -}; - export default FormattedText; diff --git a/app/components/markdown/at_mention/index.tsx b/app/components/markdown/at_mention/index.tsx index 4e8041236..8aaa4c2f0 100644 --- a/app/components/markdown/at_mention/index.tsx +++ b/app/components/markdown/at_mention/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG); + const license = database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE); + const preferences = database.get(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_DISPLAY_SETTINGS)).observe(); + const currentUserId = database.get(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))); diff --git a/app/components/markdown/channel_mention/index.tsx b/app/components/markdown/channel_mention/index.tsx index 14cef47a3..a76e6d0bd 100644 --- a/app/components/markdown/channel_mention/index.tsx +++ b/app/components/markdown/channel_mention/index.tsx @@ -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; type ChannelMentionProps = { channelMentions?: ChannelMentions; channelName: string; - channels: ChannelModelType[]; - currentTeamId: SystemModel; - currentUserId: SystemModel; + channels: ChannelModel[]; + currentTeamId: string; + currentUserId: string; linkStyle: StyleProp; - team: TeamModelType; + team: TeamModel; textStyle: StyleProp; } -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID); + const currentUserId = database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID); + const channels = currentTeamId.pipe( + switchMap(({value}) => database.get(CHANNEL).query(Q.where('team_id', value)).observeWithColumns(['display_name'])), + ); + const team = currentTeamId.pipe( + switchMap(({value}) => database.get(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)); diff --git a/app/components/markdown/markdown_link/index.tsx b/app/components/markdown/markdown_link/index.tsx index 14494739e..f070e3fa0 100644 --- a/app/components/markdown/markdown_link/index.tsx +++ b/app/components/markdown/markdown_link/index.tsx @@ -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(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)); diff --git a/app/components/post_list/combined_user_activity/index.ts b/app/components/post_list/combined_user_activity/index.ts index f2589c8dd..179d5f7dc 100644 --- a/app/components/post_list/combined_user_activity/index.ts +++ b/app/components/post_list/combined_user_activity/index.ts @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + map(({value}: {value: string}) => value), + ); + const currentUser = currentUserId.pipe( + switchMap((value) => database.get(USER).findAndObserve(value)), + ); + const postIds = getPostIdsForCombinedUserActivityPost(postId); + const posts = database.get(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(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)); + }), + ), ), - 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)); - }), ); return { canDelete, - currentUserId: of$(currentUser.id), - post: of$(post), + currentUserId, + post, usernamesById, }; }); -export default withDatabase(withPostId(withCombinedPosts(CombinedUserActivity))); +export default withDatabase(withCombinedPosts(CombinedUserActivity)); diff --git a/app/components/post_list/index.tsx b/app/components/post_list/index.tsx index 287441a9f..9b8802594 100644 --- a/app/components/post_list/index.tsx +++ b/app/components/post_list/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap((currentUserId) => database.get(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(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(MY_CHANNEL).findAndObserve(channelId).pipe( + switchMap((myChannel) => of$(myChannel.lastViewedAt)), ), - posts: database.get(POSTS_IN_CHANNEL).query( + posts: database.get(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(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(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))), ), }; }); diff --git a/app/components/post_list/post/avatar/index.tsx b/app/components/post_list/post/avatar/index.tsx index c8cbbbc75..a8886b8d5 100644 --- a/app/components/post_list/post/avatar/index.tsx +++ b/app/components/post_list/post/avatar/index.tsx @@ -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(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))); diff --git a/app/components/post_list/post/body/add_members/index.tsx b/app/components/post_list/post/body/add_members/index.tsx index 24e637226..eab121f80 100644 --- a/app/components/post_list/post/body/add_members/index.tsx +++ b/app/components/post_list/post/body/add_members/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap(({value}) => database.get(USER).findAndObserve(value)), ), channelType: post.channel.observe().pipe( switchMap( diff --git a/app/components/post_list/post/body/content/embedded_bindings/button_binding/index.tsx b/app/components/post_list/post/body/content/embedded_bindings/button_binding/index.tsx index 6df80cc2d..658eef018 100644 --- a/app/components/post_list/post/body/content/embedded_bindings/button_binding/index.tsx +++ b/app/components/post_list/post/body/content/embedded_bindings/button_binding/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe( + map(({value}) => value), ), })); diff --git a/app/components/post_list/post/body/content/embedded_bindings/menu_binding/index.tsx b/app/components/post_list/post/body/content/embedded_bindings/menu_binding/index.tsx index 7209dfb21..09401ea03 100644 --- a/app/components/post_list/post/body/content/embedded_bindings/menu_binding/index.tsx +++ b/app/components/post_list/post/body/content/embedded_bindings/menu_binding/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe( + map(({value}) => value), ), })); diff --git a/app/components/post_list/post/body/content/youtube/index.tsx b/app/components/post_list/post/body/content/youtube/index.tsx index 73e3e102f..65e105d52 100644 --- a/app/components/post_list/post/body/content/youtube/index.tsx +++ b/app/components/post_list/post/body/content/youtube/index.tsx @@ -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(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe( + switchMap(({value}: {value: ClientConfig}) => { + return of$(value.GoogleDeveloperKey); }), ), })); diff --git a/app/components/post_list/post/body/files/index.tsx b/app/components/post_list/post/body/files/index.tsx index 730b50107..145b39cd0 100644 --- a/app/components/post_list/post/body/files/index.tsx +++ b/app/components/post_list/post/body/files/index.tsx @@ -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(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe( + switchMap(({value}: {value: ClientConfig}) => of$(value.EnableMobileFileDownload !== 'false')), + ); + const complianceDisabled = database.get(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))); diff --git a/app/components/post_list/post/body/message/index.ts b/app/components/post_list/post/body/message/index.ts index 5d5cc3f3a..e498abb2c 100644 --- a/app/components/post_list/post/body/message/index.ts +++ b/app/components/post_list/post/body/message/index.ts @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap(({value}) => database.get(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)); diff --git a/app/components/post_list/post/body/reactions/index.ts b/app/components/post_list/post/body/reactions/index.ts index 0afdd87a7..033f37ea1 100644 --- a/app/components/post_list/post/body/reactions/index.ts +++ b/app/components/post_list/post/body/reactions/index.ts @@ -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(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + map(({value}: {value: string}) => value), + ); + const currentUser = currentUserId.pipe( + switchMap((id) => database.get(MM_TABLES.SERVER.USER).findAndObserve(id)), + ); + const channel = (post.channel as Relation).observe(); + const experimentalTownSquareIsReadOnly = database.get(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)); diff --git a/app/components/post_list/post/header/index.ts b/app/components/post_list/post/header/index.ts index d87d8679f..101d700e5 100644 --- a/app/components/post_list/post/header/index.ts +++ b/app/components/post_list/post/header/index.ts @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe(switchMap(({value}) => of$(value as ClientConfig))); + const license = database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.LICENSE).pipe(switchMap(({value}) => of$(value as ClientLicense))); + const preferences = database.get(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)); diff --git a/app/components/post_list/post/index.ts b/app/components/post_list/post/index.ts index a97bcb756..129675a47 100644 --- a/app/components/post_list/post/index.ts +++ b/app/components/post_list/post/index.ts @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe( + switchMap((cfg) => of$(cfg.value.FeatureFlagAppsEnabled)), + ), + currentUser: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap((currentUserId) => database.get(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(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 = { diff --git a/app/components/server_version/index.tsx b/app/components/server_version/index.tsx index 3fa19080f..c555c0e68 100644 --- a/app/components/server_version/index.tsx +++ b/app/components/server_version/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG).pipe( + switchMap(({value}: {value: ClientConfig}) => of$(value.Version)), + ), + roles: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap( + ({value}) => database.get(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)); diff --git a/app/components/system_header/index.tsx b/app/components/system_header/index.tsx index 7bbdfc335..6176c43a0 100644 --- a/app/components/system_header/index.tsx +++ b/app/components/system_header/index.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CONFIG); + const preferences = database.get(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(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))); diff --git a/app/context/user_locale/index.tsx b/app/context/user_locale/index.tsx new file mode 100644 index 000000000..e87271384 --- /dev/null +++ b/app/context/user_locale/index.tsx @@ -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 ( + + + {children} + + ); +}; + +export function withUserLocale(Component: ComponentType): ComponentType { + return function UserLocaleComponent(props) { + return ( + + {(locale: string) => ( + + + + )} + + ); + }; +} + +export function useUserLocale(): string { + return React.useContext(UserLocaleContext); +} + +const enhancedThemeProvider = withObservables([], ({database}: {database: Database}) => ({ + locale: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap(({value}) => database.get(USER).findAndObserve(value).pipe( + // eslint-disable-next-line max-nested-callbacks + switchMap((user) => of$(user.locale)), + )), + catchError(() => of$(DEFAULT_LOCALE)), + ), +})); + +export default enhancedThemeProvider(UserLocaleProvider); diff --git a/app/database/components/index.tsx b/app/database/components/index.tsx index 32b8617d6..029ed1563 100644 --- a/app/database/components/index.tsx +++ b/app/database/components/index.tsx @@ -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(Component: ComponentType): ComponentTyp return ( - - - - - + + + + + + + ); }; diff --git a/app/database/models/server/file.ts b/app/database/models/server/file.ts index 34127c79d..5050e457c 100644 --- a/app/database/models/server/file.ts +++ b/app/database/models/server/file.ts @@ -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 */ diff --git a/app/init/managed_app.ts b/app/init/managed_app.ts index adcd2e5d0..64b2294e3 100644 --- a/app/init/managed_app.ts +++ b/app/init/managed_app.ts @@ -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; diff --git a/app/screens/about/index.tsx b/app/screens/about/index.tsx index 15bb7634c..d4aaafadc 100644 --- a/app/screens/about/index.tsx +++ b/app/screens/about/index.tsx @@ -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)); diff --git a/app/screens/channel/channel_nav_bar/channel_title/index.tsx b/app/screens/channel/channel_nav_bar/channel_title/index.tsx index b89121c89..4e0fe5112 100644 --- a/app/screens/channel/channel_nav_bar/channel_title/index.tsx +++ b/app/screens/channel/channel_nav_bar/channel_title/index.tsx @@ -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(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(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)); diff --git a/app/screens/channel/index.tsx b/app/screens/channel/index.tsx index 226dbacbc..07067416c 100644 --- a/app/screens/channel/index.tsx +++ b/app/screens/channel/index.tsx @@ -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 ; } - if (!currentChannelId.value) { - return ; + if (!currentChannelId) { + return ; } return ( <> null} /> @@ -117,7 +118,7 @@ const Channel = ({currentChannelId, currentTeamId, time}: ChannelProps) => { ); - }, [currentTeamId.value, currentChannelId.value, theme]); + }, [currentTeamId, currentChannelId, theme]); return ( { ); }; -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_CHANNEL_ID).pipe( + map(({value}: {value: string}) => value), + ), + currentTeamId: database.collections.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe( + map(({value}: {value: string}) => value), + ), })); -export default withDatabase(withSystemIds(Channel)); +export default withDatabase(enhanced(Channel)); diff --git a/app/screens/custom_status_clear_after/index.tsx b/app/screens/custom_status_clear_after/index.tsx index 8b64a5440..753542513 100644 --- a/app/screens/custom_status_clear_after/index.tsx +++ b/app/screens/custom_status_clear_after/index.tsx @@ -208,7 +208,7 @@ class ClearAfterModal extends NavigationComponent { } } -const enhancedCAM = withObservables([], ({database}: WithDatabaseArgs) => ({ +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({ currentUser: database.get(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))); diff --git a/app/screens/home/account/index.tsx b/app/screens/home/account/index.tsx index b5e9c6164..98294c77f 100644 --- a/app/screens/home/account/index.tsx +++ b/app/screens/home/account/index.tsx @@ -148,7 +148,7 @@ const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpir ); }; -const withUserConfig = withObservables([], ({database}: WithDatabaseArgs) => { +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const config = database. get(SYSTEM). findAndObserve(SYSTEM_IDENTIFIERS.CONFIG); @@ -180,4 +180,4 @@ const withUserConfig = withObservables([], ({database}: WithDatabaseArgs) => { }; }); -export default withDatabase(withUserConfig(AccountScreen)); +export default withDatabase(enhanced(AccountScreen)); diff --git a/app/screens/home/tab_bar/account.tsx b/app/screens/home/tab_bar/account.tsx index befa7482c..92be8c680 100644 --- a/app/screens/home/tab_bar/account.tsx +++ b/app/screens/home/tab_bar/account.tsx @@ -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(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_USER_ID).pipe( + switchMap((id) => database.get(USER).findAndObserve(id.value)), ), })); diff --git a/app/screens/index.tsx b/app/screens/index.tsx index c4cf1dc2c..70699a290 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -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)); } diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index 419a38916..2cfddc478 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -169,15 +169,29 @@ MattermostBucket* bucket = nil; */ RNHWKeyboardEvent *hwKeyEvent = nil; - (NSMutableArray *)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 {