[Gekidou] Add performance and code improvements around post_list (#6113)

* Add performance and code improvements around post_list

* Fix test

* Move observers from utils to queries

* remove Flipper on iOS to fix CI build

* Fix observePermissionForChannel for DM/GM

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Daniel Espino García
2022-04-04 14:14:55 +02:00
committed by GitHub
parent c9d73d4512
commit d1322e84ce
22 changed files with 740 additions and 325 deletions

View File

@@ -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<boolean>;
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);

View File

@@ -21,6 +21,138 @@ exports[`components/channel_list should render channels error 1`] = `
}
}
>
<View
animatedStyle={
Object {
"value": Object {
"marginLeft": 0,
},
}
}
collapsable={false}
style={
Object {
"marginLeft": 0,
}
}
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<View
accessible={true}
collapsable={false}
focusable={false}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<Text
style={
Object {
"color": "#ffffff",
"fontFamily": "Metropolis-SemiBold",
"fontSize": 28,
"fontWeight": "600",
"lineHeight": 36,
}
}
testID="channel_list_header.team_display_name"
>
Test Team!
</Text>
<View
style={
Object {
"marginLeft": 4,
}
}
>
<Icon
name="chevron-down"
style={
Object {
"color": "rgba(255,255,255,0.8)",
"fontSize": 24,
}
}
/>
</View>
</View>
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"backgroundColor": "rgba(255,255,255,0.08)",
"borderRadius": 14,
"height": 28,
"justifyContent": "center",
"opacity": 1,
"width": 28,
}
}
>
<Icon
name="plus"
style={
Object {
"color": "rgba(255,255,255,0.8)",
"fontSize": 18,
}
}
/>
</View>
</View>
<Text
style={
Object {
"color": "rgba(255,255,255,0.64)",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 11,
"fontWeight": "600",
"lineHeight": 16,
}
}
testID="channel_list_header.server_display_name"
>
</Text>
</View>
<View
style={
Object {
@@ -191,6 +323,36 @@ exports[`components/channel_list should render team error 1`] = `
}
}
>
<View
animatedStyle={
Object {
"value": Object {
"marginLeft": 0,
},
}
}
collapsable={false}
style={
Object {
"marginLeft": 0,
}
}
>
<Text
style={
Object {
"color": "rgba(255,255,255,0.64)",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 11,
"fontWeight": "600",
"lineHeight": 16,
}
}
testID="channel_list_header.server_display_name"
>
</Text>
</View>
<View
style={
Object {

View File

@@ -3,13 +3,13 @@
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 {Permissions} from '@constants';
import {observePermissionForTeam} from '@queries/servers/role';
import {observeCurrentTeam} from '@queries/servers/team';
import {observeCurrentUser} from '@queries/servers/user';
import {hasPermissionForTeam} from '@utils/role';
import ChannelListHeader from './header';
@@ -21,15 +21,15 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
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(

View File

@@ -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(
<SafeAreaProvider>
<ChannelsList
@@ -52,7 +61,13 @@ describe('components/channel_list', () => {
</SafeAreaProvider>,
{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', () => {

View File

@@ -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');

View File

@@ -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);
}),
);

View File

@@ -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) => {

View File

@@ -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(

View File

@@ -99,7 +99,7 @@ const PostList = ({
const onScrollEndIndexListener = useRef<onScrollEndIndexListenerEvent>();
const onViewableItemsChangedListener = useRef<ViewableItemsChangedListenerEvent>();
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<NativeScrollEvent>) => {
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 (
<>
<PostListRefreshControl
enabled={offsetY === 0}
enabled={enableRefreshControl}
refreshing={refreshing}
onRefresh={onRefresh}
style={styles.container}

View File

@@ -3,13 +3,13 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, from as from$, of as of$} from 'rxjs';
import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {General, Permissions} from '@constants';
import {observePermissionForPost} from '@queries/servers/role';
import {observeConfigBooleanValue, observeCurrentUserId} from '@queries/servers/system';
import {observeUser} from '@queries/servers/user';
import {hasPermissionForPost} from '@utils/role';
import {isSystemAdmin} from '@utils/user';
import Reactions from './reactions';
@@ -34,8 +34,8 @@ const withReactions = withObservables(['post'], ({database, post}: WithReactions
map(([u, c, readOnly]) => ((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,

View File

@@ -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))));
const threadRepliedToByCurrentUser = myPosts.length > 0;
const root = await post.root.fetch();
if (root.length) {
rootPost = root[0];
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);
}
}
return of$(false);
}),
);
}
function observeHasReplies(post: PostModel) {
if (!post.rootId) {
return post.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
}
if (rootPost?.userId === currentUser.id) {
threadCreatedByCurrentUser = true;
}
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 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)))),
);

View File

@@ -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<R
export const queryRolesByNames = (database: Database, names: string[]) => {
return database.get<RoleModel>(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);
})));
}

View File

@@ -286,6 +286,12 @@ export const getMyTeamById = async (database: Database, teamId: string) => {
}
};
export const observeMyTeam = (database: Database, teamId: string) => {
return database.get<MyTeamModel>(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<TeamModel>(TEAM).find(teamId));

View File

@@ -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))),

View File

@@ -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<string>();
@@ -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;
}

View File

@@ -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;

View File

@@ -14,25 +14,6 @@
#import "Mattermost-Swift.h"
#import <os/log.h>
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
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 () <RCTBridgeDelegate>
@@ -64,10 +45,6 @@ MattermostBucket* bucket = nil;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
if (bucket == nil) {
bucket = [[MattermostBucket alloc] init];
}

View File

@@ -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)

View File

@@ -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

View File

@@ -19,8 +19,117 @@ index b121da3..82c1c24 100644
<NativeDirectionalScrollView
{...props}
style={StyleSheet.compose(baseStyle, inner)}
diff --git a/node_modules/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/node_modules/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js
index ea21ce2..05e0def 100644
--- a/node_modules/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js
+++ b/node_modules/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js
@@ -170,7 +170,9 @@ class ScrollViewStickyHeader extends React.Component<Props, State> {
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<ItemT> = {|
/**
@@ -141,6 +142,10 @@ type OptionalProps<ItemT> = {|
* 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<ItemT> extends React.PureComponent<Props<ItemT>, void> {
};
}
- _renderer = () => {
- const {ListItemComponent, renderItem, columnWrapperStyle} = this.props;
- const numColumns = numColumnsOrDefault(this.props.numColumns);
+ _renderer = (
+ ListItemComponent: ?(React.ComponentType<any> | React.Element<any>),
+ renderItem: ?RenderItemType<ItemT>,
+ columnWrapperStyle: ?ViewStyleProp,
+ numColumns: ?number,
+ extraData: ?any,
+ ) => {
+ const cols = numColumnsOrDefault(numColumns);
let virtualizedListRenderKey = ListItemComponent
? 'ListItemComponent'
@@ -606,7 +616,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT>) => {
- if (numColumns > 1) {
+ if (cols > 1) {
const {item, index} = info;
invariant(
Array.isArray(item),
@@ -617,7 +627,7 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, 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<ItemT> extends React.PureComponent<Props<ItemT>, 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 (
<VirtualizedList
{...restProps}
@@ -652,7 +667,13 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, 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<Props, State> {
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<Props, State> {
<CellRenderer
CellRendererComponent={CellRendererComponent}
ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
+ ListItemComponent={ListItemComponent}
cellKey={key}
+ debug={debug}
fillRateHelper={this._fillRateHelper}
+ getItemLayout={getItemLayout}
horizontal={horizontal}
index={ii}
inversionStyle={inversionStyle}
item={item}
key={key}
prevCellKey={prevCellKey}
+ onCellLayout={this._onCellLayout}
onUpdateSeparators={this._onUpdateSeparators}
- onLayout={e => 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<?string>, newProps: Object) => {
keys.forEach(key => {
const ref = key != null && this._cellRefs[key];
@@ -1269,7 +1279,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
};
- _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<Props, State> {
this._computeBlankness();
this._updateViewableItems(this.props.data);
- }
+ };
_onCellUnmount = (cellKey: string) => {
const curr = this._frames[cellKey];
@@ -1381,7 +1391,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
}
- _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<Props, State> {
this._maybeCallOnEndReached();
};
- _onLayoutEmpty = e => {
+ _onLayoutEmpty = (e: LayoutEvent) => {
this.props.onLayout && this.props.onLayout(e);
};
@@ -1404,12 +1414,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
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<any> | React.Element<any>),
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<?string>, props: Object) => void,
- parentProps: {
- // e.g. height, y,
- getItemLayout?: (
- data: any,
- index: number,
- ) => {
- length: number,
- offset: number,
- index: number,
- ...
- },
- renderItem?: ?RenderItemType<Item>,
- ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
- ...
- },
prevCellKey: ?string,
+ renderItem?: ?RenderItemType<Item>,
...
};
@@ -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 ?: {

View File

@@ -68,7 +68,7 @@ index a3aafb2..141f0cd 100644
namespace Animated {
type Nullable<T> = 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<Component>;
collapsable?: boolean;
}
@@ -195,6 +195,7 @@ export default function createAnimatedComponent(
_isFirstRender = true;
animatedStyle: { value: StyleProps } = { value: {} };
initialStyle = {};
+ _lastSentStyle?: StyleProps;
sv: SharedValue<null | Record<string, unknown>> | 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<AnimatedProps>
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

View File

@@ -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<ChannelMembershipModel>;