From 712324c756b1e11d4082a794602e6c1b11348607 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 05:09:27 +0200 Subject: [PATCH 01/14] Remove lineHeight from post so that it does not break inline images (#6883) --- app/components/markdown/markdown_image/index.tsx | 3 ++- app/components/post_list/post/body/message/message.tsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/markdown/markdown_image/index.tsx b/app/components/markdown/markdown_image/index.tsx index 3f734ee2e6..b266162590 100644 --- a/app/components/markdown/markdown_image/index.tsx +++ b/app/components/markdown/markdown_image/index.tsx @@ -59,7 +59,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ height: 24, }, container: { - marginBottom: 5, + marginVertical: 5, + top: 5, }, svg: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.06), diff --git a/app/components/post_list/post/body/message/message.tsx b/app/components/post_list/post/body/message/message.tsx index 74df04462f..001bbff239 100644 --- a/app/components/post_list/post/body/message/message.tsx +++ b/app/components/post_list/post/body/message/message.tsx @@ -44,6 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { message: { color: theme.centerChannelColor, ...typography('Body', 200), + lineHeight: undefined, // remove line height, not needed and causes problems with md images }, pendingPost: { opacity: 0.5, From a9464c365e05dfa57c93f6a73affbb28a7d1b1f3 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 11:41:01 +0200 Subject: [PATCH 02/14] Show newer pinned posts first (#6884) --- app/queries/servers/post.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/queries/servers/post.ts b/app/queries/servers/post.ts index aae4ed41e2..20de391292 100644 --- a/app/queries/servers/post.ts +++ b/app/queries/servers/post.ts @@ -209,6 +209,7 @@ export const queryPinnedPostsInChannel = (database: Database, channelId: string) Q.where('channel_id', channelId), Q.where('is_pinned', Q.eq(true)), ), + Q.sortBy('create_at', Q.asc), ); }; From b01a5bfab888758f6aea8eb8398955797c30468d Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 11:42:02 +0200 Subject: [PATCH 03/14] use matchDeepLink result for relative links (#6882) --- app/actions/remote/command.ts | 2 +- app/components/markdown/markdown_link/markdown_link.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/actions/remote/command.ts b/app/actions/remote/command.ts index 6ecd317fe3..bcc7c25c19 100644 --- a/app/actions/remote/command.ts +++ b/app/actions/remote/command.ts @@ -138,7 +138,7 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc const match = matchDeepLink(location, serverUrl, config?.SiteURL); if (match) { - handleDeepLink(location, intl, location); + handleDeepLink(match, intl, location); } else { const {formatMessage} = intl; const onError = () => Alert.alert( diff --git a/app/components/markdown/markdown_link/markdown_link.tsx b/app/components/markdown/markdown_link/markdown_link.tsx index 9bab60b803..82fa6d93a7 100644 --- a/app/components/markdown/markdown_link/markdown_link.tsx +++ b/app/components/markdown/markdown_link/markdown_link.tsx @@ -76,9 +76,9 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU const match = matchDeepLink(url, serverUrl, siteURL); if (match) { - const {error} = await handleDeepLink(url, intl); + const {error} = await handleDeepLink(match, intl); if (error) { - tryOpenURL(url, onError); + tryOpenURL(match, onError); } } else { tryOpenURL(url, onError); From 0f1a29e2da8f9e168837fa59b3df57639c41b799 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 11:42:50 +0200 Subject: [PATCH 04/14] Do not show favorites category if it only contain archived channels (#6881) --- app/database/models/server/category.ts | 17 ++++++++++++----- .../categories_list/categories/header/index.ts | 17 +++++++++++++---- types/database/models/servers/category.ts | 2 +- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/app/database/models/server/category.ts b/app/database/models/server/category.ts index 13296ee142..dfaeeb245a 100644 --- a/app/database/models/server/category.ts +++ b/app/database/models/server/category.ts @@ -104,11 +104,18 @@ export default class CategoryModel extends Model implements CategoryInterface { Q.sortBy('last_post_at', Q.desc), ); - /** hasChannels : Returns a boolean indicating if the category has channels */ - @lazy hasChannels = this.categoryChannels.observeCount().pipe( - map((c) => c > 0), - distinctUntilChanged(), - ); + observeHasChannels = (canViewArchived: boolean) => { + return this.channels.observeWithColumns(['delete_at']).pipe( + map((channels) => { + if (canViewArchived) { + return channels.length > 0; + } + + return channels.filter((c) => c.deleteAt === 0).length > 0; + }), + distinctUntilChanged(), + ); + }; toCategoryWithChannels = async (): Promise => { const categoryChannels = await this.categoryChannels.fetch(); diff --git a/app/screens/home/channel_list/categories_list/categories/header/index.ts b/app/screens/home/channel_list/categories_list/categories/header/index.ts index c543400d97..27048f40f5 100644 --- a/app/screens/home/channel_list/categories_list/categories/header/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/header/index.ts @@ -2,14 +2,23 @@ // See LICENSE.txt for license information. import withObservables from '@nozbe/with-observables'; +import {switchMap} from 'rxjs/operators'; + +import {observeConfigBooleanValue} from '@queries/servers/system'; import CategoryHeader from './header'; import type CategoryModel from '@typings/database/models/servers/category'; -const enhanced = withObservables(['category'], ({category}: {category: CategoryModel}) => ({ - category, - hasChannels: category.hasChannels, -})); +const enhanced = withObservables(['category'], ({category}: {category: CategoryModel}) => { + const canViewArchived = observeConfigBooleanValue(category.database, 'ExperimentalViewArchivedChannels'); + + return { + category, + hasChannels: canViewArchived.pipe( + switchMap((canView) => category.observeHasChannels(canView)), + ), + }; +}); export default enhanced(CategoryHeader); diff --git a/types/database/models/servers/category.ts b/types/database/models/servers/category.ts index fef270ae83..2b529fe816 100644 --- a/types/database/models/servers/category.ts +++ b/types/database/models/servers/category.ts @@ -58,7 +58,7 @@ declare class CategoryModel extends Model { @lazy myChannels: Query; /** hasChannels : Whether the category has any channels */ - @lazy hasChannels: Observable; + observeHasChannels(canViewArchived: boolean): Observable; /** toCategoryWithChannels returns a map of the Category with an array of ordered channel ids */ toCategoryWithChannels(): Promise; From 3de85b2bd1454a4608477158ac6ecf02c84fe45f Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 11:43:30 +0200 Subject: [PATCH 05/14] iOS notifications badge and posts in channel/thread (#6885) --- .../Gekidou/Storage/Database+Mentions.swift | 21 ++++++++++++++----- .../Gekidou/Storage/Database+Posts.swift | 7 ++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/ios/Gekidou/Sources/Gekidou/Storage/Database+Mentions.swift b/ios/Gekidou/Sources/Gekidou/Storage/Database+Mentions.swift index 6c8ca3b504..1b5cd500c1 100644 --- a/ios/Gekidou/Sources/Gekidou/Storage/Database+Mentions.swift +++ b/ios/Gekidou/Sources/Gekidou/Storage/Database+Mentions.swift @@ -35,14 +35,25 @@ extension Database { } public func getChannelMentions(_ db: Connection) -> Int { - let mentionsCol = Expression("mentions_count") - let mentions = try? db.scalar(myChannelTable.select(mentionsCol.total)) + let stmtString = """ + SELECT SUM(my.mentions_count) \ + FROM MyChannel my \ + INNER JOIN Channel c ON c.id=my.id \ + WHERE c.delete_at = 0 + """ + let mentions = try? db.prepare(stmtString).scalar() as? Double return Int(mentions ?? 0) } - + public func getThreadMentions(_ db: Connection) -> Int { - let mentionsCol = Expression("unread_mentions") - let mentions = try? db.scalar(threadTable.select(mentionsCol.total)) + let stmtString = """ + SELECT SUM(unread_mentions) \ + FROM Thread t + INNER JOIN Post p ON t.id=p.id \ + INNER JOIN Channel c ON p.channel_id=c.id + WHERE c.delete_at = 0 + """ + let mentions = try? db.prepare(stmtString).scalar() as? Double return Int(mentions ?? 0) } diff --git a/ios/Gekidou/Sources/Gekidou/Storage/Database+Posts.swift b/ios/Gekidou/Sources/Gekidou/Storage/Database+Posts.swift index 830c7941a3..cd4f46f43a 100644 --- a/ios/Gekidou/Sources/Gekidou/Storage/Database+Posts.swift +++ b/ios/Gekidou/Sources/Gekidou/Storage/Database+Posts.swift @@ -181,8 +181,9 @@ extension Database { public func handlePostData(_ db: Connection, _ postData: PostData, _ channelId: String, _ usedSince: Bool = false, _ receivingThreads: Bool = false) throws { let sortedChainedPosts = chainAndSortPosts(postData) try insertOrUpdatePosts(db, sortedChainedPosts, channelId) - let earliest = sortedChainedPosts.first!.create_at - let latest = sortedChainedPosts.last!.create_at + let sortedAndNotDeletedPosts = sortedChainedPosts.filter({$0.delete_at == 0}) + let earliest = sortedAndNotDeletedPosts.first!.create_at + let latest = sortedAndNotDeletedPosts.last!.create_at if (!receivingThreads) { try handlePostsInChannel(db, channelId, earliest, latest, usedSince) @@ -564,7 +565,7 @@ extension Database { var postsInThread = [String: [Post]]() for post in posts { - if !post.root_id.isEmpty { + if !post.root_id.isEmpty && post.delete_at == 0 { var threadPosts = postsInThread[post.root_id] ?? [Post]() threadPosts.append(post) From 4206764881663b07f7457511c19adf126ac3ab0c Mon Sep 17 00:00:00 2001 From: Anurag Shivarathri Date: Tue, 20 Dec 2022 15:14:00 +0530 Subject: [PATCH 06/14] Fix (#6887) --- app/queries/servers/thread.ts | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/app/queries/servers/thread.ts b/app/queries/servers/thread.ts index 4ee78d7dbd..4a200ad8ef 100644 --- a/app/queries/servers/thread.ts +++ b/app/queries/servers/thread.ts @@ -141,7 +141,10 @@ export const prepareThreadsFromReceivedPosts = async (operator: ServerDataOperat }; export const queryThreadsInTeam = (database: Database, teamId: string, onlyUnreads?: boolean, hasReplies?: boolean, isFollowing?: boolean, sort?: boolean, earliest?: number): Query => { - const query: Q.Clause[] = []; + const query: Q.Clause[] = [ + Q.experimentalNestedJoin(POST, CHANNEL), + Q.on(POST, Q.on(CHANNEL, Q.where('delete_at', 0))), + ]; if (isFollowing) { query.push(Q.where('is_following', true)); @@ -189,30 +192,34 @@ export const queryThreads = (database: Database, teamId?: string, onlyUnreads = Q.where('reply_count', Q.gt(0)), ]; + // Only get threads from available channel + const channelCondition: Q.Condition[] = [ + Q.where('delete_at', 0), + ]; + // If teamId is specified, only get threads in that team if (teamId) { - let condition: Q.Condition = Q.where('team_id', teamId); - if (includeDmGm) { - condition = Q.or( - Q.where('team_id', teamId), - Q.where('team_id', ''), + channelCondition.push( + Q.or( + Q.where('team_id', teamId), + Q.where('team_id', ''), + ), ); + } else { + channelCondition.push(Q.where('team_id', teamId)); } - - query.push( - Q.experimentalNestedJoin(POST, CHANNEL), - Q.on(POST, Q.on(CHANNEL, condition)), - ); } else if (!includeDmGm) { // fetching all threads from all teams // excluding DM/GM channels - query.push( - Q.experimentalNestedJoin(POST, CHANNEL), - Q.on(POST, Q.on(CHANNEL, Q.where('team_id', Q.notEq('')))), - ); + channelCondition.push(Q.where('team_id', Q.notEq(''))); } + query.push( + Q.experimentalNestedJoin(POST, CHANNEL), + Q.on(POST, Q.on(CHANNEL, Q.and(...channelCondition))), + ); + if (onlyUnreads) { query.push(Q.where('unread_replies', Q.gt(0))); } From bf5783252ea45463c02ffb1a9e43c69ccd969724 Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:54:25 +0200 Subject: [PATCH 07/14] MM-49219: fixes post priority (#6880) We have changed how priority gets saved in the server, so now instead of post.props we are using post.metadata.priority. This commit adds the relevant changes for posts' priority to work in the mobile app. --- .../post_draft/draft_input/index.tsx | 16 +++++++------- .../post_priority_action/index.tsx | 21 ++++++++---------- .../quick_actions/quick_actions.tsx | 12 +++++----- .../post_draft/send_handler/send_handler.tsx | 22 ++++++++++++------- .../post_list/post/header/header.tsx | 4 ++-- app/components/post_list/post/post.tsx | 2 +- .../post_priority/post_priority_label.tsx | 4 ++-- .../post_priority_picker/index.tsx | 8 ++----- types/api/posts.d.ts | 5 +++++ 9 files changed, 49 insertions(+), 45 deletions(-) diff --git a/app/components/post_draft/draft_input/index.tsx b/app/components/post_draft/draft_input/index.tsx index cfb4a276a4..7ded6863bd 100644 --- a/app/components/post_draft/draft_input/index.tsx +++ b/app/components/post_draft/draft_input/index.tsx @@ -24,8 +24,8 @@ type Props = { canShowPostPriority?: boolean; // Post Props - postProps: Post['props']; - updatePostProps: (postProps: Post['props']) => void; + postPriority: PostPriorityData; + updatePostPriority: (postPriority: PostPriorityData) => void; // Cursor Position Handler updateCursorPosition: React.Dispatch>; @@ -110,8 +110,8 @@ export default function DraftInput({ updateCursorPosition, cursorPosition, updatePostInputTop, - postProps, - updatePostProps, + postPriority, + updatePostPriority, setIsFocused, }: Props) { const theme = useTheme(); @@ -155,9 +155,9 @@ export default function DraftInput({ overScrollMode={'never'} disableScrollViewPanResponder={true} > - {Boolean(postProps.priority) && ( + {Boolean(postPriority?.priority) && ( - + )} diff --git a/app/components/post_draft/quick_actions/post_priority_action/index.tsx b/app/components/post_draft/quick_actions/post_priority_action/index.tsx index cc6452f239..0e0ddf2958 100644 --- a/app/components/post_draft/quick_actions/post_priority_action/index.tsx +++ b/app/components/post_draft/quick_actions/post_priority_action/index.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {StyleSheet} from 'react-native'; import CompassIcon from '@components/compass_icon'; -import PostPriorityPicker, {PostPriorityData} from '@components/post_priority/post_priority_picker'; +import PostPriorityPicker from '@components/post_priority/post_priority_picker'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {ICON_SIZE} from '@constants/post_draft'; import {useTheme} from '@context/theme'; @@ -15,8 +15,8 @@ import {changeOpacity} from '@utils/theme'; type Props = { testID?: string; - postProps: Post['props']; - updatePostProps: (postProps: Post['props']) => void; + postPriority: PostPriorityData; + updatePostPriority: (postPriority: PostPriorityData) => void; } const style = StyleSheet.create({ @@ -29,30 +29,27 @@ const style = StyleSheet.create({ export default function PostPriorityAction({ testID, - postProps, - updatePostProps, + postPriority, + updatePostPriority, }: Props) { const intl = useIntl(); const theme = useTheme(); const handlePostPriorityPicker = useCallback((postPriorityData: PostPriorityData) => { - updatePostProps((oldPostProps: Post['props']) => ({ - ...oldPostProps, - ...postPriorityData, - })); + updatePostPriority(postPriorityData); dismissBottomSheet(); - }, [updatePostProps]); + }, [updatePostPriority]); const renderContent = useCallback(() => { return ( ); - }, [handlePostPriorityPicker, postProps]); + }, [handlePostPriorityPicker, postPriority]); const onPress = useCallback(() => { bottomSheet({ diff --git a/app/components/post_draft/quick_actions/quick_actions.tsx b/app/components/post_draft/quick_actions/quick_actions.tsx index 10d4592321..944a166d0a 100644 --- a/app/components/post_draft/quick_actions/quick_actions.tsx +++ b/app/components/post_draft/quick_actions/quick_actions.tsx @@ -22,8 +22,8 @@ type Props = { value: string; updateValue: (value: string) => void; addFiles: (file: FileInfo[]) => void; - postProps: Post['props']; - updatePostProps: (postProps: Post['props']) => void; + postPriority: PostPriorityData; + updatePostPriority: (postPriority: PostPriorityData) => void; focus: () => void; } @@ -45,8 +45,8 @@ export default function QuickActions({ maxFileCount, updateValue, addFiles, - postProps, - updatePostProps, + postPriority, + updatePostPriority, focus, }: Props) { const atDisabled = value[value.length - 1] === '@'; @@ -101,8 +101,8 @@ export default function QuickActions({ {isPostPriorityEnabled && canShowPostPriority && ( )} diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index 6ffb5fb51d..97c7e7e696 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -13,6 +13,7 @@ import {setStatus} from '@actions/remote/user'; import {canEndCall, endCall, getEndCallMessage} from '@calls/actions/calls'; import ClientError from '@client/rest/error'; import {Events, Screens} from '@constants'; +import {PostPriorityType} from '@constants/post'; import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft'; import {useServerUrl} from '@context/server'; import DraftUploadManager from '@managers/draft_upload_manager'; @@ -54,6 +55,10 @@ type Props = { uploadFileError: React.ReactNode; } +const INITIAL_PRIORITY = { + priority: PostPriorityType.STANDARD, +}; + export default function SendHandler({ testID, channelId, @@ -83,8 +88,7 @@ export default function SendHandler({ const [channelTimezoneCount, setChannelTimezoneCount] = useState(0); const [sendingMessage, setSendingMessage] = useState(false); - - const [postProps, setPostProps] = useState({}); + const [postPriority, setPostPriority] = useState(INITIAL_PRIORITY); const canSend = useCallback(() => { if (sendingMessage) { @@ -120,17 +124,19 @@ export default function SendHandler({ message: value, } as Post; - if (Object.keys(postProps).length) { - post.props = postProps; + if (Object.keys(postPriority).length) { + post.metadata = { + priority: postPriority, + }; } createPost(serverUrl, post, postFiles); clearDraft(); setSendingMessage(false); - setPostProps({}); + setPostPriority(INITIAL_PRIORITY); DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); - }, [files, currentUserId, channelId, rootId, value, clearDraft, postProps]); + }, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]); const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => { const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, Boolean(isTimezoneEnabled), channelTimezoneCount, atHere); @@ -297,8 +303,8 @@ export default function SendHandler({ canSend={canSend()} maxMessageLength={maxMessageLength} updatePostInputTop={updatePostInputTop} - postProps={postProps} - updatePostProps={setPostProps} + postPriority={postPriority} + updatePostPriority={setPostPriority} setIsFocused={setIsFocused} /> ); diff --git a/app/components/post_list/post/header/header.tsx b/app/components/post_list/post/header/header.tsx index fafe6035bc..7b9eaab895 100644 --- a/app/components/post_list/post/header/header.tsx +++ b/app/components/post_list/post/header/header.tsx @@ -132,10 +132,10 @@ const Header = (props: HeaderProps) => { style={style.time} testID='post_header.date_time' /> - {showPostPriority && ( + {showPostPriority && post.metadata?.priority?.priority && ( )} diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx index a1e6f1107a..a177816680 100644 --- a/app/components/post_list/post/post.tsx +++ b/app/components/post_list/post/post.tsx @@ -226,7 +226,7 @@ const Post = ({ // If the post is a priority post: // 1. Show the priority label in channel screen // 2. Show the priority label in thread screen for the root post - const showPostPriority = Boolean(isPostPriorityEnabled && post.props?.priority) && (location !== Screens.THREAD || !post.rootId); + const showPostPriority = Boolean(isPostPriorityEnabled && post.metadata?.priority?.priority) && (location !== Screens.THREAD || !post.rootId); const sameSequence = hasReplies ? (hasReplies && post.rootId) : !post.rootId; if (!showPostPriority && hasSameRoot && isConsecutivePost && sameSequence) { diff --git a/app/components/post_priority/post_priority_label.tsx b/app/components/post_priority/post_priority_label.tsx index 3432b65540..e9aeedd1d5 100644 --- a/app/components/post_priority/post_priority_label.tsx +++ b/app/components/post_priority/post_priority_label.tsx @@ -35,7 +35,7 @@ const style = StyleSheet.create({ }); type Props = { - label: PostPriorityType; + label: PostPriorityData['priority']; }; const PostPriorityLabel = ({label}: Props) => { @@ -48,7 +48,7 @@ const PostPriorityLabel = ({label}: Props) => { containerStyle.push(style.urgent); iconName = 'alert-outline'; labelText = intl.formatMessage({id: 'post_priority.label.urgent', defaultMessage: 'URGENT'}); - } else { + } else if (label === PostPriorityType.IMPORTANT) { containerStyle.push(style.important); iconName = 'alert-circle-outline'; labelText = intl.formatMessage({id: 'post_priority.label.important', defaultMessage: 'IMPORTANT'}); diff --git a/app/components/post_priority/post_priority_picker/index.tsx b/app/components/post_priority/post_priority_picker/index.tsx index 9de0b58d0c..48c51e60a1 100644 --- a/app/components/post_priority/post_priority_picker/index.tsx +++ b/app/components/post_priority/post_priority_picker/index.tsx @@ -14,10 +14,6 @@ import {typography} from '@utils/typography'; import PostPriorityPickerItem from './post_priority_picker_item'; -export type PostPriorityData = { - priority: PostPriorityType; -}; - type Props = { data: PostPriorityData; onSubmit: (data: PostPriorityData) => void; @@ -61,8 +57,8 @@ const PostPriorityPicker = ({data, onSubmit}: Props) => { // For now, we just have one option but the spec suggest we have more in the next phase // const [data, setData] = React.useState(defaultData); - const handleUpdatePriority = React.useCallback((priority: PostPriorityType) => { - onSubmit({priority}); + const handleUpdatePriority = React.useCallback((priority: PostPriorityData['priority']) => { + onSubmit({priority: priority || ''}); }, [onSubmit]); return ( diff --git a/types/api/posts.d.ts b/types/api/posts.d.ts index c36824e4f5..51da8a0bfb 100644 --- a/types/api/posts.d.ts +++ b/types/api/posts.d.ts @@ -20,6 +20,10 @@ type PostType = type PostEmbedType = 'image' | 'message_attachment' | 'opengraph'; +type PostPriorityData = { + priority: ''|'urgent'|'important'; +}; + type PostEmbed = { type: PostEmbedType; url: string; @@ -39,6 +43,7 @@ type PostMetadata = { files?: FileInfo[]; images?: Dictionary; reactions?: Reaction[]; + priority?: PostPriorityData; }; type Post = { From 7663276710ae81e76fb7e73f236d17660602b06e Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 20 Dec 2022 22:33:12 +0200 Subject: [PATCH 08/14] Trigger tab bar animation earlier (#6889) --- app/screens/navigation.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index e8b1882097..8c77176e1b 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -5,7 +5,7 @@ import merge from 'deepmerge'; import {Appearance, DeviceEventEmitter, NativeModules, StatusBar, Platform, Alert} from 'react-native'; -import {ImageResource, Navigation, Options, OptionsModalPresentationStyle, OptionsTopBarButton, ScreenPoppedEvent} from 'react-native-navigation'; +import {ComponentWillAppearEvent, ImageResource, Navigation, Options, OptionsModalPresentationStyle, OptionsTopBarButton, ScreenPoppedEvent} from 'react-native-navigation'; import tinyColor from 'tinycolor2'; import CompassIcon from '@components/compass_icon'; @@ -32,6 +32,7 @@ const alpha = { export function registerNavigationListeners() { Navigation.events().registerScreenPoppedListener(onPoppedListener); Navigation.events().registerCommandListener(onCommandListener); + Navigation.events().registerComponentWillAppearListener(onScreenWillAppear); } function onCommandListener(name: string, params: any) { @@ -66,7 +67,10 @@ function onCommandListener(name: string, params: any) { function onPoppedListener({componentId}: ScreenPoppedEvent) { // screen pop does not trigger registerCommandListener, but does trigger screenPoppedListener NavigationStore.removeScreenFromStack(componentId); - if (NavigationStore.getVisibleScreen() === Screens.HOME) { +} + +function onScreenWillAppear(event: ComponentWillAppearEvent) { + if (event.componentId === Screens.HOME) { DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true); } } From 25291b04f1fa2c0683c4b2bc1afe76a6ba6b588d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Espino=20Garc=C3=ADa?= Date: Tue, 20 Dec 2022 21:35:12 +0100 Subject: [PATCH 09/14] Fix server unreachable message showing too often (#6890) --- app/actions/websocket/index.ts | 6 ++--- .../connection_banner/connection_banner.tsx | 12 +++++++--- app/components/connection_banner/index.ts | 2 +- app/managers/websocket_manager.ts | 23 ++++++++++--------- .../server_item/websocket/index.ts | 2 +- .../server_item/websocket/websocket.tsx | 6 ++--- types/global/websocket.d.ts | 4 ++++ 7 files changed, 33 insertions(+), 22 deletions(-) create mode 100644 types/global/websocket.d.ts diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 5f094dbd37..f2b12615b3 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -86,7 +86,7 @@ export async function handleFirstConnect(serverUrl: string) { // ESR: 5.37 if (lastDisconnect && config?.EnableReliableWebSockets !== 'true' && alreadyConnected.has(serverUrl)) { - handleReconnect(serverUrl); + await handleReconnect(serverUrl); return; } @@ -100,8 +100,8 @@ export async function handleFirstConnect(serverUrl: string) { } } -export function handleReconnect(serverUrl: string) { - doReconnect(serverUrl); +export async function handleReconnect(serverUrl: string) { + await doReconnect(serverUrl); } export async function handleClose(serverUrl: string, lastDisconnect: number) { diff --git a/app/components/connection_banner/connection_banner.tsx b/app/components/connection_banner/connection_banner.tsx index ea1340a720..44604ff32d 100644 --- a/app/components/connection_banner/connection_banner.tsx +++ b/app/components/connection_banner/connection_banner.tsx @@ -20,7 +20,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; type Props = { - isConnected: boolean; + websocketState: WebsocketConnectedState; } const getStyle = makeStyleSheetFromTheme((theme: Theme) => { @@ -74,7 +74,7 @@ const TIME_TO_OPEN = toMilliseconds({seconds: 3}); const TIME_TO_CLOSE = toMilliseconds({seconds: 1}); const ConnectionBanner = ({ - isConnected, + websocketState, }: Props) => { const intl = useIntl(); const closeTimeout = useRef(); @@ -86,6 +86,8 @@ const ConnectionBanner = ({ const appState = useAppState(); const netInfo = useNetInfo(); + const isConnected = websocketState === 'connected'; + const openCallback = useCallback(() => { setVisible(true); clearTimeoutRef(openTimeout); @@ -97,7 +99,9 @@ const ConnectionBanner = ({ }, []); useEffect(() => { - if (!isConnected) { + if (websocketState === 'connecting') { + openCallback(); + } else if (!isConnected) { openTimeout.current = setTimeout(openCallback, TIME_TO_OPEN); } return () => { @@ -158,6 +162,8 @@ const ConnectionBanner = ({ let text; if (isConnected) { text = intl.formatMessage({id: 'connection_banner.connected', defaultMessage: 'Connection restored'}); + } else if (websocketState === 'connecting') { + text = intl.formatMessage({id: 'connection_banner.connecting', defaultMessage: 'Connecting...'}); } else if (netInfo.isInternetReachable) { text = intl.formatMessage({id: 'connection_banner.not_reachable', defaultMessage: 'The server is not reachable'}); } else { diff --git a/app/components/connection_banner/index.ts b/app/components/connection_banner/index.ts index 0b5547837b..5de70a4285 100644 --- a/app/components/connection_banner/index.ts +++ b/app/components/connection_banner/index.ts @@ -9,7 +9,7 @@ import websocket_manager from '@managers/websocket_manager'; import ConnectionBanner from './connection_banner'; const enhanced = withObservables(['serverUrl'], ({serverUrl}: {serverUrl: string}) => ({ - isConnected: websocket_manager.observeConnected(serverUrl), + websocketState: websocket_manager.observeWebsocketState(serverUrl), })); export default withServerUrl(enhanced(ConnectionBanner)); diff --git a/app/managers/websocket_manager.ts b/app/managers/websocket_manager.ts index 809ad34444..1e44a3da2c 100644 --- a/app/managers/websocket_manager.ts +++ b/app/managers/websocket_manager.ts @@ -21,10 +21,10 @@ import {isMainActivity} from '@utils/helpers'; import {logError} from '@utils/log'; const WAIT_TO_CLOSE = toMilliseconds({seconds: 15}); -const WAIT_UNTIL_NEXT = toMilliseconds({seconds: 20}); +const WAIT_UNTIL_NEXT = toMilliseconds({seconds: 5}); class WebsocketManager { - private connectedSubjects: {[serverUrl: string]: BehaviorSubject} = {}; + private connectedSubjects: {[serverUrl: string]: BehaviorSubject} = {}; private clients: Record = {}; private connectionTimerIDs: Record void>> = {}; @@ -69,7 +69,7 @@ class WebsocketManager { } delete this.clients[serverUrl]; - this.getConnectedSubject(serverUrl).next(false); + this.getConnectedSubject(serverUrl).next('not_connected'); delete this.connectedSubjects[serverUrl]; }; @@ -96,7 +96,7 @@ class WebsocketManager { const client = this.clients[url]; if (client.isConnected()) { client.close(true); - this.getConnectedSubject(url).next(false); + this.getConnectedSubject(url).next('not_connected'); } } }; @@ -107,6 +107,7 @@ class WebsocketManager { if (clientUrl === activeServerUrl) { this.initializeClient(clientUrl); } else { + this.getConnectedSubject(clientUrl).next('connecting'); const bounce = debounce(this.initializeClient.bind(this, clientUrl), WAIT_UNTIL_NEXT); this.connectionTimerIDs[clientUrl] = bounce; bounce(); @@ -118,7 +119,7 @@ class WebsocketManager { return this.clients[serverUrl]?.isConnected(); }; - public observeConnected = (serverUrl: string) => { + public observeWebsocketState = (serverUrl: string) => { return this.getConnectedSubject(serverUrl).asObservable().pipe( distinctUntilChanged(), ); @@ -126,7 +127,7 @@ class WebsocketManager { private getConnectedSubject = (serverUrl: string) => { if (!this.connectedSubjects[serverUrl]) { - this.connectedSubjects[serverUrl] = new BehaviorSubject(this.isConnected(serverUrl)); + this.connectedSubjects[serverUrl] = new BehaviorSubject(this.isConnected(serverUrl) ? 'connected' : 'not_connected'); } return this.connectedSubjects[serverUrl]; @@ -153,13 +154,13 @@ class WebsocketManager { private onFirstConnect = (serverUrl: string) => { this.startPeriodicStatusUpdates(serverUrl); handleFirstConnect(serverUrl); - this.getConnectedSubject(serverUrl).next(true); + this.getConnectedSubject(serverUrl).next('connected'); }; - private onReconnect = (serverUrl: string) => { + private onReconnect = async (serverUrl: string) => { this.startPeriodicStatusUpdates(serverUrl); - handleReconnect(serverUrl); - this.getConnectedSubject(serverUrl).next(true); + await handleReconnect(serverUrl); + this.getConnectedSubject(serverUrl).next('connected'); }; private onWebsocketClose = async (serverUrl: string, connectFailCount: number, lastDisconnect: number) => { @@ -168,7 +169,7 @@ class WebsocketManager { await handleClose(serverUrl, lastDisconnect); this.stopPeriodicStatusUpdates(serverUrl); - this.getConnectedSubject(serverUrl).next(false); + this.getConnectedSubject(serverUrl).next('not_connected'); } }; diff --git a/app/screens/home/channel_list/servers/servers_list/server_item/websocket/index.ts b/app/screens/home/channel_list/servers/servers_list/server_item/websocket/index.ts index de3f977534..b3379ffe77 100644 --- a/app/screens/home/channel_list/servers/servers_list/server_item/websocket/index.ts +++ b/app/screens/home/channel_list/servers/servers_list/server_item/websocket/index.ts @@ -8,7 +8,7 @@ import WebsocketManager from '@managers/websocket_manager'; import WebSocket from './websocket'; const enhanced = withObservables(['serverUrl'], ({serverUrl}: {serverUrl: string}) => ({ - isConnected: WebsocketManager.observeConnected(serverUrl), + websocketState: WebsocketManager.observeWebsocketState(serverUrl), })); export default enhanced(WebSocket); diff --git a/app/screens/home/channel_list/servers/servers_list/server_item/websocket/websocket.tsx b/app/screens/home/channel_list/servers/servers_list/server_item/websocket/websocket.tsx index 19cac50798..f4c162a23e 100644 --- a/app/screens/home/channel_list/servers/servers_list/server_item/websocket/websocket.tsx +++ b/app/screens/home/channel_list/servers/servers_list/server_item/websocket/websocket.tsx @@ -11,7 +11,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; type Props = { - isConnected: boolean; + websocketState: WebsocketConnectedState; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -28,10 +28,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); -const WebSocket = ({isConnected}: Props) => { +const WebSocket = ({websocketState}: Props) => { const theme = useTheme(); - if (isConnected) { + if (websocketState === 'connected' || websocketState === 'connecting') { return null; } diff --git a/types/global/websocket.d.ts b/types/global/websocket.d.ts new file mode 100644 index 0000000000..9ee8f65e5e --- /dev/null +++ b/types/global/websocket.d.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +type WebsocketConnectedState = 'not_connected' | 'connected' | 'connecting'; From 58b78ec27a02637a3983253d62ff86b0c60bd85f Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 21 Dec 2022 16:00:38 +0200 Subject: [PATCH 10/14] use correct syntax highlight styles for github code theme (#6895) --- app/components/syntax_highlight/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/syntax_highlight/index.tsx b/app/components/syntax_highlight/index.tsx index a83b2e3e01..8ddcdd63b0 100644 --- a/app/components/syntax_highlight/index.tsx +++ b/app/components/syntax_highlight/index.tsx @@ -4,7 +4,7 @@ import React, {useCallback, useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import {github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs'; +import {githubGist as github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs'; import {useTheme} from '@context/theme'; From 92ca1e3704df8778ae59ca205e058217437045e3 Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo Date: Wed, 21 Dec 2022 21:34:44 +0400 Subject: [PATCH 11/14] Sentry fixes (#6830) --- app/init/push_notifications.ts | 4 +- app/screens/sso/sso_with_redirect_url.tsx | 4 +- app/utils/error_handling.ts | 6 ++- app/utils/general/index.ts | 3 ++ app/utils/sentry.ts | 23 ++++++++-- fastlane/Fastfile | 6 ++- ios/ErrorReporting/Sentry.swift | 27 +++++++++++ ios/Mattermost.xcodeproj/project.pbxproj | 45 +++++++++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 9 ++++ ios/MattermostShare/Info.plist | 8 +++- ios/MattermostShare/ShareViewController.swift | 7 ++- ios/NotificationService/Info.plist | 4 ++ .../NotificationService.swift | 6 ++- 13 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 ios/ErrorReporting/Sentry.swift diff --git a/app/init/push_notifications.ts b/app/init/push_notifications.ts index ec78be8f7e..14aac23155 100644 --- a/app/init/push_notifications.ts +++ b/app/init/push_notifications.ts @@ -2,7 +2,6 @@ // See LICENSE.txt for license information. import {AppState, DeviceEventEmitter, Platform} from 'react-native'; -import DeviceInfo from 'react-native-device-info'; import { Notification, NotificationAction, @@ -29,6 +28,7 @@ import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread'; import {dismissOverlay, showOverlay} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; +import {isBetaApp} from '@utils/general'; import {isMainActivity, isTablet} from '@utils/helpers'; import {logInfo} from '@utils/log'; import {convertToNotificationData} from '@utils/notification'; @@ -248,7 +248,7 @@ class PushNotifications { if (Platform.OS === 'ios') { prefix = Device.PUSH_NOTIFY_APPLE_REACT_NATIVE; - if (DeviceInfo.getBundleId().includes('rnbeta')) { + if (isBetaApp) { prefix = `${prefix}beta`; } } else { diff --git a/app/screens/sso/sso_with_redirect_url.tsx b/app/screens/sso/sso_with_redirect_url.tsx index 30867c24de..5eb39f7a62 100644 --- a/app/screens/sso/sso_with_redirect_url.tsx +++ b/app/screens/sso/sso_with_redirect_url.tsx @@ -6,13 +6,13 @@ import React, {useEffect, useState} from 'react'; import {useIntl} from 'react-intl'; import {Linking, Platform, Text, View} from 'react-native'; import Button from 'react-native-button'; -import DeviceInfo from 'react-native-device-info'; import urlParse from 'url-parse'; import FormattedText from '@components/formatted_text'; import {Sso} from '@constants'; import NetworkManager from '@managers/network_manager'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; +import {isBetaApp} from '@utils/general'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import {tryOpenURL} from '@utils/url'; @@ -62,7 +62,7 @@ const SSOWithRedirectURL = ({doSSOLogin, loginError, loginUrl, serverUrl, setLog const style = getStyleSheet(theme); const intl = useIntl(); let customUrlScheme = Sso.REDIRECT_URL_SCHEME; - if (DeviceInfo.getBundleId && DeviceInfo.getBundleId().includes('rnbeta')) { + if (isBetaApp) { customUrlScheme = Sso.REDIRECT_URL_SCHEME_DEV; } diff --git a/app/utils/error_handling.ts b/app/utils/error_handling.ts index 721a05b68b..2ac1145fda 100644 --- a/app/utils/error_handling.ts +++ b/app/utils/error_handling.ts @@ -11,6 +11,7 @@ import { import {DEFAULT_LOCALE, getTranslations, t} from '@i18n'; import {dismissAllModals} from '@screens/navigation'; import {ClientError} from '@utils/client_error'; +import {isBetaApp} from '@utils/general'; import { captureException, captureJSException, @@ -42,7 +43,10 @@ class JavascriptAndNativeErrorHandler { } logWarning('Handling Javascript error', e, isFatal); - captureJSException(e, isFatal); + + if (isBetaApp || isFatal) { + captureJSException(e, isFatal); + } if (isFatal && e instanceof Error) { const translations = getTranslations(DEFAULT_LOCALE); diff --git a/app/utils/general/index.ts b/app/utils/general/index.ts index 60bc8d2793..1a29fa1ba1 100644 --- a/app/utils/general/index.ts +++ b/app/utils/general/index.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import DeviceInfo from 'react-native-device-info'; import ReactNativeHapticFeedback, {HapticFeedbackTypes} from 'react-native-haptic-feedback'; type SortByCreatAt = (Session | Channel | Team | Post) & { @@ -51,3 +52,5 @@ export const sortByNewest = (a: SortByCreatAt, b: SortByCreatAt) => { return 1; }; + +export const isBetaApp = DeviceInfo.getBundleId && DeviceInfo.getBundleId().includes('rnbeta'); diff --git a/app/utils/sentry.ts b/app/utils/sentry.ts index cf071c10b3..44b5e7bb99 100644 --- a/app/utils/sentry.ts +++ b/app/utils/sentry.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Database} from '@nozbe/watermelondb'; -import {Breadcrumb} from '@sentry/types'; +import {Breadcrumb, Event} from '@sentry/types'; import {Platform} from 'react-native'; import {Navigation} from 'react-native-navigation'; @@ -10,6 +10,7 @@ import Config from '@assets/config.json'; import DatabaseManager from '@database/manager'; import {getConfig} from '@queries/servers/system'; import {getCurrentUser} from '@queries/servers/user'; +import {isBetaApp} from '@utils/general'; import {ClientError} from './client_error'; import {logError, logWarning} from './log'; @@ -39,9 +40,18 @@ export function initializeSentry() { return; } + const mmConfig = { + environment: isBetaApp ? 'beta' : 'production', + tracesSampleRate: isBetaApp ? 1.0 : 0.2, + sampleRate: isBetaApp ? 1.0 : 0.2, + attachStacktrace: isBetaApp, // For Beta, stack traces are automatically attached to all messages logged + }; + Sentry.init({ dsn, - tracesSampleRate: 0.2, + sendDefaultPii: false, + ...mmConfig, + ...Config.SentryOptions, integrations: [ new Sentry.ReactNativeTracing({ @@ -51,8 +61,13 @@ export function initializeSentry() { ), }), ], - sendDefaultPii: false, - ...Config.SentryOptions, + beforeSend: (event: Event) => { + if (isBetaApp || event?.level === 'fatal') { + return event; + } + + return null; + }, }); } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 3fad4a6b8d..7e281fb267 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -568,6 +568,9 @@ platform :ios do app_name_sub = app_name.gsub(" ", "_") config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug' method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD'] + + # Need to add xcargs to only notification and + xcargs = ENV['SENTRY_ENABLED'] == 'true' ? "SENTRY_DSN_IOS='#{ENV['SENTRY_DSN_IOS']}' SENTRY_ENABLED='#{ENV['SENTRY_ENABLED']}'" : '' setup_code_signing @@ -583,7 +586,8 @@ platform :ios do export_options: { signingStyle: 'manual', iCloudContainerEnvironment: 'Production' - } + }, + xcargs:xcargs ) end diff --git a/ios/ErrorReporting/Sentry.swift b/ios/ErrorReporting/Sentry.swift new file mode 100644 index 0000000000..71b8def203 --- /dev/null +++ b/ios/ErrorReporting/Sentry.swift @@ -0,0 +1,27 @@ +// +// Sentry.swift +// Mattermost +// +// Created by Avinash Lingaloo on 20/12/2022. +// Copyright © 2022 Facebook. All rights reserved. +// +import Foundation + +import Sentry + +func initSentryAppExt(){ + if let SENTRY_ENABLED = Bundle.main.infoDictionary?["SENTRY_ENABLED"] as? String, + let SENTRY_DSN = Bundle.main.infoDictionary?["SENTRY_DSN_IOS"] as? String { + if(SENTRY_ENABLED=="true"){ + SentrySDK.start { options in + options.dsn = SENTRY_DSN + options.enableAppHangTracking = true + options.enableCaptureFailedRequests = true + } + } + } +} + +func testSentry(msg: String){ + SentrySDK.capture(message: msg) +} diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 02f0ef2c02..11feef5184 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -11,6 +11,10 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 27C667A329523ECA00E590D5 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 27C667A229523ECA00E590D5 /* Sentry */; }; + 27C667A529523F0A00E590D5 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 27C667A429523F0A00E590D5 /* Sentry */; }; + 27C667A9295241B600E590D5 /* Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C667A8295241B600E590D5 /* Sentry.swift */; }; + 27C667AA295241B600E590D5 /* Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C667A8295241B600E590D5 /* Sentry.swift */; }; 49AE370126D4455D00EF4E52 /* Gekidou in Frameworks */ = {isa = PBXBuildFile; productRef = 49AE370026D4455D00EF4E52 /* Gekidou */; }; 536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */; }; 58495E36BF1A4EAB93609E57 /* Metropolis-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 54956DEFEBB74EF78C3A6AE5 /* Metropolis-SemiBold.ttf */; }; @@ -156,6 +160,7 @@ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = ""; }; 182D203F539AF68F1647EFAF /* Pods-Mattermost-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-Mattermost-MattermostTests/Pods-Mattermost-MattermostTests.release.xcconfig"; sourceTree = ""; }; 25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = ""; }; + 27C667A8295241B600E590D5 /* Sentry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sentry.swift; sourceTree = ""; }; 297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = ""; }; 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = ""; }; 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBold.ttf"; path = "../assets/fonts/OpenSans-ExtraBold.ttf"; sourceTree = ""; }; @@ -279,6 +284,7 @@ buildActionMask = 2147483647; files = ( 49AE370126D4455D00EF4E52 /* Gekidou in Frameworks */, + 27C667A329523ECA00E590D5 /* Sentry in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -288,6 +294,7 @@ files = ( 7FD4822C2864D73300A5B18B /* OpenGraph in Frameworks */, 7F4288042865D340006B48E1 /* Gekidou in Frameworks */, + 27C667A529523F0A00E590D5 /* Sentry in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -352,6 +359,14 @@ name = Mattermost; sourceTree = ""; }; + 27C667AB2952425700E590D5 /* ErrorReporting */ = { + isa = PBXGroup; + children = ( + 27C667A8295241B600E590D5 /* Sentry.swift */, + ); + path = ErrorReporting; + sourceTree = ""; + }; 33E107B4DC21A5C48B09F800 /* Pods */ = { isa = PBXGroup; children = ( @@ -625,6 +640,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 27C667AB2952425700E590D5 /* ErrorReporting */, 13B07FAE1A68108700A75B9A /* Mattermost */, 7F581D33221ED5C60099E66B /* NotificationService */, 7F292A701E8AB73400A450A3 /* SplashScreenResource */, @@ -698,6 +714,7 @@ name = NotificationService; packageProductDependencies = ( 49AE370026D4455D00EF4E52 /* Gekidou */, + 27C667A229523ECA00E590D5 /* Sentry */, ); productName = NotificationService; productReference = 7F581D32221ED5C60099E66B /* NotificationService.appex */; @@ -719,6 +736,7 @@ packageProductDependencies = ( 7FD4822B2864D73300A5B18B /* OpenGraph */, 7F4288032865D340006B48E1 /* Gekidou */, + 27C667A429523F0A00E590D5 /* Sentry */, ); productName = MattermostShare; productReference = 7FC5698628563FDB000B0905 /* MattermostShare.appex */; @@ -782,7 +800,8 @@ ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( - 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */, + 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */, + 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; @@ -1005,6 +1024,7 @@ buildActionMask = 2147483647; files = ( 7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */, + 27C667A9295241B600E590D5 /* Sentry.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1021,6 +1041,7 @@ 7F93AAB8287778090047B89F /* Publishers.swift in Sources */, 7F7E9F732864E8060064BFAF /* CompassIcons.swift in Sources */, 7F93AABA28777A390047B89F /* Notification.swift in Sources */, + 27C667AA295241B600E590D5 /* Sentry.swift in Sources */, 7FA9A9902868BD8800AB35A1 /* LocalFileManager.swift in Sources */, 7F93AA9E2875FD310047B89F /* CancelButton.swift in Sources */, 7F42880A286672F6006B48E1 /* ServerService.swift in Sources */, @@ -1502,7 +1523,15 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */ = { + 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/getsentry/sentry-cocoa.git"; + requirement = { + branch = 8.0.0; + kind = branch; + }; + }; + 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/satoshi-takano/OpenGraph.git"; requirement = { @@ -1513,6 +1542,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 27C667A229523ECA00E590D5 /* Sentry */ = { + isa = XCSwiftPackageProductDependency; + package = 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = Sentry; + }; + 27C667A429523F0A00E590D5 /* Sentry */ = { + isa = XCSwiftPackageProductDependency; + package = 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = Sentry; + }; 49AE370026D4455D00EF4E52 /* Gekidou */ = { isa = XCSwiftPackageProductDependency; productName = Gekidou; @@ -1527,7 +1566,7 @@ }; 7FD4822B2864D73300A5B18B /* OpenGraph */ = { isa = XCSwiftPackageProductDependency; - package = 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */; + package = 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */; productName = OpenGraph; }; /* End XCSwiftPackageProductDependency section */ diff --git a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved index 72a8937d2e..edc1406c88 100644 --- a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,15 @@ "version": "1.4.1" } }, + { + "package": "Sentry", + "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", + "state": { + "branch": "8.0.0", + "revision": "1a18683901844a2970ccfb633e4ebae565361817", + "version": null + } + }, { "package": "SQLite.swift", "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index d738e7e41d..814d39d4c0 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -50,8 +50,8 @@ NSExtensionActivationRule - NSExtensionActivationDictionaryVersion - 2 + NSExtensionActivationDictionaryVersion + 2 NSExtensionActivationSupportsAttachmentsWithMaxCount 10 NSExtensionActivationSupportsFileWithMaxCount @@ -73,5 +73,9 @@ NSExtensionPointIdentifier com.apple.share-services + SENTRY_DSN_IOS + $(SENTRY_DSN_IOS) + SENTRY_ENABLED + $(SENTRY_ENABLED) diff --git a/ios/MattermostShare/ShareViewController.swift b/ios/MattermostShare/ShareViewController.swift index f7e0cc8d58..e70489dd1a 100644 --- a/ios/MattermostShare/ShareViewController.swift +++ b/ios/MattermostShare/ShareViewController.swift @@ -10,6 +10,7 @@ import Gekidou import SwiftUI import UIKit import os.log +import Sentry class ShareViewController: UIViewController { private var fileManager: LocalFileManager? @@ -20,7 +21,6 @@ class ShareViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.isModalInPresentation = true - self.addObservers() fileManager = LocalFileManager() if let inputItems = extensionContext?.inputItems { @@ -34,6 +34,9 @@ class ShareViewController: UIViewController { ) }) } + + // Initialize Sentry + initSentryAppExt() } override func viewDidAppear(_ animated: Bool) { @@ -94,6 +97,8 @@ class ShareViewController: UIViewController { let fileCount = attachments.count let files: [String] = attachments.map{ $0.fileUrl.absoluteString } + + var message = text if linkPreviewUrl != nil && !linkPreviewUrl!.isEmpty { if text.isEmpty { diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 09ec58b325..9e2224e0aa 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -29,5 +29,9 @@ NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).NotificationService + SENTRY_DSN_IOS + $(SENTRY_DSN_IOS) + SENTRY_ENABLED + $(SENTRY_ENABLED) diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 88d4078316..9f0810ffac 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -9,6 +9,11 @@ class NotificationService: UNNotificationServiceExtension { var retryIndex = 0 + override init() { + super.init() + initSentryAppExt() + } + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler @@ -24,7 +29,6 @@ class NotificationService: UNNotificationServiceExtension { func processResponse(serverUrl: String, data: Data, bestAttemptContent: UNMutableNotificationContent, contentHandler: ((UNNotificationContent) -> Void)?) { bestAttemptContent.userInfo["server_url"] = serverUrl - let json = try? JSONSerialization.jsonObject(with: data) as! [String: Any] if let json = json { if let message = json["message"] as? String { From f32b2dfeb230a638b86965518f54314d82fd6063 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 21 Dec 2022 19:35:34 +0200 Subject: [PATCH 12/14] Perform search when hashtag is pressed (#6893) * Perform search when hashtag is pressed * PM feedback review --- app/components/markdown/hashtag/index.tsx | 10 ++++++++-- app/screens/home/search/search.tsx | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/components/markdown/hashtag/index.tsx b/app/components/markdown/hashtag/index.tsx index 2090e80737..17b041c54c 100644 --- a/app/components/markdown/hashtag/index.tsx +++ b/app/components/markdown/hashtag/index.tsx @@ -2,8 +2,9 @@ // See LICENSE.txt for license information. import React from 'react'; -import {StyleProp, Text, TextStyle} from 'react-native'; +import {DeviceEventEmitter, StyleProp, Text, TextStyle} from 'react-native'; +import {Navigation, Screens} from '@constants'; import {popToRoot, dismissAllModals} from '@screens/navigation'; type HashtagProps = { @@ -17,7 +18,12 @@ const Hashtag = ({hashtag, linkStyle}: HashtagProps) => { await dismissAllModals(); await popToRoot(); - // showSearchModal('#' + hashtag); + DeviceEventEmitter.emit(Navigation.NAVIGATE_TO_TAB, { + screen: Screens.SEARCH, + params: { + searchTerm: hashtag, + }, + }); }; return ( diff --git a/app/screens/home/search/search.tsx b/app/screens/home/search/search.tsx index f10dd256b5..c9aadd2310 100644 --- a/app/screens/home/search/search.tsx +++ b/app/screens/home/search/search.tsx @@ -109,6 +109,14 @@ const SearchScreen = ({teamId, teams}: Props) => { setSearchTeamId(teamId); }, [teamId]); + useEffect(() => { + if (searchTerm) { + resetToInitial(); + setSearchValue(searchTerm); + handleSearch(searchTeamId, searchTerm); + } + }, [searchTerm]); + const onSnap = (offset: number, animated = true) => { scrollRef.current?.scrollToOffset({offset, animated}); }; From bcb8ffa2d17589f940b209e3e9580aaa1778d437 Mon Sep 17 00:00:00 2001 From: Mattermod Date: Wed, 21 Dec 2022 19:37:15 +0200 Subject: [PATCH 13/14] Update Licences at Notice.txt to reflect dependency changes. (#6825) --- NOTICE.txt | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/NOTICE.txt b/NOTICE.txt index 336ae5e2a0..8453b77d56 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -200,6 +200,41 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- + +## @mattermost/react-native-turbo-mailer + +This product contains '@mattermost/react-native-turbo-mailer' by Avinash Lingaloo. + +An adaptation of react-native-mail that supports Turbo Module + +* HOMEPAGE: + * https://github.com/mattermost/react-native-turbo-mailer#readme + +* LICENSE: MIT + +MIT License + +Copyright (c) 2022 Mattermost +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + --- ## @msgpack/msgpack @@ -493,6 +528,21 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--- + +## @react-navigation/stack + +This product contains '@react-navigation/stack'. + +Stack navigator component for iOS and Android with animated transitions and gestures + +* HOMEPAGE: + * https://reactnavigation.org/docs/stack-navigator/ + +* LICENSE: MIT + + + --- ## @rudderstack/rudder-sdk-react-native @@ -1109,6 +1159,40 @@ Lightweight fuzzy-search limitations under the License. +--- + +## html-entities + +This product contains 'html-entities' by Marat Dulin. + +Fastest HTML entities encode/decode library. + +* HOMEPAGE: + * https://github.com/mdevils/html-entities#readme + +* LICENSE: MIT + +Copyright (c) 2021 Dulin Marat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + --- ## jail-monkey From 9da7b47827044b1ccef9a9bb31c452fd9745ecc6 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 21 Dec 2022 20:42:45 +0200 Subject: [PATCH 14/14] Show the current channel when category is collapsed on tablets (#6888) --- app/queries/servers/system.ts | 8 +++++++- .../channel_list/categories_list/categories/body/index.ts | 2 -- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts index 2780a9e276..5306d7e1f2 100644 --- a/app/queries/servers/system.ts +++ b/app/queries/servers/system.ts @@ -404,7 +404,13 @@ export async function setCurrentTeamAndChannelId(operator: ServerDataOperator, t export const observeLastUnreadChannelId = (database: Database): Observable => { return querySystemValue(database, SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID).observe().pipe( switchMap((result) => (result.length ? result[0].observe() : of$({value: ''}))), - switchMap((model) => of$(model.value)), + switchMap((model) => { + if (model.value) { + return of$(model.value); + } + + return observeCurrentChannelId(database); + }), ); }; diff --git a/app/screens/home/channel_list/categories_list/categories/body/index.ts b/app/screens/home/channel_list/categories_list/categories/body/index.ts index 4ec32b9a56..9182d24bb9 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/body/index.ts @@ -162,8 +162,6 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, return { limit, sortedChannels, - notifyProps, - lastUnreadId, unreadsOnTop, unreadIds, category,