diff --git a/app/components/autocomplete/at_mention/index.ts b/app/components/autocomplete/at_mention/index.ts index 6d9e8c52b24..d5c9c677b9e 100644 --- a/app/components/autocomplete/at_mention/index.ts +++ b/app/components/autocomplete/at_mention/index.ts @@ -3,14 +3,14 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {of as of$, from as from$, combineLatest, Observable} from 'rxjs'; +import {of as of$, combineLatest, Observable} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {observeChannel} from '@queries/servers/channel'; +import {observePermissionForChannel} from '@queries/servers/role'; import {observeLicense} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; -import {hasPermissionForChannel} from '@utils/role'; import AtMention from './at_mention'; @@ -28,9 +28,9 @@ const enhanced = withObservables([], ({database, channelId}: WithDatabaseArgs & let useGroupMentions: Observable; if (channelId) { const currentChannel = observeChannel(database, channelId); - useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => (u && c ? from$(hasPermissionForChannel(c, u, Permissions.USE_CHANNEL_MENTIONS, false)) : of$(false)))); + useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => (u && c ? observePermissionForChannel(c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false)))); useGroupMentions = combineLatest([currentUser, currentChannel, hasLicense]).pipe( - switchMap(([u, c, lcs]) => (lcs && u && c ? from$(hasPermissionForChannel(c, u, Permissions.USE_GROUP_MENTIONS, false)) : of$(false))), + switchMap(([u, c, lcs]) => (lcs && u && c ? observePermissionForChannel(c, u, Permissions.USE_GROUP_MENTIONS, false) : of$(false))), ); } else { useChannelMentions = of$(false); diff --git a/app/components/channel_list/__snapshots__/index.test.tsx.snap b/app/components/channel_list/__snapshots__/index.test.tsx.snap index 2aa41ca638c..5ba97ed56d5 100644 --- a/app/components/channel_list/__snapshots__/index.test.tsx.snap +++ b/app/components/channel_list/__snapshots__/index.test.tsx.snap @@ -21,6 +21,138 @@ exports[`components/channel_list should render channels error 1`] = ` } } > + + + + + + Test Team! + + + + + + + + + + + + + + + + + + + { const currentUser = observeCurrentUser(database); const canJoinChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? from$(hasPermissionForTeam(t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)) : of$(false))), + switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))), ); const canCreatePublicChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? from$(hasPermissionForTeam(t, u, Permissions.CREATE_PUBLIC_CHANNEL, true)) : of$(false))), + switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))), ); const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe( - switchMap(([u, t]) => (t && u ? from$(hasPermissionForTeam(t, u, Permissions.CREATE_PRIVATE_CHANNEL, false)) : of$(false))), + switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))), ); const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe( diff --git a/app/components/channel_list/index.test.tsx b/app/components/channel_list/index.test.tsx index 151f58a6002..dfae58f1394 100644 --- a/app/components/channel_list/index.test.tsx +++ b/app/components/channel_list/index.test.tsx @@ -5,6 +5,8 @@ import Database from '@nozbe/watermelondb/Database'; import React from 'react'; import {SafeAreaProvider} from 'react-native-safe-area-context'; +import {SYSTEM_IDENTIFIERS} from '@constants/database'; +import ServerDataOperator from '@database/operator/server_data_operator'; import {getTeamById} from '@queries/servers/team'; import {renderWithEverything} from '@test/intl-test-helper'; import TestHelper from '@test/test_helper'; @@ -13,9 +15,11 @@ import ChannelsList from './'; describe('components/channel_list', () => { let database: Database; + let operator: ServerDataOperator; beforeAll(async () => { const server = await TestHelper.setupServerDatabase(); database = server.database; + operator = server.operator; const team = await getTeamById(database, TestHelper.basicTeam!.id); await database.write(async () => { @@ -40,7 +44,12 @@ describe('components/channel_list', () => { expect(wrapper.toJSON()).toBeTruthy(); }); - it('should render team error', () => { + it('should render team error', async () => { + await operator.handleSystem({ + systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID, value: ''}], + prepareRecordsOnly: false, + }); + const wrapper = renderWithEverything( { , {database}, ); + expect(wrapper.toJSON()).toMatchSnapshot(); + + await operator.handleSystem({ + systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID, value: TestHelper.basicTeam!.id}], + prepareRecordsOnly: false, + }); }); it('should render channels error', () => { diff --git a/app/components/post_draft/index.ts b/app/components/post_draft/index.ts index a89a69e9b9f..e1721db36f1 100644 --- a/app/components/post_draft/index.ts +++ b/app/components/post_draft/index.ts @@ -4,15 +4,15 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {combineLatest, of as of$, from as from$} from 'rxjs'; +import {combineLatest, of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {General, Permissions} from '@constants'; import {observeChannel} from '@queries/servers/channel'; import {queryDraft} from '@queries/servers/drafts'; +import {observePermissionForChannel} from '@queries/servers/role'; import {observeConfigBooleanValue, observeCurrentChannelId} from '@queries/servers/system'; import {observeCurrentUser, observeUser} from '@queries/servers/user'; -import {hasPermissionForChannel} from '@utils/role'; import {isSystemAdmin, getUserIdFromChannelName} from '@utils/user'; import PostDraft from './post_draft'; @@ -50,7 +50,7 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) => switchMap((id) => observeChannel(database, id!)), ); - const canPost = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => (c && u ? from$(hasPermissionForChannel(c, u, Permissions.CREATE_POST, false)) : of$(false)))); + const canPost = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => (c && u ? observePermissionForChannel(c, u, Permissions.CREATE_POST, false) : of$(false)))); const channelIsArchived = channel.pipe(switchMap((c) => (ownProps.channelIsArchived ? of$(true) : of$(c?.deleteAt !== 0)))); const experimentalTownSquareIsReadOnly = observeConfigBooleanValue(database, 'ExperimentalTownSquareIsReadOnly'); diff --git a/app/components/post_draft/send_handler/index.ts b/app/components/post_draft/send_handler/index.ts index 554039d4e40..fbdea5fe64a 100644 --- a/app/components/post_draft/send_handler/index.ts +++ b/app/components/post_draft/send_handler/index.ts @@ -3,16 +3,16 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {combineLatest, of as of$, from as from$} from 'rxjs'; +import {combineLatest, of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {General, Permissions} from '@constants'; import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; import {observeChannel, observeCurrentChannel} from '@queries/servers/channel'; import {queryAllCustomEmojis} from '@queries/servers/custom_emoji'; +import {observePermissionForChannel} from '@queries/servers/role'; import {observeConfig, observeCurrentUserId} from '@queries/servers/system'; import {observeUser} from '@queries/servers/user'; -import {hasPermissionForChannel} from '@utils/role'; import SendHandler from './send_handler'; @@ -59,7 +59,7 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) => return of$(true); } - return u ? from$(hasPermissionForChannel(c, u, Permissions.USE_CHANNEL_MENTIONS, false)) : of$(false); + return u ? observePermissionForChannel(c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false); }), ); diff --git a/app/components/post_draft/uploads/index.tsx b/app/components/post_draft/uploads/index.tsx index ca7773dae46..6d7f07efb07 100644 --- a/app/components/post_draft/uploads/index.tsx +++ b/app/components/post_draft/uploads/index.tsx @@ -19,7 +19,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme'; import UploadItem from './upload_item'; const CONTAINER_HEIGHT_MAX = 67; -const CONATINER_HEIGHT_MIN = 0; +const CONTAINER_HEIGHT_MIN = 0; const ERROR_HEIGHT_MAX = 20; const ERROR_HEIGHT_MIN = 0; @@ -80,7 +80,7 @@ function Uploads({ const style = getStyleSheet(theme); const errorHeight = useSharedValue(ERROR_HEIGHT_MIN); - const containerHeight = useSharedValue(CONTAINER_HEIGHT_MAX); + const containerHeight = useSharedValue(files.length ? CONTAINER_HEIGHT_MAX : CONTAINER_HEIGHT_MIN); const filesForGallery = useRef(files.filter((f) => !f.failed && !DraftUploadManager.isUploading(f.clientId!))); const errorAnimatedStyle = useAnimatedStyle(() => { @@ -116,7 +116,7 @@ function Uploads({ containerHeight.value = CONTAINER_HEIGHT_MAX; return; } - containerHeight.value = CONATINER_HEIGHT_MIN; + containerHeight.value = CONTAINER_HEIGHT_MIN; }, [files.length > 0]); const openGallery = useCallback((file: FileInfo) => { diff --git a/app/components/post_list/combined_user_activity/index.ts b/app/components/post_list/combined_user_activity/index.ts index 05f8fe2323e..191faeaa4b5 100644 --- a/app/components/post_list/combined_user_activity/index.ts +++ b/app/components/post_list/combined_user_activity/index.ts @@ -4,15 +4,15 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {combineLatest, from as from$, of as of$} from 'rxjs'; +import {combineLatest, of as of$} from 'rxjs'; import {map, switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {queryPostsById} from '@queries/servers/post'; +import {observePermissionForPost} from '@queries/servers/role'; import {observeCurrentUserId} from '@queries/servers/system'; import {observeUser, queryUsersByIdsOrUsernames} from '@queries/servers/user'; import {generateCombinedPost, getPostIdsForCombinedUserActivityPost} from '@utils/post_list'; -import {hasPermissionForPost} from '@utils/role'; import CombinedUserActivity from './combined_user_activity'; @@ -28,7 +28,7 @@ const withCombinedPosts = withObservables(['postId'], ({database, postId}: WithD const posts = queryPostsById(database, postIds).observe(); const post = posts.pipe(map((ps) => generateCombinedPost(postId, ps))); const canDelete = combineLatest([posts, currentUser]).pipe( - switchMap(([ps, u]) => (u ? from$(hasPermissionForPost(ps[0], u, Permissions.DELETE_OTHERS_POSTS, false)) : of$(false))), + switchMap(([ps, u]) => (u ? observePermissionForPost(ps[0], u, Permissions.DELETE_OTHERS_POSTS, false) : of$(false))), ); const usernamesById = post.pipe( diff --git a/app/components/post_list/index.tsx b/app/components/post_list/index.tsx index 81eaef571c7..af7cfca39bf 100644 --- a/app/components/post_list/index.tsx +++ b/app/components/post_list/index.tsx @@ -99,7 +99,7 @@ const PostList = ({ const onScrollEndIndexListener = useRef(); const onViewableItemsChangedListener = useRef(); const scrolledToHighlighted = useRef(false); - const [offsetY, setOffsetY] = useState(0); + const [enableRefreshControl, setEnableRefreshControl] = useState(false); const [refreshing, setRefreshing] = useState(false); const theme = useTheme(); const serverUrl = useServerUrl(); @@ -145,13 +145,9 @@ const PostList = ({ const onScroll = useCallback((event: NativeSyntheticEvent) => { if (Platform.OS === 'android') { const {y} = event.nativeEvent.contentOffset; - if (y === 0) { - setOffsetY(y); - } else if (offsetY === 0 && y !== 0) { - setOffsetY(y); - } + setEnableRefreshControl(y === 0); } - }, [offsetY]); + }, []); const onScrollToIndexFailed = useCallback((info: ScrollIndexFailed) => { const index = Math.min(info.highestMeasuredFrameIndex, info.index); @@ -340,7 +336,7 @@ const PostList = ({ return ( <> ((c && c.deleteAt > 0) || (c?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(u?.roles || '') && readOnly))), ); - const canAddReaction = currentUser.pipe(switchMap((u) => (u ? from$(hasPermissionForPost(post, u, Permissions.ADD_REACTION, true)) : of$(true)))); - const canRemoveReaction = currentUser.pipe(switchMap((u) => (u ? from$(hasPermissionForPost(post, u, Permissions.REMOVE_REACTION, true)) : of$(true)))); + const canAddReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(post, u, Permissions.ADD_REACTION, true) : of$(true)))); + const canRemoveReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(post, u, Permissions.REMOVE_REACTION, true) : of$(true)))); return { canAddReaction, diff --git a/app/components/post_list/post/index.ts b/app/components/post_list/post/index.ts index 5b46fc1fffe..ea44f23d492 100644 --- a/app/components/post_list/post/index.ts +++ b/app/components/post_list/post/index.ts @@ -4,18 +4,18 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {from as from$, of as of$} from 'rxjs'; +import {of as of$, combineLatest} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {Permissions, Preferences} from '@constants'; import {queryAllCustomEmojis} from '@queries/servers/custom_emoji'; import {queryPostsBetween} from '@queries/servers/post'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; +import {observeCanManageChannelMembers, observePermissionForPost} from '@queries/servers/role'; import {observeConfigBooleanValue} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import {hasJumboEmojiOnly} from '@utils/emoji/helpers'; import {areConsecutivePosts, isPostEphemeral} from '@utils/post'; -import {canManageChannelMembers, hasPermissionForPost} from '@utils/role'; import Post from './post'; @@ -33,35 +33,47 @@ type PropsInput = WithDatabaseArgs & { previousPost: PostModel | undefined; } -async function shouldHighlightReplyBar(currentUser: UserModel, post: PostModel, postsInThread: PostsInThreadModel) { - let commentsNotifyLevel = Preferences.COMMENTS_NEVER; - let threadCreatedByCurrentUser = false; - let rootPost: PostModel | undefined; - const myPosts = await queryPostsBetween(postsInThread.database, postsInThread.earliest, postsInThread.latest, null, currentUser.id, '', post.rootId || post.id).fetch(); +function observeShouldHighlightReplyBar(currentUser: UserModel, post: PostModel, postsInThread: PostsInThreadModel) { + const myPostsCount = queryPostsBetween(postsInThread.database, postsInThread.earliest, postsInThread.latest, null, currentUser.id, '', post.rootId || post.id).observeCount(); + const root = post.root.observe().pipe(switchMap((rl) => (rl.length ? rl[0].observe() : of$(undefined)))); + + return combineLatest([myPostsCount, root]).pipe( + switchMap(([mpc, r]) => { + const threadRepliedToByCurrentUser = mpc > 0; + let threadCreatedByCurrentUser = false; + if (r?.userId === currentUser.id) { + threadCreatedByCurrentUser = true; + } + let commentsNotifyLevel = Preferences.COMMENTS_NEVER; + if (currentUser.notifyProps?.comments) { + commentsNotifyLevel = currentUser.notifyProps.comments; + } + + const notCurrentUser = post.userId !== currentUser.id || Boolean(post.props?.from_webhook); + if (notCurrentUser) { + if (commentsNotifyLevel === Preferences.COMMENTS_ANY && (threadCreatedByCurrentUser || threadRepliedToByCurrentUser)) { + return of$(true); + } else if (commentsNotifyLevel === Preferences.COMMENTS_ROOT && threadCreatedByCurrentUser) { + return of$(true); + } + } - const threadRepliedToByCurrentUser = myPosts.length > 0; - const root = await post.root.fetch(); - if (root.length) { - rootPost = root[0]; - } + return of$(false); + }), + ); +} - if (rootPost?.userId === currentUser.id) { - threadCreatedByCurrentUser = true; - } - if (currentUser.notifyProps?.comments) { - commentsNotifyLevel = currentUser.notifyProps.comments; +function observeHasReplies(post: PostModel) { + if (!post.rootId) { + return post.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0))); } - const notCurrentUser = post.userId !== currentUser.id || Boolean(post.props?.from_webhook); - if (notCurrentUser) { - if (commentsNotifyLevel === Preferences.COMMENTS_ANY && (threadCreatedByCurrentUser || threadRepliedToByCurrentUser)) { - return true; - } else if (commentsNotifyLevel === Preferences.COMMENTS_ROOT && threadCreatedByCurrentUser) { - return true; + return post.root.observe().pipe(switchMap((rl) => { + if (rl.length) { + return rl[0].postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0))); } - } - - return false; + return of$(false); + })); } function isFirstReply(post: PostModel, previousPost?: PostModel) { @@ -87,20 +99,20 @@ const withPost = withObservables( let isPostAddChannelMember = of$(false); const isOwner = currentUser.id === post.userId; const author = post.userId ? post.author.observe() : of$(null); - const canDelete = from$(hasPermissionForPost(post, currentUser, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false)); + const canDelete = observePermissionForPost(post, currentUser, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false); const isEphemeral = of$(isPostEphemeral(post)); const isSaved = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, post.id).observe().pipe( switchMap((pref) => of$(Boolean(pref.length))), ); if (post.props?.add_channel_member && isPostEphemeral(post)) { - isPostAddChannelMember = from$(canManageChannelMembers(post, currentUser)); + isPostAddChannelMember = observeCanManageChannelMembers(post, currentUser); } const highlightReplyBar = post.postsInThread.observe().pipe( switchMap((postsInThreads: PostsInThreadModel[]) => { if (postsInThreads.length) { - return from$(shouldHighlightReplyBar(currentUser, post, postsInThreads[0])); + return observeShouldHighlightReplyBar(currentUser, post, postsInThreads[0]); } return of$(false); })); @@ -118,7 +130,7 @@ const withPost = withObservables( ), ); } - const hasReplies = from$(post.hasReplies()); + const hasReplies = observeHasReplies(post); const isConsecutivePost = author.pipe( switchMap((user) => of$(Boolean(post && previousPost && !user?.isBot && areConsecutivePosts(post, previousPost)))), ); diff --git a/app/queries/servers/role.ts b/app/queries/servers/role.ts index e9aa72c1a01..c47aa4900b0 100644 --- a/app/queries/servers/role.ts +++ b/app/queries/servers/role.ts @@ -2,10 +2,17 @@ // See LICENSE.txt for license information. import {Database, Q} from '@nozbe/watermelondb'; +import {of as of$, combineLatest} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; -import {Database as DatabaseConstants} from '@constants'; +import {Database as DatabaseConstants, General, Permissions} from '@constants'; +import {hasPermission} from '@utils/role'; +import type ChannelModel from '@typings/database/models/servers/channel'; +import type PostModel from '@typings/database/models/servers/post'; import type RoleModel from '@typings/database/models/servers/role'; +import type TeamModel from '@typings/database/models/servers/team'; +import type UserModel from '@typings/database/models/servers/user'; const {ROLE} = DatabaseConstants.MM_TABLES.SERVER; @@ -25,3 +32,51 @@ export const getRoleById = async (database: Database, roleId: string): Promise { return database.get(ROLE).query(Q.where('name', Q.oneOf(names))); }; + +export function observePermissionForChannel(channel: ChannelModel, user: UserModel, permission: string, defaultValue: boolean) { + const myChannel = channel.membership.observe(); + const myTeam = channel.teamId ? channel.team.observe().pipe(switchMap((t) => (t ? t.myTeam.observe() : of$(undefined)))) : of$(undefined); + + return combineLatest([myChannel, myTeam]).pipe(switchMap(([mc, mt]) => { + const rolesArray = [...user.roles.split(' ')]; + if (mc) { + rolesArray.push(...mc.roles.split(' ')); + } + if (mt) { + rolesArray.push(...mt.roles.split(' ')); + } + return queryRolesByNames(user.database, rolesArray).observe().pipe( + switchMap((r) => of$(hasPermission(r, permission, defaultValue))), + ); + })); +} + +export function observePermissionForTeam(team: TeamModel, user: UserModel, permission: string, defaultValue: boolean) { + return team.myTeam.observe().pipe(switchMap((myTeam) => { + const rolesArray = [...user.roles.split(' ')]; + + if (myTeam) { + rolesArray.push(...myTeam.roles.split(' ')); + } + + return queryRolesByNames(user.database, rolesArray).observe().pipe( + switchMap((roles) => of$(hasPermission(roles, permission, defaultValue))), + ); + })); +} + +export function observePermissionForPost(post: PostModel, user: UserModel, permission: string, defaultValue: boolean) { + return post.channel.observe().pipe(switchMap((c) => (c ? observePermissionForChannel(c, user, permission, defaultValue) : of$(defaultValue)))); +} + +export function observeCanManageChannelMembers(post: PostModel, user: UserModel) { + return post.channel.observe().pipe((switchMap((c) => { + const directTypes: ChannelType[] = [General.DM_CHANNEL, General.GM_CHANNEL]; + if (!c || c.deleteAt !== 0 || directTypes.includes(c.type) || c.name === General.DEFAULT_CHANNEL) { + return of$(false); + } + + const permission = c.type === General.OPEN_CHANNEL ? Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS : Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS; + return observePermissionForChannel(c, user, permission, true); + }))); +} diff --git a/app/queries/servers/team.ts b/app/queries/servers/team.ts index 3f0fd310300..8046c3bce50 100644 --- a/app/queries/servers/team.ts +++ b/app/queries/servers/team.ts @@ -287,6 +287,12 @@ export const getMyTeamById = async (database: Database, teamId: string) => { } }; +export const observeMyTeam = (database: Database, teamId: string) => { + return database.get(MY_TEAM).query(Q.where('id', teamId), Q.take(1)).observe().pipe( + switchMap((result) => (result.length ? result[0].observe() : of$(undefined))), + ); +}; + export const getTeamById = async (database: Database, teamId: string) => { try { const team = (await database.get(TEAM).find(teamId)); diff --git a/app/screens/post_options/index.ts b/app/screens/post_options/index.ts index cc01fd8f3ea..a65e20ee682 100644 --- a/app/screens/post_options/index.ts +++ b/app/screens/post_options/index.ts @@ -3,19 +3,19 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {combineLatest, from as from$, of as of$, Observable} from 'rxjs'; +import {combineLatest, of as of$, Observable} from 'rxjs'; import {switchMap} from 'rxjs/operators'; import {General, Permissions, Post, Preferences, Screens} from '@constants'; import {MAX_ALLOWED_REACTIONS} from '@constants/emoji'; import {observePost} from '@queries/servers/post'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; +import {observePermissionForChannel, observePermissionForPost} from '@queries/servers/role'; import {observeConfig, observeLicense} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import {isMinimumServerVersion} from '@utils/helpers'; import {isSystemMessage} from '@utils/post'; import {getPostIdsForCombinedUserActivityPost} from '@utils/post_list'; -import {hasPermissionForChannel, hasPermissionForPost} from '@utils/role'; import {isSystemAdmin} from '@utils/user'; import PostOptions from './post_options'; @@ -33,27 +33,24 @@ type EnhancedProps = WithDatabaseArgs & { location: string; } -const canEditPost = (isOwner: boolean, post: PostModel, postEditTimeLimit: number, isLicensed: boolean, channel: ChannelModel, user: UserModel): boolean => { +const observeCanEditPost = (isOwner: boolean, post: PostModel, postEditTimeLimit: number, isLicensed: boolean, channel: ChannelModel, user: UserModel) => { if (!post || isSystemMessage(post)) { - return false; + return of$(false); } - let cep: boolean; - - const permissions = [Permissions.EDIT_POST]; - if (!isOwner) { - permissions.push(Permissions.EDIT_OTHERS_POSTS); - } - - cep = permissions.every((permission) => hasPermissionForChannel(channel, user, permission, false)); if (isLicensed && postEditTimeLimit !== -1) { const timeLeft = (post.createAt + (postEditTimeLimit * 1000)) - Date.now(); if (timeLeft <= 0) { - cep = false; + return of$(false); } } - return cep; + return observePermissionForChannel(channel, user, Permissions.EDIT_POST, false).pipe(switchMap((v) => { + if (!v || isOwner) { + return of$(v); + } + return observePermissionForChannel(channel, user, Permissions.EDIT_OTHERS_POSTS, false); + })); }; const withPost = withObservables([], ({post, database}: {post: Post | PostModel} & WithDatabaseArgs) => { @@ -81,11 +78,11 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca const serverVersion = config.pipe(switchMap((cfg) => of$(cfg?.Version || ''))); const postEditTimeLimit = config.pipe(switchMap((cfg) => of$(parseInt(cfg?.PostEditTimeLimit || '-1', 10)))); - const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => ((c && u) ? from$(hasPermissionForChannel(c, u, Permissions.CREATE_POST, false)) : of$(false)))); - const hasAddReactionPermission = currentUser.pipe(switchMap((u) => (u ? from$(hasPermissionForPost(post, u, Permissions.ADD_REACTION, true)) : of$(false)))); + const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => ((c && u) ? observePermissionForChannel(c, u, Permissions.CREATE_POST, false) : of$(false)))); + const hasAddReactionPermission = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(post, u, Permissions.ADD_REACTION, true) : of$(false)))); const canDeletePostPermission = currentUser.pipe(switchMap((u) => { const isOwner = post.userId === u?.id; - return u ? from$(hasPermissionForPost(post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false)) : of$(false); + return u ? observePermissionForPost(post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false) : of$(false); })); const experimentalTownSquareIsReadOnly = config.pipe(switchMap((value) => of$(value?.ExperimentalTownSquareIsReadOnly === 'true'))); @@ -117,12 +114,17 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca const isSaved = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, post.id).observe().pipe(switchMap((pref) => of$(Boolean(pref[0]?.value === 'true')))); - const canEdit = combineLatest([postEditTimeLimit, isLicensed, channel, currentUser, channelIsArchived, channelIsReadOnly, canEditUntil, canPostPermission]).pipe(switchMap(([lt, ls, c, u, isArchived, isReadOnly, until, canPost]) => { - const isOwner = u?.id === post.userId; - const canEditPostPermission = (c && u) ? canEditPost(isOwner, post, lt, ls, c, u) : false; - const timeNotReached = (until === -1) || (until > Date.now()); - return of$(canEditPostPermission && !isArchived && !isReadOnly && timeNotReached && canPost); - })); + const canEdit = combineLatest([postEditTimeLimit, isLicensed, channel, currentUser, channelIsArchived, channelIsReadOnly, canEditUntil, canPostPermission]).pipe( + switchMap(([lt, ls, c, u, isArchived, isReadOnly, until, canPost]) => { + const isOwner = u?.id === post.userId; + const canEditPostPermission = (c && u) ? observeCanEditPost(isOwner, post, lt, ls, c, u) : of$(false); + const timeNotReached = (until === -1) || (until > Date.now()); + return canEditPostPermission.pipe( + // eslint-disable-next-line max-nested-callbacks + switchMap((canEditPost) => of$(canEditPost && !isArchived && !isReadOnly && timeNotReached && canPost)), + ); + }), + ); const canMarkAsUnread = combineLatest([currentUser, channelIsArchived]).pipe( switchMap(([user, isArchived]) => of$(!isArchived && user?.id !== post.userId && !isSystemMessage(post))), diff --git a/app/utils/role/index.ts b/app/utils/role/index.ts index fad9acd726c..b3beff765a2 100644 --- a/app/utils/role/index.ts +++ b/app/utils/role/index.ts @@ -1,16 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {General, Permissions} from '@constants'; -import {queryRolesByNames} from '@queries/servers/role'; - -import type ChannelModel from '@typings/database/models/servers/channel'; -import type MyChannelModel from '@typings/database/models/servers/my_channel'; -import type MyTeamModel from '@typings/database/models/servers/my_team'; -import type PostModel from '@typings/database/models/servers/post'; import type RoleModel from '@typings/database/models/servers/role'; -import type TeamModel from '@typings/database/models/servers/team'; -import type UserModel from '@typings/database/models/servers/user'; export function hasPermission(roles: RoleModel[] | Role[], permission: string, defaultValue: boolean) { const permissions = new Set(); @@ -21,83 +12,3 @@ export function hasPermission(roles: RoleModel[] | Role[], permission: string, d const exists = permissions.has(permission); return defaultValue === true || exists; } - -export async function hasPermissionForChannel(channel: ChannelModel, user: UserModel, permission: string, defaultValue: boolean) { - const rolesArray = [...user.roles.split(' ')]; - - const myChannel = await channel.membership.fetch() as MyChannelModel | undefined; - if (myChannel) { - rolesArray.push(...myChannel.roles.split(' ')); - } - - const team = await channel.team.fetch() as TeamModel | undefined; - if (team) { - const myTeam = await team.myTeam.fetch() as MyTeamModel | undefined; - if (myTeam) { - rolesArray.push(...myTeam.roles.split(' ')); - } - } - - if (rolesArray.length) { - const roles = await queryRolesByNames(user.database, rolesArray).fetch(); - return hasPermission(roles, permission, defaultValue); - } - - return defaultValue; -} - -export async function hasPermissionForTeam(team: TeamModel, user: UserModel, permission: string, defaultValue: boolean) { - const rolesArray = [...user.roles.split(' ')]; - - const myTeam = await team.myTeam.fetch() as MyTeamModel | undefined; - if (myTeam) { - rolesArray.push(...myTeam.roles.split(' ')); - } - - if (rolesArray.length) { - const roles = await queryRolesByNames(user.database, rolesArray).fetch(); - return hasPermission(roles, permission, defaultValue); - } - - return defaultValue; -} - -export async function hasPermissionForPost(post: PostModel, user: UserModel, permission: string, defaultValue: boolean) { - const channel = await post.channel.fetch() as ChannelModel | undefined; - if (channel) { - return hasPermissionForChannel(channel, user, permission, defaultValue); - } - - return defaultValue; -} - -export async function canManageChannelMembers(post: PostModel, user: UserModel) { - const rolesArray = [...user.roles.split(' ')]; - const channel = await post.channel.fetch() as ChannelModel | undefined; - - const directTypes: string[] = [General.DM_CHANNEL, General.GM_CHANNEL]; - if (!channel || channel.deleteAt !== 0 || directTypes.includes(channel.type) || channel.name === General.DEFAULT_CHANNEL) { - return false; - } - - const myChannel = await channel.membership.fetch() as MyChannelModel | undefined; - if (myChannel) { - rolesArray.push(...myChannel.roles.split(' ')); - } - - const team = await channel.team.fetch() as TeamModel | undefined; - if (team) { - const myTeam = await team.myTeam.fetch() as MyTeamModel | undefined; - if (myTeam) { - rolesArray.push(...myTeam.roles.split(' ')); - } - } - - if (rolesArray.length) { - const roles = await queryRolesByNames(post.database, rolesArray).fetch() as RoleModel[]; - const permission = channel.type === General.OPEN_CHANNEL ? Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS : Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS; - return hasPermission(roles, permission, true); - } - - return true; -} diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index dd0269ba129..17c1c154eaf 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -707,14 +707,10 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Mattermost/Pods-Mattermost-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index b43c0f3093e..6e1cb990af2 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -14,25 +14,6 @@ #import "Mattermost-Swift.h" #import -#ifdef FB_SONARKIT_ENABLED -#import -#import -#import -#import -#import -#import - -static void InitializeFlipper(UIApplication *application) { - FlipperClient *client = [FlipperClient sharedClient]; - SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; - [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; - [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; - [client addPlugin:[FlipperKitReactPlugin new]]; - [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; - [client start]; -} -#endif - @import Gekidou; @interface AppDelegate () @@ -64,10 +45,6 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -#ifdef FB_SONARKIT_ENABLED - InitializeFlipper(application); -#endif - if (bucket == nil) { bucket = [[MattermostBucket alloc] init]; } diff --git a/ios/Podfile b/ios/Podfile index 050a70987b4..31091fe6987 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -33,7 +33,7 @@ end # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable these next few lines. - use_flipper!({'Flipper' => '0.138.0'}) + #use_flipper!({'Flipper' => '0.138.0'}) post_install do |installer| react_native_post_install(installer) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 641121afbf2..6523832203a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,7 +3,6 @@ PODS: - boost (1.76.0) - BVLinearGradient (2.5.6): - React - - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - EXFileSystem (13.1.4): - ExpoModulesCore @@ -22,66 +21,6 @@ PODS: - React-Core (= 0.67.4) - React-jsi (= 0.67.4) - ReactCommon/turbomodule/core (= 0.67.4) - - Flipper (0.138.0): - - Flipper-Folly (~> 2.6) - - Flipper-Boost-iOSX (1.76.0.1.11) - - Flipper-DoubleConversion (3.1.7) - - Flipper-Fmt (7.1.7) - - Flipper-Folly (2.6.7): - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt (= 7.1.7) - - Flipper-Glog - - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.180) - - Flipper-Glog (0.3.6) - - Flipper-PeerTalk (0.0.4) - - Flipper-RSocket (1.4.3): - - Flipper-Folly (~> 2.6) - - FlipperKit (0.138.0): - - FlipperKit/Core (= 0.138.0) - - FlipperKit/Core (0.138.0): - - Flipper (~> 0.138.0) - - FlipperKit/CppBridge - - FlipperKit/FBCxxFollyDynamicConvert - - FlipperKit/FBDefines - - FlipperKit/FKPortForwarding - - SocketRocket (~> 0.6.0) - - FlipperKit/CppBridge (0.138.0): - - Flipper (~> 0.138.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.138.0): - - Flipper-Folly (~> 2.6) - - FlipperKit/FBDefines (0.138.0) - - FlipperKit/FKPortForwarding (0.138.0): - - CocoaAsyncSocket (~> 7.6) - - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.138.0) - - FlipperKit/FlipperKitLayoutHelpers (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutTextSearchable - - FlipperKit/FlipperKitLayoutIOSDescriptors (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutPlugin (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - FlipperKit/FlipperKitLayoutIOSDescriptors - - FlipperKit/FlipperKitLayoutTextSearchable - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutTextSearchable (0.138.0) - - FlipperKit/FlipperKitNetworkPlugin (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitReactPlugin (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.138.0): - - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.138.0): - - FlipperKit/Core - - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - hermes-engine (0.9.0) @@ -102,7 +41,6 @@ PODS: - lottie-react-native (5.0.1): - lottie-ios (~> 3.2.3) - React-Core - - OpenSSL-Universal (1.1.180) - Permission-Camera (3.3.1): - RNPermissions - Permission-PhotoLibrary (3.3.1): @@ -522,7 +460,6 @@ PODS: - Sentry/Core (= 7.11.0) - Sentry/Core (7.11.0) - simdjson (1.0.0) - - SocketRocket (0.6.0) - Starscream (4.0.4) - SwiftyJSON (5.0.1) - Swime (3.0.6) @@ -531,8 +468,6 @@ PODS: - React-jsi - XCDYouTubeKit (2.8.2) - Yoga (1.14.0) - - YogaKit (1.18.1): - - Yoga (~> 1.14) - YoutubePlayer-in-WKWebView (0.3.8) DEPENDENCIES: @@ -545,34 +480,12 @@ DEPENDENCIES: - EXVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - Flipper (= 0.138.0) - - Flipper-Boost-iOSX (= 1.76.0.1.11) - - Flipper-DoubleConversion (= 3.1.7) - - Flipper-Fmt (= 7.1.7) - - Flipper-Folly (= 2.6.7) - - Flipper-Glog (= 0.3.6) - - Flipper-PeerTalk (= 0.0.4) - - Flipper-RSocket (= 1.4.3) - - FlipperKit (= 0.138.0) - - FlipperKit/Core (= 0.138.0) - - FlipperKit/CppBridge (= 0.138.0) - - FlipperKit/FBCxxFollyDynamicConvert (= 0.138.0) - - FlipperKit/FBDefines (= 0.138.0) - - FlipperKit/FKPortForwarding (= 0.138.0) - - FlipperKit/FlipperKitHighlightOverlay (= 0.138.0) - - FlipperKit/FlipperKitLayoutPlugin (= 0.138.0) - - FlipperKit/FlipperKitLayoutTextSearchable (= 0.138.0) - - FlipperKit/FlipperKitNetworkPlugin (= 0.138.0) - - FlipperKit/FlipperKitReactPlugin (= 0.138.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.138.0) - - FlipperKit/SKIOSNetworkPlugin (= 0.138.0) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (~> 0.9.0) - jail-monkey (from `../node_modules/jail-monkey`) - libevent (~> 2.1.12) - lottie-ios (from `../node_modules/lottie-ios`) - lottie-react-native (from `../node_modules/lottie-react-native`) - - OpenSSL-Universal (= 1.1.180) - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`) - Permission-PhotoLibrary (from `../node_modules/react-native-permissions/ios/PhotoLibrary`) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -648,31 +561,18 @@ DEPENDENCIES: SPEC REPOS: trunk: - Alamofire - - CocoaAsyncSocket - - Flipper - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt - - Flipper-Folly - - Flipper-Glog - - Flipper-PeerTalk - - Flipper-RSocket - - FlipperKit - fmt - hermes-engine - HMSegmentedControl - libevent - libwebp - - OpenSSL-Universal - Rudder - SDWebImage - SDWebImageWebPCoder - Sentry - - SocketRocket - SwiftyJSON - Swime - XCDYouTubeKit - - YogaKit - YoutubePlayer-in-WKWebView EXTERNAL SOURCES: @@ -847,7 +747,6 @@ SPEC CHECKSUMS: Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c boost: a7c83b31436843459a1961bfd74b96033dc77234 BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 - CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 EXFileSystem: 08a3033ac372b6346becf07839e1ccef26fb1058 Expo: 534e51e607aba8229293297da5585f4b26f50fa1 @@ -855,15 +754,6 @@ SPEC CHECKSUMS: EXVideoThumbnails: 847d648d6f4bc0c1afad05caa56a487dc543445e FBLazyVector: f7b0632c6437e312acf6349288d9aa4cb6d59030 FBReactNativeSpec: 0f4e1f4cfeace095694436e7c7fcc5bf4b03a0ff - Flipper: a5ed0fd7212a369f0c0f8fe096a711355cbb22cf - Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c - Flipper-DoubleConversion: 57ffbe81ef95306cc9e69c4aa3aeeeeb58a6a28c - Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b - Flipper-Folly: 83af37379faa69497529e414bd43fbfc7cae259a - Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 - Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 - Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541 - FlipperKit: 10424086e0f1b75cebf7a73427a8e3fbc793cd7b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85 hermes-engine: bf7577d12ac6ccf53ab8b5af3c6ccf0dd8458c5c @@ -873,7 +763,6 @@ SPEC CHECKSUMS: libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e lottie-react-native: a029a86e1689c86a07169c520ae770e84348cd20 - OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b Permission-Camera: bae27a8503530770c35aadfecbb97ec71823382a Permission-PhotoLibrary: ddb5a158725b29cb12e9e477e8a5f5151c66cc3c RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685 @@ -942,16 +831,14 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341 simdjson: c96317b3a50dff3468a42f586ab7ed22c6ab2fd9 - SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9 SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b WatermelonDB: baec390a1039dcebeee959218900c978af3407c9 XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f Yoga: d6b6a80659aa3e91aaba01d0012e7edcbedcbecd - YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YoutubePlayer-in-WKWebView: 4fca3b4f6f09940077bfbae7bddb771f2b43aacd -PODFILE CHECKSUM: 662a6960377d343c74022598146341c50e2b0d11 +PODFILE CHECKSUM: 201f2384bf697d449c8d0025c6b76211a2e36f98 COCOAPODS: 1.11.3 diff --git a/patches/react-native+0.67.4.patch b/patches/react-native+0.67.4.patch index c7023916f37..5150a6d1f5b 100644 --- a/patches/react-native+0.67.4.patch +++ b/patches/react-native+0.67.4.patch @@ -19,8 +19,117 @@ index b121da3..82c1c24 100644 { + + this.props.onLayout(event); + const child = React.Children.only(this.props.children); +- if (child.props.onLayout) { ++ if (child.props.onCellLayout) { ++ child.props.onCellLayout(event, child.props.cellKey, child.props.index); ++ } else if (child.props.onLayout) { + child.props.onLayout(event); + } + }; +diff --git a/node_modules/react-native/Libraries/Lists/FlatList.js b/node_modules/react-native/Libraries/Lists/FlatList.js +index 5e49715..88f8896 100644 +--- a/node_modules/react-native/Libraries/Lists/FlatList.js ++++ b/node_modules/react-native/Libraries/Lists/FlatList.js +@@ -26,6 +26,7 @@ import type { + } from './ViewabilityHelper'; + import type {RenderItemType, RenderItemProps} from './VirtualizedList'; + import {keyExtractor as defaultKeyExtractor} from './VirtualizeUtils'; ++import memoizeOne from 'memoize-one'; + + type RequiredProps = {| + /** +@@ -141,6 +142,10 @@ type OptionalProps = {| + * See `ScrollView` for flow type and further documentation. + */ + fadingEdgeLength?: ?number, ++ /** ++ * Enable an optimization to memoize the item renderer to prevent unnecessary rerenders. ++ */ ++ strictMode?: boolean, + |}; + + /** +@@ -579,9 +584,14 @@ class FlatList extends React.PureComponent, void> { + }; + } + +- _renderer = () => { +- const {ListItemComponent, renderItem, columnWrapperStyle} = this.props; +- const numColumns = numColumnsOrDefault(this.props.numColumns); ++ _renderer = ( ++ ListItemComponent: ?(React.ComponentType | React.Element), ++ renderItem: ?RenderItemType, ++ columnWrapperStyle: ?ViewStyleProp, ++ numColumns: ?number, ++ extraData: ?any, ++ ) => { ++ const cols = numColumnsOrDefault(numColumns); + + let virtualizedListRenderKey = ListItemComponent + ? 'ListItemComponent' +@@ -606,7 +616,7 @@ class FlatList extends React.PureComponent, void> { + * This comment suppresses an error found when Flow v0.111 was deployed. + * To see the error, delete this comment and run Flow. */ + [virtualizedListRenderKey]: (info: RenderItemProps) => { +- if (numColumns > 1) { ++ if (cols > 1) { + const {item, index} = info; + invariant( + Array.isArray(item), +@@ -617,7 +627,7 @@ class FlatList extends React.PureComponent, void> { + {item.map((it, kk) => { + const element = renderer({ + item: it, +- index: index * numColumns + kk, ++ index: index * cols + kk, + separators: info.separators, + }); + return element != null ? ( +@@ -633,14 +643,19 @@ class FlatList extends React.PureComponent, void> { + }; + }; + ++ _memoizedRenderer = memoizeOne(this._renderer); ++ + render(): React.Node { + const { + numColumns, + columnWrapperStyle, + removeClippedSubviews: _removeClippedSubviews, ++ strictMode = false, + ...restProps + } = this.props; + ++ const renderer = strictMode ? this._memoizedRenderer : this._renderer; ++ + return ( + extends React.PureComponent, void> { + removeClippedSubviews={removeClippedSubviewsOrDefault( + _removeClippedSubviews, + )} +- {...this._renderer()} ++ {...renderer( ++ this.props.ListItemComponent, ++ this.props.renderItem, ++ columnWrapperStyle, ++ numColumns, ++ this.props.extraData, ++ )} + /> + ); + } diff --git a/node_modules/react-native/Libraries/Lists/VirtualizedList.js b/node_modules/react-native/Libraries/Lists/VirtualizedList.js -index 2648cc3..e0c2c13 100644 +index 2648cc3..fa2511d 100644 --- a/node_modules/react-native/Libraries/Lists/VirtualizedList.js +++ b/node_modules/react-native/Libraries/Lists/VirtualizedList.js @@ -16,6 +16,7 @@ const ScrollView = require('../Components/ScrollView/ScrollView'); @@ -31,7 +140,248 @@ index 2648cc3..e0c2c13 100644 const flattenStyle = require('../StyleSheet/flattenStyle'); const infoLog = require('../Utilities/infoLog'); -@@ -2119,7 +2120,14 @@ function describeNestedLists(childList: { +@@ -34,6 +35,7 @@ import type { + ViewToken, + ViewabilityConfigCallbackPair, + } from './ViewabilityHelper'; ++import type {LayoutEvent} from '../Types/CoreEventTypes'; + import { + VirtualizedListCellContextProvider, + VirtualizedListContext, +@@ -794,12 +796,17 @@ class VirtualizedList extends React.PureComponent { + const { + CellRendererComponent, + ItemSeparatorComponent, ++ ListHeaderComponent, ++ ListItemComponent, + data, ++ debug, + getItem, + getItemCount, ++ getItemLayout, + horizontal, ++ renderItem, + } = this.props; +- const stickyOffset = this.props.ListHeaderComponent ? 1 : 0; ++ const stickyOffset = ListHeaderComponent ? 1 : 0; + const end = getItemCount(data) - 1; + let prevCellKey; + last = Math.min(end, last); +@@ -814,27 +821,30 @@ class VirtualizedList extends React.PureComponent { + this._onCellLayout(e, key, ii)} + onUnmount={this._onCellUnmount} +- parentProps={this.props} + ref={ref => { + this._cellRefs[key] = ref; + }} ++ renderItem={renderItem} + />, + ); + prevCellKey = key; + } + } +- ++1 + _onUpdateSeparators = (keys: Array, newProps: Object) => { + keys.forEach(key => { + const ref = key != null && this._cellRefs[key]; +@@ -1269,7 +1279,7 @@ class VirtualizedList extends React.PureComponent { + } + }; + +- _onCellLayout(e, cellKey, index) { ++ _onCellLayout = (e: LayoutEvent, cellKey: string, index: number): void => { + const layout = e.nativeEvent.layout; + const next = { + offset: this._selectOffset(layout), +@@ -1302,7 +1312,7 @@ class VirtualizedList extends React.PureComponent { + + this._computeBlankness(); + this._updateViewableItems(this.props.data); +- } ++ }; + + _onCellUnmount = (cellKey: string) => { + const curr = this._frames[cellKey]; +@@ -1381,7 +1391,7 @@ class VirtualizedList extends React.PureComponent { + } + } + +- _onLayout = (e: Object) => { ++ _onLayout = (e: LayoutEvent) => { + if (this._isNestedWithSameOrientation()) { + // Need to adjust our scroll metrics to be relative to our containing + // VirtualizedList before we can make claims about list item viewability +@@ -1396,7 +1406,7 @@ class VirtualizedList extends React.PureComponent { + this._maybeCallOnEndReached(); + }; + +- _onLayoutEmpty = e => { ++ _onLayoutEmpty = (e: LayoutEvent) => { + this.props.onLayout && this.props.onLayout(e); + }; + +@@ -1404,12 +1414,12 @@ class VirtualizedList extends React.PureComponent { + return this._getCellKey() + '-footer'; + } + +- _onLayoutFooter = e => { ++ _onLayoutFooter = (e: LayoutEvent) => { + this._triggerRemeasureForChildListsInCell(this._getFooterCellKey()); + this._footerLength = this._selectLength(e.nativeEvent.layout); + }; + +- _onLayoutHeader = e => { ++ _onLayoutHeader = (e: LayoutEvent) => { + this._headerLength = this._selectLength(e.nativeEvent.layout); + }; + +@@ -1898,32 +1908,29 @@ type CellRendererProps = { + ItemSeparatorComponent: ?React.ComponentType< + any | {highlighted: boolean, leadingItem: ?Item}, + >, ++ ListItemComponent?: ?(React.ComponentType | React.Element), + cellKey: string, ++ debug?: ?boolean, + fillRateHelper: FillRateHelper, ++ getItemLayout?: ( ++ data: any, ++ index: number, ++ ) => { ++ length: number, ++ offset: number, ++ index: number, ++ ... ++ }, + horizontal: ?boolean, + index: number, + inversionStyle: ViewStyleProp, + item: Item, + // This is extracted by ScrollViewStickyHeader +- onLayout: (event: Object) => void, ++ onCellLayout: (event: Object, cellKey: string, index: number) => void, + onUnmount: (cellKey: string) => void, + onUpdateSeparators: (cellKeys: Array, props: Object) => void, +- parentProps: { +- // e.g. height, y, +- getItemLayout?: ( +- data: any, +- index: number, +- ) => { +- length: number, +- offset: number, +- index: number, +- ... +- }, +- renderItem?: ?RenderItemType, +- ListItemComponent?: ?(React.ComponentType | React.Element), +- ... +- }, + prevCellKey: ?string, ++ renderItem?: ?RenderItemType, + ... + }; + +@@ -1935,7 +1942,7 @@ type CellRendererState = { + ... + }; + +-class CellRenderer extends React.Component< ++class CellRenderer extends React.PureComponent< + CellRendererProps, + CellRendererState, + > { +@@ -1950,12 +1957,16 @@ class CellRenderer extends React.Component< + props: CellRendererProps, + prevState: CellRendererState, + ): ?CellRendererState { +- return { +- separatorProps: { +- ...prevState.separatorProps, +- leadingItem: props.item, +- }, +- }; ++ if (prevState.separatorProps.leadingItem !== props.item) { ++ return { ++ separatorProps: { ++ ...prevState.separatorProps, ++ leadingItem: props.item, ++ }, ++ }; ++ } else { ++ return prevState; ++ } + } + + // TODO: consider factoring separator stuff out of VirtualizedList into FlatList since it's not +@@ -1992,6 +2003,15 @@ class CellRenderer extends React.Component< + this.props.onUnmount(this.props.cellKey); + } + ++ _onLayout = (nativeEvent: LayoutEvent): void => { ++ this.props.onCellLayout && ++ this.props.onCellLayout( ++ nativeEvent, ++ this.props.cellKey, ++ this.props.index, ++ ); ++ }; ++ + _renderElement(renderItem, ListItemComponent, item, index) { + if (renderItem && ListItemComponent) { + console.warn( +@@ -2032,14 +2052,16 @@ class CellRenderer extends React.Component< + const { + CellRendererComponent, + ItemSeparatorComponent, ++ ListItemComponent, ++ debug, + fillRateHelper, ++ getItemLayout, + horizontal, + item, + index, + inversionStyle, +- parentProps, ++ renderItem, + } = this.props; +- const {renderItem, getItemLayout, ListItemComponent} = parentProps; + const element = this._renderElement( + renderItem, + ListItemComponent, +@@ -2048,12 +2070,10 @@ class CellRenderer extends React.Component< + ); + + const onLayout = +- /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment +- * suppresses an error found when Flow v0.68 was deployed. To see the +- * error delete this comment and run Flow. */ +- getItemLayout && !parentProps.debug && !fillRateHelper.enabled() ++ (getItemLayout && !debug && !fillRateHelper.enabled()) || ++ !this.props.onCellLayout + ? undefined +- : this.props.onLayout; ++ : this._onLayout; + // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and + // called explicitly by `ScrollViewStickyHeader`. + const itemSeparator = ItemSeparatorComponent && ( +@@ -2119,7 +2139,14 @@ function describeNestedLists(childList: { const styles = StyleSheet.create({ verticallyInverted: { @@ -48,7 +398,7 @@ index 2648cc3..e0c2c13 100644 horizontallyInverted: { transform: [{scaleX: -1}], diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle -index d9e2714..bed8756 100644 +index 2aefa12..dea1771 100644 --- a/node_modules/react-native/react.gradle +++ b/node_modules/react-native/react.gradle @@ -88,7 +88,7 @@ def enableHermesForVariant = config.enableHermesForVariant ?: { diff --git a/patches/react-native-reanimated+2.5.0.patch b/patches/react-native-reanimated+2.5.0.patch index f4887fb6a2d..b3f6652a68f 100644 --- a/patches/react-native-reanimated+2.5.0.patch +++ b/patches/react-native-reanimated+2.5.0.patch @@ -68,7 +68,7 @@ index a3aafb2..141f0cd 100644 namespace Animated { type Nullable = T | null | undefined; diff --git a/node_modules/react-native-reanimated/src/createAnimatedComponent.tsx b/node_modules/react-native-reanimated/src/createAnimatedComponent.tsx -index 6bead55..1cf0c3f 100644 +index 6bead55..7ae12ba 100644 --- a/node_modules/react-native-reanimated/src/createAnimatedComponent.tsx +++ b/node_modules/react-native-reanimated/src/createAnimatedComponent.tsx @@ -161,7 +161,7 @@ interface ComponentRef extends Component { @@ -80,6 +80,52 @@ index 6bead55..1cf0c3f 100644 ref?: Ref; collapsable?: boolean; } +@@ -195,6 +195,7 @@ export default function createAnimatedComponent( + _isFirstRender = true; + animatedStyle: { value: StyleProps } = { value: {} }; + initialStyle = {}; ++ _lastSentStyle?: StyleProps; + sv: SharedValue> | null; + _propsAnimated?: PropsAnimated; + _component: ComponentRef | null = null; +@@ -580,17 +581,24 @@ export default function createAnimatedComponent( + + _filterNonAnimatedStyle(inputStyle: StyleProps) { + const style: StyleProps = {}; ++ let changed = false; + for (const key in inputStyle) { + const value = inputStyle[key]; + if (!hasAnimatedNodes(value)) { + style[key] = value; ++ changed = changed || style[key] !== this._lastSentStyle?.[key]; + } else if (value instanceof AnimatedValue) { + // if any style in animated component is set directly to the `Value` we set those styles to the first value of `Value` node in order + // to avoid flash of default styles when `Value` is being asynchrounously sent via bridge and initialized in the native side. + style[key] = value._startingValue; ++ changed = changed || style[key] !== this._lastSentStyle?.[key] + } + } +- return style; ++ if (changed) { ++ return style; ++ } else { ++ return this._lastSentStyle; ++ } + } + + _filterNonAnimatedProps( +@@ -617,9 +625,11 @@ export default function createAnimatedComponent( + return style; + } + }); ++ + props[key] = this._filterNonAnimatedStyle( + StyleSheet.flatten(processedStyle) + ); ++ this._lastSentStyle = props[key] + } else if (key === 'animatedProps') { + const animatedProp = inputProps.animatedProps as Partial< + AnimatedComponentProps diff --git a/node_modules/react-native-reanimated/src/reanimated2/component/FlatList.tsx b/node_modules/react-native-reanimated/src/reanimated2/component/FlatList.tsx index f02fc16..32bf952 100644 --- a/node_modules/react-native-reanimated/src/reanimated2/component/FlatList.tsx diff --git a/types/database/models/servers/channel.d.ts b/types/database/models/servers/channel.d.ts index 6a3dca5ec39..2014fbdfc94 100644 --- a/types/database/models/servers/channel.d.ts +++ b/types/database/models/servers/channel.d.ts @@ -53,7 +53,7 @@ export default class ChannelModel extends Model { teamId: string; /** type : The type of the channel ( e.g. G: group messages, D: direct messages, P: private channel and O: public channel) */ - type: string; + type: ChannelType; /** members : Users belonging to this channel */ members: Query;