From 63b313fc686692b14d3507651a8195ad60ee0fa2 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Sat, 14 Sep 2024 17:00:36 +0530 Subject: [PATCH 001/111] refactor: started with draft, done until new tabs for draft --- .../drafts_buttton/drafts_button.tsx | 135 ++++++++++++++++++ app/components/drafts_buttton/index.tsx | 29 ++++ app/queries/servers/drafts.ts | 7 +- .../channel_list/categories_list/index.tsx | 4 + 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 app/components/drafts_buttton/drafts_button.tsx create mode 100644 app/components/drafts_buttton/index.tsx diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx new file mode 100644 index 00000000000..8201a70a811 --- /dev/null +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -0,0 +1,135 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useMemo} from 'react'; +import {TouchableOpacity, View} from 'react-native'; + +import {HOME_PADDING} from '@app/constants/view'; +import {useTheme} from '@app/context/theme'; +import {useIsTablet} from '@app/hooks/device'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import Badge from '@components/badge'; +import { + getStyleSheet as getChannelItemStyleSheet, + ROW_HEIGHT, +} from '@components/channel_item/channel_item'; +import FormattedText from '@components/formatted_text'; + +import CompassIcon from '../compass_icon'; + +// See LICENSE.txt for license information. +type DraftListProps = { + currentChannelId: string; + shouldHighlighActive?: boolean; + files: FileInfo[][]; + messages: string[]; +}; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + icon: { + color: changeOpacity(theme.sidebarText, 0.5), + fontSize: 24, + marginRight: 12, + }, + iconActive: { + color: theme.sidebarText, + }, + iconInfo: { + color: changeOpacity(theme.centerChannelColor, 0.72), + }, + text: { + flex: 1, + }, +})); + +const DraftsButton: React.FC = ({ + currentChannelId, + shouldHighlighActive = false, + files, + messages, +}) => { + const theme = useTheme(); + const styles = getChannelItemStyleSheet(theme); + const customStyles = getStyleSheet(theme); + const isTablet = useIsTablet(); + + const handlePress = () => { + return null; + }; + const isActive = isTablet && shouldHighlighActive && !currentChannelId; + + const [containerStyle, iconStyle, textStyle, badgeStyle] = useMemo(() => { + const container = [ + styles.container, + HOME_PADDING, + isActive && styles.activeItem, + isActive && { + paddingLeft: HOME_PADDING.paddingLeft - styles.activeItem.borderLeftWidth, + }, + {minHeight: ROW_HEIGHT}, + ]; + + const icon = [ + customStyles.icon, + isActive && customStyles.iconActive, + ]; + + const text = [ + customStyles.text, + styles.text, + isActive && styles.textActive, + ]; + + const badge = [ + styles.badge, + ]; + + return [container, icon, text, badge]; + }, [customStyles, isActive, styles]); + + if (!files.length && !messages.length) { + return null; + } + + return ( + + + + + 0} + /> + + + ); + + // const renderItem = ({item, index}: {item: string; index: number}) => ( + // + // {'Draft'} {index + 1} {':'} {item} + // {'Attached files:'} {files[index]?.length || 0} + // + // ); + + // return ( + // `draft-${index}`} + // renderItem={renderItem} + // ListEmptyComponent={{'No drafts available.'}} + // /> + // ); +}; + +export default DraftsButton; diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx new file mode 100644 index 00000000000..0aa8d8c55d9 --- /dev/null +++ b/app/components/drafts_buttton/index.tsx @@ -0,0 +1,29 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +/* eslint-disable max-nested-callbacks */ + +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {switchMap} from '@nozbe/watermelondb/utils/rx'; +import React from 'react'; +import {of as of$} from 'rxjs'; + +import {observeAllDrafts} from '@app/queries/servers/drafts'; +import {observeCurrentChannelId} from '@app/queries/servers/system'; + +import DraftsButton from './drafts_button'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + const allDrafts = observeAllDrafts(database).observeWithColumns(['message', 'files', 'metadata']); + const files = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.files)))); + const messages = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.message)))); + + return { + currentChannelId: observeCurrentChannelId(database), + files, + messages, + }; +}); + +export default React.memo(withDatabase(enhanced(DraftsButton))); diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index 4822385e7da..bd08c5c92fc 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -5,8 +5,7 @@ import {Database, Q} from '@nozbe/watermelondb'; import {of as of$} from 'rxjs'; import {MM_TABLES} from '@constants/database'; - -import type DraftModel from '@typings/database/models/servers/draft'; +import DraftModel from '@typings/database/models/servers/draft'; const {SERVER: {DRAFT}} = MM_TABLES; @@ -30,3 +29,7 @@ export const queryDraft = (database: Database, channelId: string, rootId = '') = export function observeFirstDraft(v: DraftModel[]) { return v[0]?.observe() || of$(undefined); } + +export const observeAllDrafts = (database: Database) => { + return database.collections.get(DRAFT).query(); +}; diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index d61417580d5..f525ab32d32 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -5,6 +5,7 @@ import React, {useEffect, useMemo} from 'react'; import {useWindowDimensions} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import DraftsButtton from '@app/components/drafts_buttton'; import ThreadsButton from '@components/threads_button'; import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; import {useTheme} from '@context/theme'; @@ -72,6 +73,9 @@ const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: C shouldHighlighActive={true} /> } + ); From 11e758ed22122470a1887064ab538636e285dba6 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Sep 2024 18:24:50 +0530 Subject: [PATCH 002/111] refactor: change the query and added the screen for draft --- .../drafts_buttton/drafts_button.tsx | 13 ++++----- app/components/drafts_buttton/index.tsx | 21 +++++++++----- app/constants/screens.ts | 2 ++ app/queries/servers/drafts.ts | 29 ++++++++++++++++--- app/screens/global_drafts/index.tsx | 3 ++ 5 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 app/screens/global_drafts/index.tsx diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 8201a70a811..58bc78bb8fc 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -21,8 +21,7 @@ import CompassIcon from '../compass_icon'; type DraftListProps = { currentChannelId: string; shouldHighlighActive?: boolean; - files: FileInfo[][]; - messages: string[]; + draftsCount: number; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -45,8 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const DraftsButton: React.FC = ({ currentChannelId, shouldHighlighActive = false, - files, - messages, + draftsCount, }) => { const theme = useTheme(); const styles = getChannelItemStyleSheet(theme); @@ -56,6 +54,7 @@ const DraftsButton: React.FC = ({ const handlePress = () => { return null; }; + const isActive = isTablet && shouldHighlighActive && !currentChannelId; const [containerStyle, iconStyle, textStyle, badgeStyle] = useMemo(() => { @@ -87,7 +86,7 @@ const DraftsButton: React.FC = ({ return [container, icon, text, badge]; }, [customStyles, isActive, styles]); - if (!files.length && !messages.length) { + if (!draftsCount) { return null; } @@ -107,9 +106,9 @@ const DraftsButton: React.FC = ({ style={textStyle} /> 0} + visible={draftsCount > 0} /> diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index 0aa8d8c55d9..ace29cfdc2f 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -5,24 +5,29 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {switchMap} from '@nozbe/watermelondb/utils/rx'; import React from 'react'; -import {of as of$} from 'rxjs'; -import {observeAllDrafts} from '@app/queries/servers/drafts'; -import {observeCurrentChannelId} from '@app/queries/servers/system'; +import {observeDraftCount} from '@app/queries/servers/drafts'; +import {observeCurrentChannelId, observeCurrentTeamId} from '@app/queries/servers/system'; import DraftsButton from './drafts_button'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const allDrafts = observeAllDrafts(database).observeWithColumns(['message', 'files', 'metadata']); - const files = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.files)))); - const messages = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.message)))); + const currentTeamId = observeCurrentTeamId(database); + const draftsCount = currentTeamId.pipe( + switchMap((teamId) => observeDraftCount(database, teamId)), // Observe the draft count + ); + + // const allDrafts = currentTeamId.pipe( + // switchMap((teamId) => observeAllDrafts(database, teamId).observeWithColumns(['message', 'files', 'metadata'])), + // ); + // const files = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.files)))); + // const messages = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.message)))); return { currentChannelId: observeCurrentChannelId(database), - files, - messages, + draftsCount, }; }); diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 8b4496c958a..935bdd51ca2 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -28,6 +28,7 @@ export const EMOJI_PICKER = 'EmojiPicker'; export const FIND_CHANNELS = 'FindChannels'; export const FORGOT_PASSWORD = 'ForgotPassword'; export const GALLERY = 'Gallery'; +export const GLOBAL_DRAFTS = 'GlobalDrafts'; export const GLOBAL_THREADS = 'GlobalThreads'; export const HOME = 'Home'; export const INTEGRATION_SELECTOR = 'IntegrationSelector'; @@ -107,6 +108,7 @@ export default { FIND_CHANNELS, FORGOT_PASSWORD, GALLERY, + GLOBAL_DRAFTS, GLOBAL_THREADS, HOME, INTEGRATION_SELECTOR, diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index bd08c5c92fc..8da34f4509c 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -2,12 +2,12 @@ // See LICENSE.txt for license information. import {Database, Q} from '@nozbe/watermelondb'; -import {of as of$} from 'rxjs'; +import {of as of$, map} from 'rxjs'; import {MM_TABLES} from '@constants/database'; import DraftModel from '@typings/database/models/servers/draft'; -const {SERVER: {DRAFT}} = MM_TABLES; +const {SERVER: {DRAFT, CHANNEL}} = MM_TABLES; export const getDraft = async (database: Database, channelId: string, rootId = '') => { const record = await queryDraft(database, channelId, rootId).fetch(); @@ -30,6 +30,27 @@ export function observeFirstDraft(v: DraftModel[]) { return v[0]?.observe() || of$(undefined); } -export const observeAllDrafts = (database: Database) => { - return database.collections.get(DRAFT).query(); +export const observeAllDrafts = (database: Database, teamId: string) => { + return database.collections.get(DRAFT).query( + Q.on(CHANNEL, + Q.or( + Q.where('team_id', teamId), + Q.where('type', 'D'), + ), + ), + ); +}; + +export const observeDraftCount = (database: Database, teamId: string) => { + return database.collections.get(DRAFT).query( + Q.on(CHANNEL, + Q.or( + Q.where('team_id', teamId), // Channels associated with the given team + Q.where('type', 'D'), // Channels of type 'D' + ), + ), + ).observe(). // Observe the query results + pipe( + map((drafts) => drafts.length), // Map the array of drafts to their count + ); }; diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx new file mode 100644 index 00000000000..513fb0d2d3a --- /dev/null +++ b/app/screens/global_drafts/index.tsx @@ -0,0 +1,3 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + From d72a19fa8ae7f2e065e314dfc45b761d4a618169 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 17 Sep 2024 22:11:41 +0530 Subject: [PATCH 003/111] added condition for fetching draft for channel delete or not --- app/queries/servers/drafts.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index 8da34f4509c..f3ec25d5ee0 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -44,9 +44,12 @@ export const observeAllDrafts = (database: Database, teamId: string) => { export const observeDraftCount = (database: Database, teamId: string) => { return database.collections.get(DRAFT).query( Q.on(CHANNEL, - Q.or( - Q.where('team_id', teamId), // Channels associated with the given team - Q.where('type', 'D'), // Channels of type 'D' + Q.and( + Q.or( + Q.where('team_id', teamId), // Channels associated with the given team + Q.where('type', 'D'), // Channels of type 'D' + ), + Q.where('delete_at', 0), // Ensure the channel is not deleted ), ), ).observe(). // Observe the query results From 572d65b3d9a3406074849af96240a28e67054368 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 20 Sep 2024 15:03:20 +0530 Subject: [PATCH 004/111] refactor: added draft screen --- app/actions/local/draft.ts | 14 +++ .../drafts_buttton/drafts_button.tsx | 10 ++- app/screens/global_drafts/index.tsx | 89 +++++++++++++++++++ app/screens/global_threads/global_threads.tsx | 2 +- .../additional_tablet_view.tsx | 2 + app/screens/index.tsx | 3 + assets/base/i18n/en.json | 1 + 7 files changed, 116 insertions(+), 5 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index 4deaff9843a..0992da7f8aa 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -1,10 +1,24 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {DeviceEventEmitter} from 'react-native'; + +import {Navigation, Screens} from '@app/constants'; +import {goToScreen} from '@app/screens/navigation'; +import {isTablet} from '@app/utils/helpers'; import DatabaseManager from '@database/manager'; import {getDraft} from '@queries/servers/drafts'; import {logError} from '@utils/log'; +export const switchToGlobalDrafts = async () => { + const isTablelDevice = isTablet(); + if (isTablelDevice) { + DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_DRAFTS); + } else { + goToScreen(Screens.GLOBAL_DRAFTS, '', {}, {topBar: {visible: false}}); + } +}; + export async function updateDraftFile(serverUrl: string, channelId: string, rootId: string, file: FileInfo, prepareRecordsOnly = false) { try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 58bc78bb8fc..96f57beb03e 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -1,12 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {TouchableOpacity, View} from 'react-native'; +import {switchToGlobalDrafts} from '@actions/local/draft'; import {HOME_PADDING} from '@app/constants/view'; import {useTheme} from '@app/context/theme'; import {useIsTablet} from '@app/hooks/device'; +import {preventDoubleTap} from '@app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; import Badge from '@components/badge'; import { @@ -51,9 +53,9 @@ const DraftsButton: React.FC = ({ const customStyles = getStyleSheet(theme); const isTablet = useIsTablet(); - const handlePress = () => { - return null; - }; + const handlePress = useCallback(preventDoubleTap(() => { + switchToGlobalDrafts(); + }), []); const isActive = isTablet && shouldHighlighActive && !currentChannelId; diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index 513fb0d2d3a..69480abe450 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -1,3 +1,92 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import React, {useCallback, useMemo} from 'react'; +import {useIntl} from 'react-intl'; +import {Keyboard, StyleSheet, Text, View} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; + +import NavigationHeader from '@app/components/navigation_header'; +import OtherMentionsBadge from '@app/components/other_mentions_badge'; +import RoundedHeaderContext from '@app/components/rounded_header_context'; +import {Screens} from '@app/constants'; +import {useIsTablet} from '@app/hooks/device'; +import {useDefaultHeaderHeight} from '@app/hooks/header'; +import {useTeamSwitch} from '@app/hooks/team_switch'; +import {edges} from '@app/screens/global_threads/global_threads'; + +import {popTopScreen} from '../navigation'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +type Props = { + componentId?: AvailableScreens; +}; + +const styles = StyleSheet.create({ + flex: { + flex: 1, + }, +}); + +const GlobalDrafts = ({componentId}: Props) => { + const intl = useIntl(); + const switchingTeam = useTeamSwitch(); + const isTablet = useIsTablet(); + + const defaultHeight = useDefaultHeaderHeight(); + + const headerLeftComponent = useMemo(() => { + if (isTablet) { + return undefined; + } + + return (); + }, [isTablet]); + + const contextStyle = useMemo(() => ({ + top: defaultHeight, + }), [defaultHeight]); + + const containerStyle = useMemo(() => { + const marginTop = defaultHeight; + return {flex: 1, marginTop}; + }, [defaultHeight]); + + const onBackPress = useCallback(() => { + Keyboard.dismiss(); + popTopScreen(componentId); + }, [componentId]); + + return ( + + + + + + {!switchingTeam && + + {'Global Drafts'} + + } + + ); +}; + +export default GlobalDrafts; diff --git a/app/screens/global_threads/global_threads.tsx b/app/screens/global_threads/global_threads.tsx index 3102a6065c9..f0ed5619cb2 100644 --- a/app/screens/global_threads/global_threads.tsx +++ b/app/screens/global_threads/global_threads.tsx @@ -27,7 +27,7 @@ type Props = { globalThreadsTab: GlobalThreadsTab; }; -const edges: Edge[] = ['left', 'right']; +export const edges: Edge[] = ['left', 'right']; const styles = StyleSheet.create({ flex: { diff --git a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx index 95e4cc9a5cd..9146115c72d 100644 --- a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx +++ b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx @@ -4,6 +4,7 @@ import React, {useEffect, useState} from 'react'; import {DeviceEventEmitter} from 'react-native'; +import GlobalDrafts from '@app/screens/global_drafts'; import {Navigation, Screens} from '@constants'; import Channel from '@screens/channel'; import GlobalThreads from '@screens/global_threads'; @@ -22,6 +23,7 @@ type Props = { const ComponentsList: Record> = { [Screens.CHANNEL]: Channel, [Screens.GLOBAL_THREADS]: GlobalThreads, + [Screens.GLOBAL_DRAFTS]: GlobalDrafts, }; const channelScreen: SelectedView = {id: Screens.CHANNEL, Component: Channel}; diff --git a/app/screens/index.tsx b/app/screens/index.tsx index 30e9b206825..c581aec3d62 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -135,6 +135,9 @@ Navigation.setLazyComponentRegistrator((screenName) => { case Screens.GENERIC_OVERLAY: screen = withServerDatabase(require('@screens/overlay').default); break; + case Screens.GLOBAL_DRAFTS: + screen = withServerDatabase(require('@screens/global_drafts').default); + break; case Screens.GLOBAL_THREADS: screen = withServerDatabase(require('@screens/global_threads').default); break; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 76f2e399ed9..aacefeab3f1 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -304,6 +304,7 @@ "display_settings.timezone": "Timezone", "display_settings.tz.auto": "Auto", "display_settings.tz.manual": "Manual", + "drafts": "Drafts", "download.error": "Unable to download the file. Try again later", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", From d5389b811088fd4ddcf2b5a1eed8a464c0e5d4c7 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Sun, 22 Sep 2024 18:31:53 +0530 Subject: [PATCH 005/111] linter fixes --- app/components/drafts_buttton/index.tsx | 21 ++++++------- app/queries/servers/drafts.ts | 29 +++++++---------- .../global_drafts_list/global_drafts_list.tsx | 20 ++++++++++++ .../components/global_drafts_list/index.tsx | 31 +++++++++++++++++++ app/screens/global_drafts/index.tsx | 6 ++-- 5 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx create mode 100644 app/screens/global_drafts/components/global_drafts_list/index.tsx diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index ace29cfdc2f..0363c8f827c 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -3,7 +3,6 @@ /* eslint-disable max-nested-callbacks */ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; -import {switchMap} from '@nozbe/watermelondb/utils/rx'; import React from 'react'; import {observeDraftCount} from '@app/queries/servers/drafts'; @@ -13,22 +12,20 @@ import DraftsButton from './drafts_button'; import type {WithDatabaseArgs} from '@typings/database/database'; -const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const currentTeamId = observeCurrentTeamId(database); - const draftsCount = currentTeamId.pipe( - switchMap((teamId) => observeDraftCount(database, teamId)), // Observe the draft count - ); +type Props = { + teamId: string; +} & WithDatabaseArgs; - // const allDrafts = currentTeamId.pipe( - // switchMap((teamId) => observeAllDrafts(database, teamId).observeWithColumns(['message', 'files', 'metadata'])), - // ); - // const files = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.files)))); - // const messages = allDrafts.pipe(switchMap((drafts) => of$(drafts.map((d) => d.message)))); +const withTeamId = withObservables([], ({database}: WithDatabaseArgs) => ({ + teamId: observeCurrentTeamId(database), +})); +const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { + const draftsCount = observeDraftCount(database, teamId); // Observe the draft count return { currentChannelId: observeCurrentChannelId(database), draftsCount, }; }); -export default React.memo(withDatabase(enhanced(DraftsButton))); +export default React.memo(withDatabase(withTeamId(enhanced(DraftsButton)))); diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index f3ec25d5ee0..bc410e50b0a 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Database, Q} from '@nozbe/watermelondb'; -import {of as of$, map} from 'rxjs'; +import {of as of$} from 'rxjs'; import {MM_TABLES} from '@constants/database'; import DraftModel from '@typings/database/models/servers/draft'; @@ -30,30 +30,25 @@ export function observeFirstDraft(v: DraftModel[]) { return v[0]?.observe() || of$(undefined); } -export const observeAllDrafts = (database: Database, teamId: string) => { - return database.collections.get(DRAFT).query( - Q.on(CHANNEL, - Q.or( - Q.where('team_id', teamId), - Q.where('type', 'D'), - ), - ), - ); -}; - -export const observeDraftCount = (database: Database, teamId: string) => { +export const queryAllDrafts = (database: Database, teamId: string) => { return database.collections.get(DRAFT).query( Q.on(CHANNEL, Q.and( Q.or( Q.where('team_id', teamId), // Channels associated with the given team Q.where('type', 'D'), // Channels of type 'D' + Q.where('type', 'G'), // Channels of type 'D' ), Q.where('delete_at', 0), // Ensure the channel is not deleted ), ), - ).observe(). // Observe the query results - pipe( - map((drafts) => drafts.length), // Map the array of drafts to their count - ); + ); +}; + +export const observeAllDrafts = (database: Database, teamId: string) => { + return queryAllDrafts(database, teamId).observeWithColumns(['messages', 'files', 'metadata']); +}; + +export const observeDraftCount = (database: Database, teamId: string) => { + return queryAllDrafts(database, teamId).observeCount(); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx new file mode 100644 index 00000000000..50d7dd90855 --- /dev/null +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {View, Text} from 'react-native'; + +import type DraftModel from '@typings/database/models/servers/draft'; + +type Props = { + allDrafts: DraftModel[]; +} + +const GlobalDraftsList: React.FC = () => { + return ( + + {'Global Draft List'} + ); +}; + +export default GlobalDraftsList; diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx new file mode 100644 index 00000000000..eae43d0c582 --- /dev/null +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +/* eslint-disable max-nested-callbacks */ + +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import React from 'react'; + +import {observeAllDrafts} from '@app/queries/servers/drafts'; +import {observeCurrentTeamId} from '@app/queries/servers/system'; + +import GlobalDraftsList from './global_drafts_list'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const withTeamId = withObservables([], ({database}: WithDatabaseArgs) => ({ + teamId: observeCurrentTeamId(database), +})); + +type Props = { + teamId: string; +} & WithDatabaseArgs; + +const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { + const allDrafts = observeAllDrafts(database, teamId); + + return { + allDrafts, + }; +}); + +export default React.memo(withDatabase(withTeamId(enhanced(GlobalDraftsList)))); diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index 69480abe450..c84afafe6e0 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useMemo} from 'react'; import {useIntl} from 'react-intl'; -import {Keyboard, StyleSheet, Text, View} from 'react-native'; +import {Keyboard, StyleSheet, View} from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; import NavigationHeader from '@app/components/navigation_header'; @@ -17,6 +17,8 @@ import {edges} from '@app/screens/global_threads/global_threads'; import {popTopScreen} from '../navigation'; +import GlobalDraftsList from './components/global_drafts_list'; + import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { @@ -82,7 +84,7 @@ const GlobalDrafts = ({componentId}: Props) => { {!switchingTeam && - {'Global Drafts'} + } From ae55bde0becb0cb978594da0afb8835529341bee Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 24 Sep 2024 07:57:40 +0530 Subject: [PATCH 006/111] Added draft post component --- app/components/draft_post/draft_post.tsx | 60 +++++++++++++++++++ app/components/draft_post/index.tsx | 22 +++++++ .../global_drafts_list/global_drafts_list.tsx | 16 ++++- 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 app/components/draft_post/draft_post.tsx create mode 100644 app/components/draft_post/index.tsx diff --git a/app/components/draft_post/draft_post.tsx b/app/components/draft_post/draft_post.tsx new file mode 100644 index 00000000000..8057ed76db9 --- /dev/null +++ b/app/components/draft_post/draft_post.tsx @@ -0,0 +1,60 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Text, View} from 'react-native'; + +import ChannelInfo from '@app/components/post_with_channel_info/channel_info'; + +import type ChannelModel from '@typings/database/models/servers/channel'; +import type DraftModel from '@typings/database/models/servers/draft'; + +type Props = { + channel: ChannelModel; + draft: DraftModel; +} + +const DraftPost: React.FC = ({ + channel, + draft, +}) => { + const post: Post = { + id: draft.rootId || '', + channel_id: channel.id, + message: draft.message, + create_at: 0, + update_at: 0, + delete_at: 0, + user_id: '', + root_id: draft.rootId || '', + original_id: '', + type: '', + props: {}, + hashtags: '', + pending_post_id: '', + reply_count: 0, + metadata: draft.metadata ? draft.metadata : {}, + edit_at: 0, + is_pinned: false, + file_ids: [draft.files.map((file) => file.id) || ''], + last_reply_at: 0, + message_source: '', + user_activity_posts: [], + }; + + return ( + + + {/* {channel.displayName} + {channel.type} + {draft.rootId ? 'In Thread' : 'Channel'} + */} + {draft.message} + + ); +}; + +export default DraftPost; diff --git a/app/components/draft_post/index.tsx b/app/components/draft_post/index.tsx new file mode 100644 index 00000000000..9ad51800877 --- /dev/null +++ b/app/components/draft_post/index.tsx @@ -0,0 +1,22 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; + +import {observeChannel} from '@app/queries/servers/channel'; + +import DraftPost from './draft_post'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +type Props = { + channelId: string; +} & WithDatabaseArgs; + +const enhance = withObservables(['channelId'], ({channelId, database}: Props) => { + return { + channel: observeChannel(database, channelId), + }; +}); + +export default withDatabase(enhance(DraftPost)); diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 50d7dd90855..ef538861c9c 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -2,7 +2,9 @@ // See LICENSE.txt for license information. import React from 'react'; -import {View, Text} from 'react-native'; +import {View} from 'react-native'; + +import DraftPost from '@app/components/draft_post/'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -10,10 +12,18 @@ type Props = { allDrafts: DraftModel[]; } -const GlobalDraftsList: React.FC = () => { +const GlobalDraftsList: React.FC = ({allDrafts}) => { return ( - {'Global Draft List'} + {allDrafts.map((draft) => { + return ( + + ); + })} ); }; From afbe59d71c47f4bc9b4fefd987ad1af9bc06be6a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 25 Sep 2024 13:44:18 +0530 Subject: [PATCH 007/111] added avatar and header display name for the draft post list --- app/components/draft_post/draft_post.tsx | 38 ++++++++++++++++--- .../global_drafts_list/global_drafts_list.tsx | 8 +++- .../components/global_drafts_list/index.tsx | 3 ++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/app/components/draft_post/draft_post.tsx b/app/components/draft_post/draft_post.tsx index 8057ed76db9..9f4a6682a27 100644 --- a/app/components/draft_post/draft_post.tsx +++ b/app/components/draft_post/draft_post.tsx @@ -4,20 +4,31 @@ import React from 'react'; import {Text, View} from 'react-native'; -import ChannelInfo from '@app/components/post_with_channel_info/channel_info'; +import {Screens} from '@app/constants'; +import {useTheme} from '@app/context/theme'; +import {getUserCustomStatus} from '@app/utils/user'; + +import Avatar from '../post_list/post/avatar/avatar'; +import HeaderDisplayName from '../post_list/post/header/display_name'; import type ChannelModel from '@typings/database/models/servers/channel'; import type DraftModel from '@typings/database/models/servers/draft'; +import type PostModel from '@typings/database/models/servers/post'; +import type UserModel from '@typings/database/models/servers/user'; type Props = { channel: ChannelModel; draft: DraftModel; + currentUser: UserModel; } const DraftPost: React.FC = ({ channel, draft, + currentUser, }) => { + const theme = useTheme(); + const post: Post = { id: draft.rootId || '', channel_id: channel.id, @@ -25,7 +36,7 @@ const DraftPost: React.FC = ({ create_at: 0, update_at: 0, delete_at: 0, - user_id: '', + user_id: currentUser.id, root_id: draft.rootId || '', original_id: '', type: '', @@ -42,16 +53,31 @@ const DraftPost: React.FC = ({ user_activity_posts: [], }; + const customStatus = getUserCustomStatus(currentUser); + return ( - {/* {channel.displayName} {channel.type} {draft.rootId ? 'In Thread' : 'Channel'} */} + + {draft.message} ); diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index ef538861c9c..4fb00e01810 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -7,12 +7,17 @@ import {View} from 'react-native'; import DraftPost from '@app/components/draft_post/'; import type DraftModel from '@typings/database/models/servers/draft'; +import type UserModel from '@typings/database/models/servers/user'; type Props = { allDrafts: DraftModel[]; + currentUser: UserModel; } -const GlobalDraftsList: React.FC = ({allDrafts}) => { +const GlobalDraftsList: React.FC = ({ + allDrafts, + currentUser, +}) => { return ( {allDrafts.map((draft) => { @@ -21,6 +26,7 @@ const GlobalDraftsList: React.FC = ({allDrafts}) => { key={draft.id} channelId={draft.channelId} draft={draft} + currentUser={currentUser} /> ); })} diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx index eae43d0c582..6cdf5f8d607 100644 --- a/app/screens/global_drafts/components/global_drafts_list/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import {observeAllDrafts} from '@app/queries/servers/drafts'; import {observeCurrentTeamId} from '@app/queries/servers/system'; +import {observeCurrentUser} from '@app/queries/servers/user'; import GlobalDraftsList from './global_drafts_list'; @@ -22,9 +23,11 @@ type Props = { const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { const allDrafts = observeAllDrafts(database, teamId); + const currentUser = observeCurrentUser(database); return { allDrafts, + currentUser, }; }); From 6c99a7cd723e76c62db0b32ff3cb3a0913c2e123 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 2 Oct 2024 14:24:50 +0530 Subject: [PATCH 008/111] added channel info component --- app/components/channel_info/avatar/index.tsx | 72 ++++++++++++ app/components/channel_info/index.tsx | 114 +++++++++++++++++++ app/components/draft_post/draft_post.tsx | 95 +++++++--------- app/components/draft_post/index.tsx | 27 ++++- 4 files changed, 252 insertions(+), 56 deletions(-) create mode 100644 app/components/channel_info/avatar/index.tsx create mode 100644 app/components/channel_info/index.tsx diff --git a/app/components/channel_info/avatar/index.tsx b/app/components/channel_info/avatar/index.tsx new file mode 100644 index 00000000000..d50d698bc31 --- /dev/null +++ b/app/components/channel_info/avatar/index.tsx @@ -0,0 +1,72 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Image} from 'expo-image'; +import React from 'react'; +import {StyleSheet, View} from 'react-native'; + +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; +import {useServerUrl} from '@app/context/server'; +import CompassIcon from '@components/compass_icon'; +import {changeOpacity} from '@utils/theme'; + +import type UserModel from '@typings/database/models/servers/user'; + +type Props = { + author: UserModel; +} + +const styles = StyleSheet.create({ + avatarContainer: { + backgroundColor: 'rgba(255, 255, 255, 0.4)', + margin: 2, + width: 20, + height: 20, + }, + avatar: { + height: 20, + width: 20, + }, + avatarRadius: { + borderRadius: 18, + }, +}); + +const Avatar = ({ + author, +}: Props) => { + const serverUrl = useServerUrl(); + + let uri = ''; + if (!uri && author) { + uri = buildProfileImageUrlFromUser(serverUrl, author); + } + + let picture; + if (uri) { + picture = ( + + ); + } else { + picture = ( + + ); + } + + return ( + + {picture} + + ); +}; + +export default Avatar; + diff --git a/app/components/channel_info/index.tsx b/app/components/channel_info/index.tsx new file mode 100644 index 00000000000..dd00ed578df --- /dev/null +++ b/app/components/channel_info/index.tsx @@ -0,0 +1,114 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Text, View} from 'react-native'; + +import {General} from '@app/constants'; +import {useTheme} from '@app/context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import FormattedText from '@components/formatted_text'; + +import CompassIcon from '../compass_icon'; + +import Avatar from './avatar'; + +import type ChannelModel from '@typings/database/models/servers/channel'; +import type PostModel from '@typings/database/models/servers/post'; +import type UserModel from '@typings/database/models/servers/user'; + +type Props = { + channel: ChannelModel; + sendToUser?: UserModel; + updateAt?: string; + rootId?: PostModel['rootId']; + testID?: string; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + infoContainer: { + display: 'flex', + flexDirection: 'row', + gap: 5, + alignItems: 'center', + }, + channelInfo: { + display: 'flex', + flexDirection: 'row', + gap: 5, + alignItems: 'center', + }, + displayName: { + fontSize: 12, + fontWeight: '600', + color: changeOpacity(theme.centerChannelColor, 0.64), + lineHeight: 16, + fontFamily: 'Open Sans', + }, + timeInfo: { + fontSize: 12, + fontWeight: '400', + color: changeOpacity(theme.centerChannelColor, 0.64), + lineHeight: 16, + }, + }; +}); + +const ChannelInfo: React.FC = ({ + channel, + sendToUser, + updateAt = 'Yesterday', + + // rootId, + // testID, +}) => { + const theme = useTheme(); + const style = getStyleSheet(theme); + const isChannelTypeDM = channel.type === General.DM_CHANNEL; + + return ( + + + + {!isChannelTypeDM && + + + + } + {isChannelTypeDM && + + + {sendToUser && + } + } + + {channel.displayName} + + + {updateAt && {updateAt}} + + ); +}; + +export default ChannelInfo; diff --git a/app/components/draft_post/draft_post.tsx b/app/components/draft_post/draft_post.tsx index 9f4a6682a27..7da2ca56149 100644 --- a/app/components/draft_post/draft_post.tsx +++ b/app/components/draft_post/draft_post.tsx @@ -2,81 +2,68 @@ // See LICENSE.txt for license information. import React from 'react'; -import {Text, View} from 'react-native'; +import {StyleSheet, Text, View} from 'react-native'; -import {Screens} from '@app/constants'; -import {useTheme} from '@app/context/theme'; -import {getUserCustomStatus} from '@app/utils/user'; - -import Avatar from '../post_list/post/avatar/avatar'; -import HeaderDisplayName from '../post_list/post/header/display_name'; +import ChannelInfo from '../channel_info'; import type ChannelModel from '@typings/database/models/servers/channel'; import type DraftModel from '@typings/database/models/servers/draft'; -import type PostModel from '@typings/database/models/servers/post'; import type UserModel from '@typings/database/models/servers/user'; type Props = { channel: ChannelModel; + sendToUser?: UserModel; draft: DraftModel; currentUser: UserModel; } +const style = StyleSheet.create({ + container: { + paddingHorizontal: 20, + paddingVertical: 16, + }, +}); + const DraftPost: React.FC = ({ channel, draft, - currentUser, -}) => { - const theme = useTheme(); - const post: Post = { - id: draft.rootId || '', - channel_id: channel.id, - message: draft.message, - create_at: 0, - update_at: 0, - delete_at: 0, - user_id: currentUser.id, - root_id: draft.rootId || '', - original_id: '', - type: '', - props: {}, - hashtags: '', - pending_post_id: '', - reply_count: 0, - metadata: draft.metadata ? draft.metadata : {}, - edit_at: 0, - is_pinned: false, - file_ids: [draft.files.map((file) => file.id) || ''], - last_reply_at: 0, - message_source: '', - user_activity_posts: [], - }; - - const customStatus = getUserCustomStatus(currentUser); + // currentUser, + sendToUser, +}) => { + // const post: Post = { + // id: draft.rootId || '', + // channel_id: channel.id, + // message: draft.message, + // create_at: 0, + // update_at: 0, + // delete_at: 0, + // user_id: currentUser.id, + // root_id: draft.rootId || '', + // original_id: '', + // type: '', + // props: {}, + // hashtags: '', + // pending_post_id: '', + // reply_count: 0, + // metadata: draft.metadata ? draft.metadata : {}, + // edit_at: 0, + // is_pinned: false, + // file_ids: [draft.files.map((file) => file.id) || ''], + // last_reply_at: 0, + // message_source: '', + // user_activity_posts: [], + // }; return ( - + {/* {channel.displayName} {channel.type} {draft.rootId ? 'In Thread' : 'Channel'} - */} - - {draft.message} diff --git a/app/components/draft_post/index.tsx b/app/components/draft_post/index.tsx index 9ad51800877..4acc52261ed 100644 --- a/app/components/draft_post/index.tsx +++ b/app/components/draft_post/index.tsx @@ -2,8 +2,10 @@ // See LICENSE.txt for license information. import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {switchMap, of} from 'rxjs'; -import {observeChannel} from '@app/queries/servers/channel'; +import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; +import {observeUser} from '@app/queries/servers/user'; import DraftPost from './draft_post'; @@ -14,8 +16,29 @@ type Props = { } & WithDatabaseArgs; const enhance = withObservables(['channelId'], ({channelId, database}: Props) => { + const channel = observeChannel(database, channelId); + const sendToUser = channel.pipe( + switchMap((channelData) => { + if (channelData?.type === 'D') { + // Fetch the channel member for direct message channels + return observeChannelMembers(database, channelId).pipe( + // eslint-disable-next-line max-nested-callbacks + switchMap((members) => { + if (members.length > 0) { + const userId = members[0]?.userId; + return observeUser(database, userId); + } + return of(undefined); + }), + ); + } + return of(undefined); + }), + ); + return { - channel: observeChannel(database, channelId), + channel, + sendToUser, }; }); From 9fcbf84d39c85c282fad8b623dca24ee53d03da4 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 7 Oct 2024 16:50:12 +0530 Subject: [PATCH 009/111] channel info completed --- app/components/channel_info/index.tsx | 81 ++++++++++++------- app/components/draft_post/draft_post.tsx | 9 +-- .../global_drafts_list/global_drafts_list.tsx | 4 - .../components/global_drafts_list/index.tsx | 3 - assets/base/i18n/en.json | 3 + 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/app/components/channel_info/index.tsx b/app/components/channel_info/index.tsx index dd00ed578df..92f4d383c7f 100644 --- a/app/components/channel_info/index.tsx +++ b/app/components/channel_info/index.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, {type ReactNode} from 'react'; import {Text, View} from 'react-native'; import {General} from '@app/constants'; @@ -65,43 +65,64 @@ const ChannelInfo: React.FC = ({ channel, sendToUser, updateAt = 'Yesterday', - - // rootId, - // testID, + rootId, + testID, }) => { const theme = useTheme(); const style = getStyleSheet(theme); const isChannelTypeDM = channel.type === General.DM_CHANNEL; + let headerComponent: ReactNode = null; + const profileComponent = sendToUser ? : ( + ); + + if (rootId) { + headerComponent = ( + + + {profileComponent} + + ); + } else if (isChannelTypeDM) { + headerComponent = ( + + + {profileComponent} + + ); + } else { + headerComponent = ( + + + {profileComponent} + + ); + } + return ( - + - {!isChannelTypeDM && - - - - } - {isChannelTypeDM && - - - {sendToUser && - } - } + {headerComponent} {channel.displayName} diff --git a/app/components/draft_post/draft_post.tsx b/app/components/draft_post/draft_post.tsx index 7da2ca56149..221da3e1485 100644 --- a/app/components/draft_post/draft_post.tsx +++ b/app/components/draft_post/draft_post.tsx @@ -14,7 +14,6 @@ type Props = { channel: ChannelModel; sendToUser?: UserModel; draft: DraftModel; - currentUser: UserModel; } const style = StyleSheet.create({ @@ -27,8 +26,6 @@ const style = StyleSheet.create({ const DraftPost: React.FC = ({ channel, draft, - - // currentUser, sendToUser, }) => { // const post: Post = { @@ -57,13 +54,11 @@ const DraftPost: React.FC = ({ return ( - {/* {channel.displayName} - {channel.type} - {draft.rootId ? 'In Thread' : 'Channel'} - */} {draft.message} diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 4fb00e01810..b455f0e2427 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -7,16 +7,13 @@ import {View} from 'react-native'; import DraftPost from '@app/components/draft_post/'; import type DraftModel from '@typings/database/models/servers/draft'; -import type UserModel from '@typings/database/models/servers/user'; type Props = { allDrafts: DraftModel[]; - currentUser: UserModel; } const GlobalDraftsList: React.FC = ({ allDrafts, - currentUser, }) => { return ( @@ -26,7 +23,6 @@ const GlobalDraftsList: React.FC = ({ key={draft.id} channelId={draft.channelId} draft={draft} - currentUser={currentUser} /> ); })} diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx index 6cdf5f8d607..eae43d0c582 100644 --- a/app/screens/global_drafts/components/global_drafts_list/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -7,7 +7,6 @@ import React from 'react'; import {observeAllDrafts} from '@app/queries/servers/drafts'; import {observeCurrentTeamId} from '@app/queries/servers/system'; -import {observeCurrentUser} from '@app/queries/servers/user'; import GlobalDraftsList from './global_drafts_list'; @@ -23,11 +22,9 @@ type Props = { const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { const allDrafts = observeAllDrafts(database, teamId); - const currentUser = observeCurrentUser(database); return { allDrafts, - currentUser, }; }); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index aacefeab3f1..00ea08e2947 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -194,6 +194,9 @@ "channel_info.send_a_mesasge": "Send a message", "channel_info.send_mesasge": "Send message", "channel_info.set_header": "Set Header", + "channel_info.in": "In:", + "channel_info.thread_in": "Thread in:", + "channel_info.to": "To:", "channel_info.unarchive": "Unarchive Channel", "channel_info.unarchive_description": "Are you sure you want to unarchive the {term} {name}?", "channel_info.unarchive_failed": "An error occurred trying to unarchive the channel {displayName}", From d688908249744525364fe0d99ad095bd8885906c Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 7 Oct 2024 19:22:22 +0530 Subject: [PATCH 010/111] proper naming --- app/components/drafts/draft_post/index.tsx | 15 +++++++++++++++ .../draft_post.tsx => drafts/drafts.tsx} | 11 ++++++----- app/components/{draft_post => drafts}/index.tsx | 4 ++-- .../global_drafts_list/global_drafts_list.tsx | 4 ++-- 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 app/components/drafts/draft_post/index.tsx rename app/components/{draft_post/draft_post.tsx => drafts/drafts.tsx} (86%) rename app/components/{draft_post => drafts}/index.tsx (94%) diff --git a/app/components/drafts/draft_post/index.tsx b/app/components/drafts/draft_post/index.tsx new file mode 100644 index 00000000000..27e939a2fa6 --- /dev/null +++ b/app/components/drafts/draft_post/index.tsx @@ -0,0 +1,15 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Text, View} from 'react-native'; + +const DraftPost = () => { + return ( + + {'Draft Post'} + + ); +}; + +export default DraftPost; diff --git a/app/components/draft_post/draft_post.tsx b/app/components/drafts/drafts.tsx similarity index 86% rename from app/components/draft_post/draft_post.tsx rename to app/components/drafts/drafts.tsx index 221da3e1485..bdb92452f41 100644 --- a/app/components/draft_post/draft_post.tsx +++ b/app/components/drafts/drafts.tsx @@ -2,9 +2,10 @@ // See LICENSE.txt for license information. import React from 'react'; -import {StyleSheet, Text, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; -import ChannelInfo from '../channel_info'; +import ChannelInfo from '@app/components/channel_info'; +import DraftPost from '@app/components/drafts/draft_post'; import type ChannelModel from '@typings/database/models/servers/channel'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -23,7 +24,7 @@ const style = StyleSheet.create({ }, }); -const DraftPost: React.FC = ({ +const Drafts: React.FC = ({ channel, draft, sendToUser, @@ -60,9 +61,9 @@ const DraftPost: React.FC = ({ rootId={draft.rootId} testID='draft_post.channel_info' /> - {draft.message} + ); }; -export default DraftPost; +export default Drafts; diff --git a/app/components/draft_post/index.tsx b/app/components/drafts/index.tsx similarity index 94% rename from app/components/draft_post/index.tsx rename to app/components/drafts/index.tsx index 4acc52261ed..fe7bf9ed98c 100644 --- a/app/components/draft_post/index.tsx +++ b/app/components/drafts/index.tsx @@ -7,7 +7,7 @@ import {switchMap, of} from 'rxjs'; import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; import {observeUser} from '@app/queries/servers/user'; -import DraftPost from './draft_post'; +import Drafts from './drafts'; import type {WithDatabaseArgs} from '@typings/database/database'; @@ -42,4 +42,4 @@ const enhance = withObservables(['channelId'], ({channelId, database}: Props) => }; }); -export default withDatabase(enhance(DraftPost)); +export default withDatabase(enhance(Drafts)); diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index b455f0e2427..3e93600ed5d 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {View} from 'react-native'; -import DraftPost from '@app/components/draft_post/'; +import Drafts from '@app/components/drafts'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -19,7 +19,7 @@ const GlobalDraftsList: React.FC = ({ {allDrafts.map((draft) => { return ( - Date: Mon, 7 Oct 2024 23:48:50 +0530 Subject: [PATCH 011/111] added image file markdown acknowledgement support --- app/components/draft/draft.tsx | 65 ++++++++++++++++ .../draft/draft_post/draft_files/index.ts | 23 ++++++ .../draft/draft_post/draft_message/index.tsx | 78 +++++++++++++++++++ app/components/draft/draft_post/index.tsx | 75 ++++++++++++++++++ app/components/{drafts => draft}/index.tsx | 2 +- app/components/drafts/draft_post/index.tsx | 15 ---- app/components/drafts/drafts.tsx | 69 ---------------- .../global_drafts_list/global_drafts_list.tsx | 28 +++++-- app/screens/global_drafts/index.tsx | 4 +- 9 files changed, 267 insertions(+), 92 deletions(-) create mode 100644 app/components/draft/draft.tsx create mode 100644 app/components/draft/draft_post/draft_files/index.ts create mode 100644 app/components/draft/draft_post/draft_message/index.tsx create mode 100644 app/components/draft/draft_post/index.tsx rename app/components/{drafts => draft}/index.tsx (97%) delete mode 100644 app/components/drafts/draft_post/index.tsx delete mode 100644 app/components/drafts/drafts.tsx diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx new file mode 100644 index 00000000000..4468c078fbe --- /dev/null +++ b/app/components/draft/draft.tsx @@ -0,0 +1,65 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {View} from 'react-native'; + +import ChannelInfo from '@app/components/channel_info'; +import DraftPost from '@app/components/draft/draft_post'; +import {useTheme} from '@app/context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; + +import type ChannelModel from '@typings/database/models/servers/channel'; +import type DraftModel from '@typings/database/models/servers/draft'; +import type UserModel from '@typings/database/models/servers/user'; + +type Props = { + channel: ChannelModel; + location: string; + sendToUser?: UserModel; + draft: DraftModel; + layoutWidth: number; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + paddingHorizontal: 20, + paddingVertical: 16, + width: '100%', + borderTopColor: changeOpacity(theme.centerChannelColor, 0.16), + borderTopWidth: 1, + }, + }; +}); + +const Draft: React.FC = ({ + channel, + location, + draft, + sendToUser, + layoutWidth, +}) => { + const theme = useTheme(); + const style = getStyleSheet(theme); + + return ( + + + + + ); +}; + +export default Draft; diff --git a/app/components/draft/draft_post/draft_files/index.ts b/app/components/draft/draft_post/draft_files/index.ts new file mode 100644 index 00000000000..d7a2eeef91a --- /dev/null +++ b/app/components/draft/draft_post/draft_files/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {of as of$} from 'rxjs'; + +import Files from '@app/components/files/files'; +import {observeCanDownloadFiles, observeConfigBooleanValue} from '@queries/servers/system'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhance = withObservables(['draftId'], ({database, draftId}: WithDatabaseArgs & {draftId: string}) => { + const publicLinkEnabled = observeConfigBooleanValue(database, 'EnablePublicLink'); + + return { + canDownloadFiles: observeCanDownloadFiles(database), + publicLinkEnabled, + postId: of$(draftId), + postProps: of$({}), + }; +}); + +export default withDatabase(enhance(Files)); diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message/index.tsx new file mode 100644 index 00000000000..39f27c12dd2 --- /dev/null +++ b/app/components/draft/draft_post/draft_message/index.tsx @@ -0,0 +1,78 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {View} from 'react-native'; + +import {Screens} from '@app/constants'; +import {useTheme} from '@app/context/theme'; +import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@app/utils/markdown'; +import {makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; +import Markdown from '@components/markdown'; + +import type {DraftModel} from '@app/database/models/server'; +import type {UserMentionKey} from '@typings/global/markdown'; + +type Props = { + draft: DraftModel; +} + +const EMPTY_MENTION_KEYS: UserMentionKey[] = []; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + messageContainer: { + width: '100%', + }, + reply: { + paddingRight: 10, + }, + message: { + color: theme.centerChannelColor, + ...typography('Body', 200), + lineHeight: undefined, // remove line height, not needed and causes problems with md images + }, + pendingPost: { + opacity: 0.5, + }, + }; +}); + +const DraftMessage: React.FC = ({ + draft, +}) => { + const theme = useTheme(); + const style = getStyleSheet(theme); + const blockStyles = getMarkdownBlockStyles(theme); + const textStyles = getMarkdownTextStyles(theme); + return ( + + + + ); +}; + +export default DraftMessage; diff --git a/app/components/draft/draft_post/index.tsx b/app/components/draft/draft_post/index.tsx new file mode 100644 index 00000000000..effa91e5715 --- /dev/null +++ b/app/components/draft/draft_post/index.tsx @@ -0,0 +1,75 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {View, TouchableOpacity} from 'react-native'; + +import CompassIcon from '@app/components/compass_icon'; +import {useTheme} from '@app/context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import DraftMessage from '@app/components/draft/draft_post/draft_message'; + +import DraftFiles from './draft_files'; + +import type {DraftModel} from '@app/database/models/server'; + +type Props = { + draft: DraftModel; + location: string; + layoutWidth: number; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + acknowledgementContainer: { + marginTop: 8, + alignItems: 'center', + borderRadius: 4, + backgroundColor: changeOpacity(theme.onlineIndicator, 0.12), + flexDirection: 'row', + height: 32, + width: 42, + justifyContent: 'center', + paddingHorizontal: 8, + }, + }; +}); + +const DraftPost: React.FC = ({ + draft, + location, + layoutWidth, +}) => { + const theme = useTheme(); + const hasFiles = draft.files.length > 0; + const acknowledgementsVisible = draft.metadata?.priority?.requested_ack; + const style = getStyleSheet(theme); + + return ( + + + { + hasFiles && + + } + { + acknowledgementsVisible && + + + + } + + ); +}; + +export default DraftPost; diff --git a/app/components/drafts/index.tsx b/app/components/draft/index.tsx similarity index 97% rename from app/components/drafts/index.tsx rename to app/components/draft/index.tsx index fe7bf9ed98c..242d21dd810 100644 --- a/app/components/drafts/index.tsx +++ b/app/components/draft/index.tsx @@ -7,7 +7,7 @@ import {switchMap, of} from 'rxjs'; import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; import {observeUser} from '@app/queries/servers/user'; -import Drafts from './drafts'; +import Drafts from './draft'; import type {WithDatabaseArgs} from '@typings/database/database'; diff --git a/app/components/drafts/draft_post/index.tsx b/app/components/drafts/draft_post/index.tsx deleted file mode 100644 index 27e939a2fa6..00000000000 --- a/app/components/drafts/draft_post/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Text, View} from 'react-native'; - -const DraftPost = () => { - return ( - - {'Draft Post'} - - ); -}; - -export default DraftPost; diff --git a/app/components/drafts/drafts.tsx b/app/components/drafts/drafts.tsx deleted file mode 100644 index bdb92452f41..00000000000 --- a/app/components/drafts/drafts.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {StyleSheet, View} from 'react-native'; - -import ChannelInfo from '@app/components/channel_info'; -import DraftPost from '@app/components/drafts/draft_post'; - -import type ChannelModel from '@typings/database/models/servers/channel'; -import type DraftModel from '@typings/database/models/servers/draft'; -import type UserModel from '@typings/database/models/servers/user'; - -type Props = { - channel: ChannelModel; - sendToUser?: UserModel; - draft: DraftModel; -} - -const style = StyleSheet.create({ - container: { - paddingHorizontal: 20, - paddingVertical: 16, - }, -}); - -const Drafts: React.FC = ({ - channel, - draft, - sendToUser, -}) => { - // const post: Post = { - // id: draft.rootId || '', - // channel_id: channel.id, - // message: draft.message, - // create_at: 0, - // update_at: 0, - // delete_at: 0, - // user_id: currentUser.id, - // root_id: draft.rootId || '', - // original_id: '', - // type: '', - // props: {}, - // hashtags: '', - // pending_post_id: '', - // reply_count: 0, - // metadata: draft.metadata ? draft.metadata : {}, - // edit_at: 0, - // is_pinned: false, - // file_ids: [draft.files.map((file) => file.id) || ''], - // last_reply_at: 0, - // message_source: '', - // user_activity_posts: [], - // }; - - return ( - - - - - ); -}; - -export default Drafts; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 3e93600ed5d..5734f0e0dbf 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -1,32 +1,48 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; -import {View} from 'react-native'; +import React, {useCallback, useState} from 'react'; +import {ScrollView, type LayoutChangeEvent} from 'react-native'; -import Drafts from '@app/components/drafts'; +import Draft from '@app/components/draft'; +import {Screens} from '@app/constants'; import type DraftModel from '@typings/database/models/servers/draft'; type Props = { allDrafts: DraftModel[]; + location: string; } const GlobalDraftsList: React.FC = ({ allDrafts, + location, }) => { + const [layoutWidth, setLayoutWidth] = useState(0); + const onLayout = useCallback((e: LayoutChangeEvent) => { + if (location === Screens.GLOBAL_DRAFTS) { + setLayoutWidth(e.nativeEvent.layout.width - 40); // 40 is the padding of the container + } + }, [location]); return ( - + {allDrafts.map((draft) => { return ( - ); })} - ); + ); }; export default GlobalDraftsList; diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index c84afafe6e0..50639c04621 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -84,7 +84,9 @@ const GlobalDrafts = ({componentId}: Props) => { {!switchingTeam && - + } From 65b6edd329d375e328298d15f268c16c65058b34 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 9 Oct 2024 20:06:09 +0530 Subject: [PATCH 012/111] draft actions --- app/components/draft/draft.tsx | 59 ++++++++++++----- .../draft/draft_post/draft_message/index.tsx | 21 ++----- app/components/draft/draft_post/index.tsx | 13 +++- app/constants/screens.ts | 3 + .../draft_options/delete_draft/index.tsx | 63 +++++++++++++++++++ .../draft_options/edit_draft/index.tsx | 63 +++++++++++++++++++ app/screens/draft_options/index.tsx | 60 ++++++++++++++++++ .../draft_options/send_draft/index.tsx | 63 +++++++++++++++++++ app/screens/index.tsx | 3 + assets/base/i18n/en.json | 5 ++ 10 files changed, 320 insertions(+), 33 deletions(-) create mode 100644 app/screens/draft_options/delete_draft/index.tsx create mode 100644 app/screens/draft_options/edit_draft/index.tsx create mode 100644 app/screens/draft_options/index.tsx create mode 100644 app/screens/draft_options/send_draft/index.tsx diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 4468c078fbe..5595fae9183 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -2,11 +2,16 @@ // See LICENSE.txt for license information. import React from 'react'; -import {View} from 'react-native'; +import {useIntl} from 'react-intl'; +import {Keyboard, TouchableHighlight, View} from 'react-native'; import ChannelInfo from '@app/components/channel_info'; import DraftPost from '@app/components/draft/draft_post'; +import {Screens} from '@app/constants'; import {useTheme} from '@app/context/theme'; +import {useIsTablet} from '@app/hooks/device'; +import {DRAFT_OPTIONS_BUTTON} from '@app/screens/draft_options'; +import {openAsBottomSheet} from '@app/screens/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; import type ChannelModel from '@typings/database/models/servers/channel'; @@ -30,6 +35,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { borderTopColor: changeOpacity(theme.centerChannelColor, 0.16), borderTopWidth: 1, }, + pressInContainer: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.16), + }, }; }); @@ -40,25 +48,46 @@ const Draft: React.FC = ({ sendToUser, layoutWidth, }) => { + const intl = useIntl(); const theme = useTheme(); const style = getStyleSheet(theme); + const isTablet = useIsTablet(); + + const onLongPress = () => { + Keyboard.dismiss(); + const title = isTablet ? intl.formatMessage({id: 'draft.options.title', defaultMessage: 'Draft Options'}) : 'Draft Options'; + openAsBottomSheet({ + closeButtonId: DRAFT_OPTIONS_BUTTON, + screen: Screens.DRAFT_OPTIONS, + theme, + title, + props: {}, + }); + }; return ( - - - - + + + + + + ); }; diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message/index.tsx index 39f27c12dd2..28423a9543e 100644 --- a/app/components/draft/draft_post/draft_message/index.tsx +++ b/app/components/draft/draft_post/draft_message/index.tsx @@ -4,7 +4,6 @@ import React from 'react'; import {View} from 'react-native'; -import {Screens} from '@app/constants'; import {useTheme} from '@app/context/theme'; import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@app/utils/markdown'; import {makeStyleSheetFromTheme} from '@app/utils/theme'; @@ -16,6 +15,8 @@ import type {UserMentionKey} from '@typings/global/markdown'; type Props = { draft: DraftModel; + layoutWidth: number; + location: string; } const EMPTY_MENTION_KEYS: UserMentionKey[] = []; @@ -31,7 +32,6 @@ 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, @@ -41,6 +41,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const DraftMessage: React.FC = ({ draft, + layoutWidth, + location, }) => { const theme = useTheme(); const style = getStyleSheet(theme); @@ -52,24 +54,13 @@ const DraftMessage: React.FC = ({ baseTextStyle={style.message} blockStyles={blockStyles} channelId={draft.channelId} - - // channelMentions={post.props?.channel_mentions} - // imagesMetadata={post.metadata?.images} - // isEdited={isEdited} - // isReplyPost={isReplyPost} - // isSearchResult={location === SEARCH} - // layoutWidth={layoutWidth} - location={Screens.GLOBAL_DRAFTS} + layoutWidth={layoutWidth} + location={location} postId={draft.id} textStyles={textStyles} value={draft.message} mentionKeys={EMPTY_MENTION_KEYS} - - // highlightKeys={isHighlightWithoutNotificationLicensed ? (currentUser?.highlightKeys ?? EMPTY_HIGHLIGHT_KEYS) : EMPTY_HIGHLIGHT_KEYS} - // searchPatterns={searchPatterns} theme={theme} - - // isUnsafeLinksPost={post.props.unsafe_links && post.props.unsafe_links !== ''} /> ); diff --git a/app/components/draft/draft_post/index.tsx b/app/components/draft/draft_post/index.tsx index effa91e5715..02047c9fa38 100644 --- a/app/components/draft/draft_post/index.tsx +++ b/app/components/draft/draft_post/index.tsx @@ -5,9 +5,9 @@ import React from 'react'; import {View, TouchableOpacity} from 'react-native'; import CompassIcon from '@app/components/compass_icon'; +import DraftMessage from '@app/components/draft/draft_post/draft_message'; import {useTheme} from '@app/context/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import DraftMessage from '@app/components/draft/draft_post/draft_message'; import DraftFiles from './draft_files'; @@ -21,6 +21,9 @@ type Props = { const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { + container: { + marginTop: 12, + }, acknowledgementContainer: { marginTop: 8, alignItems: 'center', @@ -46,8 +49,12 @@ const DraftPost: React.FC = ({ const style = getStyleSheet(theme); return ( - - + + { hasFiles && ([ export const SCREENS_AS_BOTTOM_SHEET = new Set([ BOTTOM_SHEET, + DRAFT_OPTIONS, EMOJI_PICKER, POST_OPTIONS, POST_PRIORITY_PICKER, diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx new file mode 100644 index 00000000000..a0325f83d4d --- /dev/null +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -0,0 +1,63 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Text} from 'react-native'; + +import CompassIcon from '@app/components/compass_icon'; +import TouchableWithFeedback from '@app/components/touchable_with_feedback'; +import {ICON_SIZE} from '@app/constants/post_draft'; +import {useTheme} from '@app/context/theme'; +import {dismissBottomSheet} from '@app/screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +type Props = { + bottomSheetId: AvailableScreens; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + title: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + draftOptions: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 16, + paddingVertical: 12, + }, +})); + +const DeleteDraft: React.FC = ({ + bottomSheetId, +}) => { + const theme = useTheme(); + const intl = useIntl(); + const style = getStyleSheet(theme); + + const draftDeleteHandler = async () => { + await dismissBottomSheet(bottomSheetId); + }; + + return ( + + + {intl.formatMessage({id: 'draft.options.delete.title', defaultMessage: 'Delete'})} + + ); +}; + +export default DeleteDraft; diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx new file mode 100644 index 00000000000..fa401be9809 --- /dev/null +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -0,0 +1,63 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Text} from 'react-native'; + +import CompassIcon from '@app/components/compass_icon'; +import TouchableWithFeedback from '@app/components/touchable_with_feedback'; +import {ICON_SIZE} from '@app/constants/post_draft'; +import {useTheme} from '@app/context/theme'; +import {dismissBottomSheet} from '@app/screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +type Props = { + bottomSheetId: AvailableScreens; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + title: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + draftOptions: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 16, + paddingVertical: 12, + }, +})); + +const EditDraft: React.FC = ({ + bottomSheetId, +}) => { + const theme = useTheme(); + const intl = useIntl(); + const style = getStyleSheet(theme); + + const editHandler = async () => { + await dismissBottomSheet(bottomSheetId); + }; + + return ( + + + {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Edit'})} + + ); +}; + +export default EditDraft; diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx new file mode 100644 index 00000000000..835ca0ee851 --- /dev/null +++ b/app/screens/draft_options/index.tsx @@ -0,0 +1,60 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {StyleSheet, View, Text} from 'react-native'; + +import {Screens} from '@app/constants'; +import {useIsTablet} from '@app/hooks/device'; +import {typography} from '@app/utils/typography'; +import BottomSheet from '@screens/bottom_sheet'; + +import DeleteDraft from './delete_draft'; +import EditDraft from './edit_draft'; +import SendDraft from './send_draft'; + +export const DRAFT_OPTIONS_BUTTON = 'close-post-options'; + +const styles = StyleSheet.create({ + header: { + ...typography('Heading', 600, 'SemiBold'), + display: 'flex', + paddingBottom: 4, + }, +}); + +const DraftOptions = () => { + const {formatMessage} = useIntl(); + const isTablet = useIsTablet(); + const renderContent = () => { + return ( + + {!isTablet && {formatMessage( + {id: 'draft.option.header', defaultMessage: 'Draft actions'}, + )}} + + + + + ); + }; + + return ( + + ); +}; + +export default DraftOptions; diff --git a/app/screens/draft_options/send_draft/index.tsx b/app/screens/draft_options/send_draft/index.tsx new file mode 100644 index 00000000000..140e52d2625 --- /dev/null +++ b/app/screens/draft_options/send_draft/index.tsx @@ -0,0 +1,63 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Text} from 'react-native'; + +import CompassIcon from '@app/components/compass_icon'; +import TouchableWithFeedback from '@app/components/touchable_with_feedback'; +import {ICON_SIZE} from '@app/constants/post_draft'; +import {useTheme} from '@app/context/theme'; +import {dismissBottomSheet} from '@app/screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +type Props = { + bottomSheetId: AvailableScreens; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + title: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + draftOptions: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 16, + paddingVertical: 12, + }, +})); + +const SendDraft: React.FC = ({ + bottomSheetId, +}) => { + const theme = useTheme(); + const intl = useIntl(); + const style = getStyleSheet(theme); + + const draftSendHandler = async () => { + await dismissBottomSheet(bottomSheetId); + }; + + return ( + + + {intl.formatMessage({id: 'draft.options.save.title', defaultMessage: 'Save'})} + + ); +}; + +export default SendDraft; diff --git a/app/screens/index.tsx b/app/screens/index.tsx index c581aec3d62..540c80b1521 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -111,6 +111,9 @@ Navigation.setLazyComponentRegistrator((screenName) => { case Screens.CHANNEL_ADD_MEMBERS: screen = withServerDatabase(require('@screens/channel_add_members').default); break; + case Screens.DRAFT_OPTIONS: + screen = withServerDatabase(require('@screens/draft_options').default); + break; case Screens.EDIT_POST: screen = withServerDatabase(require('@screens/edit_post').default); break; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 00ea08e2947..eab074c2329 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -308,6 +308,11 @@ "display_settings.tz.auto": "Auto", "display_settings.tz.manual": "Manual", "drafts": "Drafts", + "draft.options.delete.title": "Delete", + "draft.options.edit.title": "Edit", + "draft.options.save.title": "Save", + "draft.options.header": "Draft actions", + "draft.options.title": "Draft Options", "download.error": "Unable to download the file. Try again later", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", From 272c468ce764810cc6dde20f90ce4356fb2b913f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 14 Oct 2024 15:35:20 +0530 Subject: [PATCH 013/111] Fix the draft receiver in drafts --- app/components/channel_info/index.tsx | 6 +++--- app/components/draft/draft.tsx | 6 +++--- app/components/draft/index.tsx | 20 +++++++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/components/channel_info/index.tsx b/app/components/channel_info/index.tsx index 92f4d383c7f..568010eceb2 100644 --- a/app/components/channel_info/index.tsx +++ b/app/components/channel_info/index.tsx @@ -19,7 +19,7 @@ import type UserModel from '@typings/database/models/servers/user'; type Props = { channel: ChannelModel; - sendToUser?: UserModel; + draftReceiverUser?: UserModel; updateAt?: string; rootId?: PostModel['rootId']; testID?: string; @@ -63,7 +63,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const ChannelInfo: React.FC = ({ channel, - sendToUser, + draftReceiverUser, updateAt = 'Yesterday', rootId, testID, @@ -73,7 +73,7 @@ const ChannelInfo: React.FC = ({ const isChannelTypeDM = channel.type === General.DM_CHANNEL; let headerComponent: ReactNode = null; - const profileComponent = sendToUser ? : ( + const profileComponent = draftReceiverUser ? : ( = ({ channel, location, draft, - sendToUser, + draftReceiverUser, layoutWidth, }) => { const intl = useIntl(); @@ -76,7 +76,7 @@ const Draft: React.FC = ({ > diff --git a/app/components/draft/index.tsx b/app/components/draft/index.tsx index 242d21dd810..609b73c99d9 100644 --- a/app/components/draft/index.tsx +++ b/app/components/draft/index.tsx @@ -5,7 +5,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {switchMap, of} from 'rxjs'; import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; -import {observeUser} from '@app/queries/servers/user'; +import {observeCurrentUser, observeUser} from '@app/queries/servers/user'; import Drafts from './draft'; @@ -17,7 +17,8 @@ type Props = { const enhance = withObservables(['channelId'], ({channelId, database}: Props) => { const channel = observeChannel(database, channelId); - const sendToUser = channel.pipe( + const currentUser = observeCurrentUser(database); + const draftReceiverUser = channel.pipe( switchMap((channelData) => { if (channelData?.type === 'D') { // Fetch the channel member for direct message channels @@ -25,8 +26,17 @@ const enhance = withObservables(['channelId'], ({channelId, database}: Props) => // eslint-disable-next-line max-nested-callbacks switchMap((members) => { if (members.length > 0) { - const userId = members[0]?.userId; - return observeUser(database, userId); + return currentUser.pipe( + // eslint-disable-next-line max-nested-callbacks + switchMap((user) => { + // eslint-disable-next-line max-nested-callbacks + const validMember = members.find((member) => member.userId !== user?.id); + if (validMember) { + return observeUser(database, validMember.userId); + } + return of(undefined); + }), + ); } return of(undefined); }), @@ -38,7 +48,7 @@ const enhance = withObservables(['channelId'], ({channelId, database}: Props) => return { channel, - sendToUser, + draftReceiverUser, }; }); From 1fa189a6db99ac0a26c8d87917be2cc48ddb606f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 14 Oct 2024 23:03:13 +0530 Subject: [PATCH 014/111] separated send message handler --- app/components/draft/draft.tsx | 2 +- app/components/draft/index.tsx | 85 +++++-- .../post_draft/send_handler/send_handler.tsx | 197 ++------------- app/hooks/handle_send_message.ts | 231 ++++++++++++++++++ .../draft_options/edit_draft/index.tsx | 14 ++ app/screens/draft_options/index.tsx | 14 +- 6 files changed, 336 insertions(+), 207 deletions(-) create mode 100644 app/hooks/handle_send_message.ts diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 1aaa97c27ef..31e7f448620 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -61,7 +61,7 @@ const Draft: React.FC = ({ screen: Screens.DRAFT_OPTIONS, theme, title, - props: {}, + props: {channel, rootId: draft.rootId}, }); }; diff --git a/app/components/draft/index.tsx b/app/components/draft/index.tsx index 609b73c99d9..49543a2ed61 100644 --- a/app/components/draft/index.tsx +++ b/app/components/draft/index.tsx @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import React from 'react'; import {switchMap, of} from 'rxjs'; import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; @@ -10,46 +11,82 @@ import {observeCurrentUser, observeUser} from '@app/queries/servers/user'; import Drafts from './draft'; import type {WithDatabaseArgs} from '@typings/database/database'; +import type ChannelModel from '@typings/database/models/servers/channel'; +import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership'; +import type UserModel from '@typings/database/models/servers/user'; type Props = { channelId: string; + currentUser?: UserModel; + members?: ChannelMembershipModel[]; + channel?: ChannelModel; } & WithDatabaseArgs; -const enhance = withObservables(['channelId'], ({channelId, database}: Props) => { +const withCurrentUser = withObservables([], ({database}: WithDatabaseArgs) => ({ + currentUser: observeCurrentUser(database), +})); + +const withChannel = withObservables(['channelId'], ({channelId, database}: Props) => ({ + channel: observeChannel(database, channelId), +})); + +const withChannelMembers = withObservables(['channelId'], ({channelId, database}: Props) => { const channel = observeChannel(database, channelId); - const currentUser = observeCurrentUser(database); - const draftReceiverUser = channel.pipe( + + const members = channel.pipe( switchMap((channelData) => { if (channelData?.type === 'D') { - // Fetch the channel member for direct message channels - return observeChannelMembers(database, channelId).pipe( - // eslint-disable-next-line max-nested-callbacks - switchMap((members) => { - if (members.length > 0) { - return currentUser.pipe( - // eslint-disable-next-line max-nested-callbacks - switchMap((user) => { - // eslint-disable-next-line max-nested-callbacks - const validMember = members.find((member) => member.userId !== user?.id); - if (validMember) { - return observeUser(database, validMember.userId); - } - return of(undefined); - }), - ); - } - return of(undefined); - }), - ); + return observeChannelMembers(database, channelId); } return of(undefined); }), ); + return { + members, + }; +}); + +const observeDraftReceiverUser = ({ + members, + database, + channelData, + currentUser, +}: { + members?: ChannelMembershipModel[]; + database: WithDatabaseArgs['database']; + channelData?: ChannelModel; + currentUser?: UserModel; +}) => { + if (channelData?.type === 'D') { + if (members && members.length > 0) { + const validMember = members.find((member) => member.userId !== currentUser?.id); + if (validMember) { + return observeUser(database, validMember.userId); + } + return of(undefined); + } + return of(undefined); + } + return of(undefined); +}; + +const enhance = withObservables(['channel', 'members'], ({channel, database, currentUser, members}: Props) => { + const draftReceiverUser = observeDraftReceiverUser({members, database, channelData: channel, currentUser}); return { channel, draftReceiverUser, }; }); -export default withDatabase(enhance(Drafts)); +export default React.memo( + withDatabase( + withChannel( + withCurrentUser( + withChannelMembers( + enhance(Drafts), + ), + ), + ), + ), +); diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index a936bbe81ad..9440438263c 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -1,27 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useState} from 'react'; -import {useIntl} from 'react-intl'; -import {DeviceEventEmitter} from 'react-native'; +import React, {useCallback} from 'react'; import {updateDraftPriority} from '@actions/local/draft'; -import {getChannelTimezones} from '@actions/remote/channel'; -import {executeCommand, handleGotoLocation} from '@actions/remote/command'; -import {createPost} from '@actions/remote/post'; -import {handleReactionToLatestPost} from '@actions/remote/reactions'; -import {setStatus} from '@actions/remote/user'; -import {handleCallsSlashCommand} from '@calls/actions/calls'; -import {Events, Screens} from '@constants'; +import {useHandleSendMessage} from '@app/hooks/handle_send_message'; 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'; -import * as DraftUtils from '@utils/draft'; -import {isReactionMatch} from '@utils/emoji/helpers'; -import {getFullErrorMessage} from '@utils/errors'; -import {preventDoubleTap} from '@utils/tap'; -import {confirmOutOfOfficeDisabled} from '@utils/user'; import DraftInput from '../draft_input'; @@ -94,178 +79,28 @@ export default function SendHandler({ persistentNotificationMaxRecipients, postPriority, }: Props) { - const intl = useIntl(); const serverUrl = useServerUrl(); - const [channelTimezoneCount, setChannelTimezoneCount] = useState(0); - const [sendingMessage, setSendingMessage] = useState(false); - - const canSend = useCallback(() => { - if (sendingMessage) { - return false; - } - - const messageLength = value.trim().length; - - if (messageLength > maxMessageLength) { - return false; - } - - if (files.length) { - const loadingComplete = !files.some((file) => DraftUploadManager.isUploading(file.clientId!)); - return loadingComplete; - } - - return messageLength > 0; - }, [sendingMessage, value, files, maxMessageLength]); - - const handleReaction = useCallback((emoji: string, add: boolean) => { - handleReactionToLatestPost(serverUrl, emoji, add, rootId); - clearDraft(); - setSendingMessage(false); - }, [serverUrl, rootId, clearDraft]); - const handlePostPriority = useCallback((priority: PostPriority) => { updateDraftPriority(serverUrl, channelId, rootId, priority); }, [serverUrl, rootId]); - const doSubmitMessage = useCallback(() => { - const postFiles = files.filter((f) => !f.failed); - const post = { - user_id: currentUserId, - channel_id: channelId, - root_id: rootId, - message: value, - } as Post; - - if (!rootId && ( - postPriority.priority || - postPriority.requested_ack || - postPriority.persistent_notifications) - ) { - post.metadata = { - priority: postPriority, - }; - } - - createPost(serverUrl, post, postFiles); - - clearDraft(); - setSendingMessage(false); - DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); - }, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]); - - const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => { - const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, channelTimezoneCount, atHere); - const cancel = () => { - setSendingMessage(false); - }; - - DraftUtils.alertChannelWideMention(intl, notifyAllMessage, doSubmitMessage, cancel); - }, [intl, channelTimezoneCount, doSubmitMessage]); - - const sendCommand = useCallback(async () => { - if (value.trim().startsWith('/call')) { - const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, channelType ?? '', rootId, currentUserId, intl); - if (handled) { - setSendingMessage(false); - clearDraft(); - return; - } - if (error) { - setSendingMessage(false); - DraftUtils.alertSlashCommandFailed(intl, error); - return; - } - } - - const status = DraftUtils.getStatusFromSlashCommand(value); - if (userIsOutOfOffice && status) { - const updateStatus = (newStatus: string) => { - setStatus(serverUrl, { - status: newStatus, - last_activity_at: Date.now(), - manual: true, - user_id: currentUserId, - }); - }; - confirmOutOfOfficeDisabled(intl, status, updateStatus); - setSendingMessage(false); - return; - } - - const {data, error} = await executeCommand(serverUrl, intl, value, channelId, rootId); - setSendingMessage(false); - - if (error) { - const errorMessage = getFullErrorMessage(error); - DraftUtils.alertSlashCommandFailed(intl, errorMessage); - return; - } - - clearDraft(); - - if (data?.goto_location && !value.startsWith('/leave')) { - handleGotoLocation(serverUrl, intl, data.goto_location); - } - }, [userIsOutOfOffice, currentUserId, intl, value, serverUrl, channelId, rootId]); - - const sendMessage = useCallback(() => { - const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions; - const toAllOrChannel = DraftUtils.textContainsAtAllAtChannel(value); - const toHere = DraftUtils.textContainsAtHere(value); - - if (value.indexOf('/') === 0) { - sendCommand(); - } else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && (toAllOrChannel || toHere)) { - showSendToAllOrChannelOrHereAlert(membersCount, toHere && !toAllOrChannel); - } else { - doSubmitMessage(); - } - }, [ + const {handleSendMessage, canSend} = useHandleSendMessage({ + value, + channelId, + rootId, + files, + maxMessageLength, + customEmojis, enableConfirmNotificationsToChannel, useChannelMentions, - value, - channelTimezoneCount, - sendCommand, - showSendToAllOrChannelOrHereAlert, - doSubmitMessage, - ]); - - const handleSendMessage = useCallback(preventDoubleTap(() => { - if (!canSend()) { - return; - } - - setSendingMessage(true); - - const match = isReactionMatch(value, customEmojis); - if (match && !files.length) { - handleReaction(match.emoji, match.add); - return; - } - - const hasFailedAttachments = files.some((f) => f.failed); - if (hasFailedAttachments) { - const cancel = () => { - setSendingMessage(false); - }; - const accept = () => { - // Files are filtered on doSubmitMessage - sendMessage(); - }; - - DraftUtils.alertAttachmentFail(intl, accept, cancel); - } else { - sendMessage(); - } - }), [canSend, value, handleReaction, files, sendMessage, customEmojis]); - - useEffect(() => { - getChannelTimezones(serverUrl, channelId).then(({channelTimezones}) => { - setChannelTimezoneCount(channelTimezones?.length || 0); - }); - }, [serverUrl, channelId]); + membersCount, + userIsOutOfOffice, + currentUserId, + channelType, + postPriority, + clearDraft, + }); return ( void; +} + +export const useHandleSendMessage = ({ + value, + channelId, + rootId, + files, + maxMessageLength, + customEmojis, + enableConfirmNotificationsToChannel, + useChannelMentions, + membersCount = 0, + userIsOutOfOffice, + currentUserId, + channelType, + postPriority, + clearDraft, +}: Props) => { + const intl = useIntl(); + const serverUrl = useServerUrl(); + const [sendingMessage, setSendingMessage] = useState(false); + const [channelTimezoneCount, setChannelTimezoneCount] = useState(0); + + const canSend = useCallback(() => { + if (sendingMessage) { + return false; + } + + const messageLength = value.trim().length; + + if (messageLength > maxMessageLength) { + return false; + } + + if (files.length) { + const loadingComplete = !files.some((file) => DraftUploadManager.isUploading(file.clientId!)); + return loadingComplete; + } + + return messageLength > 0; + }, [sendingMessage, value, files, maxMessageLength]); + + const handleReaction = useCallback((emoji: string, add: boolean) => { + handleReactionToLatestPost(serverUrl, emoji, add, rootId); + clearDraft(); + setSendingMessage(false); + }, [serverUrl, rootId, clearDraft]); + + const doSubmitMessage = useCallback(() => { + const postFiles = files.filter((f) => !f.failed); + const post = { + user_id: currentUserId, + channel_id: channelId, + root_id: rootId, + message: value, + } as Post; + + if (!rootId && ( + postPriority.priority || + postPriority.requested_ack || + postPriority.persistent_notifications) + ) { + post.metadata = { + priority: postPriority, + }; + } + + createPost(serverUrl, post, postFiles); + + clearDraft(); + setSendingMessage(false); + DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); + }, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]); + + const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => { + const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, channelTimezoneCount, atHere); + const cancel = () => { + setSendingMessage(false); + }; + + DraftUtils.alertChannelWideMention(intl, notifyAllMessage, doSubmitMessage, cancel); + }, [intl, channelTimezoneCount, doSubmitMessage]); + + const sendCommand = useCallback(async () => { + if (value.trim().startsWith('/call')) { + const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, channelType ?? '', rootId, currentUserId, intl); + if (handled) { + setSendingMessage(false); + clearDraft(); + return; + } + if (error) { + setSendingMessage(false); + DraftUtils.alertSlashCommandFailed(intl, error); + return; + } + } + + const status = DraftUtils.getStatusFromSlashCommand(value); + if (userIsOutOfOffice && status) { + const updateStatus = (newStatus: string) => { + setStatus(serverUrl, { + status: newStatus, + last_activity_at: Date.now(), + manual: true, + user_id: currentUserId, + }); + }; + confirmOutOfOfficeDisabled(intl, status, updateStatus); + setSendingMessage(false); + return; + } + + const {data, error} = await executeCommand(serverUrl, intl, value, channelId, rootId); + setSendingMessage(false); + + if (error) { + const errorMessage = getFullErrorMessage(error); + DraftUtils.alertSlashCommandFailed(intl, errorMessage); + return; + } + + clearDraft(); + + if (data?.goto_location && !value.startsWith('/leave')) { + handleGotoLocation(serverUrl, intl, data.goto_location); + } + }, [userIsOutOfOffice, currentUserId, intl, value, serverUrl, channelId, rootId]); + + const sendMessage = useCallback(() => { + const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions; + const toAllOrChannel = DraftUtils.textContainsAtAllAtChannel(value); + const toHere = DraftUtils.textContainsAtHere(value); + + if (value.indexOf('/') === 0) { + sendCommand(); + } else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && (toAllOrChannel || toHere)) { + showSendToAllOrChannelOrHereAlert(membersCount, toHere && !toAllOrChannel); + } else { + doSubmitMessage(); + } + }, [ + enableConfirmNotificationsToChannel, + useChannelMentions, + value, + channelTimezoneCount, + sendCommand, + showSendToAllOrChannelOrHereAlert, + doSubmitMessage, + ]); + + const handleSendMessage = useCallback(preventDoubleTap(() => { + if (!canSend()) { + return; + } + + setSendingMessage(true); + + const match = isReactionMatch(value, customEmojis); + if (match && !files.length) { + handleReaction(match.emoji, match.add); + return; + } + + const hasFailedAttachments = files.some((f) => f.failed); + if (hasFailedAttachments) { + const cancel = () => { + setSendingMessage(false); + }; + const accept = () => { + // Files are filtered on doSubmitMessage + sendMessage(); + }; + + DraftUtils.alertAttachmentFail(intl, accept, cancel); + } else { + sendMessage(); + } + }), [canSend, value, handleReaction, files, sendMessage, customEmojis]); + + useEffect(() => { + getChannelTimezones(serverUrl, channelId).then(({channelTimezones}) => { + setChannelTimezoneCount(channelTimezones?.length || 0); + }); + }, [serverUrl, channelId]); + + return { + handleSendMessage, + canSend, + }; +}; diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index fa401be9809..91cd309b177 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -5,18 +5,24 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {Text} from 'react-native'; +import {switchToThread} from '@actions/local/thread'; +import {switchToChannelById} from '@actions/remote/channel'; import CompassIcon from '@app/components/compass_icon'; import TouchableWithFeedback from '@app/components/touchable_with_feedback'; import {ICON_SIZE} from '@app/constants/post_draft'; +import {useServerUrl} from '@app/context/server'; import {useTheme} from '@app/context/theme'; import {dismissBottomSheet} from '@app/screens/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; import {typography} from '@app/utils/typography'; +import type ChannelModel from '@typings/database/models/servers/channel'; import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { bottomSheetId: AvailableScreens; + channel: ChannelModel; + rootId: string; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -35,13 +41,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const EditDraft: React.FC = ({ bottomSheetId, + channel, + rootId, }) => { const theme = useTheme(); const intl = useIntl(); const style = getStyleSheet(theme); + const serverUrl = useServerUrl(); const editHandler = async () => { await dismissBottomSheet(bottomSheetId); + if (rootId) { + switchToThread(serverUrl, rootId); + return; + } + switchToChannelById(serverUrl, channel.id, channel.teamId); }; return ( diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index 835ca0ee851..f7624d5e027 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -14,6 +14,13 @@ import DeleteDraft from './delete_draft'; import EditDraft from './edit_draft'; import SendDraft from './send_draft'; +import type ChannelModel from '@typings/database/models/servers/channel'; + +type Props = { + channel: ChannelModel; + rootId: string; +} + export const DRAFT_OPTIONS_BUTTON = 'close-post-options'; const styles = StyleSheet.create({ @@ -24,7 +31,10 @@ const styles = StyleSheet.create({ }, }); -const DraftOptions = () => { +const DraftOptions: React.FC = ({ + channel, + rootId, +}) => { const {formatMessage} = useIntl(); const isTablet = useIsTablet(); const renderContent = () => { @@ -35,6 +45,8 @@ const DraftOptions = () => { )}} Date: Mon, 14 Oct 2024 23:42:11 +0530 Subject: [PATCH 015/111] Done with send drafts --- app/components/draft/draft.tsx | 2 +- .../post_draft/send_handler/send_handler.tsx | 2 +- app/hooks/handle_send_message.ts | 12 +- app/screens/draft_options/index.tsx | 7 + .../draft_options/send_draft/index.tsx | 145 +++++++++++------- .../draft_options/send_draft/send_draft.tsx | 115 ++++++++++++++ 6 files changed, 218 insertions(+), 65 deletions(-) create mode 100644 app/screens/draft_options/send_draft/send_draft.tsx diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 31e7f448620..cb08460d795 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -61,7 +61,7 @@ const Draft: React.FC = ({ screen: Screens.DRAFT_OPTIONS, theme, title, - props: {channel, rootId: draft.rootId}, + props: {channel, rootId: draft.rootId, draft}, }); }; diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index 9440438263c..514101dc7e7 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -119,7 +119,7 @@ export default function SendHandler({ addFiles={addFiles} uploadFileError={uploadFileError} sendMessage={handleSendMessage} - canSend={canSend()} + canSend={canSend} maxMessageLength={maxMessageLength} updatePostInputTop={updatePostInputTop} postPriority={postPriority} diff --git a/app/hooks/handle_send_message.ts b/app/hooks/handle_send_message.ts index 817f49ce9e6..01980a7226f 100644 --- a/app/hooks/handle_send_message.ts +++ b/app/hooks/handle_send_message.ts @@ -37,7 +37,7 @@ type Props = { currentUserId: string; channelType: ChannelType | undefined; postPriority: PostPriority; - clearDraft: () => void; + clearDraft?: () => void; } export const useHandleSendMessage = ({ @@ -82,7 +82,7 @@ export const useHandleSendMessage = ({ const handleReaction = useCallback((emoji: string, add: boolean) => { handleReactionToLatestPost(serverUrl, emoji, add, rootId); - clearDraft(); + clearDraft?.(); setSendingMessage(false); }, [serverUrl, rootId, clearDraft]); @@ -107,7 +107,7 @@ export const useHandleSendMessage = ({ createPost(serverUrl, post, postFiles); - clearDraft(); + clearDraft?.(); setSendingMessage(false); DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); }, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]); @@ -126,7 +126,7 @@ export const useHandleSendMessage = ({ const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, channelType ?? '', rootId, currentUserId, intl); if (handled) { setSendingMessage(false); - clearDraft(); + clearDraft?.(); return; } if (error) { @@ -160,7 +160,7 @@ export const useHandleSendMessage = ({ return; } - clearDraft(); + clearDraft?.(); if (data?.goto_location && !value.startsWith('/leave')) { handleGotoLocation(serverUrl, intl, data.goto_location); @@ -226,6 +226,6 @@ export const useHandleSendMessage = ({ return { handleSendMessage, - canSend, + canSend: canSend(), }; }; diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index f7624d5e027..2ba4ea45457 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -15,10 +15,12 @@ import EditDraft from './edit_draft'; import SendDraft from './send_draft'; import type ChannelModel from '@typings/database/models/servers/channel'; +import type DraftModel from '@typings/database/models/servers/draft'; type Props = { channel: ChannelModel; rootId: string; + draft: DraftModel; } export const DRAFT_OPTIONS_BUTTON = 'close-post-options'; @@ -34,6 +36,7 @@ const styles = StyleSheet.create({ const DraftOptions: React.FC = ({ channel, rootId, + draft, }) => { const {formatMessage} = useIntl(); const isTablet = useIsTablet(); @@ -50,6 +53,10 @@ const DraftOptions: React.FC = ({ /> ({ - title: { - color: theme.centerChannelColor, - ...typography('Body', 200), - }, - draftOptions: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: 16, - paddingVertical: 12, - }, -})); - -const SendDraft: React.FC = ({ - bottomSheetId, -}) => { - const theme = useTheme(); - const intl = useIntl(); - const style = getStyleSheet(theme); - - const draftSendHandler = async () => { - await dismissBottomSheet(bottomSheetId); - }; +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {combineLatest, of as of$} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; + +import {INITIAL_PRIORITY} from '@app/components/post_draft/send_handler/send_handler'; +import {General, Permissions} from '@constants'; +import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; +import {observeChannel, observeChannelInfo, observeCurrentChannel} from '@queries/servers/channel'; +import {queryAllCustomEmojis} from '@queries/servers/custom_emoji'; +import {observeFirstDraft, queryDraft} from '@queries/servers/drafts'; +import {observePermissionForChannel} from '@queries/servers/role'; +import {observeConfigBooleanValue, observeConfigIntValue, observeCurrentUserId} from '@queries/servers/system'; +import {observeUser} from '@queries/servers/user'; + +import SendDraft from './send_draft'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +type Props ={ + rootId: string; + channelId: string; +} & WithDatabaseArgs; + +const enhanced = withObservables([], ({database, rootId, channelId}: Props) => { + let channel; + if (rootId) { + channel = observeChannel(database, channelId); + } else { + channel = observeCurrentChannel(database); + } + + const currentUserId = observeCurrentUserId(database); + const currentUser = currentUserId.pipe( + switchMap((id) => observeUser(database, id)), + ); + const userIsOutOfOffice = currentUser.pipe( + switchMap((u) => of$(u?.status === General.OUT_OF_OFFICE)), + ); + + const postPriority = queryDraft(database, channelId, rootId).observeWithColumns(['metadata']).pipe( + switchMap(observeFirstDraft), + switchMap((d) => { + if (!d?.metadata?.priority) { + return of$(INITIAL_PRIORITY); + } - return ( - - - {intl.formatMessage({id: 'draft.options.save.title', defaultMessage: 'Save'})} - + return of$(d.metadata.priority); + }), ); -}; -export default SendDraft; + const enableConfirmNotificationsToChannel = observeConfigBooleanValue(database, 'EnableConfirmNotificationsToChannel'); + const maxMessageLength = observeConfigIntValue(database, 'MaxPostSize', MAX_MESSAGE_LENGTH_FALLBACK); + const persistentNotificationInterval = observeConfigIntValue(database, 'PersistentNotificationIntervalMinutes'); + const persistentNotificationMaxRecipients = observeConfigIntValue(database, 'PersistentNotificationMaxRecipients'); + + const useChannelMentions = combineLatest([channel, currentUser]).pipe( + switchMap(([c, u]) => { + if (!c) { + return of$(true); + } + + return u ? observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false); + }), + ); + + const channelInfo = channel.pipe(switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$(undefined)))); + const channelType = channel.pipe(switchMap((c) => of$(c?.type))); + const channelName = channel.pipe(switchMap((c) => of$(c?.name))); + const membersCount = channelInfo.pipe( + switchMap((i) => (i ? of$(i.memberCount) : of$(0))), + ); + + const customEmojis = queryAllCustomEmojis(database).observe(); + + return { + channelType, + channelName, + currentUserId, + enableConfirmNotificationsToChannel, + maxMessageLength, + membersCount, + userIsOutOfOffice, + useChannelMentions, + customEmojis, + persistentNotificationInterval, + persistentNotificationMaxRecipients, + postPriority, + }; +}); + +export default withDatabase(enhanced(SendDraft)); diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx new file mode 100644 index 00000000000..466e31027b3 --- /dev/null +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -0,0 +1,115 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Text} from 'react-native'; + +import {removeDraft} from '@actions/local/draft'; +import CompassIcon from '@app/components/compass_icon'; +import TouchableWithFeedback from '@app/components/touchable_with_feedback'; +import {ICON_SIZE} from '@app/constants/post_draft'; +import {useServerUrl} from '@app/context/server'; +import {useTheme} from '@app/context/theme'; +import {useHandleSendMessage} from '@app/hooks/handle_send_message'; +import {dismissBottomSheet} from '@app/screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; + +import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; +import type {AvailableScreens} from '@typings/screens/navigation'; + +type Props = { + channelId: string; + rootId: string; + channelType: ChannelType | undefined; + currentUserId: string; + enableConfirmNotificationsToChannel?: boolean; + maxMessageLength: number; + membersCount?: number; + useChannelMentions: boolean; + userIsOutOfOffice: boolean; + customEmojis: CustomEmojiModel[]; + bottomSheetId: AvailableScreens; + value: string; + files: FileInfo[]; + postPriority: PostPriority; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + title: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + draftOptions: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 16, + paddingVertical: 12, + }, +})); + +const SendDraft: React.FC = ({ + channelId, + rootId, + channelType, + bottomSheetId, + currentUserId, + enableConfirmNotificationsToChannel, + maxMessageLength, + membersCount = 0, + useChannelMentions, + userIsOutOfOffice, + customEmojis, + value, + files, + postPriority, +}) => { + const theme = useTheme(); + const intl = useIntl(); + const style = getStyleSheet(theme); + const serverUrl = useServerUrl(); + const clearDraft = () => { + removeDraft(serverUrl, channelId, rootId); + }; + + const {handleSendMessage} = useHandleSendMessage({ + value, + channelId, + rootId, + files, + maxMessageLength, + customEmojis, + enableConfirmNotificationsToChannel, + useChannelMentions, + membersCount, + userIsOutOfOffice, + currentUserId, + channelType, + postPriority, + clearDraft, + }); + + const draftSendHandler = async () => { + await dismissBottomSheet(bottomSheetId); + handleSendMessage(); + }; + + return ( + + + {intl.formatMessage({id: 'draft.options.save.title', defaultMessage: 'Save'})} + + ); +}; + +export default SendDraft; From 554123f649b3f9bb2a1e24a4bc8b75d2d1ded7dd Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 15 Oct 2024 15:15:02 +0530 Subject: [PATCH 016/111] done with delete drafts --- app/screens/draft_options/delete_draft/index.tsx | 8 ++++++++ app/screens/draft_options/index.tsx | 2 ++ 2 files changed, 10 insertions(+) diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx index a0325f83d4d..480ba1f7b58 100644 --- a/app/screens/draft_options/delete_draft/index.tsx +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -5,9 +5,11 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {Text} from 'react-native'; +import {removeDraft} from '@actions/local/draft'; import CompassIcon from '@app/components/compass_icon'; import TouchableWithFeedback from '@app/components/touchable_with_feedback'; import {ICON_SIZE} from '@app/constants/post_draft'; +import {useServerUrl} from '@app/context/server'; import {useTheme} from '@app/context/theme'; import {dismissBottomSheet} from '@app/screens/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; @@ -17,6 +19,8 @@ import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { bottomSheetId: AvailableScreens; + channelId: string; + rootId: string; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -35,13 +39,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const DeleteDraft: React.FC = ({ bottomSheetId, + channelId, + rootId, }) => { const theme = useTheme(); const intl = useIntl(); const style = getStyleSheet(theme); + const serverUrl = useServerUrl(); const draftDeleteHandler = async () => { await dismissBottomSheet(bottomSheetId); + removeDraft(serverUrl, channelId, rootId); }; return ( diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index 2ba4ea45457..89261de8a6e 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -60,6 +60,8 @@ const DraftOptions: React.FC = ({ /> ); From e629247d63cabea9ded93e6ec90572ea72b778ac Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 15 Oct 2024 16:51:59 +0530 Subject: [PATCH 017/111] change save to send draft --- app/screens/draft_options/send_draft/send_draft.tsx | 2 +- assets/base/i18n/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index 466e31027b3..eea9b6fe708 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -107,7 +107,7 @@ const SendDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.save.title', defaultMessage: 'Save'})} + {intl.formatMessage({id: 'draft.options.send.title', defaultMessage: 'Send'})} ); }; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index eab074c2329..2555833b9aa 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -310,7 +310,7 @@ "drafts": "Drafts", "draft.options.delete.title": "Delete", "draft.options.edit.title": "Edit", - "draft.options.save.title": "Save", + "draft.options.send.title": "Send", "draft.options.header": "Draft actions", "draft.options.title": "Draft Options", "download.error": "Unable to download the file. Try again later", From f8bcead69ef340fb900352ad63e03c68f18bfc8c Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 15 Oct 2024 17:09:43 +0530 Subject: [PATCH 018/111] handle lengthy message with show more button --- .../draft/draft_post/draft_message/index.tsx | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message/index.tsx index 28423a9543e..993a61a7fd9 100644 --- a/app/components/draft/draft_post/draft_message/index.tsx +++ b/app/components/draft/draft_post/draft_message/index.tsx @@ -1,10 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; -import {View} from 'react-native'; +import React, {useCallback, useState} from 'react'; +import {ScrollView, View, useWindowDimensions, type LayoutChangeEvent} from 'react-native'; +import Animated from 'react-native-reanimated'; +import ShowMoreButton from '@app/components/post_list/post/body/message/show_more_button'; import {useTheme} from '@app/context/theme'; +import {useShowMoreAnimatedStyle} from '@app/hooks/show_more'; import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@app/utils/markdown'; import {makeStyleSheetFromTheme} from '@app/utils/theme'; import {typography} from '@app/utils/typography'; @@ -20,6 +23,7 @@ type Props = { } const EMPTY_MENTION_KEYS: UserMentionKey[] = []; +const SHOW_MORE_HEIGHT = 54; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { @@ -48,21 +52,52 @@ const DraftMessage: React.FC = ({ const style = getStyleSheet(theme); const blockStyles = getMarkdownBlockStyles(theme); const textStyles = getMarkdownTextStyles(theme); + const [height, setHeight] = useState(); + const [open, setOpen] = useState(false); + const dimensions = useWindowDimensions(); + const maxHeight = Math.round((dimensions.height * 0.5) + SHOW_MORE_HEIGHT); + const animatedStyle = useShowMoreAnimatedStyle(height, maxHeight, open); + + const onLayout = useCallback((event: LayoutChangeEvent) => setHeight(event.nativeEvent.layout.height), []); + const onPress = () => setOpen(!open); + return ( - - + + + + + + + + {(height || 0) > maxHeight && + - + } + ); }; From 6577c6e8a6ef2e24dc353568b3ec7a02f292ce4f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 17 Oct 2024 15:48:51 +0530 Subject: [PATCH 019/111] done with persistent message edit, send and delete drafts --- app/components/draft/draft.tsx | 16 ++++++++ app/components/draft/draft_post/index.tsx | 14 +------ app/components/draft/index.tsx | 2 + .../post_draft/draft_input/index.tsx | 25 ++++------- app/hooks/persistent_notification_props.ts | 41 +++++++++++++++++++ .../draft_options/delete_draft/index.tsx | 2 +- .../draft_options/edit_draft/index.tsx | 2 +- .../draft_options/send_draft/index.tsx | 9 +--- .../draft_options/send_draft/send_draft.tsx | 27 ++++++++++-- assets/base/i18n/en.json | 6 +-- 10 files changed, 98 insertions(+), 46 deletions(-) create mode 100644 app/hooks/persistent_notification_props.ts diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index cb08460d795..83e15c0cbb1 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -13,6 +13,7 @@ import {useIsTablet} from '@app/hooks/device'; import {DRAFT_OPTIONS_BUTTON} from '@app/screens/draft_options'; import {openAsBottomSheet} from '@app/screens/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import Header from '@components/post_draft/draft_input/header'; import type ChannelModel from '@typings/database/models/servers/channel'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -24,6 +25,7 @@ type Props = { draftReceiverUser?: UserModel; draft: DraftModel; layoutWidth: number; + isPostPriorityEnabled: boolean; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { @@ -38,6 +40,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { pressInContainer: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.16), }, + postPriority: { + marginTop: 10, + marginLeft: -12, + }, }; }); @@ -47,11 +53,13 @@ const Draft: React.FC = ({ draft, draftReceiverUser, layoutWidth, + isPostPriorityEnabled, }) => { const intl = useIntl(); const theme = useTheme(); const style = getStyleSheet(theme); const isTablet = useIsTablet(); + const showPostPriority = Boolean(isPostPriorityEnabled && draft.metadata?.priority && draft.metadata?.priority?.priority); const onLongPress = () => { Keyboard.dismiss(); @@ -80,6 +88,14 @@ const Draft: React.FC = ({ rootId={draft.rootId} testID='draft_post.channel_info' /> + {showPostPriority && draft.metadata?.priority && + +
+ + } = ({ }) => { const theme = useTheme(); const hasFiles = draft.files.length > 0; - const acknowledgementsVisible = draft.metadata?.priority?.requested_ack; const style = getStyleSheet(theme); return ( @@ -65,16 +63,6 @@ const DraftPost: React.FC = ({ layoutWidth={layoutWidth} /> } - { - acknowledgementsVisible && - - - - } ); }; diff --git a/app/components/draft/index.tsx b/app/components/draft/index.tsx index 49543a2ed61..5797eecf578 100644 --- a/app/components/draft/index.tsx +++ b/app/components/draft/index.tsx @@ -6,6 +6,7 @@ import React from 'react'; import {switchMap, of} from 'rxjs'; import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; +import {observeIsPostPriorityEnabled} from '@app/queries/servers/post'; import {observeCurrentUser, observeUser} from '@app/queries/servers/user'; import Drafts from './draft'; @@ -76,6 +77,7 @@ const enhance = withObservables(['channel', 'members'], ({channel, database, cur return { channel, draftReceiverUser, + isPostPriorityEnabled: observeIsPostPriorityEnabled(database), }; }); diff --git a/app/components/post_draft/draft_input/index.tsx b/app/components/post_draft/draft_input/index.tsx index 4a2e4e7b4df..7b8066d8b46 100644 --- a/app/components/post_draft/draft_input/index.tsx +++ b/app/components/post_draft/draft_input/index.tsx @@ -1,14 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {useCallback, useRef} from 'react'; import {useIntl} from 'react-intl'; import {type LayoutChangeEvent, Platform, ScrollView, View} from 'react-native'; import {type Edge, SafeAreaView} from 'react-native-safe-area-context'; -import {General} from '@constants'; -import {MENTIONS_REGEX} from '@constants/autocomplete'; -import {PostPriorityType} from '@constants/post'; +import {usePersistentNotificationProps} from '@app/hooks/persistent_notification_props'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {persistentNotificationsConfirmation} from '@utils/post'; @@ -149,20 +147,11 @@ export default function DraftInput({ const sendActionTestID = `${testID}.send_action`; const style = getStyleSheet(theme); - const persistentNotificationsEnabled = postPriority.persistent_notifications && postPriority.priority === PostPriorityType.URGENT; - const {noMentionsError, mentionsList} = useMemo(() => { - let error = false; - let mentions: string[] = []; - if ( - channelType !== General.DM_CHANNEL && - persistentNotificationsEnabled - ) { - mentions = (value.match(MENTIONS_REGEX) || []); - error = mentions.length === 0; - } - - return {noMentionsError: error, mentionsList: mentions}; - }, [channelType, persistentNotificationsEnabled, value]); + const {persistentNotificationsEnabled, noMentionsError, mentionsList} = usePersistentNotificationProps({ + value, + channelType, + postPriority, + }); const handleSendMessage = useCallback(async () => { if (persistentNotificationsEnabled) { diff --git a/app/hooks/persistent_notification_props.ts b/app/hooks/persistent_notification_props.ts new file mode 100644 index 00000000000..6d18c4ac073 --- /dev/null +++ b/app/hooks/persistent_notification_props.ts @@ -0,0 +1,41 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {useMemo} from 'react'; + +import {General} from '@app/constants'; +import {MENTIONS_REGEX} from '@app/constants/autocomplete'; +import {PostPriorityType} from '@app/constants/post'; + +type Props = { + value: string; + channelType: ChannelType | undefined; + postPriority: PostPriority; +} + +export const usePersistentNotificationProps = ({ + value, + channelType, + postPriority, +}: Props) => { + const persistentNotificationsEnabled = postPriority.persistent_notifications && postPriority.priority === PostPriorityType.URGENT; + const {noMentionsError, mentionsList} = useMemo(() => { + let error = false; + let mentions: string[] = []; + if ( + channelType !== General.DM_CHANNEL && + persistentNotificationsEnabled + ) { + mentions = (value.match(MENTIONS_REGEX) || []); + error = mentions.length === 0; + } + + return {noMentionsError: error, mentionsList: mentions}; + }, [channelType, persistentNotificationsEnabled, value]); + + return { + noMentionsError, + mentionsList, + persistentNotificationsEnabled, + }; +}; diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx index 480ba1f7b58..db97a20ae80 100644 --- a/app/screens/draft_options/delete_draft/index.tsx +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -63,7 +63,7 @@ const DeleteDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.delete.title', defaultMessage: 'Delete'})} + {intl.formatMessage({id: 'draft.options.delete.title', defaultMessage: 'Delete draft'})} ); }; diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 91cd309b177..9a248e7eebd 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -69,7 +69,7 @@ const EditDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Edit'})} + {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Send draft'})} ); }; diff --git a/app/screens/draft_options/send_draft/index.tsx b/app/screens/draft_options/send_draft/index.tsx index b9f54f3c1e7..db4e55e1651 100644 --- a/app/screens/draft_options/send_draft/index.tsx +++ b/app/screens/draft_options/send_draft/index.tsx @@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators'; import {INITIAL_PRIORITY} from '@app/components/post_draft/send_handler/send_handler'; import {General, Permissions} from '@constants'; import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; -import {observeChannel, observeChannelInfo, observeCurrentChannel} from '@queries/servers/channel'; +import {observeChannel, observeChannelInfo} from '@queries/servers/channel'; import {queryAllCustomEmojis} from '@queries/servers/custom_emoji'; import {observeFirstDraft, queryDraft} from '@queries/servers/drafts'; import {observePermissionForChannel} from '@queries/servers/role'; @@ -25,12 +25,7 @@ type Props ={ } & WithDatabaseArgs; const enhanced = withObservables([], ({database, rootId, channelId}: Props) => { - let channel; - if (rootId) { - channel = observeChannel(database, channelId); - } else { - channel = observeCurrentChannel(database); - } + const channel = observeChannel(database, channelId); const currentUserId = observeCurrentUserId(database); const currentUser = currentUserId.pipe( diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index eea9b6fe708..06c3b22a144 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -12,7 +12,9 @@ import {ICON_SIZE} from '@app/constants/post_draft'; import {useServerUrl} from '@app/context/server'; import {useTheme} from '@app/context/theme'; import {useHandleSendMessage} from '@app/hooks/handle_send_message'; +import {usePersistentNotificationProps} from '@app/hooks/persistent_notification_props'; import {dismissBottomSheet} from '@app/screens/navigation'; +import {persistentNotificationsConfirmation} from '@app/utils/post'; import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; import {typography} from '@app/utils/typography'; @@ -24,6 +26,7 @@ type Props = { rootId: string; channelType: ChannelType | undefined; currentUserId: string; + channelName: string | undefined; enableConfirmNotificationsToChannel?: boolean; maxMessageLength: number; membersCount?: number; @@ -34,6 +37,8 @@ type Props = { value: string; files: FileInfo[]; postPriority: PostPriority; + persistentNotificationInterval: number; + persistentNotificationMaxRecipients: number; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -48,10 +53,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ gap: 16, paddingVertical: 12, }, + disabled: { + color: 'red', + }, })); const SendDraft: React.FC = ({ channelId, + channelName, rootId, channelType, bottomSheetId, @@ -65,6 +74,8 @@ const SendDraft: React.FC = ({ value, files, postPriority, + persistentNotificationInterval, + persistentNotificationMaxRecipients, }) => { const theme = useTheme(); const intl = useIntl(); @@ -74,6 +85,12 @@ const SendDraft: React.FC = ({ removeDraft(serverUrl, channelId, rootId); }; + const {persistentNotificationsEnabled, mentionsList} = usePersistentNotificationProps({ + value, + channelType, + postPriority, + }); + const {handleSendMessage} = useHandleSendMessage({ value, channelId, @@ -93,13 +110,17 @@ const SendDraft: React.FC = ({ const draftSendHandler = async () => { await dismissBottomSheet(bottomSheetId); - handleSendMessage(); + if (persistentNotificationsEnabled) { + persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, handleSendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType); + } else { + handleSendMessage(); + } }; return ( = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.send.title', defaultMessage: 'Send'})} + {intl.formatMessage({id: 'draft.options.send.title', defaultMessage: 'Edit draft'})} ); }; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 2555833b9aa..8ee6f3d3c02 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -308,9 +308,9 @@ "display_settings.tz.auto": "Auto", "display_settings.tz.manual": "Manual", "drafts": "Drafts", - "draft.options.delete.title": "Delete", - "draft.options.edit.title": "Edit", - "draft.options.send.title": "Send", + "draft.options.delete.title": "Delete draft", + "draft.options.edit.title": "Edit draft", + "draft.options.send.title": "Send draft", "draft.options.header": "Draft actions", "draft.options.title": "Draft Options", "download.error": "Unable to download the file. Try again later", From d331d46db9b4dcd5fb88439a9e3fbfb5ca4576ad Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 17 Oct 2024 16:14:24 +0530 Subject: [PATCH 020/111] added alert for sending message --- app/components/draft/draft.tsx | 2 +- app/screens/draft_options/index.tsx | 3 ++ .../draft_options/send_draft/send_draft.tsx | 16 +++++++-- app/utils/post/index.ts | 34 +++++++++++++++++++ assets/base/i18n/en.json | 3 ++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 83e15c0cbb1..47cda49da4c 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -69,7 +69,7 @@ const Draft: React.FC = ({ screen: Screens.DRAFT_OPTIONS, theme, title, - props: {channel, rootId: draft.rootId, draft}, + props: {channel, rootId: draft.rootId, draft, draftReceiverUserName: draftReceiverUser?.username}, }); }; diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index 89261de8a6e..81d27da8a93 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -21,6 +21,7 @@ type Props = { channel: ChannelModel; rootId: string; draft: DraftModel; + draftReceiverUserName: string | undefined; } export const DRAFT_OPTIONS_BUTTON = 'close-post-options'; @@ -37,6 +38,7 @@ const DraftOptions: React.FC = ({ channel, rootId, draft, + draftReceiverUserName, }) => { const {formatMessage} = useIntl(); const isTablet = useIsTablet(); @@ -57,6 +59,7 @@ const DraftOptions: React.FC = ({ rootId={rootId} files={draft.files} value={draft.message} + draftReceiverUserName={draftReceiverUserName} /> ({ @@ -76,6 +78,7 @@ const SendDraft: React.FC = ({ postPriority, persistentNotificationInterval, persistentNotificationMaxRecipients, + draftReceiverUserName, }) => { const theme = useTheme(); const intl = useIntl(); @@ -113,7 +116,16 @@ const SendDraft: React.FC = ({ if (persistentNotificationsEnabled) { persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, handleSendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType); } else { - handleSendMessage(); + const receivingChannel = channelType === General.DM_CHANNEL ? draftReceiverUserName : channelName; + sendMessageWithAlert({ + title: intl.formatMessage({ + id: 'send_message.confirm.title', + defaultMessage: 'Send message now', + }), + intl, + channelName: receivingChannel || '', + sendMessageHandler: handleSendMessage, + }); } }; diff --git a/app/utils/post/index.ts b/app/utils/post/index.ts index dd29cdd5c6e..202fb08cac2 100644 --- a/app/utils/post/index.ts +++ b/app/utils/post/index.ts @@ -218,3 +218,37 @@ export async function persistentNotificationsConfirmation(serverUrl: string, val buttons, ); } + +export async function sendMessageWithAlert({title, channelName, intl, sendMessageHandler}: { + title: string; + channelName: string; + intl: IntlShape; + sendMessageHandler: () => void; +}) { + const buttons: AlertButton[] = [{ + text: intl.formatMessage({ + id: 'send_message.confirm.cancel', + defaultMessage: 'Cancel', + }), + style: 'cancel', + }, { + text: intl.formatMessage({ + id: 'send_message.confirm.send', + defaultMessage: 'Send', + }), + onPress: sendMessageHandler, + }]; + + const description = intl.formatMessage({ + id: 'send_message.confirm.description', + defaultMessage: 'Are you sure you want to send this message to {channelName} now?', + }, { + channelName, + }); + + Alert.alert( + title, + description, + buttons, + ); +} diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 8ee6f3d3c02..185248a261c 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1015,6 +1015,9 @@ "screens.channel_info.gm": "Group message info", "search_bar.search": "Search", "search_bar.search.placeholder": "Search timezone", + "send_message.confirm.description": "Are you sure you want to send this message to {channelName} now?", + "send_message.confirm.cancel": "Cancel", + "send_message.confirm.send": "Send", "select_team.description": "You are not yet a member of any teams. Select one below to get started.", "select_team.no_team.description": "To join a team, ask a team admin for an invite, or create your own team. You may also want to check your email inbox for an invitation.", "select_team.no_team.title": "No teams are available to join", From 62d0bbbcdc97f319f5c731edb0dc09c3a806de97 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 17 Oct 2024 23:14:58 +0530 Subject: [PATCH 021/111] added update at time for the drafts --- app/components/channel_info/channel_info.tsx | 154 +++++++++++++++++++ app/components/channel_info/index.tsx | 138 ++--------------- app/components/draft/draft.tsx | 1 + app/queries/servers/drafts.ts | 1 + 4 files changed, 170 insertions(+), 124 deletions(-) create mode 100644 app/components/channel_info/channel_info.tsx diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx new file mode 100644 index 00000000000..6c33859e343 --- /dev/null +++ b/app/components/channel_info/channel_info.tsx @@ -0,0 +1,154 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {type ReactNode} from 'react'; +import {Text, View} from 'react-native'; + +import {General} from '@app/constants'; +import {useTheme} from '@app/context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; +import {getUserTimezone} from '@app/utils/user'; +import FormattedText from '@components/formatted_text'; +import FormattedTime from '@components/formatted_time'; + +import CompassIcon from '../compass_icon'; + +import Avatar from './avatar'; + +import type ChannelModel from '@typings/database/models/servers/channel'; +import type PostModel from '@typings/database/models/servers/post'; +import type UserModel from '@typings/database/models/servers/user'; + +type Props = { + channel: ChannelModel; + draftReceiverUser?: UserModel; + updateAt: number; + rootId?: PostModel['rootId']; + testID?: string; + currentUser?: UserModel; + isMilitaryTime: boolean; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + infoContainer: { + display: 'flex', + flexDirection: 'row', + gap: 5, + alignItems: 'center', + }, + channelInfo: { + display: 'flex', + flexDirection: 'row', + gap: 5, + alignItems: 'center', + }, + displayName: { + fontSize: 12, + fontWeight: '600', + color: changeOpacity(theme.centerChannelColor, 0.64), + lineHeight: 16, + fontFamily: 'Open Sans', + }, + timeInfo: { + fontSize: 12, + fontWeight: '400', + color: changeOpacity(theme.centerChannelColor, 0.64), + lineHeight: 16, + }, + time: { + color: theme.centerChannelColor, + marginTop: 5, + opacity: 0.5, + ...typography('Body', 75, 'Regular'), + }, + }; +}); + +const ChannelInfo: React.FC = ({ + channel, + draftReceiverUser, + updateAt, + rootId, + testID, + currentUser, + isMilitaryTime, +}) => { + const theme = useTheme(); + const style = getStyleSheet(theme); + const isChannelTypeDM = channel.type === General.DM_CHANNEL; + + let headerComponent: ReactNode = null; + const profileComponent = draftReceiverUser ? : ( + ); + + if (rootId) { + headerComponent = ( + + + {profileComponent} + + ); + } else if (isChannelTypeDM) { + headerComponent = ( + + + {profileComponent} + + ); + } else { + headerComponent = ( + + + {profileComponent} + + ); + } + + return ( + + + + {headerComponent} + + {channel.displayName} + + + + + ); +}; + +export default ChannelInfo; diff --git a/app/components/channel_info/index.tsx b/app/components/channel_info/index.tsx index 568010eceb2..97bbd0d634f 100644 --- a/app/components/channel_info/index.tsx +++ b/app/components/channel_info/index.tsx @@ -1,135 +1,25 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {type ReactNode} from 'react'; -import {Text, View} from 'react-native'; +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {map} from 'rxjs/operators'; -import {General} from '@app/constants'; -import {useTheme} from '@app/context/theme'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import FormattedText from '@components/formatted_text'; +import {getDisplayNamePreferenceAsBool} from '@app/helpers/api/preference'; +import {queryDisplayNamePreferences} from '@app/queries/servers/preference'; +import {observeCurrentUser} from '@app/queries/servers/user'; -import CompassIcon from '../compass_icon'; +import ChannelInfo from './channel_info'; -import Avatar from './avatar'; +const enhance = withObservables([], ({database}) => { + const currentUser = observeCurrentUser(database); + const preferences = queryDisplayNamePreferences(database). + observeWithColumns(['value']); + const isMilitaryTime = preferences.pipe(map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time'))); -import type ChannelModel from '@typings/database/models/servers/channel'; -import type PostModel from '@typings/database/models/servers/post'; -import type UserModel from '@typings/database/models/servers/user'; - -type Props = { - channel: ChannelModel; - draftReceiverUser?: UserModel; - updateAt?: string; - rootId?: PostModel['rootId']; - testID?: string; -} - -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { - container: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - infoContainer: { - display: 'flex', - flexDirection: 'row', - gap: 5, - alignItems: 'center', - }, - channelInfo: { - display: 'flex', - flexDirection: 'row', - gap: 5, - alignItems: 'center', - }, - displayName: { - fontSize: 12, - fontWeight: '600', - color: changeOpacity(theme.centerChannelColor, 0.64), - lineHeight: 16, - fontFamily: 'Open Sans', - }, - timeInfo: { - fontSize: 12, - fontWeight: '400', - color: changeOpacity(theme.centerChannelColor, 0.64), - lineHeight: 16, - }, + currentUser, + isMilitaryTime, }; }); -const ChannelInfo: React.FC = ({ - channel, - draftReceiverUser, - updateAt = 'Yesterday', - rootId, - testID, -}) => { - const theme = useTheme(); - const style = getStyleSheet(theme); - const isChannelTypeDM = channel.type === General.DM_CHANNEL; - - let headerComponent: ReactNode = null; - const profileComponent = draftReceiverUser ? : ( - ); - - if (rootId) { - headerComponent = ( - - - {profileComponent} - - ); - } else if (isChannelTypeDM) { - headerComponent = ( - - - {profileComponent} - - ); - } else { - headerComponent = ( - - - {profileComponent} - - ); - } - - return ( - - - - {headerComponent} - - {channel.displayName} - - - {updateAt && {updateAt}} - - ); -}; - -export default ChannelInfo; +export default withDatabase(enhance(ChannelInfo)); diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 47cda49da4c..9feab03046c 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -87,6 +87,7 @@ const Draft: React.FC = ({ draftReceiverUser={draftReceiverUser} rootId={draft.rootId} testID='draft_post.channel_info' + updateAt={draft.updateAt} /> {showPostPriority && draft.metadata?.priority && diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index bc410e50b0a..bb970b3d461 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -42,6 +42,7 @@ export const queryAllDrafts = (database: Database, teamId: string) => { Q.where('delete_at', 0), // Ensure the channel is not deleted ), ), + Q.sortBy('update_at', Q.desc), ); }; From 5308824a5bcbcf7b2cb37623436eccbfb938c3ea Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 22 Oct 2024 17:48:02 +0530 Subject: [PATCH 022/111] en.json extract fix --- assets/base/i18n/en.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 185248a261c..3aa4a2de780 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -173,6 +173,7 @@ "channel_info.favorited": "Favorited", "channel_info.header": "Header:", "channel_info.ignore_mentions": "Ignore @channel, @here, @all", + "channel_info.in": "In:", "channel_info.leave": "Leave", "channel_info.leave_channel": "Leave channel", "channel_info.leave_private_channel": "Are you sure you want to leave the private channel {displayName}? You cannot rejoin the channel unless you're invited again.", @@ -194,7 +195,6 @@ "channel_info.send_a_mesasge": "Send a message", "channel_info.send_mesasge": "Send message", "channel_info.set_header": "Set Header", - "channel_info.in": "In:", "channel_info.thread_in": "Thread in:", "channel_info.to": "To:", "channel_info.unarchive": "Unarchive Channel", @@ -307,13 +307,14 @@ "display_settings.timezone": "Timezone", "display_settings.tz.auto": "Auto", "display_settings.tz.manual": "Manual", - "drafts": "Drafts", + "download.error": "Unable to download the file. Try again later", + "draft.option.header": "Draft actions", "draft.options.delete.title": "Delete draft", "draft.options.edit.title": "Edit draft", "draft.options.send.title": "Send draft", - "draft.options.header": "Draft actions", "draft.options.title": "Draft Options", - "download.error": "Unable to download the file. Try again later", + "drafts": "Drafts", + "Drafts": "Drafts", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", "edit_server.description": "Specify a display name for this server", @@ -1015,13 +1016,14 @@ "screens.channel_info.gm": "Group message info", "search_bar.search": "Search", "search_bar.search.placeholder": "Search timezone", - "send_message.confirm.description": "Are you sure you want to send this message to {channelName} now?", - "send_message.confirm.cancel": "Cancel", - "send_message.confirm.send": "Send", "select_team.description": "You are not yet a member of any teams. Select one below to get started.", "select_team.no_team.description": "To join a team, ask a team admin for an invite, or create your own team. You may also want to check your email inbox for an invitation.", "select_team.no_team.title": "No teams are available to join", "select_team.title": "Select a team", + "send_message.confirm.cancel": "Cancel", + "send_message.confirm.description": "Are you sure you want to send this message to {channelName} now?", + "send_message.confirm.send": "Send", + "send_message.confirm.title": "Send message now", "server_list.push_proxy_error": "Notifications cannot be received from this server because of its configuration. Contact your system admin.", "server_list.push_proxy_unknown": "Notifications could not be received from this server because of its configuration. Log out and Log in again to retry.", "server_upgrade.alert_description": "Your server, {serverDisplayName}, is running an unsupported server version. Users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {supportedServerVersion} or later is required.", From 611cff03732920cc8e9dc4bba4b8cf2089aaeee7 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 23 Oct 2024 14:05:19 +0530 Subject: [PATCH 023/111] Updated dependencies for useCallback --- app/hooks/handle_send_message.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/hooks/handle_send_message.ts b/app/hooks/handle_send_message.ts index 01980a7226f..dbf3c086a20 100644 --- a/app/hooks/handle_send_message.ts +++ b/app/hooks/handle_send_message.ts @@ -110,7 +110,7 @@ export const useHandleSendMessage = ({ clearDraft?.(); setSendingMessage(false); DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); - }, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]); + }, [files, currentUserId, channelId, rootId, value, postPriority, serverUrl, clearDraft]); const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => { const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, channelTimezoneCount, atHere); @@ -165,7 +165,7 @@ export const useHandleSendMessage = ({ if (data?.goto_location && !value.startsWith('/leave')) { handleGotoLocation(serverUrl, intl, data.goto_location); } - }, [userIsOutOfOffice, currentUserId, intl, value, serverUrl, channelId, rootId]); + }, [value, userIsOutOfOffice, serverUrl, intl, channelId, rootId, clearDraft, channelType, currentUserId]); const sendMessage = useCallback(() => { const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions; @@ -179,15 +179,7 @@ export const useHandleSendMessage = ({ } else { doSubmitMessage(); } - }, [ - enableConfirmNotificationsToChannel, - useChannelMentions, - value, - channelTimezoneCount, - sendCommand, - showSendToAllOrChannelOrHereAlert, - doSubmitMessage, - ]); + }, [enableConfirmNotificationsToChannel, useChannelMentions, value, membersCount, sendCommand, showSendToAllOrChannelOrHereAlert, doSubmitMessage]); const handleSendMessage = useCallback(preventDoubleTap(() => { if (!canSend()) { From aac1f38fc6c56219ec2add1c793ddbf51995c2f6 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 24 Oct 2024 16:43:32 +0530 Subject: [PATCH 024/111] refactor: added drafts list to animated list --- .../global_drafts_list/global_drafts_list.tsx | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 5734f0e0dbf..6cec0343de9 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -1,10 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useState} from 'react'; -import {ScrollView, type LayoutChangeEvent} from 'react-native'; +import {View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; +import Animated from 'react-native-reanimated'; import Draft from '@app/components/draft'; +import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@app/components/post_list/config'; import {Screens} from '@app/constants'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -14,6 +17,9 @@ type Props = { location: string; } +const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); +const keyExtractor = (item: DraftModel) => item.id; + const GlobalDraftsList: React.FC = ({ allDrafts, location, @@ -24,25 +30,35 @@ const GlobalDraftsList: React.FC = ({ setLayoutWidth(e.nativeEvent.layout.width - 40); // 40 is the padding of the container } }, [location]); + + const renderItem = useCallback(({item}: ListRenderItemInfo) => { + return ( + + ); + }, [layoutWidth, location]); + return ( - - {allDrafts.map((draft) => { - return ( - - ); - })} - ); + + + ); }; export default GlobalDraftsList; From 8c153d6e4b662e27b8fca99aa3fa0a63bd2a9f37 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Sun, 27 Oct 2024 01:27:25 +0530 Subject: [PATCH 025/111] added swipeable component and delete conformation for drafts --- app/constants/events.ts | 1 + .../draft_options/edit_draft/index.tsx | 2 +- .../SwipeableDraft/index.tsx | 128 ++++++++++++++++++ .../global_drafts_list/global_drafts_list.tsx | 9 +- app/utils/draft/index.ts | 48 +++++++ 5 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx diff --git a/app/constants/events.ts b/app/constants/events.ts index 27ed3268472..13c73a2d943 100644 --- a/app/constants/events.ts +++ b/app/constants/events.ts @@ -31,4 +31,5 @@ export default keyMirror({ SEND_TO_POST_DRAFT: null, CRT_TOGGLED: null, JOIN_CALL_BAR_VISIBLE: null, + DRAFT_SWIPEABLE: null, }); diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 9a248e7eebd..78bcac20484 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -69,7 +69,7 @@ const EditDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Send draft'})} + {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Edit draft'})} ); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx new file mode 100644 index 00000000000..5e7f10dc44c --- /dev/null +++ b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx @@ -0,0 +1,128 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useEffect, useRef} from 'react'; +import {useIntl} from 'react-intl'; +import {Text, Animated, DeviceEventEmitter, TouchableWithoutFeedback} from 'react-native'; +import {Swipeable} from 'react-native-gesture-handler'; + +import CompassIcon from '@app/components/compass_icon'; +import Draft from '@app/components/draft'; +import {Events} from '@app/constants'; +import {useServerUrl} from '@app/context/server'; +import {useTheme} from '@app/context/theme'; +import {deleteDraftConfirmation} from '@app/utils/draft'; +import {makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@app/utils/typography'; + +import type DraftModel from '@typings/database/models/servers/draft'; + +type Props = { + item: DraftModel; + location: string; + layoutWidth: number; +} + +const getStyles = makeStyleSheetFromTheme((theme) => { + return { + deleteContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: theme.dndIndicator, + paddingHorizontal: 24, + paddingVertical: 16, + }, + deleteText: { + color: theme.sidebarText, + ...typography('Body'), + }, + deleteIcon: { + color: theme.sidebarText, + ...typography('Heading'), + }, + }; +}); + +const SwipeableDraft: React.FC = ({ + item, + location, + layoutWidth, +}) => { + const swipeable = useRef(null); + const theme = useTheme(); + const intl = useIntl(); + const styles = getStyles(theme); + const serverUrl = useServerUrl(); + + const onSwipeableWillOpen = useCallback(() => { + DeviceEventEmitter.emit(Events.DRAFT_SWIPEABLE, item.id); + }, [item.id]); + + const deleteDraft = useCallback(() => { + deleteDraftConfirmation({ + intl, + serverUrl, + channelId: item.channelId, + rootId: item.rootId, + swipeable, + }); + }, [intl, item.channelId, item.rootId, serverUrl]); + + const renderAction = useCallback((progress: Animated.AnimatedInterpolation) => { + const trans = progress.interpolate({ + inputRange: [0, 1], + outputRange: [layoutWidth + 40, 0], + extrapolate: 'clamp', + }); + + return ( + + + + {intl.formatMessage({ + id: 'drafts.delete', + defaultMessage: 'Delete', + })} + + + ); + }, [deleteDraft, intl, layoutWidth, styles.deleteContainer, styles.deleteText, theme.sidebarText]); + + useEffect(() => { + const listener = DeviceEventEmitter.addListener(Events.DRAFT_SWIPEABLE, (draftId: string) => { + if (item.id !== draftId) { + swipeable.current?.close(); + } + }); + + return () => listener.remove(); + }, [item.id]); + + return ( + + + + ); +}; + +export default SwipeableDraft; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 6cec0343de9..c72151c9ad1 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -6,10 +6,11 @@ import React, {useCallback, useState} from 'react'; import {View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; -import Draft from '@app/components/draft'; import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@app/components/post_list/config'; import {Screens} from '@app/constants'; +import SwipeableDraft from './SwipeableDraft'; + import type DraftModel from '@typings/database/models/servers/draft'; type Props = { @@ -33,10 +34,8 @@ const GlobalDraftsList: React.FC = ({ const renderItem = useCallback(({item}: ListRenderItemInfo) => { return ( - diff --git a/app/utils/draft/index.ts b/app/utils/draft/index.ts index 3f88fca614b..0d41b7f037f 100644 --- a/app/utils/draft/index.ts +++ b/app/utils/draft/index.ts @@ -3,11 +3,13 @@ import {Alert, type AlertButton} from 'react-native'; +import {removeDraft} from '@actions/local/draft'; import {General} from '@constants'; import {CODE_REGEX} from '@constants/autocomplete'; import {t} from '@i18n'; import type {IntlShape, MessageDescriptor} from 'react-intl'; +import type {Swipeable} from 'react-native-gesture-handler'; type AlertCallback = (value?: string) => void; @@ -170,3 +172,49 @@ export function alertSlashCommandFailed(intl: IntlShape, error: string) { error, ); } + +export function deleteDraftConfirmation({intl, serverUrl, channelId, rootId, swipeable}: { + intl: IntlShape; + serverUrl: string; + channelId: string; + rootId: string; + swipeable?: React.RefObject; +}) { + const deleteDraft = async () => { + removeDraft(serverUrl, channelId, rootId); + }; + + const onDismiss = () => { + if (swipeable?.current) { + swipeable.current.close(); + } + }; + + Alert.alert( + intl.formatMessage({ + id: 'draft.options.delete.title', + defaultMessage: 'Delete draft', + }), + intl.formatMessage({ + id: 'draft.options.delete.confirmation', + defaultMessage: 'Are you sure you want to delete this draft?', + }), + [ + { + text: intl.formatMessage({ + id: 'draft.options.delete.cancel', + defaultMessage: 'Cancel', + }), + style: 'cancel', + onPress: onDismiss, + }, + { + text: intl.formatMessage({ + id: 'draft.options.delete.confirm', + defaultMessage: 'Delete', + }), + onPress: deleteDraft, + }, + ], + ); +} From 112048a8e6a371648b3fbf11af01a2727721510f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 30 Oct 2024 17:36:16 +0530 Subject: [PATCH 026/111] done with rendering of images in markdown for drafts --- app/actions/local/draft.ts | 93 ++++++++++++++++++- .../draft/draft_post/draft_message/index.tsx | 1 + .../post_draft/post_input/post_input.tsx | 9 +- .../post_draft/send_handler/send_handler.tsx | 2 +- 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index 0992da7f8aa..f4323cc7a78 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {DeviceEventEmitter} from 'react-native'; +import {DeviceEventEmitter, Image} from 'react-native'; import {Navigation, Screens} from '@app/constants'; import {goToScreen} from '@app/screens/navigation'; @@ -211,3 +211,94 @@ export async function updateDraftPriority(serverUrl: string, channelId: string, return {error}; } } + +export async function updateDraftMarkdownImageMetadata({ + serverUrl, + channelId, + rootId, + imageMetadata, + prepareRecordsOnly = false, +}: { + serverUrl: string; + channelId: string; + rootId: string; + imageMetadata: Dictionary; + prepareRecordsOnly?: boolean; +}) { + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (!draft) { + const newDraft: Draft = { + channel_id: channelId, + root_id: rootId, + metadata: { + images: imageMetadata, + }, + update_at: Date.now(), + }; + + return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); + } + draft.prepareUpdate((d) => { + d.metadata = { + ...d.metadata, + images: imageMetadata, + }; + d.updateAt = Date.now(); + }); + if (!prepareRecordsOnly) { + await operator.batchRecords([draft], 'updateDraftImageMetadata'); + } + + return {draft}; + } catch (error) { + logError('Failed updateDraftImages', error); + return {error}; + } +} + +async function getImageMetadata(url: string) { + let height = 0; + let width = 0; + let format; + try { + await new Promise((resolve, reject) => { + Image.getSize( + url, + (imageWidth, imageHeight) => { + width = imageWidth; + height = imageHeight; + resolve(null); + }, + (error) => { + logError('Failed to get image size', error); + reject(error); + }, + ); + }); + } catch (error) { + width = 0; + height = 0; + } + const match = url.match(/\.(\w+)(?=\?|$)/); + if (match) { + format = match[1]; + } + return { + height, + width, + format, + frame_count: 1, + }; +} + +export async function parseMarkdownImages(markdown: string, imageMetadata: Dictionary) { + let match; + const imageRegex = /!\[.*?\]\((https:\/\/[^\s)]+)\)/g; + while ((match = imageRegex.exec(markdown)) !== null) { + const imageUrl = match[1]; + // eslint-disable-next-line no-await-in-loop + imageMetadata[imageUrl] = await getImageMetadata(imageUrl); + } +} diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message/index.tsx index 993a61a7fd9..6e67c6d0b4b 100644 --- a/app/components/draft/draft_post/draft_message/index.tsx +++ b/app/components/draft/draft_post/draft_message/index.tsx @@ -85,6 +85,7 @@ const DraftMessage: React.FC = ({ value={draft.message} mentionKeys={EMPTY_MENTION_KEYS} theme={theme} + imagesMetadata={draft.metadata?.images} /> diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index ea7208c9580..561e55aeb64 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -11,7 +11,7 @@ import { type NativeSyntheticEvent, Platform, type TextInputSelectionChangeEventData, } from 'react-native'; -import {updateDraftMessage} from '@actions/local/draft'; +import {parseMarkdownImages, updateDraftMarkdownImageMetadata, updateDraftMessage} from '@actions/local/draft'; import {userTyping} from '@actions/websocket/users'; import {Events, Screens} from '@constants'; import {useExtraKeyboardContext} from '@context/extra_keyboard'; @@ -146,9 +146,14 @@ export default function PostInput({ onFocus(); }; - const onBlur = useCallback(() => { + const onBlur = useCallback(async () => { keyboardContext?.registerTextInputBlur(); updateDraftMessage(serverUrl, channelId, rootId, value); + const imageMetadata: Dictionary = {}; + await parseMarkdownImages(value, imageMetadata); + if (Object.keys(imageMetadata).length !== 0) { + updateDraftMarkdownImageMetadata({serverUrl, channelId, rootId, imageMetadata}); + } setIsFocused(false); }, [keyboardContext, serverUrl, channelId, rootId, value, setIsFocused]); diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index 514101dc7e7..db9c31882cf 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -83,7 +83,7 @@ export default function SendHandler({ const handlePostPriority = useCallback((priority: PostPriority) => { updateDraftPriority(serverUrl, channelId, rootId, priority); - }, [serverUrl, rootId]); + }, [serverUrl, channelId, rootId]); const {handleSendMessage, canSend} = useHandleSendMessage({ value, From 25891274f5d92a96720a835acffe6a9fa38fe783 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 5 Nov 2024 18:04:24 +0530 Subject: [PATCH 027/111] en.json issue fixed --- .../components/global_drafts_list/SwipeableDraft/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx index 5e7f10dc44c..19d490b39ba 100644 --- a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx @@ -88,7 +88,7 @@ const SwipeableDraft: React.FC = ({ style={{}} /> {intl.formatMessage({ - id: 'drafts.delete', + id: 'draft.options.delete.confirm', defaultMessage: 'Delete', })} From a70b1651f8a371b2094f2c5b0275694748fad2a6 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 5 Nov 2024 19:13:02 +0530 Subject: [PATCH 028/111] fix en.json issue --- assets/base/i18n/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 3aa4a2de780..988f88392f3 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -313,6 +313,9 @@ "draft.options.edit.title": "Edit draft", "draft.options.send.title": "Send draft", "draft.options.title": "Draft Options", + "draft.options.delete.cancel": "Cancel", + "draft.options.delete.confirm": "Delete", + "draft.options.delete.confirmation": "Are you sure you want to delete this draft?", "drafts": "Drafts", "Drafts": "Drafts", "edit_post.editPost": "Edit the post...", From 6f56a413fe7e52a7ff86c2628d47d5f522f93bb5 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 5 Nov 2024 19:21:52 +0530 Subject: [PATCH 029/111] refactor: en.json fix --- assets/base/i18n/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 988f88392f3..44b996a898a 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -309,13 +309,13 @@ "display_settings.tz.manual": "Manual", "download.error": "Unable to download the file. Try again later", "draft.option.header": "Draft actions", + "draft.options.delete.cancel": "Cancel", + "draft.options.delete.confirm": "Delete", + "draft.options.delete.confirmation": "Are you sure you want to delete this draft?", "draft.options.delete.title": "Delete draft", "draft.options.edit.title": "Edit draft", "draft.options.send.title": "Send draft", "draft.options.title": "Draft Options", - "draft.options.delete.cancel": "Cancel", - "draft.options.delete.confirm": "Delete", - "draft.options.delete.confirmation": "Are you sure you want to delete this draft?", "drafts": "Drafts", "Drafts": "Drafts", "edit_post.editPost": "Edit the post...", From e14082655afaab8db853371fe697b1d679f5102b Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 12 Nov 2024 18:08:52 +0530 Subject: [PATCH 030/111] addressed review comments --- app/components/channel_info/channel_info.tsx | 4 ++-- app/components/draft/draft.tsx | 8 ++++---- .../draft/draft_post/draft_files/index.ts | 9 ++------- app/components/draft/draft_post/index.tsx | 1 - app/components/drafts_buttton/drafts_button.tsx | 17 ----------------- app/components/files/files.tsx | 4 ++-- app/queries/servers/drafts.ts | 4 ++-- app/screens/global_drafts/index.tsx | 7 ++++--- app/screens/global_threads/global_threads.tsx | 2 +- assets/base/i18n/en.json | 5 ++--- 10 files changed, 19 insertions(+), 42 deletions(-) diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx index 6c33859e343..c6fbc2e67a3 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/channel_info/channel_info.tsx @@ -108,7 +108,7 @@ const ChannelInfo: React.FC = ({ headerComponent = ( @@ -119,7 +119,7 @@ const ChannelInfo: React.FC = ({ headerComponent = ( diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 9feab03046c..d04e3a3d519 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, TouchableHighlight, View} from 'react-native'; @@ -61,7 +61,7 @@ const Draft: React.FC = ({ const isTablet = useIsTablet(); const showPostPriority = Boolean(isPostPriorityEnabled && draft.metadata?.priority && draft.metadata?.priority?.priority); - const onLongPress = () => { + const onLongPress = useCallback(() => { Keyboard.dismiss(); const title = isTablet ? intl.formatMessage({id: 'draft.options.title', defaultMessage: 'Draft Options'}) : 'Draft Options'; openAsBottomSheet({ @@ -71,7 +71,7 @@ const Draft: React.FC = ({ title, props: {channel, rootId: draft.rootId, draft, draftReceiverUserName: draftReceiverUser?.username}, }); - }; + }, [channel, draft, draftReceiverUser?.username, intl, isTablet, theme]); return ( = ({ underlayColor={changeOpacity(theme.centerChannelColor, 0.1)} > { - const publicLinkEnabled = observeConfigBooleanValue(database, 'EnablePublicLink'); - +const enhance = withObservables([], ({database}: WithDatabaseArgs) => { return { canDownloadFiles: observeCanDownloadFiles(database), - publicLinkEnabled, - postId: of$(draftId), - postProps: of$({}), + publicLinkEnabled: observeConfigBooleanValue(database, 'EnablePublicLink'), }; }); diff --git a/app/components/draft/draft_post/index.tsx b/app/components/draft/draft_post/index.tsx index 85838d27da6..69e7304323f 100644 --- a/app/components/draft/draft_post/index.tsx +++ b/app/components/draft/draft_post/index.tsx @@ -58,7 +58,6 @@ const DraftPost: React.FC = ({ diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 96f57beb03e..929eea3cd41 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -19,7 +19,6 @@ import FormattedText from '@components/formatted_text'; import CompassIcon from '../compass_icon'; -// See LICENSE.txt for license information. type DraftListProps = { currentChannelId: string; shouldHighlighActive?: boolean; @@ -115,22 +114,6 @@ const DraftsButton: React.FC = ({ ); - - // const renderItem = ({item, index}: {item: string; index: number}) => ( - // - // {'Draft'} {index + 1} {':'} {item} - // {'Attached files:'} {files[index]?.length || 0} - // - // ); - - // return ( - // `draft-${index}`} - // renderItem={renderItem} - // ListEmptyComponent={{'No drafts available.'}} - // /> - // ); }; export default DraftsButton; diff --git a/app/components/files/files.tsx b/app/components/files/files.tsx index 710466ea79e..1006393637c 100644 --- a/app/components/files/files.tsx +++ b/app/components/files/files.tsx @@ -23,8 +23,8 @@ type FilesProps = { layoutWidth?: number; location: string; isReplyPost: boolean; - postId: string; - postProps: Record; + postId?: string; + postProps?: Record; publicLinkEnabled: boolean; } diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index bb970b3d461..b05d8034318 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -36,8 +36,8 @@ export const queryAllDrafts = (database: Database, teamId: string) => { Q.and( Q.or( Q.where('team_id', teamId), // Channels associated with the given team - Q.where('type', 'D'), // Channels of type 'D' - Q.where('type', 'G'), // Channels of type 'D' + Q.where('type', 'D'), // Direct Message + Q.where('type', 'G'), // Group Message ), Q.where('delete_at', 0), // Ensure the channel is not deleted ), diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index 50639c04621..aaaecd5d574 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -4,7 +4,7 @@ import React, {useCallback, useMemo} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, StyleSheet, View} from 'react-native'; -import {SafeAreaView} from 'react-native-safe-area-context'; +import {SafeAreaView, type Edge} from 'react-native-safe-area-context'; import NavigationHeader from '@app/components/navigation_header'; import OtherMentionsBadge from '@app/components/other_mentions_badge'; @@ -13,7 +13,6 @@ import {Screens} from '@app/constants'; import {useIsTablet} from '@app/hooks/device'; import {useDefaultHeaderHeight} from '@app/hooks/header'; import {useTeamSwitch} from '@app/hooks/team_switch'; -import {edges} from '@app/screens/global_threads/global_threads'; import {popTopScreen} from '../navigation'; @@ -21,6 +20,8 @@ import GlobalDraftsList from './components/global_drafts_list'; import type {AvailableScreens} from '@typings/screens/navigation'; +const edges: Edge[] = ['left', 'right']; + type Props = { componentId?: AvailableScreens; }; @@ -73,7 +74,7 @@ const GlobalDrafts = ({componentId}: Props) => { onBackPress={onBackPress} title={ intl.formatMessage({ - id: 'Drafts', + id: 'drafts', defaultMessage: 'Drafts', }) } diff --git a/app/screens/global_threads/global_threads.tsx b/app/screens/global_threads/global_threads.tsx index f0ed5619cb2..3102a6065c9 100644 --- a/app/screens/global_threads/global_threads.tsx +++ b/app/screens/global_threads/global_threads.tsx @@ -27,7 +27,7 @@ type Props = { globalThreadsTab: GlobalThreadsTab; }; -export const edges: Edge[] = ['left', 'right']; +const edges: Edge[] = ['left', 'right']; const styles = StyleSheet.create({ flex: { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 44b996a898a..73cf7bce074 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -167,13 +167,14 @@ "channel_info.copy_link": "Copy Link", "channel_info.copy_purpose_text": "Copy Purpose Text", "channel_info.custom_status": "Custom status:", + "channel_info.draft_in_channel": "In:", + "channel_info.draft_to_user": "To:", "channel_info.edit_header": "Edit Header", "channel_info.error_close": "Close", "channel_info.favorite": "Favorite", "channel_info.favorited": "Favorited", "channel_info.header": "Header:", "channel_info.ignore_mentions": "Ignore @channel, @here, @all", - "channel_info.in": "In:", "channel_info.leave": "Leave", "channel_info.leave_channel": "Leave channel", "channel_info.leave_private_channel": "Are you sure you want to leave the private channel {displayName}? You cannot rejoin the channel unless you're invited again.", @@ -196,7 +197,6 @@ "channel_info.send_mesasge": "Send message", "channel_info.set_header": "Set Header", "channel_info.thread_in": "Thread in:", - "channel_info.to": "To:", "channel_info.unarchive": "Unarchive Channel", "channel_info.unarchive_description": "Are you sure you want to unarchive the {term} {name}?", "channel_info.unarchive_failed": "An error occurred trying to unarchive the channel {displayName}", @@ -317,7 +317,6 @@ "draft.options.send.title": "Send draft", "draft.options.title": "Draft Options", "drafts": "Drafts", - "Drafts": "Drafts", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", "edit_server.description": "Specify a display name for this server", From a85a1a4896364909954be1544bab8ead791e9b56 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 12 Nov 2024 18:21:58 +0530 Subject: [PATCH 031/111] updated image metadata handling code --- app/actions/local/draft.ts | 31 ++++++------------- .../post_draft/post_input/post_input.tsx | 2 +- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index f4323cc7a78..2547d511bd7 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -228,29 +228,18 @@ export async function updateDraftMarkdownImageMetadata({ try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const draft = await getDraft(database, channelId, rootId); - if (!draft) { - const newDraft: Draft = { - channel_id: channelId, - root_id: rootId, - metadata: { + if (draft) { + draft.prepareUpdate((d) => { + d.metadata = { + ...d.metadata, images: imageMetadata, - }, - update_at: Date.now(), - }; - - return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); - } - draft.prepareUpdate((d) => { - d.metadata = { - ...d.metadata, - images: imageMetadata, - }; - d.updateAt = Date.now(); - }); - if (!prepareRecordsOnly) { - await operator.batchRecords([draft], 'updateDraftImageMetadata'); + }; + d.updateAt = Date.now(); + }); + if (!prepareRecordsOnly) { + await operator.batchRecords([draft], 'updateDraftImageMetadata'); + } } - return {draft}; } catch (error) { logError('Failed updateDraftImages', error); diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index 561e55aeb64..8fa2f99838c 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -148,7 +148,7 @@ export default function PostInput({ const onBlur = useCallback(async () => { keyboardContext?.registerTextInputBlur(); - updateDraftMessage(serverUrl, channelId, rootId, value); + await updateDraftMessage(serverUrl, channelId, rootId, value); const imageMetadata: Dictionary = {}; await parseMarkdownImages(value, imageMetadata); if (Object.keys(imageMetadata).length !== 0) { From c72c14d8ea0708f9b64aef91ed0ee15cdff46ced Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 13 Nov 2024 14:33:17 +0530 Subject: [PATCH 032/111] linter fixes --- app/actions/local/draft.ts | 6 ++--- app/components/channel_info/avatar/index.tsx | 2 +- app/components/channel_info/channel_info.tsx | 10 ++++---- app/components/channel_info/index.tsx | 6 ++--- app/components/draft/draft.tsx | 16 ++++++------- .../draft/draft_post/draft_files/index.ts | 2 +- .../draft/draft_post/draft_message/index.tsx | 14 +++++------ app/components/draft/draft_post/index.tsx | 8 +++---- app/components/draft/index.tsx | 6 ++--- .../drafts_buttton/drafts_button.tsx | 10 ++++---- app/components/drafts_buttton/index.tsx | 4 ++-- .../post_draft/draft_input/index.tsx | 2 +- .../post_draft/send_handler/send_handler.tsx | 2 +- app/hooks/handle_send_message.ts | 16 ++++++------- app/hooks/persistent_notification_props.ts | 6 ++--- .../draft_options/delete_draft/index.tsx | 16 ++++++------- .../draft_options/edit_draft/index.tsx | 16 ++++++------- app/screens/draft_options/index.tsx | 6 ++--- .../draft_options/send_draft/index.tsx | 2 +- .../draft_options/send_draft/send_draft.tsx | 24 +++++++++---------- .../SwipeableDraft/index.tsx | 16 ++++++------- .../global_drafts_list/global_drafts_list.tsx | 4 ++-- .../components/global_drafts_list/index.tsx | 4 ++-- app/screens/global_drafts/index.tsx | 14 +++++------ .../additional_tablet_view.tsx | 2 +- .../channel_list/categories_list/index.tsx | 2 +- .../xcshareddata/swiftpm/Package.resolved | 18 -------------- 27 files changed, 108 insertions(+), 126 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index 2547d511bd7..bf98bd48b16 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -3,11 +3,11 @@ import {DeviceEventEmitter, Image} from 'react-native'; -import {Navigation, Screens} from '@app/constants'; -import {goToScreen} from '@app/screens/navigation'; -import {isTablet} from '@app/utils/helpers'; +import {Navigation, Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {getDraft} from '@queries/servers/drafts'; +import {goToScreen} from '@screens/navigation'; +import {isTablet} from '@utils/helpers'; import {logError} from '@utils/log'; export const switchToGlobalDrafts = async () => { diff --git a/app/components/channel_info/avatar/index.tsx b/app/components/channel_info/avatar/index.tsx index d50d698bc31..177e6afe64f 100644 --- a/app/components/channel_info/avatar/index.tsx +++ b/app/components/channel_info/avatar/index.tsx @@ -7,8 +7,8 @@ import {StyleSheet, View} from 'react-native'; import {buildAbsoluteUrl} from '@actions/remote/file'; import {buildProfileImageUrlFromUser} from '@actions/remote/user'; -import {useServerUrl} from '@app/context/server'; import CompassIcon from '@components/compass_icon'; +import {useServerUrl} from '@context/server'; import {changeOpacity} from '@utils/theme'; import type UserModel from '@typings/database/models/servers/user'; diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx index c6fbc2e67a3..542d0e38098 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/channel_info/channel_info.tsx @@ -4,13 +4,13 @@ import React, {type ReactNode} from 'react'; import {Text, View} from 'react-native'; -import {General} from '@app/constants'; -import {useTheme} from '@app/context/theme'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; -import {getUserTimezone} from '@app/utils/user'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; +import {General} from '@constants'; +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; +import {getUserTimezone} from '@utils/user'; import CompassIcon from '../compass_icon'; diff --git a/app/components/channel_info/index.tsx b/app/components/channel_info/index.tsx index 97bbd0d634f..0e2a351106c 100644 --- a/app/components/channel_info/index.tsx +++ b/app/components/channel_info/index.tsx @@ -4,9 +4,9 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {map} from 'rxjs/operators'; -import {getDisplayNamePreferenceAsBool} from '@app/helpers/api/preference'; -import {queryDisplayNamePreferences} from '@app/queries/servers/preference'; -import {observeCurrentUser} from '@app/queries/servers/user'; +import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference'; +import {queryDisplayNamePreferences} from '@queries/servers/preference'; +import {observeCurrentUser} from '@queries/servers/user'; import ChannelInfo from './channel_info'; diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index d04e3a3d519..142f109cc57 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -5,15 +5,15 @@ import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, TouchableHighlight, View} from 'react-native'; -import ChannelInfo from '@app/components/channel_info'; -import DraftPost from '@app/components/draft/draft_post'; -import {Screens} from '@app/constants'; -import {useTheme} from '@app/context/theme'; -import {useIsTablet} from '@app/hooks/device'; -import {DRAFT_OPTIONS_BUTTON} from '@app/screens/draft_options'; -import {openAsBottomSheet} from '@app/screens/navigation'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import ChannelInfo from '@components/channel_info'; +import DraftPost from '@components/draft/draft_post'; import Header from '@components/post_draft/draft_input/header'; +import {Screens} from '@constants'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {DRAFT_OPTIONS_BUTTON} from '@screens/draft_options'; +import {openAsBottomSheet} from '@screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import type ChannelModel from '@typings/database/models/servers/channel'; import type DraftModel from '@typings/database/models/servers/draft'; diff --git a/app/components/draft/draft_post/draft_files/index.ts b/app/components/draft/draft_post/draft_files/index.ts index e41dcc00baf..ab603b8ea4e 100644 --- a/app/components/draft/draft_post/draft_files/index.ts +++ b/app/components/draft/draft_post/draft_files/index.ts @@ -3,7 +3,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; -import Files from '@app/components/files/files'; +import Files from '@components/files/files'; import {observeCanDownloadFiles, observeConfigBooleanValue} from '@queries/servers/system'; import type {WithDatabaseArgs} from '@typings/database/database'; diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message/index.tsx index 6e67c6d0b4b..eb18d838936 100644 --- a/app/components/draft/draft_post/draft_message/index.tsx +++ b/app/components/draft/draft_post/draft_message/index.tsx @@ -5,15 +5,15 @@ import React, {useCallback, useState} from 'react'; import {ScrollView, View, useWindowDimensions, type LayoutChangeEvent} from 'react-native'; import Animated from 'react-native-reanimated'; -import ShowMoreButton from '@app/components/post_list/post/body/message/show_more_button'; -import {useTheme} from '@app/context/theme'; -import {useShowMoreAnimatedStyle} from '@app/hooks/show_more'; -import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@app/utils/markdown'; -import {makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; import Markdown from '@components/markdown'; +import ShowMoreButton from '@components/post_list/post/body/message/show_more_button'; +import {useTheme} from '@context/theme'; +import {useShowMoreAnimatedStyle} from '@hooks/show_more'; +import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown'; +import {makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; -import type {DraftModel} from '@app/database/models/server'; +import type DraftModel from '@typings/database/models/servers/draft'; import type {UserMentionKey} from '@typings/global/markdown'; type Props = { diff --git a/app/components/draft/draft_post/index.tsx b/app/components/draft/draft_post/index.tsx index 69e7304323f..c5c2671e10c 100644 --- a/app/components/draft/draft_post/index.tsx +++ b/app/components/draft/draft_post/index.tsx @@ -4,13 +4,13 @@ import React from 'react'; import {View} from 'react-native'; -import DraftMessage from '@app/components/draft/draft_post/draft_message'; -import {useTheme} from '@app/context/theme'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import DraftMessage from '@components/draft/draft_post/draft_message'; +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import DraftFiles from './draft_files'; -import type {DraftModel} from '@app/database/models/server'; +import type DraftModel from '@typings/database/models/servers/draft'; type Props = { draft: DraftModel; diff --git a/app/components/draft/index.tsx b/app/components/draft/index.tsx index 5797eecf578..5f9e7290a0c 100644 --- a/app/components/draft/index.tsx +++ b/app/components/draft/index.tsx @@ -5,9 +5,9 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import React from 'react'; import {switchMap, of} from 'rxjs'; -import {observeChannel, observeChannelMembers} from '@app/queries/servers/channel'; -import {observeIsPostPriorityEnabled} from '@app/queries/servers/post'; -import {observeCurrentUser, observeUser} from '@app/queries/servers/user'; +import {observeChannel, observeChannelMembers} from '@queries/servers/channel'; +import {observeIsPostPriorityEnabled} from '@queries/servers/post'; +import {observeCurrentUser, observeUser} from '@queries/servers/user'; import Drafts from './draft'; diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 929eea3cd41..e3ab01a29dc 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -5,17 +5,17 @@ import React, {useCallback, useMemo} from 'react'; import {TouchableOpacity, View} from 'react-native'; import {switchToGlobalDrafts} from '@actions/local/draft'; -import {HOME_PADDING} from '@app/constants/view'; -import {useTheme} from '@app/context/theme'; -import {useIsTablet} from '@app/hooks/device'; -import {preventDoubleTap} from '@app/utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; import Badge from '@components/badge'; import { getStyleSheet as getChannelItemStyleSheet, ROW_HEIGHT, } from '@components/channel_item/channel_item'; import FormattedText from '@components/formatted_text'; +import {HOME_PADDING} from '@constants/view'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {preventDoubleTap} from '@utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import CompassIcon from '../compass_icon'; diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index 0363c8f827c..bd89c62f1e6 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -5,8 +5,8 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import React from 'react'; -import {observeDraftCount} from '@app/queries/servers/drafts'; -import {observeCurrentChannelId, observeCurrentTeamId} from '@app/queries/servers/system'; +import {observeDraftCount} from '@queries/servers/drafts'; +import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system'; import DraftsButton from './drafts_button'; diff --git a/app/components/post_draft/draft_input/index.tsx b/app/components/post_draft/draft_input/index.tsx index 7b8066d8b46..d286e088fa5 100644 --- a/app/components/post_draft/draft_input/index.tsx +++ b/app/components/post_draft/draft_input/index.tsx @@ -6,9 +6,9 @@ import {useIntl} from 'react-intl'; import {type LayoutChangeEvent, Platform, ScrollView, View} from 'react-native'; import {type Edge, SafeAreaView} from 'react-native-safe-area-context'; -import {usePersistentNotificationProps} from '@app/hooks/persistent_notification_props'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; +import {usePersistentNotificationProps} from '@hooks/persistent_notification_props'; import {persistentNotificationsConfirmation} from '@utils/post'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index db9c31882cf..2b7129aef07 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -4,9 +4,9 @@ import React, {useCallback} from 'react'; import {updateDraftPriority} from '@actions/local/draft'; -import {useHandleSendMessage} from '@app/hooks/handle_send_message'; import {PostPriorityType} from '@constants/post'; import {useServerUrl} from '@context/server'; +import {useHandleSendMessage} from '@hooks/handle_send_message'; import DraftInput from '../draft_input'; diff --git a/app/hooks/handle_send_message.ts b/app/hooks/handle_send_message.ts index dbf3c086a20..b3196eb5a3d 100644 --- a/app/hooks/handle_send_message.ts +++ b/app/hooks/handle_send_message.ts @@ -10,16 +10,16 @@ import {executeCommand, handleGotoLocation} from '@actions/remote/command'; import {createPost} from '@actions/remote/post'; import {handleReactionToLatestPost} from '@actions/remote/reactions'; import {setStatus} from '@actions/remote/user'; -import {Events, Screens} from '@app/constants'; -import {NOTIFY_ALL_MEMBERS} from '@app/constants/post_draft'; -import {useServerUrl} from '@app/context/server'; -import {handleCallsSlashCommand} from '@app/products/calls/actions'; -import {isReactionMatch} from '@app/utils/emoji/helpers'; -import {getFullErrorMessage} from '@app/utils/errors'; -import {preventDoubleTap} from '@app/utils/tap'; -import {confirmOutOfOfficeDisabled} from '@app/utils/user'; +import {handleCallsSlashCommand} from '@calls/actions'; +import {Events, Screens} from '@constants'; +import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft'; +import {useServerUrl} from '@context/server'; import DraftUploadManager from '@managers/draft_upload_manager'; import * as DraftUtils from '@utils/draft'; +import {isReactionMatch} from '@utils/emoji/helpers'; +import {getFullErrorMessage} from '@utils/errors'; +import {preventDoubleTap} from '@utils/tap'; +import {confirmOutOfOfficeDisabled} from '@utils/user'; import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; diff --git a/app/hooks/persistent_notification_props.ts b/app/hooks/persistent_notification_props.ts index 6d18c4ac073..108dc3dd489 100644 --- a/app/hooks/persistent_notification_props.ts +++ b/app/hooks/persistent_notification_props.ts @@ -3,9 +3,9 @@ import {useMemo} from 'react'; -import {General} from '@app/constants'; -import {MENTIONS_REGEX} from '@app/constants/autocomplete'; -import {PostPriorityType} from '@app/constants/post'; +import {General} from '@constants'; +import {MENTIONS_REGEX} from '@constants/autocomplete'; +import {PostPriorityType} from '@constants/post'; type Props = { value: string; diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx index db97a20ae80..1588dd5db61 100644 --- a/app/screens/draft_options/delete_draft/index.tsx +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -6,14 +6,14 @@ import {useIntl} from 'react-intl'; import {Text} from 'react-native'; import {removeDraft} from '@actions/local/draft'; -import CompassIcon from '@app/components/compass_icon'; -import TouchableWithFeedback from '@app/components/touchable_with_feedback'; -import {ICON_SIZE} from '@app/constants/post_draft'; -import {useServerUrl} from '@app/context/server'; -import {useTheme} from '@app/context/theme'; -import {dismissBottomSheet} from '@app/screens/navigation'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; +import CompassIcon from '@components/compass_icon'; +import TouchableWithFeedback from '@components/touchable_with_feedback'; +import {ICON_SIZE} from '@constants/post_draft'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {dismissBottomSheet} from '@screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; import type {AvailableScreens} from '@typings/screens/navigation'; diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 78bcac20484..6825715780e 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -7,14 +7,14 @@ import {Text} from 'react-native'; import {switchToThread} from '@actions/local/thread'; import {switchToChannelById} from '@actions/remote/channel'; -import CompassIcon from '@app/components/compass_icon'; -import TouchableWithFeedback from '@app/components/touchable_with_feedback'; -import {ICON_SIZE} from '@app/constants/post_draft'; -import {useServerUrl} from '@app/context/server'; -import {useTheme} from '@app/context/theme'; -import {dismissBottomSheet} from '@app/screens/navigation'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; +import CompassIcon from '@components/compass_icon'; +import TouchableWithFeedback from '@components/touchable_with_feedback'; +import {ICON_SIZE} from '@constants/post_draft'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {dismissBottomSheet} from '@screens/navigation'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; import type ChannelModel from '@typings/database/models/servers/channel'; import type {AvailableScreens} from '@typings/screens/navigation'; diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index 81d27da8a93..4ea9a80836e 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -5,10 +5,10 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {StyleSheet, View, Text} from 'react-native'; -import {Screens} from '@app/constants'; -import {useIsTablet} from '@app/hooks/device'; -import {typography} from '@app/utils/typography'; +import {Screens} from '@constants'; +import {useIsTablet} from '@hooks/device'; import BottomSheet from '@screens/bottom_sheet'; +import {typography} from '@utils/typography'; import DeleteDraft from './delete_draft'; import EditDraft from './edit_draft'; diff --git a/app/screens/draft_options/send_draft/index.tsx b/app/screens/draft_options/send_draft/index.tsx index db4e55e1651..4a0b2e56048 100644 --- a/app/screens/draft_options/send_draft/index.tsx +++ b/app/screens/draft_options/send_draft/index.tsx @@ -5,7 +5,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {combineLatest, of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import {INITIAL_PRIORITY} from '@app/components/post_draft/send_handler/send_handler'; +import {INITIAL_PRIORITY} from '@components/post_draft/send_handler/send_handler'; import {General, Permissions} from '@constants'; import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; import {observeChannel, observeChannelInfo} from '@queries/servers/channel'; diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index 203aa90016b..3b723854b95 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -6,18 +6,18 @@ import {useIntl} from 'react-intl'; import {Text} from 'react-native'; import {removeDraft} from '@actions/local/draft'; -import CompassIcon from '@app/components/compass_icon'; -import TouchableWithFeedback from '@app/components/touchable_with_feedback'; -import {General} from '@app/constants'; -import {ICON_SIZE} from '@app/constants/post_draft'; -import {useServerUrl} from '@app/context/server'; -import {useTheme} from '@app/context/theme'; -import {useHandleSendMessage} from '@app/hooks/handle_send_message'; -import {usePersistentNotificationProps} from '@app/hooks/persistent_notification_props'; -import {dismissBottomSheet} from '@app/screens/navigation'; -import {persistentNotificationsConfirmation, sendMessageWithAlert} from '@app/utils/post'; -import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; +import CompassIcon from '@components/compass_icon'; +import TouchableWithFeedback from '@components/touchable_with_feedback'; +import {General} from '@constants'; +import {ICON_SIZE} from '@constants/post_draft'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {useHandleSendMessage} from '@hooks/handle_send_message'; +import {usePersistentNotificationProps} from '@hooks/persistent_notification_props'; +import {dismissBottomSheet} from '@screens/navigation'; +import {persistentNotificationsConfirmation, sendMessageWithAlert} from '@utils/post'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; import type {AvailableScreens} from '@typings/screens/navigation'; diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx index 19d490b39ba..6beb62541c6 100644 --- a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx @@ -6,14 +6,14 @@ import {useIntl} from 'react-intl'; import {Text, Animated, DeviceEventEmitter, TouchableWithoutFeedback} from 'react-native'; import {Swipeable} from 'react-native-gesture-handler'; -import CompassIcon from '@app/components/compass_icon'; -import Draft from '@app/components/draft'; -import {Events} from '@app/constants'; -import {useServerUrl} from '@app/context/server'; -import {useTheme} from '@app/context/theme'; -import {deleteDraftConfirmation} from '@app/utils/draft'; -import {makeStyleSheetFromTheme} from '@app/utils/theme'; -import {typography} from '@app/utils/typography'; +import CompassIcon from '@components/compass_icon'; +import Draft from '@components/draft'; +import {Events} from '@constants'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {deleteDraftConfirmation} from '@utils/draft'; +import {makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; import type DraftModel from '@typings/database/models/servers/draft'; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index c72151c9ad1..a03c4e56267 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -6,8 +6,8 @@ import React, {useCallback, useState} from 'react'; import {View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; -import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@app/components/post_list/config'; -import {Screens} from '@app/constants'; +import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@components/post_list/config'; +import {Screens} from '@constants'; import SwipeableDraft from './SwipeableDraft'; diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx index eae43d0c582..5e681c0ab82 100644 --- a/app/screens/global_drafts/components/global_drafts_list/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -5,8 +5,8 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import React from 'react'; -import {observeAllDrafts} from '@app/queries/servers/drafts'; -import {observeCurrentTeamId} from '@app/queries/servers/system'; +import {observeAllDrafts} from '@queries/servers/drafts'; +import {observeCurrentTeamId} from '@queries/servers/system'; import GlobalDraftsList from './global_drafts_list'; diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index aaaecd5d574..354d00b289a 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -6,13 +6,13 @@ import {useIntl} from 'react-intl'; import {Keyboard, StyleSheet, View} from 'react-native'; import {SafeAreaView, type Edge} from 'react-native-safe-area-context'; -import NavigationHeader from '@app/components/navigation_header'; -import OtherMentionsBadge from '@app/components/other_mentions_badge'; -import RoundedHeaderContext from '@app/components/rounded_header_context'; -import {Screens} from '@app/constants'; -import {useIsTablet} from '@app/hooks/device'; -import {useDefaultHeaderHeight} from '@app/hooks/header'; -import {useTeamSwitch} from '@app/hooks/team_switch'; +import NavigationHeader from '@components/navigation_header'; +import OtherMentionsBadge from '@components/other_mentions_badge'; +import RoundedHeaderContext from '@components/rounded_header_context'; +import {Screens} from '@constants'; +import {useIsTablet} from '@hooks/device'; +import {useDefaultHeaderHeight} from '@hooks/header'; +import {useTeamSwitch} from '@hooks/team_switch'; import {popTopScreen} from '../navigation'; diff --git a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx index 9146115c72d..1a1263f486e 100644 --- a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx +++ b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx @@ -4,9 +4,9 @@ import React, {useEffect, useState} from 'react'; import {DeviceEventEmitter} from 'react-native'; -import GlobalDrafts from '@app/screens/global_drafts'; import {Navigation, Screens} from '@constants'; import Channel from '@screens/channel'; +import GlobalDrafts from '@screens/global_drafts'; import GlobalThreads from '@screens/global_threads'; type SelectedView = { diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index f525ab32d32..cc026a4f57d 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -5,7 +5,7 @@ import React, {useEffect, useMemo} from 'react'; import {useWindowDimensions} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import DraftsButtton from '@app/components/drafts_buttton'; +import DraftsButtton from '@components/drafts_buttton'; import ThreadsButton from '@components/threads_button'; import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; import {useTheme} from '@context/theme'; diff --git a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9be5f0f2633..62cc6b8f771 100644 --- a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,24 +46,6 @@ "version": "1.9.200" } }, - { - "package": "OpenGraph", - "repositoryURL": "https://github.com/satoshi-takano/OpenGraph.git", - "state": { - "branch": null, - "revision": "382972f1963580eeabafd88ad012e66576b4d213", - "version": "1.4.1" - } - }, - { - "package": "Sentry", - "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", - "state": { - "branch": null, - "revision": "5575af93efb776414f243e93d6af9f6258dc539a", - "version": "8.36.0" - } - }, { "package": "SQLite.swift", "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", From f5b884a23680580992ae4293ce24ba550d5384c0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 18 Nov 2024 20:41:49 +0530 Subject: [PATCH 033/111] added the empty draft screen --- .../draft_empty_component/index.tsx | 58 ++++++++++++++++++ .../global_drafts_list/global_drafts_list.tsx | 14 ++++- assets/base/images/Draft_Message.png | Bin 0 -> 2954 bytes 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 app/screens/global_drafts/components/draft_empty_component/index.tsx create mode 100644 assets/base/images/Draft_Message.png diff --git a/app/screens/global_drafts/components/draft_empty_component/index.tsx b/app/screens/global_drafts/components/draft_empty_component/index.tsx new file mode 100644 index 00000000000..60ebfaecc56 --- /dev/null +++ b/app/screens/global_drafts/components/draft_empty_component/index.tsx @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Image, Text, View} from 'react-native'; + +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import { typography } from '@utils/typography'; + +const draft_message_image = require('@assets/images/Draft_Message.png'); + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + image: { + width: 120, + height: 120, + marginBottom: 20, + }, + title: { + ...typography('Heading'), + color: theme.centerChannelColor, + }, + subtitle: { + ...typography('Body'), + color: changeOpacity(theme.centerChannelColor, 0.72), + textAlign: 'center', + marginTop: 8, + }, + }; +}); + +const DraftEmptyComponent = () => { + const intl = useIntl(); + const theme = useTheme(); + const styles = getStyleSheet(theme); + return ( + + + + {intl.formatMessage({id: 'drafts.empty.title', defaultMessage: 'No drafts at the moment'})} + + + {intl.formatMessage({id: 'drafts.empty.subtitle', defaultMessage: 'Any message you have started will show here.'})} + + + ); +}; + +export default DraftEmptyComponent; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index a03c4e56267..93e3cc0f900 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -3,12 +3,14 @@ import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useState} from 'react'; -import {View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; +import {StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@components/post_list/config'; import {Screens} from '@constants'; +import DraftEmptyComponent from '../draft_empty_component'; + import SwipeableDraft from './SwipeableDraft'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -18,6 +20,14 @@ type Props = { location: string; } +const styles = StyleSheet.create({ + empty: { + alignItems: 'center', + flexGrow: 1, + justifyContent: 'center', + }, +}); + const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); const keyExtractor = (item: DraftModel) => item.id; @@ -52,9 +62,11 @@ const GlobalDraftsList: React.FC = ({ keyExtractor={keyExtractor} initialNumToRender={INITIAL_BATCH_TO_RENDER + 5} maintainVisibleContentPosition={SCROLL_POSITION_CONFIG} + contentContainerStyle={!allDrafts.length && styles.empty} maxToRenderPerBatch={10} nativeID={location} renderItem={renderItem} + ListEmptyComponent={DraftEmptyComponent} /> ); diff --git a/assets/base/images/Draft_Message.png b/assets/base/images/Draft_Message.png new file mode 100644 index 0000000000000000000000000000000000000000..0f065934acfd5ecb1a2a2caa7a1e1b375a1771a4 GIT binary patch literal 2954 zcmai0dpr}28lDidMz-89^+~$OC^OewhS0t#sfiJ}4#RS(+*1^}%%xn0G$NDnEq9_w zmdmgsbN`Sfvbi%7j_>@=-{(BP-}652`@GNl|C?~l+5#*hD*^xjz*d%K*AF!JfF$9= z2esvr-q`~I5-jb5000p5AMyc6dC~_aU(j_6B%q>Ke*Rz(@G?Q0001;1==UALgVb>= zGm{$+__z}RcqmVSzS6hEF^0T)t*Q~TwdznUWVIL*hSi1e`Be4->I5xDOiqpYlam$Sodp%JC& zh?ceO#fZK4$}{a=bVSt7{b+mvpVI%uvtx09d81Q$ce0)1F!?OCd4O(F+M|D+#m&_D z1pNfZI-a1R%}dt(7wFch+Rr5vzuw;1z9EL};tj8-wfp0mg>D?b!uzOkkAkL{#3ohJ z=1+F>?5le6Uxh80{^=)zB^-_@6gz!S13syIhtwo~u_X!r3#Y_qTkuX2mroUFpJk^c zOP0uOz8l+MzqbrFG`J zsoXALYngb2p$5f@LpBPMJqOm zHC8qDR5!f!gyjMcwu(Y3PmP(=V#r&%@?9QcRsCA9pj58Sv9aZ*S2z$1A=8O}2Trc)`vQX1whif8?1P@Q zOu2TmEf6x(DzJxz>K}(!)rVF*N=v5unhdW@CoZ5**AKT314=^$wB?FRJ`I1-PD+mW zuRuAYrE+Tb`9QS*Dk|s$v7ozo-;X125W+j`vdGWc?{{jL{H|AV{OOx#ODIq2t+`Yl z@07CaX+)??roT{TK!ND1{tiTVGSl$>2G}AhH*xId!;{$Nn^ofy0tEqfdif^Vu|^1O zU$+@i^^Cg>n?KjXI-JI{l6KI#(2OKT4hmN5tvj)GPONmG+fv6`eQo*=9n7aE>|nh_ z4PZ2WwSenxmb%tBGWRpMX+%(JXc~Sk6mmrVAr_jK+_+3!!f5NKt$hidNEI#v`?Di? zav_uZHXBzJ8&d8b^z@Da;f#)WwBk6l53@(i@(xtgt;DWg|C+CqZclb3VmGa z8@2xJ^8+AtJTgt{_o<6wi>P7GqGVo$SVC8#RGVs=w7WZ{PH!?RxOH@n;_gK+y)a(< zO7ZJP1$>-F#N;kly|Cx3NV7vHp)u2}t(st-=i4=}2R zdH8U`$}3x#D~t^B7qrZwV7-*}B|C1v1J|as{>^4Ad08$264E}b^mCRVId94{b@r?L zwE$4=3Oh_6Ryw2UKlRNQMXs!ArToXZjMU%lib%R0->L>&RPWr#UzT=DJgSbYIvW@H zPb;i$+Re18w9xL5{tB-LWCoM)*w#hb5fQ2X(>1-(9U;+{%li#ngGOnU0P@i>y3+58ye=H4;l z1MVGNXRhV6v>Y>NAC|Q;!PV9)wxzEOiEDdB31*B~zTDaW61|M$B+ zey#bJqGf*cI-7(sT^)aQ)J1~OXSU3aTg%t%aRpxdxPzkv4dj|6ZF*$$<`#6KBUv|a zZE)r;qcWNzWVjmO2Y=$P$~{(Etrw@_?%uS=vaOGpZ606Zi&cq`8;La}S$_O#k+whX zTGi!1td00}Hg~XL!oi*#qFnp+R5db#Mw~GMg&zWH`>AJS`M%dyJXwgKfIm>=WbzJyf`J|DMY+7PoR)aX&szg|t$` zH1uU`SqZ$hH-$24++;@9T}#moc3QuOddLa0G9%IsQ>2r%^ZnO_0jAvvQTQWj@Ir;$ zwpS9P8AsIQ3+1pdxD4}9=vv8Q^b4{?f+#zjC(mE zWZ)pX$;i0#+V;boP+oJmPRQzsikI8Io!&MgXafDBV(2WT65mYlQ099I@vWPFDIw>T zr&!Y;Yp2f$211H<Vq)zf-u4-tKzTkz70sMr+WW%n}KTcqL-I=DH7<4>b}EHo?*3ZlNu<;+GQ`^ zPWh{I~0_4M_y6nV(mz_BIAsKa;?=u>SZ%m)f+lZ)kN;V!!fU7_RC9hS|CxS#Cc}< za&Vv4PZ8wwqvmsZ3a6qZi!&b|JEv|m=v!15`qaB|uKKzv*fXb~%A9U&i8&hoKOw(u ZbhR7^F-*Bxaqv0=tjw*=Dv&OZ{tcKEryl?S literal 0 HcmV?d00001 From cffb22bf2ba41ba6d3927de0fc6e3fca3729983f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 18 Nov 2024 20:43:40 +0530 Subject: [PATCH 034/111] linter fix --- .../global_drafts/components/draft_empty_component/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/global_drafts/components/draft_empty_component/index.tsx b/app/screens/global_drafts/components/draft_empty_component/index.tsx index 60ebfaecc56..efdb3433a86 100644 --- a/app/screens/global_drafts/components/draft_empty_component/index.tsx +++ b/app/screens/global_drafts/components/draft_empty_component/index.tsx @@ -7,7 +7,7 @@ import {Image, Text, View} from 'react-native'; import {useTheme} from '@context/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import { typography } from '@utils/typography'; +import {typography} from '@utils/typography'; const draft_message_image = require('@assets/images/Draft_Message.png'); From b9c48d5d43c21eb236c84e7e6896ddafa5252be2 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 18 Nov 2024 20:53:56 +0530 Subject: [PATCH 035/111] style fix --- .../global_drafts/components/draft_empty_component/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/global_drafts/components/draft_empty_component/index.tsx b/app/screens/global_drafts/components/draft_empty_component/index.tsx index efdb3433a86..8d89bb503d5 100644 --- a/app/screens/global_drafts/components/draft_empty_component/index.tsx +++ b/app/screens/global_drafts/components/draft_empty_component/index.tsx @@ -24,7 +24,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { marginBottom: 20, }, title: { - ...typography('Heading'), + ...typography('Heading', 400, 'SemiBold'), color: theme.centerChannelColor, }, subtitle: { From 424c77386b55712dd81a961eb7aecd28b499f3c7 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 18 Nov 2024 22:56:42 +0530 Subject: [PATCH 036/111] back button an android takes to the channel list page --- .../global_drafts_list/global_drafts_list.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 93e3cc0f900..5226780d22b 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -3,11 +3,13 @@ import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useState} from 'react'; -import {StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; +import {Platform, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@components/post_list/config'; import {Screens} from '@constants'; +import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; +import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; @@ -42,6 +44,14 @@ const GlobalDraftsList: React.FC = ({ } }, [location]); + const collapse = useCallback(() => { + if (Platform.OS === 'android') { + popTopScreen(Screens.GLOBAL_DRAFTS); + } + }, []); + + useAndroidHardwareBackHandler(Screens.GLOBAL_DRAFTS, collapse); + const renderItem = useCallback(({item}: ListRenderItemInfo) => { return ( Date: Mon, 18 Nov 2024 23:22:13 +0530 Subject: [PATCH 037/111] en.json fix --- assets/base/i18n/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 73cf7bce074..79020c2ea70 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -317,6 +317,8 @@ "draft.options.send.title": "Send draft", "draft.options.title": "Draft Options", "drafts": "Drafts", + "drafts.empty.subtitle": "Any message you have started will show here.", + "drafts.empty.title": "No drafts at the moment", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", "edit_server.description": "Specify a display name for this server", From 15e6bc37b95bcf37234dbe994e509e5726cc0527 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 13:55:52 +0530 Subject: [PATCH 038/111] draft actions theme compatible --- app/screens/draft_options/index.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index 4ea9a80836e..c4bdd2317d5 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -3,11 +3,13 @@ import React from 'react'; import {useIntl} from 'react-intl'; -import {StyleSheet, View, Text} from 'react-native'; +import {View, Text} from 'react-native'; import {Screens} from '@constants'; +import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import BottomSheet from '@screens/bottom_sheet'; +import {makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import DeleteDraft from './delete_draft'; @@ -26,12 +28,15 @@ type Props = { export const DRAFT_OPTIONS_BUTTON = 'close-post-options'; -const styles = StyleSheet.create({ - header: { - ...typography('Heading', 600, 'SemiBold'), - display: 'flex', - paddingBottom: 4, - }, +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + header: { + ...typography('Heading', 600, 'SemiBold'), + display: 'flex', + paddingBottom: 4, + color: theme.centerChannelColor, + }, + }; }); const DraftOptions: React.FC = ({ @@ -42,6 +47,8 @@ const DraftOptions: React.FC = ({ }) => { const {formatMessage} = useIntl(); const isTablet = useIsTablet(); + const theme = useTheme(); + const styles = getStyleSheet(theme); const renderContent = () => { return ( From faabf4ea7cc3aecaecd2f9758d8af7b988944a2c Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 14:30:37 +0530 Subject: [PATCH 039/111] CSS fix for draft channel_info and avatar component --- app/components/channel_info/avatar/index.tsx | 11 ++-- app/components/channel_info/channel_info.tsx | 58 +++++++++++++------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/app/components/channel_info/avatar/index.tsx b/app/components/channel_info/avatar/index.tsx index 177e6afe64f..3c7a9b9a430 100644 --- a/app/components/channel_info/avatar/index.tsx +++ b/app/components/channel_info/avatar/index.tsx @@ -20,13 +20,12 @@ type Props = { const styles = StyleSheet.create({ avatarContainer: { backgroundColor: 'rgba(255, 255, 255, 0.4)', - margin: 2, - width: 20, - height: 20, + width: 24, + height: 24, }, avatar: { - height: 20, - width: 20, + height: 24, + width: 24, }, avatarRadius: { borderRadius: 18, @@ -55,7 +54,7 @@ const Avatar = ({ picture = ( ); diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx index 542d0e38098..e18fc10d35c 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/channel_info/channel_info.tsx @@ -41,21 +41,31 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { infoContainer: { display: 'flex', flexDirection: 'row', - gap: 5, alignItems: 'center', }, channelInfo: { display: 'flex', flexDirection: 'row', - gap: 5, alignItems: 'center', }, + category: { + color: changeOpacity(theme.centerChannelColor, 0.64), + ...typography('Body', 75, 'SemiBold'), + marginRight: 8, + }, + categoryIconContainer: { + width: 24, + height: 24, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.08), + padding: 4, + borderRadius: 555, + }, + profileComponentContainer: { + marginRight: 6, + }, displayName: { - fontSize: 12, - fontWeight: '600', color: changeOpacity(theme.centerChannelColor, 0.64), - lineHeight: 16, - fontFamily: 'Open Sans', + ...typography('Body', 75, 'SemiBold'), }, timeInfo: { fontSize: 12, @@ -64,10 +74,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { lineHeight: 16, }, time: { - color: theme.centerChannelColor, - marginTop: 5, - opacity: 0.5, - ...typography('Body', 75, 'Regular'), + color: changeOpacity(theme.centerChannelColor, 0.64), + ...typography('Body', 75), }, }; }); @@ -87,11 +95,13 @@ const ChannelInfo: React.FC = ({ let headerComponent: ReactNode = null; const profileComponent = draftReceiverUser ? : ( - ); + + + ); if (rootId) { headerComponent = ( @@ -99,9 +109,11 @@ const ChannelInfo: React.FC = ({ - {profileComponent} + + {profileComponent} + ); } else if (isChannelTypeDM) { @@ -110,9 +122,11 @@ const ChannelInfo: React.FC = ({ - {profileComponent} + + {profileComponent} + ); } else { @@ -121,9 +135,11 @@ const ChannelInfo: React.FC = ({ - {profileComponent} + + {profileComponent} + ); } From 9b130b652164399bc6284939f099e479e74646c0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 14:52:18 +0530 Subject: [PATCH 040/111] removed the badge icon and change font style drafts --- app/components/channel_item/channel_item.tsx | 3 +- .../drafts_buttton/drafts_button.tsx | 40 +++++++++++++------ .../channel_list/categories_list/index.tsx | 4 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/components/channel_item/channel_item.tsx b/app/components/channel_item/channel_item.tsx index e8f3f19b9fc..5f4eb9e7d3a 100644 --- a/app/components/channel_item/channel_item.tsx +++ b/app/components/channel_item/channel_item.tsx @@ -51,7 +51,8 @@ export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ marginRight: 12, }, text: { - color: changeOpacity(theme.sidebarText, 0.72), + color: theme.sidebarText, + ...typography('Body', 200), }, highlight: { color: theme.sidebarUnreadText, diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index e3ab01a29dc..5793f389fbe 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -2,10 +2,9 @@ // See LICENSE.txt for license information. import React, {useCallback, useMemo} from 'react'; -import {TouchableOpacity, View} from 'react-native'; +import {Text, TouchableOpacity, View} from 'react-native'; import {switchToGlobalDrafts} from '@actions/local/draft'; -import Badge from '@components/badge'; import { getStyleSheet as getChannelItemStyleSheet, ROW_HEIGHT, @@ -16,6 +15,7 @@ import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; import CompassIcon from '../compass_icon'; @@ -40,6 +40,20 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ text: { flex: 1, }, + countContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + count: { + color: theme.sidebarText, + ...typography('Body', 75, 'SemiBold'), + opacity: 0.64, + }, + opacity: { + opacity: 0.56, + }, })); const DraftsButton: React.FC = ({ @@ -58,7 +72,7 @@ const DraftsButton: React.FC = ({ const isActive = isTablet && shouldHighlighActive && !currentChannelId; - const [containerStyle, iconStyle, textStyle, badgeStyle] = useMemo(() => { + const [containerStyle, iconStyle, textStyle] = useMemo(() => { const container = [ styles.container, HOME_PADDING, @@ -80,11 +94,7 @@ const DraftsButton: React.FC = ({ isActive && styles.textActive, ]; - const badge = [ - styles.badge, - ]; - - return [container, icon, text, badge]; + return [container, icon, text]; }, [customStyles, isActive, styles]); if (!draftsCount) { @@ -106,11 +116,15 @@ const DraftsButton: React.FC = ({ defaultMessage='Drafts' style={textStyle} /> - 0} - /> + + + {draftsCount} + ); diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index cc026a4f57d..085e8597361 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -73,9 +73,7 @@ const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: C shouldHighlighActive={true} /> } - + ); From c595c3dc9d0d0e7bd4f30f1464344efac56b4a7c Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 15:01:30 +0530 Subject: [PATCH 041/111] fix send alert sender name for GMs --- app/screens/draft_options/send_draft/index.tsx | 2 ++ .../draft_options/send_draft/send_draft.tsx | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/screens/draft_options/send_draft/index.tsx b/app/screens/draft_options/send_draft/index.tsx index 4a0b2e56048..b1fd21d14b9 100644 --- a/app/screens/draft_options/send_draft/index.tsx +++ b/app/screens/draft_options/send_draft/index.tsx @@ -64,6 +64,7 @@ const enhanced = withObservables([], ({database, rootId, channelId}: Props) => { const channelInfo = channel.pipe(switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$(undefined)))); const channelType = channel.pipe(switchMap((c) => of$(c?.type))); const channelName = channel.pipe(switchMap((c) => of$(c?.name))); + const channelDisplayName = channel.pipe(switchMap((c) => of$(c?.displayName))); const membersCount = channelInfo.pipe( switchMap((i) => (i ? of$(i.memberCount) : of$(0))), ); @@ -73,6 +74,7 @@ const enhanced = withObservables([], ({database, rootId, channelId}: Props) => { return { channelType, channelName, + channelDisplayName, currentUserId, enableConfirmNotificationsToChannel, maxMessageLength, diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index 3b723854b95..71089810c6a 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -28,6 +28,7 @@ type Props = { channelType: ChannelType | undefined; currentUserId: string; channelName: string | undefined; + channelDisplayName?: string; enableConfirmNotificationsToChannel?: boolean; maxMessageLength: number; membersCount?: number; @@ -63,6 +64,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const SendDraft: React.FC = ({ channelId, channelName, + channelDisplayName, rootId, channelType, bottomSheetId, @@ -116,7 +118,18 @@ const SendDraft: React.FC = ({ if (persistentNotificationsEnabled) { persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, handleSendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType); } else { - const receivingChannel = channelType === General.DM_CHANNEL ? draftReceiverUserName : channelName; + let receivingChannel = channelName; + switch (channelType) { + case General.DM_CHANNEL: + receivingChannel = draftReceiverUserName; + break; + case General.GM_CHANNEL: + receivingChannel = channelDisplayName; + break; + default: + receivingChannel = channelName; + break; + } sendMessageWithAlert({ title: intl.formatMessage({ id: 'send_message.confirm.title', From b167354523318d5a2e3e68dbc7f86bff290b26fd Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 15:05:26 +0530 Subject: [PATCH 042/111] updated snapshot --- .../__snapshots__/channel_item.test.tsx.snap | 24 +++++++++++++++---- .../threads_button.test.tsx.snap | 12 ++++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/components/channel_item/__snapshots__/channel_item.test.tsx.snap b/app/components/channel_item/__snapshots__/channel_item.test.tsx.snap index 4db607bcde8..6a64876703e 100644 --- a/app/components/channel_item/__snapshots__/channel_item.test.tsx.snap +++ b/app/components/channel_item/__snapshots__/channel_item.test.tsx.snap @@ -92,7 +92,11 @@ exports[`components/channel_list/categories/body/channel_item should match snaps "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, null, @@ -213,7 +217,11 @@ exports[`components/channel_list/categories/body/channel_item should match snaps "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, null, @@ -251,7 +259,11 @@ exports[`components/channel_list/categories/body/channel_item should match snaps "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, null, @@ -361,7 +373,11 @@ exports[`components/channel_list/categories/body/channel_item should match snaps "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, null, diff --git a/app/components/threads_button/__snapshots__/threads_button.test.tsx.snap b/app/components/threads_button/__snapshots__/threads_button.test.tsx.snap index 857a7e3dab4..f3a48348a99 100644 --- a/app/components/threads_button/__snapshots__/threads_button.test.tsx.snap +++ b/app/components/threads_button/__snapshots__/threads_button.test.tsx.snap @@ -79,7 +79,11 @@ exports[`Thread item in the channel list Threads Component should match snapshot "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, false, @@ -174,7 +178,11 @@ exports[`Thread item in the channel list Threads Component should match snapshot "lineHeight": 24, }, { - "color": "rgba(255,255,255,0.72)", + "color": "#ffffff", + "fontFamily": "OpenSans", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 24, }, false, false, From eee6c26dc61430da5aae335384d6069b6510d930 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 19:12:14 +0530 Subject: [PATCH 043/111] added testId to the drafts components --- app/components/draft/draft.tsx | 1 + app/components/draft/draft_post/draft_message/index.tsx | 5 ++++- app/components/draft/draft_post/index.tsx | 5 ++++- app/screens/draft_options/delete_draft/index.tsx | 1 + app/screens/draft_options/edit_draft/index.tsx | 1 + app/screens/draft_options/send_draft/send_draft.tsx | 1 + .../global_drafts/components/draft_empty_component/index.tsx | 5 ++++- .../components/global_drafts_list/SwipeableDraft/index.tsx | 1 + .../components/global_drafts_list/global_drafts_list.tsx | 1 + 9 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 142f109cc57..cf73ccd3306 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -78,6 +78,7 @@ const Draft: React.FC = ({ delayLongPress={200} onLongPress={onLongPress} underlayColor={changeOpacity(theme.centerChannelColor, 0.1)} + testID='draft_post' > = ({ return ( <> - + = ({ const style = getStyleSheet(theme); return ( - + = ({ type={'opacity'} style={style.draftOptions} onPress={draftDeleteHandler} + testID='delete_draft' > = ({ type={'opacity'} style={style.draftOptions} onPress={editHandler} + testID='edit_draft' > = ({ type={'opacity'} style={[style.draftOptions]} onPress={draftSendHandler} + testID='send_draft' > { const theme = useTheme(); const styles = getStyleSheet(theme); return ( - + diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx index 6beb62541c6..b4f2950d122 100644 --- a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx @@ -113,6 +113,7 @@ const SwipeableDraft: React.FC = ({ ref={swipeable} onSwipeableWillOpen={onSwipeableWillOpen} childrenContainerStyle={{flex: 1}} + testID='draft_swipeable' > = ({ Date: Tue, 26 Nov 2024 19:14:41 +0530 Subject: [PATCH 044/111] updated send draft test id --- app/screens/draft_options/send_draft/send_draft.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index 9c4e0ddd6e0..0dfd3cb74c2 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -147,7 +147,7 @@ const SendDraft: React.FC = ({ type={'opacity'} style={[style.draftOptions]} onPress={draftSendHandler} - testID='send_draft' + testID='send_draft_button' > Date: Mon, 2 Dec 2024 22:36:13 +0530 Subject: [PATCH 045/111] clicking on draft takes to the channel --- app/components/draft/draft.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index cf73ccd3306..dd092831c73 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -5,10 +5,13 @@ import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, TouchableHighlight, View} from 'react-native'; +import {switchToThread} from '@actions/local/thread'; +import {switchToChannelById} from '@actions/remote/channel'; import ChannelInfo from '@components/channel_info'; import DraftPost from '@components/draft/draft_post'; import Header from '@components/post_draft/draft_input/header'; import {Screens} from '@constants'; +import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {DRAFT_OPTIONS_BUTTON} from '@screens/draft_options'; @@ -59,6 +62,7 @@ const Draft: React.FC = ({ const theme = useTheme(); const style = getStyleSheet(theme); const isTablet = useIsTablet(); + const serverUrl = useServerUrl(); const showPostPriority = Boolean(isPostPriorityEnabled && draft.metadata?.priority && draft.metadata?.priority?.priority); const onLongPress = useCallback(() => { @@ -73,10 +77,19 @@ const Draft: React.FC = ({ }); }, [channel, draft, draftReceiverUser?.username, intl, isTablet, theme]); + const onPress = useCallback(() => { + if (draft.rootId) { + switchToThread(serverUrl, draft.rootId); + return; + } + switchToChannelById(serverUrl, channel.id, channel.teamId); + }, [channel.id, channel.teamId, draft.rootId, serverUrl]); + return ( From b26aa59bbb7bc28dae728fb9bfeb9ed019211ca7 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 4 Dec 2024 23:43:05 +0530 Subject: [PATCH 046/111] Added toptip for draft tours --- app/actions/app/global.ts | 4 + app/constants/tutorial.ts | 2 + .../global_drafts_list/global_drafts_list.tsx | 84 ++++++++++++++++- .../components/global_drafts_list/index.tsx | 4 + .../components/global_drafts_list/tooltip.tsx | 89 ++++++++++++++++++ assets/base/images/emojis/swipe.png | Bin 0 -> 3876 bytes 6 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 app/screens/global_drafts/components/global_drafts_list/tooltip.tsx create mode 100644 assets/base/images/emojis/swipe.png diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index fde5c8d11f9..9c1159bc910 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -40,6 +40,10 @@ export const storeSkinEmojiSelectorTutorial = async (prepareRecordsOnly = false) return storeGlobal(Tutorial.EMOJI_SKIN_SELECTOR, 'true', prepareRecordsOnly); }; +export const storeDraftsTutorial = async () => { + return storeGlobal(Tutorial.DRAFTS, 'true', false); +}; + export const storeDontAskForReview = async (prepareRecordsOnly = false) => { return storeGlobal(GLOBAL_IDENTIFIERS.DONT_ASK_FOR_REVIEW, 'true', prepareRecordsOnly); }; diff --git a/app/constants/tutorial.ts b/app/constants/tutorial.ts index c830f519ada..1977ab700e8 100644 --- a/app/constants/tutorial.ts +++ b/app/constants/tutorial.ts @@ -4,9 +4,11 @@ export const MULTI_SERVER = 'multiServerTutorial'; export const PROFILE_LONG_PRESS = 'profileLongPressTutorial'; export const EMOJI_SKIN_SELECTOR = 'emojiSkinSelectorTutorial'; +export const DRAFTS = 'draftsTutorial'; export default { MULTI_SERVER, PROFILE_LONG_PRESS, EMOJI_SKIN_SELECTOR, + DRAFTS, }; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 34d11bd80b4..2d4c7e5472b 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -2,24 +2,29 @@ // See LICENSE.txt for license information. import {FlatList} from '@stream-io/flat-list-mvcp'; -import React, {useCallback, useState} from 'react'; -import {Platform, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; -import Animated from 'react-native-reanimated'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {InteractionManager, Platform, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; +import Animated, {FadeIn, FadeOut, useAnimatedStyle, withDelay, withTiming} from 'react-native-reanimated'; +import Tooltip from 'react-native-walkthrough-tooltip'; +import {storeDraftsTutorial} from '@actions/app/global'; import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@components/post_list/config'; import {Screens} from '@constants'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; +import {useIsTablet} from '@hooks/device'; import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; import SwipeableDraft from './SwipeableDraft'; +import DraftTooltip from './tooltip'; import type DraftModel from '@typings/database/models/servers/draft'; type Props = { allDrafts: DraftModel[]; location: string; + tutorialWatched: boolean; } const styles = StyleSheet.create({ @@ -28,6 +33,12 @@ const styles = StyleSheet.create({ flexGrow: 1, justifyContent: 'center', }, + tooltipStyle: { + shadowColor: '#000', + shadowOffset: {width: 0, height: 2}, + shadowRadius: 2, + shadowOpacity: 0.16, + }, }); const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); @@ -36,14 +47,34 @@ const keyExtractor = (item: DraftModel) => item.id; const GlobalDraftsList: React.FC = ({ allDrafts, location, + tutorialWatched, }) => { const [layoutWidth, setLayoutWidth] = useState(0); + const [tooltipVisible, setTooltipVisible] = useState(false); + const isTablet = useIsTablet(); const onLayout = useCallback((e: LayoutChangeEvent) => { if (location === Screens.GLOBAL_DRAFTS) { setLayoutWidth(e.nativeEvent.layout.width - 40); // 40 is the padding of the container } }, [location]); + const firstDraftId = allDrafts.length ? allDrafts[0].id : ''; + + const tooltipContentStyle = useMemo(() => ({ + borderRadius: 8, + maxWidth: isTablet ? 352 : 247, + padding: 0, + height: 160, + }), [isTablet]); + + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (tutorialWatched) { + setTooltipVisible(true); + } + }); + }, [tutorialWatched]); + const collapse = useCallback(() => { if (Platform.OS === 'android') { popTopScreen(Screens.GLOBAL_DRAFTS); @@ -52,7 +83,52 @@ const GlobalDraftsList: React.FC = ({ useAndroidHardwareBackHandler(Screens.GLOBAL_DRAFTS, collapse); + const close = useCallback(() => { + setTooltipVisible(false); + storeDraftsTutorial(); + }, []); + + const widthAnimatedStyle = useAnimatedStyle(() => { + return { + width: withDelay(400, withTiming(layoutWidth + 40, {duration: 300})), + marginLeft: Platform.OS === 'android' ? 10 : undefined, + }; + }, [layoutWidth]); + + const opacityStyle = useAnimatedStyle(() => { + return { + opacity: withDelay(700, withTiming(1, {duration: 350})), + }; + }, []); + const renderItem = useCallback(({item}: ListRenderItemInfo) => { + if (item.id === firstDraftId && tutorialWatched) { + return ( + } + onClose={close} + tooltipStyle={styles.tooltipStyle} + > + + + + + + + ); + } return ( = ({ layoutWidth={layoutWidth} /> ); - }, [layoutWidth, location]); + }, [close, firstDraftId, isTablet, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, widthAnimatedStyle]); return ( { const allDrafts = observeAllDrafts(database, teamId); + const tutorialWatched = observeTutorialWatched(Tutorial.DRAFTS); return { allDrafts, + tutorialWatched, }; }); diff --git a/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx b/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx new file mode 100644 index 00000000000..5a4f6e3b2b8 --- /dev/null +++ b/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx @@ -0,0 +1,89 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Image} from 'expo-image'; +import React from 'react'; +import {StyleSheet, TouchableOpacity, View} from 'react-native'; + +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; +import {Preferences} from '@constants'; +import {changeOpacity} from '@utils/theme'; +import {typography} from '@utils/typography'; + +type Props = { + onClose: () => void; +} + +const logo = require('@assets/images/emojis/swipe.png'); + +const hitSlop = {top: 10, bottom: 10, left: 10, right: 10}; + +const styles = StyleSheet.create({ + container: { + marginHorizontal: 24, + }, + close: { + position: 'absolute', + top: 0, + right: 0, + }, + descriptionContainer: { + marginBottom: 24, + marginTop: 12, + }, + description: { + color: Preferences.THEMES.denim.centerChannelColor, + ...typography('Heading', 200), + textAlign: 'center', + }, + titleContainer: { + alignItems: 'center', + justifyContent: 'center', + marginTop: 22, + position: 'relative', + }, + title: { + color: Preferences.THEMES.denim.centerChannelColor, + ...typography('Body', 200, 'SemiBold'), + }, + image: { + height: 68, + width: 68, + }, +}); + +const DraftTooltip = ({onClose}: Props) => { + return ( + + + + + + + + + + + + ); +}; + +export default DraftTooltip; diff --git a/assets/base/images/emojis/swipe.png b/assets/base/images/emojis/swipe.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef4d61366a2153e804cbffd0467e5b25d0bc2ee GIT binary patch literal 3876 zcmV+<58LpGP)ByBcu+51WHIDYoLCvz3Y9?yZ4N}WW6(M@6&E1KFQ4N+&gz> zzI$HxoC^hYMn^{n+tt;@wY0P_0|RMJ(^N_jcskcENwV&8c{PsXjaclKvTN5ah1{dU z9D#y5fP4_$+uJKfBC?qO@udPuYG~lIq9{s7j~>-v7QsLbfkY^Z*L~+szekpVV_Ak` z7}g*kCq<*I#In(JD6}!%*?A}x4&O-Q-@bi6%zpYQ$N;Kl7@Fa9iiALe#{1K0)vT@ zPN$kVe*AbE>Wr!hX>V_5qET?GYL>&_NJJn2$y$gyj@{ocIdjC+a_nNLiV%F^yLaP0 zv=f3L>XAqwQHey%givT7u|+ATBxPx|FD)(J@p_R`w!zCGpA47Fqj|kBUJo)5Jw2US z9G@tUiqFI6;`5;ns7ONc$xswZ+OgxY6fvlJvHtt_@8>Sx9Pv6e$^il+>YB>s+C-Yo z%Ce@U86&f1X=D7*q3KE8v}unARj6K)QZ^yzNxHhu`;m-HMwLJO@I<^=-<><3cd2Ts zL8T2Rln7R*(k$zWoILrbRIxtWx3~MyW+5r(&u@s4x6+|TC|m4!o_A7|z@yFBvSn?& zr>Do_4$(`jTGiGngWRA~K!Pz%cQ8iWP^svOWebKLS^dz+;kSQ3(s%26c_};`SPamnN+P*ltsvf4ZG%{s(3t*wu=dn42ANxF$_bqJe6M7($HVYiy|>T zU!)mTO*cSewdid-Qr4~8?IiEwR8?L3;Df(Mp#~_K5Oe0i<3?5K>N+|GmX{;Jly`6m zlEFE(vD3$n4MCOIwr9^mVr}OkDGx7g{L+S8YS*sz0J>v{DeXbPAceS>Xi&8Ar~%kgI)y?Vq+c&PAswAr zYx}bex|TI{e(Q$E2G4dMvmHaGFD_;%)<*;50?^5rw5o%a;sJ&8-@2Z`-P#A zI5>3;xYHkAolh=}A&o+g>Mn9dz&2+LDqagy+wh>?f0w6pvN=pLXUt(^yJBtme^tA*6+9R6tm-s4pSeY}9nk9*X3JTklkl*om=LB@%_F+4NfkKj3$&VOz`BuO$QgBahc zlF>8Hf*_2L2Y85XTH6{0Nr|Ny&@|RUhz@j$sEx%AP^=pSjtn{WK5-{$^G{^yMb;P7 zEHRCdlti3Dk1=jz#2bkk#Yym(tSUEBg35%!Vk_^=};u|+^%oMg|GAZ-#n;5 zQ?ymp-H?Bs!~-fZmVJf&CD~e~WEp*mQDs53?DY=-hx##%wM**U$vDInqd`P zW4N)m9$Tz>Wa6DLLr+cA@eX-!m@TI?Dq zIWSXImpQ|v9^ww~#VZdhg6@VM}nfZQHX888RM@yS#0Yt?EgV zV6J{i#w-IhK*@wmWV0qJXS;XziiPbcMVt{gNzqv~u$AONFoRX<_>cvE?i7tA-mg&E zd1mIGjBBSVm}JhCQSGvAm7uJ+-z*b&27FkJpEr+<%~Xa!G1t<<_^MTb7=BRi8}Q|# z1^!_3LZ5YTg?g~}dU(yF54XHVQ|x?2r&4K|x&F!Zz)!dQ;v@PyVHsu(O&hHOtFH_=m8U~%hhD<=OK*Tb@|VVaMt3lThIZol2r*kLG7x1i?yg)p znMM&sQQf}%s>iMm5=Rov{XU1c-CSk(a|y;(HL!Qqw&nm8^CRtiwAinxcY5Bm`mKrox(!-hhyCI%wJ!r~#@<5ame0plxEep~sCXWVe~cI1}`f z%o=YIE% zE;4NbafbpSM#4HyUi{#PDqtsBiIjA}?aC8M%6N^uS7$Y9RMEC1>(fE^EklfW;kEWPT=+83qP<`Cx)@{2?lJ2Do{2d$=HR<%Gf7i2&^Yb&IY>| z3i`atsek?Y)8!M}CK5485>pNC)U<>krX$3zjRbKvJ&h1lfwBn+?1h*-cKJnRgd~c6 z>+CxbQh+ut8K;6TV4{dr=re91V-n(Y@V2)Id~tJzZFUmkbZ~apgHQ!3@-m)w=#e8A z4ywtpy_DR8(P^9vuE8@R+@>fCoW0pnH4XS8r1-rK(r%Ryl7%OQcq>uHiC_>aM8$5` z-41Wc!)FicFvEhDG2TUwLBzuK?{z1u&>+RoXO^@r z2zp$_w`U+}(|vHIKx3OoWv9Apl1iYS3WuQrR4s374?@_`^etc98Vrv{lSTX0gA4?! zj9u(fDQcpCND3KfOW)nm@W9YGe4SAbMKhl#(~eVkwLsXW9i|%|N{lRiZBc z_dg$4CLj+vgUkLEOBR|rj5;AkZSM;}m8h$PAkgCw)j5cXoX>9!!DzHTMwS%IDo!lt zqwa(t2q|x3Y=WeO??#{=3&3bH9flfURtP~5QabJhY-Fu7U8D>cc-oDz})Zm1)lj1|5RMZu$xw{-&TgJHlpIbJ5J(Njxl mxR3j|kNdcf`?!xe#s2{KaM{HrlLVmv0000 Date: Wed, 4 Dec 2024 23:44:39 +0530 Subject: [PATCH 047/111] intl extract --- assets/base/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 79020c2ea70..38f62b4838a 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -316,6 +316,7 @@ "draft.options.edit.title": "Edit draft", "draft.options.send.title": "Send draft", "draft.options.title": "Draft Options", + "draft.tooltip.description": "Long-press on an item to see draft actions", "drafts": "Drafts", "drafts.empty.subtitle": "Any message you have started will show here.", "drafts.empty.title": "No drafts at the moment", From 5d7bc78dea33b717f610bb4ce8a3da3c6fea47ee Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 5 Dec 2024 09:19:40 +0530 Subject: [PATCH 048/111] Rebase to main and reverted local testing changes --- .../components/global_drafts_list/global_drafts_list.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 2d4c7e5472b..88974afc386 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -69,7 +69,7 @@ const GlobalDraftsList: React.FC = ({ useEffect(() => { InteractionManager.runAfterInteractions(() => { - if (tutorialWatched) { + if (!tutorialWatched) { setTooltipVisible(true); } }); @@ -102,7 +102,7 @@ const GlobalDraftsList: React.FC = ({ }, []); const renderItem = useCallback(({item}: ListRenderItemInfo) => { - if (item.id === firstDraftId && tutorialWatched) { + if (item.id === firstDraftId && !tutorialWatched) { return ( Date: Thu, 5 Dec 2024 11:40:35 +0530 Subject: [PATCH 049/111] Added tooltip for drafts --- .../global_drafts_list/global_drafts_list.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 88974afc386..b580ad23248 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -90,7 +90,7 @@ const GlobalDraftsList: React.FC = ({ const widthAnimatedStyle = useAnimatedStyle(() => { return { - width: withDelay(400, withTiming(layoutWidth + 40, {duration: 300})), + width: withDelay(200, withTiming(layoutWidth + 40, {duration: 300})), marginLeft: Platform.OS === 'android' ? 10 : undefined, }; }, [layoutWidth]); @@ -102,10 +102,10 @@ const GlobalDraftsList: React.FC = ({ }, []); const renderItem = useCallback(({item}: ListRenderItemInfo) => { - if (item.id === firstDraftId && !tutorialWatched) { + if (item.id === firstDraftId && tutorialWatched) { return ( = ({ tooltipStyle={styles.tooltipStyle} > - - - + ); @@ -136,7 +134,7 @@ const GlobalDraftsList: React.FC = ({ layoutWidth={layoutWidth} /> ); - }, [close, firstDraftId, isTablet, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, widthAnimatedStyle]); + }, [close, firstDraftId, isTablet, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, tutorialWatched, widthAnimatedStyle]); return ( Date: Thu, 5 Dec 2024 18:24:08 +0530 Subject: [PATCH 050/111] addressed review comments --- app/components/channel_info/avatar/index.tsx | 2 +- app/components/channel_info/channel_info.tsx | 6 ----- app/components/drafts_buttton/index.tsx | 3 +-- .../post_draft/post_input/post_input.tsx | 15 +++++++------ app/queries/servers/drafts.ts | 2 +- .../global_drafts_list/global_drafts_list.tsx | 15 +++++++------ app/screens/global_drafts/index.tsx | 6 +++-- app/utils/draft/index.ts | 22 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 18 +++++++++++++++ 9 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/components/channel_info/avatar/index.tsx b/app/components/channel_info/avatar/index.tsx index 3c7a9b9a430..f1d36e49c28 100644 --- a/app/components/channel_info/avatar/index.tsx +++ b/app/components/channel_info/avatar/index.tsx @@ -38,7 +38,7 @@ const Avatar = ({ const serverUrl = useServerUrl(); let uri = ''; - if (!uri && author) { + if (author) { uri = buildProfileImageUrlFromUser(serverUrl, author); } diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx index e18fc10d35c..5404e1cf8ec 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/channel_info/channel_info.tsx @@ -67,12 +67,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 75, 'SemiBold'), }, - timeInfo: { - fontSize: 12, - fontWeight: '400', - color: changeOpacity(theme.centerChannelColor, 0.64), - lineHeight: 16, - }, time: { color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 75), diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index bd89c62f1e6..3fd1c22647f 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -1,6 +1,5 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable max-nested-callbacks */ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import React from 'react'; @@ -21,7 +20,7 @@ const withTeamId = withObservables([], ({database}: WithDatabaseArgs) => ({ })); const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { - const draftsCount = observeDraftCount(database, teamId); // Observe the draft count + const draftsCount = observeDraftCount(database, teamId); return { currentChannelId: observeCurrentChannelId(database), draftsCount, diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index 8fa2f99838c..ea50ef45b03 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -11,7 +11,7 @@ import { type NativeSyntheticEvent, Platform, type TextInputSelectionChangeEventData, } from 'react-native'; -import {parseMarkdownImages, updateDraftMarkdownImageMetadata, updateDraftMessage} from '@actions/local/draft'; +import {updateDraftMessage} from '@actions/local/draft'; import {userTyping} from '@actions/websocket/users'; import {Events, Screens} from '@constants'; import {useExtraKeyboardContext} from '@context/extra_keyboard'; @@ -21,6 +21,7 @@ import {useIsTablet} from '@hooks/device'; import {useInputPropagation} from '@hooks/input'; import {t} from '@i18n'; import NavigationStore from '@store/navigation_store'; +import {handleDraftUpdate} from '@utils/draft'; import {extractFileInfo} from '@utils/file'; import {changeOpacity, makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme} from '@utils/theme'; @@ -148,12 +149,12 @@ export default function PostInput({ const onBlur = useCallback(async () => { keyboardContext?.registerTextInputBlur(); - await updateDraftMessage(serverUrl, channelId, rootId, value); - const imageMetadata: Dictionary = {}; - await parseMarkdownImages(value, imageMetadata); - if (Object.keys(imageMetadata).length !== 0) { - updateDraftMarkdownImageMetadata({serverUrl, channelId, rootId, imageMetadata}); - } + handleDraftUpdate({ + serverUrl, + channelId, + rootId, + value, + }); setIsFocused(false); }, [keyboardContext, serverUrl, channelId, rootId, value, setIsFocused]); diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index b05d8034318..5f7e868f3c4 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -47,7 +47,7 @@ export const queryAllDrafts = (database: Database, teamId: string) => { }; export const observeAllDrafts = (database: Database, teamId: string) => { - return queryAllDrafts(database, teamId).observeWithColumns(['messages', 'files', 'metadata']); + return queryAllDrafts(database, teamId).observeWithColumns(['update_at']); }; export const observeDraftCount = (database: Database, teamId: string) => { diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index b580ad23248..e22c86aa9c9 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -68,12 +68,13 @@ const GlobalDraftsList: React.FC = ({ }), [isTablet]); useEffect(() => { + if (tutorialWatched) { + return; + } InteractionManager.runAfterInteractions(() => { - if (!tutorialWatched) { - setTooltipVisible(true); - } + setTooltipVisible(true); }); - }, [tutorialWatched]); + }, []); const collapse = useCallback(() => { if (Platform.OS === 'android') { @@ -102,13 +103,13 @@ const GlobalDraftsList: React.FC = ({ }, []); const renderItem = useCallback(({item}: ListRenderItemInfo) => { - if (item.id === firstDraftId && tutorialWatched) { + if (item.id === firstDraftId && !tutorialWatched) { return ( } onClose={close} tooltipStyle={styles.tooltipStyle} diff --git a/app/screens/global_drafts/index.tsx b/app/screens/global_drafts/index.tsx index 354d00b289a..0ca96c48958 100644 --- a/app/screens/global_drafts/index.tsx +++ b/app/screens/global_drafts/index.tsx @@ -58,8 +58,10 @@ const GlobalDrafts = ({componentId}: Props) => { const onBackPress = useCallback(() => { Keyboard.dismiss(); - popTopScreen(componentId); - }, [componentId]); + if (!isTablet) { + popTopScreen(componentId); + } + }, [componentId, isTablet]); return ( { + await updateDraftMessage(serverUrl, channelId, rootId, value); + const imageMetadata: Dictionary = {}; + await parseMarkdownImages(value, imageMetadata); + + if (Object.keys(imageMetadata).length !== 0) { + updateDraftMarkdownImageMetadata({serverUrl, channelId, rootId, imageMetadata}); + } +}; + export function deleteDraftConfirmation({intl, serverUrl, channelId, rootId, swipeable}: { intl: IntlShape; serverUrl: string; diff --git a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved index 62cc6b8f771..9be5f0f2633 100644 --- a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,6 +46,24 @@ "version": "1.9.200" } }, + { + "package": "OpenGraph", + "repositoryURL": "https://github.com/satoshi-takano/OpenGraph.git", + "state": { + "branch": null, + "revision": "382972f1963580eeabafd88ad012e66576b4d213", + "version": "1.4.1" + } + }, + { + "package": "Sentry", + "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", + "state": { + "branch": null, + "revision": "5575af93efb776414f243e93d6af9f6258dc539a", + "version": "8.36.0" + } + }, { "package": "SQLite.swift", "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", From aa1ad72d4d73a4c7554be47618ef2390aed65056 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 10 Dec 2024 15:08:54 +0530 Subject: [PATCH 051/111] reset navigation when click on a draft in draft tabs --- app/actions/local/channel.ts | 7 ++- app/actions/local/thread.ts | 49 +++++++++++++++-- app/actions/remote/channel.ts | 4 +- app/components/draft/draft.tsx | 4 +- .../draft_options/edit_draft/index.tsx | 4 +- .../global_drafts_list/global_drafts_list.tsx | 2 +- app/screens/navigation.ts | 54 +++++++++++++++++++ 7 files changed, 110 insertions(+), 14 deletions(-) diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index 493d58e0df9..435cd729eea 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -17,7 +17,7 @@ import {queryDisplayNamePreferences} from '@queries/servers/preference'; import {prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team'; import {getCurrentUser, queryUsersById} from '@queries/servers/user'; -import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation'; +import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen, resetToRootAndAddScreenOnTop} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {isTablet} from '@utils/helpers'; import {logError, logInfo} from '@utils/log'; @@ -27,7 +27,7 @@ import type {Model} from '@nozbe/watermelondb'; import type ChannelModel from '@typings/database/models/servers/channel'; import type UserModel from '@typings/database/models/servers/user'; -export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false) { +export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false, isNavigatedFromDraft = false) { try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); let models: Model[] = []; @@ -88,6 +88,9 @@ export async function switchToChannel(serverUrl: string, channelId: string, team if (isTabletDevice) { dismissAllModalsAndPopToRoot(); DeviceEventEmitter.emit(NavigationConstants.NAVIGATION_HOME, Screens.CHANNEL); + } else if (isNavigatedFromDraft) { + dismissAllModals(); + resetToRootAndAddScreenOnTop(Screens.CHANNEL, {}, {topBar: {visible: false}}); } else { dismissAllModalsAndPopToScreen(Screens.CHANNEL, '', undefined, {topBar: {visible: false}}); } diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index 76b5344cf6d..abb10bd615b 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -1,7 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import merge from 'deepmerge'; import {DeviceEventEmitter} from 'react-native'; +import tinycolor from 'tinycolor2'; import {ActionType, General, Navigation, Screens} from '@constants'; import DatabaseManager from '@database/manager'; @@ -12,7 +14,7 @@ import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, type Prep import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team'; import {getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, goToScreen} from '@screens/navigation'; +import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, getThemeFromState, goToScreen, resetToRootAndAddScreenOnTop} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; @@ -20,6 +22,7 @@ import {logError} from '@utils/log'; import {changeOpacity} from '@utils/theme'; import type Model from '@nozbe/watermelondb/Model'; +import type {Options} from 'react-native-navigation'; export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, prepareRecordsOnly = false) => { try { @@ -57,7 +60,7 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, } }; -export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => { +export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false, isNavigatedFromDraft = false) => { try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const user = await getCurrentUser(database); @@ -122,8 +125,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo subtitle = translations[t('thread.header.thread_in')] || 'in {channelName}'; subtitle = subtitle.replace('{channelName}', channel.displayName); } - - goToScreen(Screens.THREAD, '', {rootId}, { + const threadOptions = { topBar: { title: { text: title, @@ -138,7 +140,44 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo active: true, }, }, - }); + }; + if (isNavigatedFromDraft && !isTabletDevice) { + const theme = getThemeFromState(); + const isDark = tinycolor(theme.sidebarBg).isDark(); + const defaultOptions: Options = { + layout: { + componentBackgroundColor: theme.centerChannelBg, + }, + popGesture: true, + sideMenu: { + left: {enabled: false}, + right: {enabled: false}, + }, + statusBar: { + style: isDark ? 'light' : 'dark', + }, + topBar: { + animate: true, + visible: true, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + testID: 'screen.back.button', + }, + background: { + color: theme.sidebarBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + text: title, + }, + }, + }; + resetToRootAndAddScreenOnTop(Screens.THREAD, {rootId}, merge(defaultOptions, threadOptions)); + return {}; + } + + goToScreen(Screens.THREAD, '', {rootId}, threadOptions); return {}; } catch (error) { diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index e3e15f9f19c..523f1e8b685 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -1039,7 +1039,7 @@ export async function getChannelTimezones(serverUrl: string, channelId: string) } } -export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false) { +export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, isNavigatedFromDraft = false) { if (channelId === Screens.GLOBAL_THREADS) { return switchToGlobalThreads(serverUrl, teamId); } @@ -1053,7 +1053,7 @@ export async function switchToChannelById(serverUrl: string, channelId: string, fetchPostsForChannel(serverUrl, channelId); fetchChannelBookmarks(serverUrl, channelId); - await switchToChannel(serverUrl, channelId, teamId, skipLastUnread); + await switchToChannel(serverUrl, channelId, teamId, skipLastUnread, false, isNavigatedFromDraft); openChannelIfNeeded(serverUrl, channelId); markChannelAsRead(serverUrl, channelId); fetchChannelStats(serverUrl, channelId); diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index dd092831c73..8ad2a45e0e7 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -79,10 +79,10 @@ const Draft: React.FC = ({ const onPress = useCallback(() => { if (draft.rootId) { - switchToThread(serverUrl, draft.rootId); + switchToThread(serverUrl, draft.rootId, false, true); return; } - switchToChannelById(serverUrl, channel.id, channel.teamId); + switchToChannelById(serverUrl, channel.id, channel.teamId, false, true); }, [channel.id, channel.teamId, draft.rootId, serverUrl]); return ( diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 3c23170e836..9d823332ded 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -52,10 +52,10 @@ const EditDraft: React.FC = ({ const editHandler = async () => { await dismissBottomSheet(bottomSheetId); if (rootId) { - switchToThread(serverUrl, rootId); + switchToThread(serverUrl, rootId, false, true); return; } - switchToChannelById(serverUrl, channel.id, channel.teamId); + switchToChannelById(serverUrl, channel.id, channel.teamId, false, true); }; return ( diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index e22c86aa9c9..e71d8313aa2 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -135,7 +135,7 @@ const GlobalDraftsList: React.FC = ({ layoutWidth={layoutWidth} /> ); - }, [close, firstDraftId, isTablet, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, tutorialWatched, widthAnimatedStyle]); + }, [close, firstDraftId, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, tutorialWatched, widthAnimatedStyle]); return ( { + Navigation.push(Screens.HOME, { + component: { + id: availableScreen, + name: availableScreen, + passProps: { + ...passProps, + theme, + }, + options, + }, + }); + }); +} + export function resetToSelectServer(passProps: LaunchProps) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); From cd93248301a41907ca9b33baf89ed80c975c8d8e Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 11 Dec 2024 15:59:36 +0530 Subject: [PATCH 052/111] fix the theme issue and navigation issue --- app/actions/local/channel.ts | 39 ++++++++++++++++++++++++++++++++++-- app/screens/navigation.ts | 26 ++++++++++++------------ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index 435cd729eea..e4458b30d23 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -17,10 +17,11 @@ import {queryDisplayNamePreferences} from '@queries/servers/preference'; import {prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team'; import {getCurrentUser, queryUsersById} from '@queries/servers/user'; -import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen, resetToRootAndAddScreenOnTop} from '@screens/navigation'; +import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen, getThemeFromState, resetToRootAndAddScreenOnTop} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {isTablet} from '@utils/helpers'; import {logError, logInfo} from '@utils/log'; +import {changeOpacity} from '@utils/theme'; import {displayGroupMessageName, displayUsername, getUserIdFromChannelName} from '@utils/user'; import type {Model} from '@nozbe/watermelondb'; @@ -89,8 +90,42 @@ export async function switchToChannel(serverUrl: string, channelId: string, team dismissAllModalsAndPopToRoot(); DeviceEventEmitter.emit(NavigationConstants.NAVIGATION_HOME, Screens.CHANNEL); } else if (isNavigatedFromDraft) { + const theme = getThemeFromState(); + const options = { + layout: { + componentBackgroundColor: theme.centerChannelBg, + }, + popGesture: true, + sideMenu: { + left: {enabled: false}, + right: {enabled: false}, + }, + topBar: { + animate: true, + visible: false, + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + testID: 'screen.back.button', + }, + background: { + color: theme.sidebarBg, + }, + title: { + color: theme.sidebarHeaderTextColor, + }, + subtitle: { + color: changeOpacity(EphemeralStore.theme!.sidebarHeaderTextColor, 0.72), + }, + noBorder: true, + scrollEdgeAppearance: { + noBorder: true, + active: true, + }, + }, + }; dismissAllModals(); - resetToRootAndAddScreenOnTop(Screens.CHANNEL, {}, {topBar: {visible: false}}); + resetToRootAndAddScreenOnTop(Screens.CHANNEL, {}, options); } else { dismissAllModalsAndPopToScreen(Screens.CHANNEL, '', undefined, {topBar: {visible: false}}); } diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 359e669c41d..a8cf2c9b5db 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -346,24 +346,24 @@ export function resetToRootAndAddScreenOnTop(availableScreen: AvailableScreens, }, }, }, - }], + }, + { + component: { + id: availableScreen, + name: availableScreen, + passProps: { + ...passProps, + theme, + }, + options, + }, + }, + ], }, }; Navigation.setRoot({ root: rootStack, - }).then(() => { - Navigation.push(Screens.HOME, { - component: { - id: availableScreen, - name: availableScreen, - passProps: { - ...passProps, - theme, - }, - options, - }, - }); }); } From 89653946280a97c36923e6e64039815e9ea1d81e Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 11 Dec 2024 16:56:31 +0530 Subject: [PATCH 053/111] reverted back the draft click navigation changes --- app/actions/local/channel.ts | 42 +-------------- app/actions/local/thread.ts | 49 ++--------------- app/actions/remote/channel.ts | 4 +- app/components/draft/draft.tsx | 4 +- .../draft_options/edit_draft/index.tsx | 4 +- app/screens/navigation.ts | 54 ------------------- 6 files changed, 13 insertions(+), 144 deletions(-) diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index e4458b30d23..493d58e0df9 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -17,18 +17,17 @@ import {queryDisplayNamePreferences} from '@queries/servers/preference'; import {prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team'; import {getCurrentUser, queryUsersById} from '@queries/servers/user'; -import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen, getThemeFromState, resetToRootAndAddScreenOnTop} from '@screens/navigation'; +import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {isTablet} from '@utils/helpers'; import {logError, logInfo} from '@utils/log'; -import {changeOpacity} from '@utils/theme'; import {displayGroupMessageName, displayUsername, getUserIdFromChannelName} from '@utils/user'; import type {Model} from '@nozbe/watermelondb'; import type ChannelModel from '@typings/database/models/servers/channel'; import type UserModel from '@typings/database/models/servers/user'; -export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false, isNavigatedFromDraft = false) { +export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false) { try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); let models: Model[] = []; @@ -89,43 +88,6 @@ export async function switchToChannel(serverUrl: string, channelId: string, team if (isTabletDevice) { dismissAllModalsAndPopToRoot(); DeviceEventEmitter.emit(NavigationConstants.NAVIGATION_HOME, Screens.CHANNEL); - } else if (isNavigatedFromDraft) { - const theme = getThemeFromState(); - const options = { - layout: { - componentBackgroundColor: theme.centerChannelBg, - }, - popGesture: true, - sideMenu: { - left: {enabled: false}, - right: {enabled: false}, - }, - topBar: { - animate: true, - visible: false, - backButton: { - color: theme.sidebarHeaderTextColor, - title: '', - testID: 'screen.back.button', - }, - background: { - color: theme.sidebarBg, - }, - title: { - color: theme.sidebarHeaderTextColor, - }, - subtitle: { - color: changeOpacity(EphemeralStore.theme!.sidebarHeaderTextColor, 0.72), - }, - noBorder: true, - scrollEdgeAppearance: { - noBorder: true, - active: true, - }, - }, - }; - dismissAllModals(); - resetToRootAndAddScreenOnTop(Screens.CHANNEL, {}, options); } else { dismissAllModalsAndPopToScreen(Screens.CHANNEL, '', undefined, {topBar: {visible: false}}); } diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index abb10bd615b..76b5344cf6d 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -1,9 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import merge from 'deepmerge'; import {DeviceEventEmitter} from 'react-native'; -import tinycolor from 'tinycolor2'; import {ActionType, General, Navigation, Screens} from '@constants'; import DatabaseManager from '@database/manager'; @@ -14,7 +12,7 @@ import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, type Prep import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team'; import {getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, getThemeFromState, goToScreen, resetToRootAndAddScreenOnTop} from '@screens/navigation'; +import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, goToScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; @@ -22,7 +20,6 @@ import {logError} from '@utils/log'; import {changeOpacity} from '@utils/theme'; import type Model from '@nozbe/watermelondb/Model'; -import type {Options} from 'react-native-navigation'; export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, prepareRecordsOnly = false) => { try { @@ -60,7 +57,7 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, } }; -export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false, isNavigatedFromDraft = false) => { +export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => { try { const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const user = await getCurrentUser(database); @@ -125,7 +122,8 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo subtitle = translations[t('thread.header.thread_in')] || 'in {channelName}'; subtitle = subtitle.replace('{channelName}', channel.displayName); } - const threadOptions = { + + goToScreen(Screens.THREAD, '', {rootId}, { topBar: { title: { text: title, @@ -140,44 +138,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo active: true, }, }, - }; - if (isNavigatedFromDraft && !isTabletDevice) { - const theme = getThemeFromState(); - const isDark = tinycolor(theme.sidebarBg).isDark(); - const defaultOptions: Options = { - layout: { - componentBackgroundColor: theme.centerChannelBg, - }, - popGesture: true, - sideMenu: { - left: {enabled: false}, - right: {enabled: false}, - }, - statusBar: { - style: isDark ? 'light' : 'dark', - }, - topBar: { - animate: true, - visible: true, - backButton: { - color: theme.sidebarHeaderTextColor, - title: '', - testID: 'screen.back.button', - }, - background: { - color: theme.sidebarBg, - }, - title: { - color: theme.sidebarHeaderTextColor, - text: title, - }, - }, - }; - resetToRootAndAddScreenOnTop(Screens.THREAD, {rootId}, merge(defaultOptions, threadOptions)); - return {}; - } - - goToScreen(Screens.THREAD, '', {rootId}, threadOptions); + }); return {}; } catch (error) { diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index 523f1e8b685..b4ff1d1cff5 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -1039,7 +1039,7 @@ export async function getChannelTimezones(serverUrl: string, channelId: string) } } -export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, isNavigatedFromDraft = false) { +export async function switchToChannelById(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false) { if (channelId === Screens.GLOBAL_THREADS) { return switchToGlobalThreads(serverUrl, teamId); } @@ -1053,7 +1053,7 @@ export async function switchToChannelById(serverUrl: string, channelId: string, fetchPostsForChannel(serverUrl, channelId); fetchChannelBookmarks(serverUrl, channelId); - await switchToChannel(serverUrl, channelId, teamId, skipLastUnread, false, isNavigatedFromDraft); + await switchToChannel(serverUrl, channelId, teamId, skipLastUnread, false); openChannelIfNeeded(serverUrl, channelId); markChannelAsRead(serverUrl, channelId); fetchChannelStats(serverUrl, channelId); diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 8ad2a45e0e7..0ad34dda48b 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -79,10 +79,10 @@ const Draft: React.FC = ({ const onPress = useCallback(() => { if (draft.rootId) { - switchToThread(serverUrl, draft.rootId, false, true); + switchToThread(serverUrl, draft.rootId, false); return; } - switchToChannelById(serverUrl, channel.id, channel.teamId, false, true); + switchToChannelById(serverUrl, channel.id, channel.teamId, false); }, [channel.id, channel.teamId, draft.rootId, serverUrl]); return ( diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 9d823332ded..2dcc7784303 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -52,10 +52,10 @@ const EditDraft: React.FC = ({ const editHandler = async () => { await dismissBottomSheet(bottomSheetId); if (rootId) { - switchToThread(serverUrl, rootId, false, true); + switchToThread(serverUrl, rootId, false); return; } - switchToChannelById(serverUrl, channel.id, channel.teamId, false, true); + switchToChannelById(serverUrl, channel.id, channel.teamId, false); }; return ( diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index a8cf2c9b5db..8484109ff10 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -313,60 +313,6 @@ export function resetToHome(passProps: LaunchProps = {launchType: Launch.Normal} }); } -export function resetToRootAndAddScreenOnTop(availableScreen: AvailableScreens, passProps = {}, options: Options = {}) { - const theme = getDefaultThemeByAppearance(); - const isDark = tinyColor(theme.sidebarBg).isDark(); - StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); - - const rootStack = { - stack: { - children: [{ - component: { - id: Screens.HOME, - name: Screens.HOME, - options: { - layout: { - backgroundColor: theme.centerChannelBg, - componentBackgroundColor: theme.centerChannelBg, - }, - statusBar: { - visible: true, - backgroundColor: theme.sidebarBg, - }, - topBar: { - visible: false, - height: 0, - background: { - color: theme.sidebarBg, - }, - backButton: { - visible: false, - color: theme.sidebarHeaderTextColor, - }, - }, - }, - }, - }, - { - component: { - id: availableScreen, - name: availableScreen, - passProps: { - ...passProps, - theme, - }, - options, - }, - }, - ], - }, - }; - - Navigation.setRoot({ - root: rootStack, - }); -} - export function resetToSelectServer(passProps: LaunchProps) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); From 8c8e6f6792cc95299c2336cb22f87798e981b98a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 11 Dec 2024 18:33:09 +0530 Subject: [PATCH 054/111] observing draft when hitting back button --- app/components/draft/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/draft/index.tsx b/app/components/draft/index.tsx index 5f9e7290a0c..871c77f03e3 100644 --- a/app/components/draft/index.tsx +++ b/app/components/draft/index.tsx @@ -14,6 +14,7 @@ import Drafts from './draft'; import type {WithDatabaseArgs} from '@typings/database/database'; import type ChannelModel from '@typings/database/models/servers/channel'; import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership'; +import type DraftModel from '@typings/database/models/servers/draft'; import type UserModel from '@typings/database/models/servers/user'; type Props = { @@ -21,6 +22,7 @@ type Props = { currentUser?: UserModel; members?: ChannelMembershipModel[]; channel?: ChannelModel; + draft: DraftModel; } & WithDatabaseArgs; const withCurrentUser = withObservables([], ({database}: WithDatabaseArgs) => ({ @@ -72,9 +74,10 @@ const observeDraftReceiverUser = ({ return of(undefined); }; -const enhance = withObservables(['channel', 'members'], ({channel, database, currentUser, members}: Props) => { +const enhance = withObservables(['channel', 'members', 'draft'], ({channel, database, currentUser, members, draft}: Props) => { const draftReceiverUser = observeDraftReceiverUser({members, database, channelData: channel, currentUser}); return { + draft: draft.observe(), channel, draftReceiverUser, isPostPriorityEnabled: observeIsPostPriorityEnabled(database), From be07659d9ff35a3cc6fde64e532b9e8834db0a5d Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 12 Dec 2024 19:25:22 +0530 Subject: [PATCH 055/111] removed the unwanted animiation --- .../global_drafts_list/global_drafts_list.tsx | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index e71d8313aa2..792fa7c7c75 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -4,7 +4,7 @@ import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {InteractionManager, Platform, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; -import Animated, {FadeIn, FadeOut, useAnimatedStyle, withDelay, withTiming} from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import Tooltip from 'react-native-walkthrough-tooltip'; import {storeDraftsTutorial} from '@actions/app/global'; @@ -68,7 +68,7 @@ const GlobalDraftsList: React.FC = ({ }), [isTablet]); useEffect(() => { - if (tutorialWatched) { + if (!tutorialWatched) { return; } InteractionManager.runAfterInteractions(() => { @@ -89,21 +89,8 @@ const GlobalDraftsList: React.FC = ({ storeDraftsTutorial(); }, []); - const widthAnimatedStyle = useAnimatedStyle(() => { - return { - width: withDelay(200, withTiming(layoutWidth + 40, {duration: 300})), - marginLeft: Platform.OS === 'android' ? 10 : undefined, - }; - }, [layoutWidth]); - - const opacityStyle = useAnimatedStyle(() => { - return { - opacity: withDelay(700, withTiming(1, {duration: 350})), - }; - }, []); - const renderItem = useCallback(({item}: ListRenderItemInfo) => { - if (item.id === firstDraftId && !tutorialWatched) { + if (item.id === firstDraftId && tutorialWatched) { return ( = ({ onClose={close} tooltipStyle={styles.tooltipStyle} > - - + ); } @@ -135,7 +120,7 @@ const GlobalDraftsList: React.FC = ({ layoutWidth={layoutWidth} /> ); - }, [close, firstDraftId, layoutWidth, location, opacityStyle, tooltipContentStyle, tooltipVisible, tutorialWatched, widthAnimatedStyle]); + }, [close, firstDraftId, layoutWidth, location, tooltipContentStyle, tooltipVisible, tutorialWatched]); return ( Date: Mon, 16 Dec 2024 13:58:47 +0530 Subject: [PATCH 056/111] updated regex for parsing markdown --- app/actions/local/draft.ts | 24 +++++++++++++------ .../global_drafts_list/global_drafts_list.tsx | 4 ++-- app/utils/helpers.ts | 9 +++++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index bf98bd48b16..e62c06e67d7 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -7,7 +7,7 @@ import {Navigation, Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {getDraft} from '@queries/servers/drafts'; import {goToScreen} from '@screens/navigation'; -import {isTablet} from '@utils/helpers'; +import {isTablet, isValidUrl} from '@utils/helpers'; import {logError} from '@utils/log'; export const switchToGlobalDrafts = async () => { @@ -283,11 +283,21 @@ async function getImageMetadata(url: string) { } export async function parseMarkdownImages(markdown: string, imageMetadata: Dictionary) { - let match; - const imageRegex = /!\[.*?\]\((https:\/\/[^\s)]+)\)/g; - while ((match = imageRegex.exec(markdown)) !== null) { + // Regex break down + // ([a-zA-Z][a-zA-Z\d+\-.]*):\/\/ - Matches any valid scheme (protocol), such as http, https, ftp, mailto, file, etc. + // [^\s()<>]+ - Matches the main part of the URL, excluding spaces, parentheses, and angle brackets. + // (?:\([^\s()<>]+\))* - Allows balanced parentheses inside the URL path or query parameters. + // !\[.*?\]\((...)\) - Matches an image markdown syntax ![alt text](image url) + const imageRegex = /!\[.*?\]\((([a-zA-Z][a-zA-Z\d+\-.]*):\/\/[^\s()<>]+(?:\([^\s()<>]+\))*)\)/g; + const matches = Array.from(markdown.matchAll(imageRegex)); + + const promises = matches.map(async (match) => { const imageUrl = match[1]; - // eslint-disable-next-line no-await-in-loop - imageMetadata[imageUrl] = await getImageMetadata(imageUrl); - } + if (isValidUrl(imageUrl)) { + const metadata = await getImageMetadata(imageUrl); + imageMetadata[imageUrl] = metadata; + } + }); + + await Promise.all(promises); } diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 792fa7c7c75..beaa77611df 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -68,7 +68,7 @@ const GlobalDraftsList: React.FC = ({ }), [isTablet]); useEffect(() => { - if (!tutorialWatched) { + if (tutorialWatched) { return; } InteractionManager.runAfterInteractions(() => { @@ -90,7 +90,7 @@ const GlobalDraftsList: React.FC = ({ }, []); const renderItem = useCallback(({item}: ListRenderItemInfo) => { - if (item.id === firstDraftId && tutorialWatched) { + if (item.id === firstDraftId && !tutorialWatched) { return ( Date: Mon, 16 Dec 2024 14:45:40 +0530 Subject: [PATCH 057/111] removed unnecessary checks and change folder name --- .../channel_info/{avatar => ProfileAvatar}/index.tsx | 11 ++++------- app/components/channel_info/channel_info.tsx | 8 +++----- 2 files changed, 7 insertions(+), 12 deletions(-) rename app/components/channel_info/{avatar => ProfileAvatar}/index.tsx (88%) diff --git a/app/components/channel_info/avatar/index.tsx b/app/components/channel_info/ProfileAvatar/index.tsx similarity index 88% rename from app/components/channel_info/avatar/index.tsx rename to app/components/channel_info/ProfileAvatar/index.tsx index f1d36e49c28..db36bad66bf 100644 --- a/app/components/channel_info/avatar/index.tsx +++ b/app/components/channel_info/ProfileAvatar/index.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Image} from 'expo-image'; -import React from 'react'; +import React, {useMemo} from 'react'; import {StyleSheet, View} from 'react-native'; import {buildAbsoluteUrl} from '@actions/remote/file'; @@ -32,15 +32,12 @@ const styles = StyleSheet.create({ }, }); -const Avatar = ({ +const ProfileAvatar = ({ author, }: Props) => { const serverUrl = useServerUrl(); - let uri = ''; - if (author) { - uri = buildProfileImageUrlFromUser(serverUrl, author); - } + const uri = useMemo(() => buildProfileImageUrlFromUser(serverUrl, author), [serverUrl, author]); let picture; if (uri) { @@ -67,5 +64,5 @@ const Avatar = ({ ); }; -export default Avatar; +export default ProfileAvatar; diff --git a/app/components/channel_info/channel_info.tsx b/app/components/channel_info/channel_info.tsx index 5404e1cf8ec..5d0de44635f 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/channel_info/channel_info.tsx @@ -4,6 +4,8 @@ import React, {type ReactNode} from 'react'; import {Text, View} from 'react-native'; +import ProfileAvatar from '@components/channel_info/ProfileAvatar'; +import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; import {General} from '@constants'; @@ -12,10 +14,6 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import {getUserTimezone} from '@utils/user'; -import CompassIcon from '../compass_icon'; - -import Avatar from './avatar'; - import type ChannelModel from '@typings/database/models/servers/channel'; import type PostModel from '@typings/database/models/servers/post'; import type UserModel from '@typings/database/models/servers/user'; @@ -88,7 +86,7 @@ const ChannelInfo: React.FC = ({ const isChannelTypeDM = channel.type === General.DM_CHANNEL; let headerComponent: ReactNode = null; - const profileComponent = draftReceiverUser ? : ( + const profileComponent = draftReceiverUser ? : ( Date: Mon, 16 Dec 2024 15:14:37 +0530 Subject: [PATCH 058/111] removed react memo and merge unwanted observes function --- app/components/draft/draft.tsx | 1 - .../draft/draft_post/draft_message/index.tsx | 2 +- .../drafts_buttton/drafts_button.tsx | 3 +-- app/components/drafts_buttton/index.tsx | 19 +++++++------------ 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 0ad34dda48b..9166bc2bd55 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -87,7 +87,6 @@ const Draft: React.FC = ({ return ( = ({ const animatedStyle = useShowMoreAnimatedStyle(height, maxHeight, open); const onLayout = useCallback((event: LayoutChangeEvent) => setHeight(event.nativeEvent.layout.height), []); - const onPress = () => setOpen(!open); + const onPress = useCallback(() => setOpen(!open), [open]); return ( <> diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 5793f389fbe..073f0f6d3e1 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -9,6 +9,7 @@ import { getStyleSheet as getChannelItemStyleSheet, ROW_HEIGHT, } from '@components/channel_item/channel_item'; +import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import {HOME_PADDING} from '@constants/view'; import {useTheme} from '@context/theme'; @@ -17,8 +18,6 @@ import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; -import CompassIcon from '../compass_icon'; - type DraftListProps = { currentChannelId: string; shouldHighlighActive?: boolean; diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index 3fd1c22647f..551c2784e73 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -1,8 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +/* eslint-disable max-nested-callbacks */ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; -import React from 'react'; +import {switchMap} from 'rxjs/operators'; import {observeDraftCount} from '@queries/servers/drafts'; import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system'; @@ -11,20 +12,14 @@ import DraftsButton from './drafts_button'; import type {WithDatabaseArgs} from '@typings/database/database'; -type Props = { - teamId: string; -} & WithDatabaseArgs; - -const withTeamId = withObservables([], ({database}: WithDatabaseArgs) => ({ - teamId: observeCurrentTeamId(database), -})); - -const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { - const draftsCount = observeDraftCount(database, teamId); +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + const currentTeamId = observeCurrentTeamId(database); // Observe teamId + const draftsCount = currentTeamId.pipe(switchMap((teamId) => observeDraftCount(database, teamId))); // Observe draft count return { currentChannelId: observeCurrentChannelId(database), draftsCount, }; }); -export default React.memo(withDatabase(withTeamId(enhanced(DraftsButton)))); +export default withDatabase(enhanced(DraftsButton)); + From a542590e988aa8d2ec9f2e3949128f6d5a2462c0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 15:15:14 +0530 Subject: [PATCH 059/111] removed unnecessary comments --- app/components/drafts_buttton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index 551c2784e73..d366bc141d9 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -13,7 +13,7 @@ import DraftsButton from './drafts_button'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const currentTeamId = observeCurrentTeamId(database); // Observe teamId + const currentTeamId = observeCurrentTeamId(database); const draftsCount = currentTeamId.pipe(switchMap((teamId) => observeDraftCount(database, teamId))); // Observe draft count return { currentChannelId: observeCurrentChannelId(database), From ea131a8200501cb86ae212190092a9f2fa3cc993 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 15:45:33 +0530 Subject: [PATCH 060/111] changed the name for observing and querying draft function --- app/components/post_draft/post_input/post_input.tsx | 2 +- app/queries/servers/drafts.ts | 8 ++++---- .../global_drafts/components/global_drafts_list/index.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/components/post_draft/post_input/post_input.tsx b/app/components/post_draft/post_input/post_input.tsx index ea50ef45b03..35ae48f9eff 100644 --- a/app/components/post_draft/post_input/post_input.tsx +++ b/app/components/post_draft/post_input/post_input.tsx @@ -147,7 +147,7 @@ export default function PostInput({ onFocus(); }; - const onBlur = useCallback(async () => { + const onBlur = useCallback(() => { keyboardContext?.registerTextInputBlur(); handleDraftUpdate({ serverUrl, diff --git a/app/queries/servers/drafts.ts b/app/queries/servers/drafts.ts index 5f7e868f3c4..18ea9bb1f1a 100644 --- a/app/queries/servers/drafts.ts +++ b/app/queries/servers/drafts.ts @@ -30,7 +30,7 @@ export function observeFirstDraft(v: DraftModel[]) { return v[0]?.observe() || of$(undefined); } -export const queryAllDrafts = (database: Database, teamId: string) => { +export const queryDraftsForTeam = (database: Database, teamId: string) => { return database.collections.get(DRAFT).query( Q.on(CHANNEL, Q.and( @@ -46,10 +46,10 @@ export const queryAllDrafts = (database: Database, teamId: string) => { ); }; -export const observeAllDrafts = (database: Database, teamId: string) => { - return queryAllDrafts(database, teamId).observeWithColumns(['update_at']); +export const observeDraftsForTeam = (database: Database, teamId: string) => { + return queryDraftsForTeam(database, teamId).observeWithColumns(['update_at']); }; export const observeDraftCount = (database: Database, teamId: string) => { - return queryAllDrafts(database, teamId).observeCount(); + return queryDraftsForTeam(database, teamId).observeCount(); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx index 82355e1eaab..b384506ea4d 100644 --- a/app/screens/global_drafts/components/global_drafts_list/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import {Tutorial} from '@constants'; import {observeTutorialWatched} from '@queries/app/global'; -import {observeAllDrafts} from '@queries/servers/drafts'; +import {observeDraftsForTeam} from '@queries/servers/drafts'; import {observeCurrentTeamId} from '@queries/servers/system'; import GlobalDraftsList from './global_drafts_list'; @@ -23,7 +23,7 @@ type Props = { } & WithDatabaseArgs; const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { - const allDrafts = observeAllDrafts(database, teamId); + const allDrafts = observeDraftsForTeam(database, teamId); const tutorialWatched = observeTutorialWatched(Tutorial.DRAFTS); return { From 1ca7ddb484342600aafa84e54194c8df37719cf0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 17:18:08 +0530 Subject: [PATCH 061/111] removed memo from component level --- .../global_drafts/components/global_drafts_list/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/index.tsx b/app/screens/global_drafts/components/global_drafts_list/index.tsx index b384506ea4d..9f5f2cd7bdb 100644 --- a/app/screens/global_drafts/components/global_drafts_list/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/index.tsx @@ -1,9 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable max-nested-callbacks */ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; -import React from 'react'; import {Tutorial} from '@constants'; import {observeTutorialWatched} from '@queries/app/global'; @@ -32,4 +30,4 @@ const enhanced = withObservables(['teamId'], ({database, teamId}: Props) => { }; }); -export default React.memo(withDatabase(withTeamId(enhanced(GlobalDraftsList)))); +export default withDatabase(withTeamId(enhanced(GlobalDraftsList))); From 8a1952eb24e4a7311833879ee15f3a891d7e28e0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 17:30:39 +0530 Subject: [PATCH 062/111] Text to FormattedText component --- .../draft_options/delete_draft/index.tsx | 10 +++++---- .../draft_empty_component/index.tsx | 22 +++++++++++-------- .../SwipeableDraft/index.tsx | 15 +++++++------ 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx index 5939351be19..178c6284029 100644 --- a/app/screens/draft_options/delete_draft/index.tsx +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -2,11 +2,10 @@ // See LICENSE.txt for license information. import React from 'react'; -import {useIntl} from 'react-intl'; -import {Text} from 'react-native'; import {removeDraft} from '@actions/local/draft'; import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {ICON_SIZE} from '@constants/post_draft'; import {useServerUrl} from '@context/server'; @@ -43,7 +42,6 @@ const DeleteDraft: React.FC = ({ rootId, }) => { const theme = useTheme(); - const intl = useIntl(); const style = getStyleSheet(theme); const serverUrl = useServerUrl(); @@ -64,7 +62,11 @@ const DeleteDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.delete.title', defaultMessage: 'Delete draft'})} + ); }; diff --git a/app/screens/global_drafts/components/draft_empty_component/index.tsx b/app/screens/global_drafts/components/draft_empty_component/index.tsx index 192d25fd030..b913f37d7d6 100644 --- a/app/screens/global_drafts/components/draft_empty_component/index.tsx +++ b/app/screens/global_drafts/components/draft_empty_component/index.tsx @@ -1,10 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {Image} from 'expo-image'; import React from 'react'; -import {useIntl} from 'react-intl'; -import {Image, Text, View} from 'react-native'; +import {View} from 'react-native'; +import FormattedText from '@components/formatted_text'; import {useTheme} from '@context/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -37,7 +38,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }); const DraftEmptyComponent = () => { - const intl = useIntl(); const theme = useTheme(); const styles = getStyleSheet(theme); return ( @@ -48,12 +48,16 @@ const DraftEmptyComponent = () => { - - {intl.formatMessage({id: 'drafts.empty.title', defaultMessage: 'No drafts at the moment'})} - - - {intl.formatMessage({id: 'drafts.empty.subtitle', defaultMessage: 'Any message you have started will show here.'})} - + + ); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx index b4f2950d122..91428dc6149 100644 --- a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx @@ -3,11 +3,12 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {useIntl} from 'react-intl'; -import {Text, Animated, DeviceEventEmitter, TouchableWithoutFeedback} from 'react-native'; +import {Animated, DeviceEventEmitter, TouchableWithoutFeedback} from 'react-native'; import {Swipeable} from 'react-native-gesture-handler'; import CompassIcon from '@components/compass_icon'; import Draft from '@components/draft'; +import FormattedText from '@components/formatted_text'; import {Events} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; @@ -85,16 +86,16 @@ const SwipeableDraft: React.FC = ({ color={theme.sidebarText} name='trash-can-outline' size={18} - style={{}} /> - {intl.formatMessage({ - id: 'draft.options.delete.confirm', - defaultMessage: 'Delete', - })} + ); - }, [deleteDraft, intl, layoutWidth, styles.deleteContainer, styles.deleteText, theme.sidebarText]); + }, [deleteDraft, layoutWidth, styles.deleteContainer, styles.deleteText, theme.sidebarText]); useEffect(() => { const listener = DeviceEventEmitter.addListener(Events.DRAFT_SWIPEABLE, (draftId: string) => { From 2941ab1c62ca5ead2d156c2714be19c314d7c1a8 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 18:01:27 +0530 Subject: [PATCH 063/111] Text to formatted text, change image name --- .../draft_options/send_draft/send_draft.tsx | 8 ++++++-- .../global_drafts_list/global_drafts_list.tsx | 16 ++++++++++------ .../components/global_drafts_list/tooltip.tsx | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index 0dfd3cb74c2..c90bdcc8ea0 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -3,10 +3,10 @@ import React from 'react'; import {useIntl} from 'react-intl'; -import {Text} from 'react-native'; import {removeDraft} from '@actions/local/draft'; import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {General} from '@constants'; import {ICON_SIZE} from '@constants/post_draft'; @@ -154,7 +154,11 @@ const SendDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.send.title', defaultMessage: 'Edit draft'})} + ); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index beaa77611df..8b684e1f81a 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -3,7 +3,7 @@ import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {InteractionManager, Platform, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; +import {InteractionManager, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; import Tooltip from 'react-native-walkthrough-tooltip'; @@ -28,6 +28,9 @@ type Props = { } const styles = StyleSheet.create({ + container: { + flex: 1, + }, empty: { alignItems: 'center', flexGrow: 1, @@ -39,6 +42,9 @@ const styles = StyleSheet.create({ shadowRadius: 2, shadowOpacity: 0.16, }, + swippeableContainer: { + width: '100%', + }, }); const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); @@ -77,9 +83,7 @@ const GlobalDraftsList: React.FC = ({ }, []); const collapse = useCallback(() => { - if (Platform.OS === 'android') { - popTopScreen(Screens.GLOBAL_DRAFTS); - } + popTopScreen(Screens.GLOBAL_DRAFTS); }, []); useAndroidHardwareBackHandler(Screens.GLOBAL_DRAFTS, collapse); @@ -102,7 +106,7 @@ const GlobalDraftsList: React.FC = ({ tooltipStyle={styles.tooltipStyle} > = ({ return ( diff --git a/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx b/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx index 5a4f6e3b2b8..d5cef49f306 100644 --- a/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx @@ -15,7 +15,7 @@ type Props = { onClose: () => void; } -const logo = require('@assets/images/emojis/swipe.png'); +const longPressGestureHandLogo = require('@assets/images/emojis/swipe.png'); const hitSlop = {top: 10, bottom: 10, left: 10, right: 10}; @@ -58,7 +58,7 @@ const DraftTooltip = ({onClose}: Props) => { Date: Mon, 16 Dec 2024 18:11:01 +0530 Subject: [PATCH 064/111] added confirmation modal for deleting draft from bottomsheet --- app/screens/draft_options/delete_draft/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft/index.tsx index 178c6284029..3b1bdfd0a75 100644 --- a/app/screens/draft_options/delete_draft/index.tsx +++ b/app/screens/draft_options/delete_draft/index.tsx @@ -2,8 +2,8 @@ // See LICENSE.txt for license information. import React from 'react'; +import {useIntl} from 'react-intl'; -import {removeDraft} from '@actions/local/draft'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import TouchableWithFeedback from '@components/touchable_with_feedback'; @@ -11,6 +11,7 @@ import {ICON_SIZE} from '@constants/post_draft'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {dismissBottomSheet} from '@screens/navigation'; +import {deleteDraftConfirmation} from '@utils/draft'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -44,10 +45,16 @@ const DeleteDraft: React.FC = ({ const theme = useTheme(); const style = getStyleSheet(theme); const serverUrl = useServerUrl(); + const intl = useIntl(); const draftDeleteHandler = async () => { await dismissBottomSheet(bottomSheetId); - removeDraft(serverUrl, channelId, rootId); + deleteDraftConfirmation({ + intl, + serverUrl, + channelId, + rootId, + }); }; return ( From 80681bde6aa9ce2022e750438b5e60fa9f89d4b9 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 16 Dec 2024 19:03:41 +0530 Subject: [PATCH 065/111] using common send_handler for both draft and post --- .../post_draft/send_handler/index.ts | 2 + .../post_draft/send_handler/send_handler.tsx | 37 ++++++++ app/screens/draft_options/index.tsx | 29 ++++-- .../draft_options/send_draft/index.tsx | 91 ------------------- .../draft_options/send_draft/send_draft.tsx | 4 +- 5 files changed, 62 insertions(+), 101 deletions(-) delete mode 100644 app/screens/draft_options/send_draft/index.tsx diff --git a/app/components/post_draft/send_handler/index.ts b/app/components/post_draft/send_handler/index.ts index 40117521bc7..ae8c4d67c28 100644 --- a/app/components/post_draft/send_handler/index.ts +++ b/app/components/post_draft/send_handler/index.ts @@ -71,6 +71,7 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) => const channelInfo = channel.pipe(switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$(undefined)))); const channelType = channel.pipe(switchMap((c) => of$(c?.type))); const channelName = channel.pipe(switchMap((c) => of$(c?.name))); + const channelDisplayName = channel.pipe(switchMap((c) => of$(c?.displayName))); const membersCount = channelInfo.pipe( switchMap((i) => (i ? of$(i.memberCount) : of$(0))), ); @@ -81,6 +82,7 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) => channelType, channelName, currentUserId, + channelDisplayName, enableConfirmNotificationsToChannel, maxMessageLength, membersCount, diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index 2b7129aef07..848724b7a4e 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -7,10 +7,12 @@ import {updateDraftPriority} from '@actions/local/draft'; import {PostPriorityType} from '@constants/post'; import {useServerUrl} from '@context/server'; import {useHandleSendMessage} from '@hooks/handle_send_message'; +import SendDraft from '@screens/draft_options/send_draft/send_draft'; import DraftInput from '../draft_input'; import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; +import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { testID?: string; @@ -43,6 +45,11 @@ type Props = { persistentNotificationInterval: number; persistentNotificationMaxRecipients: number; postPriority: PostPriority; + + bottomSheetId?: AvailableScreens; + channelDisplayName?: string; + isFromDraftView?: boolean; + draftReceiverUserName?: string; } export const INITIAL_PRIORITY = { @@ -56,6 +63,7 @@ export default function SendHandler({ channelId, channelType, channelName, + channelDisplayName, currentUserId, enableConfirmNotificationsToChannel, files, @@ -78,6 +86,9 @@ export default function SendHandler({ persistentNotificationInterval, persistentNotificationMaxRecipients, postPriority, + bottomSheetId, + draftReceiverUserName, + isFromDraftView, }: Props) { const serverUrl = useServerUrl(); @@ -102,6 +113,32 @@ export default function SendHandler({ clearDraft, }); + if (isFromDraftView) { + return ( + + ); + } + return ( = ({ draft, draftReceiverUserName, }) => { - const {formatMessage} = useIntl(); const isTablet = useIsTablet(); const theme = useTheme(); const styles = getStyleSheet(theme); const renderContent = () => { return ( - {!isTablet && {formatMessage( - {id: 'draft.option.header', defaultMessage: 'Draft actions'}, - )}} + {!isTablet && + } - {}} + updateCursorPosition={() => {}} + updatePostInputTop={() => {}} + addFiles={() => {}} + setIsFocused={() => {}} + updateValue={() => {}} + /* eslint-enable no-empty-function */ /> { - const channel = observeChannel(database, channelId); - - const currentUserId = observeCurrentUserId(database); - const currentUser = currentUserId.pipe( - switchMap((id) => observeUser(database, id)), - ); - const userIsOutOfOffice = currentUser.pipe( - switchMap((u) => of$(u?.status === General.OUT_OF_OFFICE)), - ); - - const postPriority = queryDraft(database, channelId, rootId).observeWithColumns(['metadata']).pipe( - switchMap(observeFirstDraft), - switchMap((d) => { - if (!d?.metadata?.priority) { - return of$(INITIAL_PRIORITY); - } - - return of$(d.metadata.priority); - }), - ); - - const enableConfirmNotificationsToChannel = observeConfigBooleanValue(database, 'EnableConfirmNotificationsToChannel'); - const maxMessageLength = observeConfigIntValue(database, 'MaxPostSize', MAX_MESSAGE_LENGTH_FALLBACK); - const persistentNotificationInterval = observeConfigIntValue(database, 'PersistentNotificationIntervalMinutes'); - const persistentNotificationMaxRecipients = observeConfigIntValue(database, 'PersistentNotificationMaxRecipients'); - - const useChannelMentions = combineLatest([channel, currentUser]).pipe( - switchMap(([c, u]) => { - if (!c) { - return of$(true); - } - - return u ? observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false); - }), - ); - - const channelInfo = channel.pipe(switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$(undefined)))); - const channelType = channel.pipe(switchMap((c) => of$(c?.type))); - const channelName = channel.pipe(switchMap((c) => of$(c?.name))); - const channelDisplayName = channel.pipe(switchMap((c) => of$(c?.displayName))); - const membersCount = channelInfo.pipe( - switchMap((i) => (i ? of$(i.memberCount) : of$(0))), - ); - - const customEmojis = queryAllCustomEmojis(database).observe(); - - return { - channelType, - channelName, - channelDisplayName, - currentUserId, - enableConfirmNotificationsToChannel, - maxMessageLength, - membersCount, - userIsOutOfOffice, - useChannelMentions, - customEmojis, - persistentNotificationInterval, - persistentNotificationMaxRecipients, - postPriority, - }; -}); - -export default withDatabase(enhanced(SendDraft)); diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft/send_draft.tsx index c90bdcc8ea0..2dc50fa2766 100644 --- a/app/screens/draft_options/send_draft/send_draft.tsx +++ b/app/screens/draft_options/send_draft/send_draft.tsx @@ -35,13 +35,13 @@ type Props = { useChannelMentions: boolean; userIsOutOfOffice: boolean; customEmojis: CustomEmojiModel[]; - bottomSheetId: AvailableScreens; + bottomSheetId?: AvailableScreens; value: string; files: FileInfo[]; postPriority: PostPriority; persistentNotificationInterval: number; persistentNotificationMaxRecipients: number; - draftReceiverUserName: string | undefined; + draftReceiverUserName?: string; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ From 9ea09144271692a916ff486d905fa8c1caf0af6a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 17 Dec 2024 17:07:42 +0530 Subject: [PATCH 066/111] removed magic number for tooltip and bottomsheet --- app/screens/draft_options/index.tsx | 15 ++++++++++--- .../global_drafts_list/global_drafts_list.tsx | 21 ++++++++----------- .../components/global_drafts_list/tooltip.tsx | 8 ++----- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/app/screens/draft_options/index.tsx b/app/screens/draft_options/index.tsx index a86373d18bf..e72b8f5a682 100644 --- a/app/screens/draft_options/index.tsx +++ b/app/screens/draft_options/index.tsx @@ -1,8 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; -import {View} from 'react-native'; +import React, {useMemo} from 'react'; +import {Platform, View} from 'react-native'; import FormattedText from '@components/formatted_text'; import SendHandler from '@components/post_draft/send_handler/'; @@ -39,6 +39,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }; }); +const TITLE_HEIGHT = 54; +const ITEM_HEIGHT = 48; + const DraftOptions: React.FC = ({ channel, rootId, @@ -48,6 +51,12 @@ const DraftOptions: React.FC = ({ const isTablet = useIsTablet(); const theme = useTheme(); const styles = getStyleSheet(theme); + const snapPoints = useMemo(() => { + const bottomSheetAdjust = Platform.select({ios: 5, default: 20}); + const COMPONENT_HIEGHT = TITLE_HEIGHT + (3 * ITEM_HEIGHT) + bottomSheetAdjust; + return [1, COMPONENT_HIEGHT]; + }, []); + const renderContent = () => { return ( @@ -95,7 +104,7 @@ const DraftOptions: React.FC = ({ componentId={Screens.DRAFT_OPTIONS} renderContent={renderContent} closeButtonId={DRAFT_OPTIONS_BUTTON} - snapPoints={[200, 250]} + snapPoints={snapPoints} testID='draft_options' /> ); diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 8b684e1f81a..fc3f03e0c7e 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {FlatList} from '@stream-io/flat-list-mvcp'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {InteractionManager, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; import Tooltip from 'react-native-walkthrough-tooltip'; @@ -11,7 +11,6 @@ import {storeDraftsTutorial} from '@actions/app/global'; import {INITIAL_BATCH_TO_RENDER, SCROLL_POSITION_CONFIG} from '@components/post_list/config'; import {Screens} from '@constants'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import {useIsTablet} from '@hooks/device'; import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; @@ -45,6 +44,12 @@ const styles = StyleSheet.create({ swippeableContainer: { width: '100%', }, + tooltipContentStyle: { + borderRadius: 8, + width: 247, + padding: 16, + height: 160, + }, }); const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); @@ -57,7 +62,6 @@ const GlobalDraftsList: React.FC = ({ }) => { const [layoutWidth, setLayoutWidth] = useState(0); const [tooltipVisible, setTooltipVisible] = useState(false); - const isTablet = useIsTablet(); const onLayout = useCallback((e: LayoutChangeEvent) => { if (location === Screens.GLOBAL_DRAFTS) { setLayoutWidth(e.nativeEvent.layout.width - 40); // 40 is the padding of the container @@ -66,13 +70,6 @@ const GlobalDraftsList: React.FC = ({ const firstDraftId = allDrafts.length ? allDrafts[0].id : ''; - const tooltipContentStyle = useMemo(() => ({ - borderRadius: 8, - maxWidth: isTablet ? 352 : 247, - padding: 0, - height: 160, - }), [isTablet]); - useEffect(() => { if (tutorialWatched) { return; @@ -99,7 +96,7 @@ const GlobalDraftsList: React.FC = ({ } onClose={close} @@ -124,7 +121,7 @@ const GlobalDraftsList: React.FC = ({ layoutWidth={layoutWidth} /> ); - }, [close, firstDraftId, layoutWidth, location, tooltipContentStyle, tooltipVisible, tutorialWatched]); + }, [close, firstDraftId, layoutWidth, location, tooltipVisible, tutorialWatched]); return ( { return ( - + Date: Tue, 17 Dec 2024 17:16:09 +0530 Subject: [PATCH 067/111] renamed channel_info to draft_post_header --- app/components/draft/draft.tsx | 2 +- .../ProfileAvatar/index.tsx | 0 .../draft_post_header.tsx} | 6 +++--- .../{channel_info => draft_post_header}/index.tsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename app/components/{channel_info => draft_post_header}/ProfileAvatar/index.tsx (100%) rename app/components/{channel_info/channel_info.tsx => draft_post_header/draft_post_header.tsx} (97%) rename app/components/{channel_info => draft_post_header}/index.tsx (88%) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index 9166bc2bd55..a897b86951a 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -7,8 +7,8 @@ import {Keyboard, TouchableHighlight, View} from 'react-native'; import {switchToThread} from '@actions/local/thread'; import {switchToChannelById} from '@actions/remote/channel'; -import ChannelInfo from '@components/channel_info'; import DraftPost from '@components/draft/draft_post'; +import ChannelInfo from '@components/draft_post_header'; import Header from '@components/post_draft/draft_input/header'; import {Screens} from '@constants'; import {useServerUrl} from '@context/server'; diff --git a/app/components/channel_info/ProfileAvatar/index.tsx b/app/components/draft_post_header/ProfileAvatar/index.tsx similarity index 100% rename from app/components/channel_info/ProfileAvatar/index.tsx rename to app/components/draft_post_header/ProfileAvatar/index.tsx diff --git a/app/components/channel_info/channel_info.tsx b/app/components/draft_post_header/draft_post_header.tsx similarity index 97% rename from app/components/channel_info/channel_info.tsx rename to app/components/draft_post_header/draft_post_header.tsx index 5d0de44635f..653926df490 100644 --- a/app/components/channel_info/channel_info.tsx +++ b/app/components/draft_post_header/draft_post_header.tsx @@ -4,8 +4,8 @@ import React, {type ReactNode} from 'react'; import {Text, View} from 'react-native'; -import ProfileAvatar from '@components/channel_info/ProfileAvatar'; import CompassIcon from '@components/compass_icon'; +import ProfileAvatar from '@components/draft_post_header/ProfileAvatar'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; import {General} from '@constants'; @@ -72,7 +72,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }; }); -const ChannelInfo: React.FC = ({ +const DraftPostHeader: React.FC = ({ channel, draftReceiverUser, updateAt, @@ -159,4 +159,4 @@ const ChannelInfo: React.FC = ({ ); }; -export default ChannelInfo; +export default DraftPostHeader; diff --git a/app/components/channel_info/index.tsx b/app/components/draft_post_header/index.tsx similarity index 88% rename from app/components/channel_info/index.tsx rename to app/components/draft_post_header/index.tsx index 0e2a351106c..02fe16a6cd1 100644 --- a/app/components/channel_info/index.tsx +++ b/app/components/draft_post_header/index.tsx @@ -8,7 +8,7 @@ import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference'; import {queryDisplayNamePreferences} from '@queries/servers/preference'; import {observeCurrentUser} from '@queries/servers/user'; -import ChannelInfo from './channel_info'; +import DraftPostHeader from './draft_post_header'; const enhance = withObservables([], ({database}) => { const currentUser = observeCurrentUser(database); @@ -22,4 +22,4 @@ const enhance = withObservables([], ({database}) => { }; }); -export default withDatabase(enhance(ChannelInfo)); +export default withDatabase(enhance(DraftPostHeader)); From 8d427db69a1bdad6902a9382b4be563ceb805c53 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 17 Dec 2024 17:19:14 +0530 Subject: [PATCH 068/111] text to formattedText for Edit drafts --- app/screens/draft_options/edit_draft/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft/index.tsx index 2dcc7784303..7062b85fa5e 100644 --- a/app/screens/draft_options/edit_draft/index.tsx +++ b/app/screens/draft_options/edit_draft/index.tsx @@ -2,12 +2,11 @@ // See LICENSE.txt for license information. import React from 'react'; -import {useIntl} from 'react-intl'; -import {Text} from 'react-native'; import {switchToThread} from '@actions/local/thread'; import {switchToChannelById} from '@actions/remote/channel'; import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {ICON_SIZE} from '@constants/post_draft'; import {useServerUrl} from '@context/server'; @@ -45,7 +44,6 @@ const EditDraft: React.FC = ({ rootId, }) => { const theme = useTheme(); - const intl = useIntl(); const style = getStyleSheet(theme); const serverUrl = useServerUrl(); @@ -70,7 +68,11 @@ const EditDraft: React.FC = ({ size={ICON_SIZE} color={changeOpacity(theme.centerChannelColor, 0.56)} /> - {intl.formatMessage({id: 'draft.options.edit.title', defaultMessage: 'Edit draft'})} + ); }; From 29548439babc7d8df9d04e7c5d6862a58f2f8763 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 17 Dec 2024 17:39:30 +0530 Subject: [PATCH 069/111] removed unnecessary changes --- app/actions/remote/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index b4ff1d1cff5..e3e15f9f19c 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -1053,7 +1053,7 @@ export async function switchToChannelById(serverUrl: string, channelId: string, fetchPostsForChannel(serverUrl, channelId); fetchChannelBookmarks(serverUrl, channelId); - await switchToChannel(serverUrl, channelId, teamId, skipLastUnread, false); + await switchToChannel(serverUrl, channelId, teamId, skipLastUnread); openChannelIfNeeded(serverUrl, channelId); markChannelAsRead(serverUrl, channelId); fetchChannelStats(serverUrl, channelId); From d4c988b01e33a2cf3f1fc9f75a6834d6f8f0ad7e Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 17 Dec 2024 18:38:56 +0530 Subject: [PATCH 070/111] minor fixes --- app/components/draft/draft.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/draft/draft.tsx b/app/components/draft/draft.tsx index a897b86951a..6751af17c3f 100644 --- a/app/components/draft/draft.tsx +++ b/app/components/draft/draft.tsx @@ -8,7 +8,7 @@ import {Keyboard, TouchableHighlight, View} from 'react-native'; import {switchToThread} from '@actions/local/thread'; import {switchToChannelById} from '@actions/remote/channel'; import DraftPost from '@components/draft/draft_post'; -import ChannelInfo from '@components/draft_post_header'; +import DraftPostHeader from '@components/draft_post_header'; import Header from '@components/post_draft/draft_input/header'; import {Screens} from '@constants'; import {useServerUrl} from '@context/server'; @@ -95,7 +95,7 @@ const Draft: React.FC = ({ - Date: Tue, 31 Dec 2024 16:29:09 +0530 Subject: [PATCH 071/111] mounting draft only when there is draft --- .../drafts_buttton/drafts_button.tsx | 132 ----------------- app/components/drafts_buttton/index.tsx | 135 +++++++++++++++--- .../categories_list/categories_list.tsx | 104 ++++++++++++++ .../categories_list/index.test.tsx | 24 +++- .../channel_list/categories_list/index.tsx | 97 ++----------- 5 files changed, 261 insertions(+), 231 deletions(-) delete mode 100644 app/components/drafts_buttton/drafts_button.tsx create mode 100644 app/screens/home/channel_list/categories_list/categories_list.tsx diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx deleted file mode 100644 index 073f0f6d3e1..00000000000 --- a/app/components/drafts_buttton/drafts_button.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useCallback, useMemo} from 'react'; -import {Text, TouchableOpacity, View} from 'react-native'; - -import {switchToGlobalDrafts} from '@actions/local/draft'; -import { - getStyleSheet as getChannelItemStyleSheet, - ROW_HEIGHT, -} from '@components/channel_item/channel_item'; -import CompassIcon from '@components/compass_icon'; -import FormattedText from '@components/formatted_text'; -import {HOME_PADDING} from '@constants/view'; -import {useTheme} from '@context/theme'; -import {useIsTablet} from '@hooks/device'; -import {preventDoubleTap} from '@utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import {typography} from '@utils/typography'; - -type DraftListProps = { - currentChannelId: string; - shouldHighlighActive?: boolean; - draftsCount: number; -}; - -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ - icon: { - color: changeOpacity(theme.sidebarText, 0.5), - fontSize: 24, - marginRight: 12, - }, - iconActive: { - color: theme.sidebarText, - }, - iconInfo: { - color: changeOpacity(theme.centerChannelColor, 0.72), - }, - text: { - flex: 1, - }, - countContainer: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: 4, - }, - count: { - color: theme.sidebarText, - ...typography('Body', 75, 'SemiBold'), - opacity: 0.64, - }, - opacity: { - opacity: 0.56, - }, -})); - -const DraftsButton: React.FC = ({ - currentChannelId, - shouldHighlighActive = false, - draftsCount, -}) => { - const theme = useTheme(); - const styles = getChannelItemStyleSheet(theme); - const customStyles = getStyleSheet(theme); - const isTablet = useIsTablet(); - - const handlePress = useCallback(preventDoubleTap(() => { - switchToGlobalDrafts(); - }), []); - - const isActive = isTablet && shouldHighlighActive && !currentChannelId; - - const [containerStyle, iconStyle, textStyle] = useMemo(() => { - const container = [ - styles.container, - HOME_PADDING, - isActive && styles.activeItem, - isActive && { - paddingLeft: HOME_PADDING.paddingLeft - styles.activeItem.borderLeftWidth, - }, - {minHeight: ROW_HEIGHT}, - ]; - - const icon = [ - customStyles.icon, - isActive && customStyles.iconActive, - ]; - - const text = [ - customStyles.text, - styles.text, - isActive && styles.textActive, - ]; - - return [container, icon, text]; - }, [customStyles, isActive, styles]); - - if (!draftsCount) { - return null; - } - - return ( - - - - - - - {draftsCount} - - - - ); -}; - -export default DraftsButton; diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index d366bc141d9..c10466be4b5 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -1,25 +1,128 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable max-nested-callbacks */ -import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; -import {switchMap} from 'rxjs/operators'; +import React, {useCallback, useMemo} from 'react'; +import {Text, TouchableOpacity, View} from 'react-native'; -import {observeDraftCount} from '@queries/servers/drafts'; -import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system'; +import {switchToGlobalDrafts} from '@actions/local/draft'; +import { + getStyleSheet as getChannelItemStyleSheet, + ROW_HEIGHT, +} from '@components/channel_item/channel_item'; +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; +import {HOME_PADDING} from '@constants/view'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {preventDoubleTap} from '@utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; -import DraftsButton from './drafts_button'; +type DraftListProps = { + currentChannelId: string; + shouldHighlighActive?: boolean; + draftsCount: number; +}; -import type {WithDatabaseArgs} from '@typings/database/database'; +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + icon: { + color: changeOpacity(theme.sidebarText, 0.5), + fontSize: 24, + marginRight: 12, + }, + iconActive: { + color: theme.sidebarText, + }, + iconInfo: { + color: changeOpacity(theme.centerChannelColor, 0.72), + }, + text: { + flex: 1, + }, + countContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + count: { + color: theme.sidebarText, + ...typography('Body', 75, 'SemiBold'), + opacity: 0.64, + }, + opacity: { + opacity: 0.56, + }, +})); -const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const currentTeamId = observeCurrentTeamId(database); - const draftsCount = currentTeamId.pipe(switchMap((teamId) => observeDraftCount(database, teamId))); // Observe draft count - return { - currentChannelId: observeCurrentChannelId(database), - draftsCount, - }; -}); +const DraftsButton: React.FC = ({ + currentChannelId, + shouldHighlighActive = false, + draftsCount, +}) => { + const theme = useTheme(); + const styles = getChannelItemStyleSheet(theme); + const customStyles = getStyleSheet(theme); + const isTablet = useIsTablet(); -export default withDatabase(enhanced(DraftsButton)); + const handlePress = useCallback(preventDoubleTap(() => { + switchToGlobalDrafts(); + }), []); + const isActive = isTablet && shouldHighlighActive && !currentChannelId; + + const [containerStyle, iconStyle, textStyle] = useMemo(() => { + const container = [ + styles.container, + HOME_PADDING, + isActive && styles.activeItem, + isActive && { + paddingLeft: HOME_PADDING.paddingLeft - styles.activeItem.borderLeftWidth, + }, + {minHeight: ROW_HEIGHT}, + ]; + + const icon = [ + customStyles.icon, + isActive && customStyles.iconActive, + ]; + + const text = [ + customStyles.text, + styles.text, + isActive && styles.textActive, + ]; + + return [container, icon, text]; + }, [customStyles, isActive, styles]); + + return ( + + + + + + + {draftsCount} + + + + ); +}; + +export default DraftsButton; diff --git a/app/screens/home/channel_list/categories_list/categories_list.tsx b/app/screens/home/channel_list/categories_list/categories_list.tsx new file mode 100644 index 00000000000..22f5256b37d --- /dev/null +++ b/app/screens/home/channel_list/categories_list/categories_list.tsx @@ -0,0 +1,104 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useEffect, useMemo} from 'react'; +import {useWindowDimensions} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; + +import DraftsButton from '@components/drafts_buttton'; +import ThreadsButton from '@components/threads_button'; +import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {makeStyleSheetFromTheme} from '@utils/theme'; + +import Categories from './categories'; +import ChannelListHeader from './header'; +import LoadChannelsError from './load_channels_error'; +import SubHeader from './subheader'; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + container: { + flex: 1, + backgroundColor: theme.sidebarBg, + paddingTop: 10, + }, +})); + +type ChannelListProps = { + hasChannels: boolean; + iconPad?: boolean; + isCRTEnabled?: boolean; + moreThanOneTeam: boolean; + currentChannelId: string; + draftsCount: number; +}; + +const getTabletWidth = (moreThanOneTeam: boolean) => { + return TABLET_SIDEBAR_WIDTH - (moreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0); +}; + +const CategoriesList = ({ + hasChannels, + iconPad, + isCRTEnabled, + moreThanOneTeam, + currentChannelId, + draftsCount, +}: ChannelListProps) => { + const theme = useTheme(); + const styles = getStyleSheet(theme); + const {width} = useWindowDimensions(); + const isTablet = useIsTablet(); + const tabletWidth = useSharedValue(isTablet ? getTabletWidth(moreThanOneTeam) : 0); + + useEffect(() => { + if (isTablet) { + tabletWidth.value = getTabletWidth(moreThanOneTeam); + } + }, [isTablet, moreThanOneTeam]); + + const tabletStyle = useAnimatedStyle(() => { + if (!isTablet) { + return { + maxWidth: width, + }; + } + + return {maxWidth: withTiming(tabletWidth.value, {duration: 350})}; + }, [isTablet, width]); + + const content = useMemo(() => { + if (!hasChannels) { + return (); + } + + return ( + <> + + {isCRTEnabled && + + } + {draftsCount > 0 && ( + + )} + + + ); + }, [currentChannelId, draftsCount, hasChannels, isCRTEnabled]); + + return ( + + + {content} + + ); +}; + +export default CategoriesList; diff --git a/app/screens/home/channel_list/categories_list/index.test.tsx b/app/screens/home/channel_list/categories_list/index.test.tsx index 8f9f1681839..d1da0605e65 100644 --- a/app/screens/home/channel_list/categories_list/index.test.tsx +++ b/app/screens/home/channel_list/categories_list/index.test.tsx @@ -9,7 +9,7 @@ import {getTeamById} from '@queries/servers/team'; import {renderWithEverything} from '@test/intl-test-helper'; import TestHelper from '@test/test_helper'; -import CategoriesList from '.'; +import CategoriesList from './categories_list'; import type ServerDataOperator from '@database/operator/server_data_operator'; import type Database from '@nozbe/watermelondb/Database'; @@ -35,6 +35,8 @@ describe('components/categories_list', () => { , {database}, ); @@ -48,6 +50,8 @@ describe('components/categories_list', () => { isCRTEnabled={true} moreThanOneTeam={false} hasChannels={true} + draftsCount={0} + currentChannelId='' />, {database}, ); @@ -58,6 +62,20 @@ describe('components/categories_list', () => { jest.useRealTimers(); }); + it('should render channel list with Draft menu', () => { + const wrapper = renderWithEverything( + , + {database}, + ); + expect(wrapper.getByText('Drafts')).toBeTruthy(); + }); + it('should render team error', async () => { await operator.handleSystem({ systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID, value: ''}], @@ -69,6 +87,8 @@ describe('components/categories_list', () => { , {database}, ); @@ -91,6 +111,8 @@ describe('components/categories_list', () => { , {database}, ); diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index 085e8597361..e62a5bc4e66 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -1,90 +1,23 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useEffect, useMemo} from 'react'; -import {useWindowDimensions} from 'react-native'; -import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; +import {switchMap} from 'rxjs/operators'; -import DraftsButtton from '@components/drafts_buttton'; -import ThreadsButton from '@components/threads_button'; -import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; -import {useTheme} from '@context/theme'; -import {useIsTablet} from '@hooks/device'; -import {makeStyleSheetFromTheme} from '@utils/theme'; +import {observeDraftCount} from '@queries/servers/drafts'; +import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system'; -import Categories from './categories'; -import ChannelListHeader from './header'; -import LoadChannelsError from './load_channels_error'; -import SubHeader from './subheader'; +import CategoriesList from './categories_list'; -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ - container: { - flex: 1, - backgroundColor: theme.sidebarBg, - paddingTop: 10, - }, -})); +import type {WithDatabaseArgs} from '@typings/database/database'; -type ChannelListProps = { - hasChannels: boolean; - iconPad?: boolean; - isCRTEnabled?: boolean; - moreThanOneTeam: boolean; -}; +const enchanced = withObservables([], ({database}: WithDatabaseArgs) => { + const currentTeamId = observeCurrentTeamId(database); + const draftsCount = currentTeamId.pipe(switchMap((teamId) => observeDraftCount(database, teamId))); // Observe draft count + return { + currentChannelId: observeCurrentChannelId(database), + draftsCount, + }; +}); -const getTabletWidth = (moreThanOneTeam: boolean) => { - return TABLET_SIDEBAR_WIDTH - (moreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0); -}; - -const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: ChannelListProps) => { - const theme = useTheme(); - const styles = getStyleSheet(theme); - const {width} = useWindowDimensions(); - const isTablet = useIsTablet(); - const tabletWidth = useSharedValue(isTablet ? getTabletWidth(moreThanOneTeam) : 0); - - useEffect(() => { - if (isTablet) { - tabletWidth.value = getTabletWidth(moreThanOneTeam); - } - }, [isTablet, moreThanOneTeam]); - - const tabletStyle = useAnimatedStyle(() => { - if (!isTablet) { - return { - maxWidth: width, - }; - } - - return {maxWidth: withTiming(tabletWidth.value, {duration: 350})}; - }, [isTablet, width]); - - const content = useMemo(() => { - if (!hasChannels) { - return (); - } - - return ( - <> - - {isCRTEnabled && - - } - - - - ); - }, [isCRTEnabled]); - - return ( - - - {content} - - ); -}; - -export default CategoriesList; +export default withDatabase(enchanced(CategoriesList)); From 1d7b0bec08d5c5c9f67f98f7a51904ecdfd8732b Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 6 Jan 2025 14:10:12 +0530 Subject: [PATCH 072/111] map to reduce --- app/actions/local/draft.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index e62c06e67d7..ce8b281b9eb 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -279,6 +279,7 @@ async function getImageMetadata(url: string) { width, format, frame_count: 1, + url, }; } @@ -291,13 +292,18 @@ export async function parseMarkdownImages(markdown: string, imageMetadata: Dicti const imageRegex = /!\[.*?\]\((([a-zA-Z][a-zA-Z\d+\-.]*):\/\/[^\s()<>]+(?:\([^\s()<>]+\))*)\)/g; const matches = Array.from(markdown.matchAll(imageRegex)); - const promises = matches.map(async (match) => { + const promises = matches.reduce>>((result, match) => { const imageUrl = match[1]; if (isValidUrl(imageUrl)) { - const metadata = await getImageMetadata(imageUrl); - imageMetadata[imageUrl] = metadata; + result.push(getImageMetadata(imageUrl)); } - }); + return result; + }, []); - await Promise.all(promises); + const metadataArray = await Promise.all(promises); + metadataArray.forEach((metadata) => { + if (metadata) { + imageMetadata[metadata.url] = metadata; + } + }); } From 1261a4b5e429f6e98cf4a81c995bc5551c0f9f8c Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 6 Jan 2025 14:29:10 +0530 Subject: [PATCH 073/111] renamed SwipeableDraft to DraftSwipeAction --- .../{SwipeableDraft => DraftSwipeAction}/index.tsx | 4 ++-- .../components/global_drafts_list/global_drafts_list.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename app/screens/global_drafts/components/global_drafts_list/{SwipeableDraft => DraftSwipeAction}/index.tsx (98%) diff --git a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx b/app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx similarity index 98% rename from app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx rename to app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx index 91428dc6149..345df3b89c5 100644 --- a/app/screens/global_drafts/components/global_drafts_list/SwipeableDraft/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx @@ -45,7 +45,7 @@ const getStyles = makeStyleSheetFromTheme((theme) => { }; }); -const SwipeableDraft: React.FC = ({ +const DraftSwipeAction: React.FC = ({ item, location, layoutWidth, @@ -127,4 +127,4 @@ const SwipeableDraft: React.FC = ({ ); }; -export default SwipeableDraft; +export default DraftSwipeAction; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index fc3f03e0c7e..655741b3fab 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -15,7 +15,7 @@ import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; -import SwipeableDraft from './SwipeableDraft'; +import DraftSwipeAction from './DraftSwipeAction'; import DraftTooltip from './tooltip'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -105,7 +105,7 @@ const GlobalDraftsList: React.FC = ({ - = ({ ); } return ( - Date: Mon, 6 Jan 2025 14:30:37 +0530 Subject: [PATCH 074/111] name fixes --- .../{DraftSwipeAction => DraftSwipeActions}/index.tsx | 4 ++-- .../components/global_drafts_list/global_drafts_list.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename app/screens/global_drafts/components/global_drafts_list/{DraftSwipeAction => DraftSwipeActions}/index.tsx (98%) diff --git a/app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx b/app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx similarity index 98% rename from app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx rename to app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx index 345df3b89c5..9b1a53447d6 100644 --- a/app/screens/global_drafts/components/global_drafts_list/DraftSwipeAction/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx @@ -45,7 +45,7 @@ const getStyles = makeStyleSheetFromTheme((theme) => { }; }); -const DraftSwipeAction: React.FC = ({ +const DraftSwipeActions: React.FC = ({ item, location, layoutWidth, @@ -127,4 +127,4 @@ const DraftSwipeAction: React.FC = ({ ); }; -export default DraftSwipeAction; +export default DraftSwipeActions; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 655741b3fab..3683deba691 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -15,7 +15,7 @@ import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; -import DraftSwipeAction from './DraftSwipeAction'; +import DraftSwipeActions from './DraftSwipeActions'; import DraftTooltip from './tooltip'; import type DraftModel from '@typings/database/models/servers/draft'; @@ -105,7 +105,7 @@ const GlobalDraftsList: React.FC = ({ - = ({ ); } return ( - Date: Mon, 6 Jan 2025 14:44:35 +0530 Subject: [PATCH 075/111] isValidUrl to isParsableUrl and added test --- app/actions/local/draft.ts | 5 +++-- app/utils/helpers.ts | 9 --------- app/utils/url/index.test.ts | 37 ++++++++++++++++++++++++++++++++++++- app/utils/url/index.ts | 9 +++++++++ 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index ce8b281b9eb..9bf343b3254 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -7,8 +7,9 @@ import {Navigation, Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {getDraft} from '@queries/servers/drafts'; import {goToScreen} from '@screens/navigation'; -import {isTablet, isValidUrl} from '@utils/helpers'; +import {isTablet} from '@utils/helpers'; import {logError} from '@utils/log'; +import {isParsableUrl} from '@utils/url'; export const switchToGlobalDrafts = async () => { const isTablelDevice = isTablet(); @@ -294,7 +295,7 @@ export async function parseMarkdownImages(markdown: string, imageMetadata: Dicti const promises = matches.reduce>>((result, match) => { const imageUrl = match[1]; - if (isValidUrl(imageUrl)) { + if (isParsableUrl(imageUrl)) { result.push(getImageMetadata(imageUrl)); } return result; diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 45034aa36bb..1555f0a7739 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -182,12 +182,3 @@ export function areBothStringArraysEqual(a: string[], b: string[]) { return areBothEqual; } - -export function isValidUrl(url: string): boolean { - try { - const parsedUrl = new URL(url); - return Boolean(parsedUrl); - } catch { - return false; - } -} diff --git a/app/utils/url/index.test.ts b/app/utils/url/index.test.ts index 09402049e8e..efd93bdd866 100644 --- a/app/utils/url/index.test.ts +++ b/app/utils/url/index.test.ts @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {safeDecodeURIComponent} from './index'; +import {isParsableUrl, safeDecodeURIComponent} from './index'; describe('safeDecodeURIComponent', () => { test('should decode a valid URI component', () => { @@ -27,3 +27,38 @@ describe('safeDecodeURIComponent', () => { expect(decoded).toBe(''); }); }); + +describe('isParsableUrl', () => { + it('should return true for valid URLs', () => { + expect(isParsableUrl('http://example.com')).toBe(true); + expect(isParsableUrl('https://example.com')).toBe(true); + expect(isParsableUrl('https://example.com/path')).toBe(true); + expect(isParsableUrl('https://example.com:8080/path?query=1')).toBe(true); + expect(isParsableUrl('https://sub.domain.example.com')).toBe(true); + expect(isParsableUrl('ftp://example.com')).toBe(true); + }); + + it('should return false for invalid URLs', () => { + expect(isParsableUrl('example')).toBe(false); + expect(isParsableUrl('://example.com')).toBe(false); + expect(isParsableUrl('http//example.com')).toBe(false); + expect(isParsableUrl('')).toBe(false); + }); + + it('should return false for non-URL strings', () => { + expect(isParsableUrl('plain text')).toBe(false); + expect(isParsableUrl('12345')).toBe(false); + }); + + it('should handle URLs with special characters correctly', () => { + expect(isParsableUrl('https://example.com/path?query=value&other=value')).toBe(true); + expect(isParsableUrl('https://example.com/path#hash')).toBe(true); + expect(isParsableUrl('https://example.com:3000/path?query=1')).toBe(true); + }); + + it('should handle edge cases gracefully', () => { + expect(isParsableUrl(' ')).toBe(false); + expect(isParsableUrl(null as unknown as string)).toBe(false); + expect(isParsableUrl(undefined as unknown as string)).toBe(false); + }); +}); diff --git a/app/utils/url/index.ts b/app/utils/url/index.ts index 2054d7c85ed..38e5f0fac1a 100644 --- a/app/utils/url/index.ts +++ b/app/utils/url/index.ts @@ -18,6 +18,15 @@ export function isValidUrl(url = '') { return regex.test(url); } +export function isParsableUrl(url: string): boolean { + try { + const parsedUrl = new URL(url); + return Boolean(parsedUrl); + } catch { + return false; + } +} + export function sanitizeUrl(url: string, useHttp = false) { let preUrl = urlParse(url, true); let protocol = useHttp ? 'http:' : preUrl.protocol; From 3d426c012452c1f7a89eeb0c14b909961296c255 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 8 Jan 2025 11:48:14 +0530 Subject: [PATCH 076/111] added test and addressed minor review comments --- app/actions/local/draft.test.ts | 38 +++++++++++++++++++++++++++ app/actions/local/draft.ts | 46 +++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/app/actions/local/draft.test.ts b/app/actions/local/draft.test.ts index a876c450148..d9248b3542a 100644 --- a/app/actions/local/draft.test.ts +++ b/app/actions/local/draft.test.ts @@ -12,6 +12,7 @@ import { addFilesToDraft, removeDraft, updateDraftPriority, + updateDraftMarkdownImageMetadata, } from './draft'; import type ServerDataOperator from '@database/operator/server_data_operator'; @@ -242,3 +243,40 @@ describe('updateDraftPriority', () => { expect(result.draft.metadata?.priority?.priority).toBe(postPriority.priority); }); }); + +describe('updateDraftMarkdownImageMetadata', () => { + const postImageData: PostImage = { + height: 1080, + width: 1920, + format: 'jpg', + frame_count: undefined, + }; + + it('handle not found database', async () => { + const result = await updateDraftMarkdownImageMetadata({ + serverUrl: 'foo', + channelId, + rootId: '', + imageMetadata: { + image1: postImageData, + }, + }) as {draft: unknown; error: unknown}; + expect(result.error).toBeDefined(); + expect(result.draft).toBeUndefined(); + }); + + it('handle update image metadata', async () => { + await operator.handleDraft({drafts: [draft], prepareRecordsOnly: false}); + const result = await updateDraftMarkdownImageMetadata({ + serverUrl, + channelId, + rootId: '', + imageMetadata: { + image1: postImageData, + }, + }) as {draft: DraftModel; error: unknown}; + expect(result.error).toBeUndefined(); + expect(result.draft).toBeDefined(); + expect(result.draft.metadata?.images?.image1).toEqual(postImageData); + }); +}); diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index 9bf343b3254..42aa96af3c5 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -243,7 +243,7 @@ export async function updateDraftMarkdownImageMetadata({ } return {draft}; } catch (error) { - logError('Failed updateDraftImages', error); + logError('Failed updateDraftMarkdownImageMetadata', error); return {error}; } } @@ -252,25 +252,31 @@ async function getImageMetadata(url: string) { let height = 0; let width = 0; let format; - try { - await new Promise((resolve, reject) => { - Image.getSize( - url, - (imageWidth, imageHeight) => { - width = imageWidth; - height = imageHeight; - resolve(null); - }, - (error) => { - logError('Failed to get image size', error); - reject(error); - }, - ); - }); - } catch (error) { - width = 0; - height = 0; - } + await new Promise((resolve) => { + Image.getSize( + url, + (imageWidth, imageHeight) => { + width = imageWidth; + height = imageHeight; + resolve(null); + }, + (error) => { + logError('Failed getImageMetadata to get image size', error); + }, + ); + }); + + /** + * Regex Explanation: + * \. - Matches a literal period (e.g., before "jpg"). + * (\w+) - Captures the file extension (letters, digits, or underscores). + * (?=\?|$) - Ensures the extension is followed by "?" or the end of the URL. + * + * * Example Matches: + * "https://example.com/image.jpg" -> Matches "jpg" + * "https://example.com/image.png?size=1" -> Matches "png" + * "https://example.com/file" -> No match (no file extension). + */ const match = url.match(/\.(\w+)(?=\?|$)/); if (match) { format = match[1]; From 83d4c21fd943019fa8d9ac532eab12c6431582de Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 8 Jan 2025 12:00:23 +0530 Subject: [PATCH 077/111] added inline component for the duplicate code --- .../draft_post_header/draft_post_header.tsx | 73 +++++++++---------- app/components/drafts_buttton/index.tsx | 6 +- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/app/components/draft_post_header/draft_post_header.tsx b/app/components/draft_post_header/draft_post_header.tsx index 653926df490..89c2282b4e3 100644 --- a/app/components/draft_post_header/draft_post_header.tsx +++ b/app/components/draft_post_header/draft_post_header.tsx @@ -85,54 +85,51 @@ const DraftPostHeader: React.FC = ({ const style = getStyleSheet(theme); const isChannelTypeDM = channel.type === General.DM_CHANNEL; - let headerComponent: ReactNode = null; - const profileComponent = draftReceiverUser ? : ( - - ( + + - ); + + {draftReceiverUser ? ( + + ) : ( + + + + )} + + + ); + + let headerComponent: ReactNode = null; if (rootId) { headerComponent = ( - - - - {profileComponent} - - + ); } else if (isChannelTypeDM) { headerComponent = ( - - - - {profileComponent} - - + ); } else { headerComponent = ( - - - - {profileComponent} - - + ); } diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/index.tsx index c10466be4b5..b07b79a3a27 100644 --- a/app/components/drafts_buttton/index.tsx +++ b/app/components/drafts_buttton/index.tsx @@ -20,7 +20,7 @@ import {typography} from '@utils/typography'; type DraftListProps = { currentChannelId: string; - shouldHighlighActive?: boolean; + shouldHighlightActive?: boolean; draftsCount: number; }; @@ -57,7 +57,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const DraftsButton: React.FC = ({ currentChannelId, - shouldHighlighActive = false, + shouldHighlightActive = false, draftsCount, }) => { const theme = useTheme(); @@ -69,7 +69,7 @@ const DraftsButton: React.FC = ({ switchToGlobalDrafts(); }), []); - const isActive = isTablet && shouldHighlighActive && !currentChannelId; + const isActive = isTablet && shouldHighlightActive && !currentChannelId; const [containerStyle, iconStyle, textStyle] = useMemo(() => { const container = [ From f0dd52da68584fa578a99b19b30dbc1a3eab49cc Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 8 Jan 2025 12:17:01 +0530 Subject: [PATCH 078/111] inlt fixes --- assets/base/i18n/en.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 38f62b4838a..e127ccbb8bd 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -167,8 +167,6 @@ "channel_info.copy_link": "Copy Link", "channel_info.copy_purpose_text": "Copy Purpose Text", "channel_info.custom_status": "Custom status:", - "channel_info.draft_in_channel": "In:", - "channel_info.draft_to_user": "To:", "channel_info.edit_header": "Edit Header", "channel_info.error_close": "Close", "channel_info.favorite": "Favorite", @@ -196,7 +194,6 @@ "channel_info.send_a_mesasge": "Send a message", "channel_info.send_mesasge": "Send message", "channel_info.set_header": "Set Header", - "channel_info.thread_in": "Thread in:", "channel_info.unarchive": "Unarchive Channel", "channel_info.unarchive_description": "Are you sure you want to unarchive the {term} {name}?", "channel_info.unarchive_failed": "An error occurred trying to unarchive the channel {displayName}", From c75c9af6cfa4c55724b1cefb06855dce6ffde786 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 13:17:11 +0530 Subject: [PATCH 079/111] clearDraft is not optional --- app/hooks/handle_send_message.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/hooks/handle_send_message.ts b/app/hooks/handle_send_message.ts index b3196eb5a3d..ab7d6c7297e 100644 --- a/app/hooks/handle_send_message.ts +++ b/app/hooks/handle_send_message.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useMemo, useState} from 'react'; import {useIntl} from 'react-intl'; import {DeviceEventEmitter} from 'react-native'; @@ -37,7 +37,7 @@ type Props = { currentUserId: string; channelType: ChannelType | undefined; postPriority: PostPriority; - clearDraft?: () => void; + clearDraft: () => void; } export const useHandleSendMessage = ({ @@ -61,7 +61,7 @@ export const useHandleSendMessage = ({ const [sendingMessage, setSendingMessage] = useState(false); const [channelTimezoneCount, setChannelTimezoneCount] = useState(0); - const canSend = useCallback(() => { + const canSend = useMemo(() => { if (sendingMessage) { return false; } @@ -82,7 +82,7 @@ export const useHandleSendMessage = ({ const handleReaction = useCallback((emoji: string, add: boolean) => { handleReactionToLatestPost(serverUrl, emoji, add, rootId); - clearDraft?.(); + clearDraft(); setSendingMessage(false); }, [serverUrl, rootId, clearDraft]); @@ -107,7 +107,7 @@ export const useHandleSendMessage = ({ createPost(serverUrl, post, postFiles); - clearDraft?.(); + clearDraft(); setSendingMessage(false); DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL); }, [files, currentUserId, channelId, rootId, value, postPriority, serverUrl, clearDraft]); @@ -126,7 +126,7 @@ export const useHandleSendMessage = ({ const {handled, error} = await handleCallsSlashCommand(value.trim(), serverUrl, channelId, channelType ?? '', rootId, currentUserId, intl); if (handled) { setSendingMessage(false); - clearDraft?.(); + clearDraft(); return; } if (error) { @@ -160,7 +160,7 @@ export const useHandleSendMessage = ({ return; } - clearDraft?.(); + clearDraft(); if (data?.goto_location && !value.startsWith('/leave')) { handleGotoLocation(serverUrl, intl, data.goto_location); @@ -182,7 +182,7 @@ export const useHandleSendMessage = ({ }, [enableConfirmNotificationsToChannel, useChannelMentions, value, membersCount, sendCommand, showSendToAllOrChannelOrHereAlert, doSubmitMessage]); const handleSendMessage = useCallback(preventDoubleTap(() => { - if (!canSend()) { + if (!canSend) { return; } @@ -218,6 +218,6 @@ export const useHandleSendMessage = ({ return { handleSendMessage, - canSend: canSend(), + canSend, }; }; From 4f7c2cb0747745909d326f03598820185312666f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 13:51:46 +0530 Subject: [PATCH 080/111] optimised categories_list.tsx component --- .../categories_list/categories_list.tsx | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/app/screens/home/channel_list/categories_list/categories_list.tsx b/app/screens/home/channel_list/categories_list/categories_list.tsx index 22f5256b37d..5265fd72cdc 100644 --- a/app/screens/home/channel_list/categories_list/categories_list.tsx +++ b/app/screens/home/channel_list/categories_list/categories_list.tsx @@ -56,6 +56,9 @@ const CategoriesList = ({ if (isTablet) { tabletWidth.value = getTabletWidth(moreThanOneTeam); } + + // tabletWidth is a sharedValue, so it's safe to ignore this warning + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isTablet, moreThanOneTeam]); const tabletStyle = useAnimatedStyle(() => { @@ -68,6 +71,32 @@ const CategoriesList = ({ return {maxWidth: withTiming(tabletWidth.value, {duration: 350})}; }, [isTablet, width]); + const threadButtonComponent = useMemo(() => { + if (!isCRTEnabled) { + return null; + } + + return ( + + ); + }, [isCRTEnabled]); + + const draftsButtonComponent = useMemo(() => { + if (draftsCount > 0) { + return ( + + ); + } + + return null; + }, [currentChannelId, draftsCount]); + const content = useMemo(() => { if (!hasChannels) { return (); @@ -76,22 +105,12 @@ const CategoriesList = ({ return ( <> - {isCRTEnabled && - - } - {draftsCount > 0 && ( - - )} + {threadButtonComponent} + {draftsButtonComponent} ); - }, [currentChannelId, draftsCount, hasChannels, isCRTEnabled]); + }, [draftsButtonComponent, hasChannels, threadButtonComponent]); return ( From da2cbdf1db7c2a9fa8aea725b3b9a7ede6bea55a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 18:40:42 +0530 Subject: [PATCH 081/111] Swipeable to ReanimatedSwipeable, TouchableWithoutFeedback to Pressable and folder name changes --- .../index.tsx | 131 +++++++++++------- .../global_drafts_list/global_drafts_list.tsx | 2 +- app/utils/draft/index.ts | 4 +- 3 files changed, 83 insertions(+), 54 deletions(-) rename app/screens/global_drafts/components/global_drafts_list/{DraftSwipeActions => draft_swipe_actions}/index.tsx (50%) diff --git a/app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx b/app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions/index.tsx similarity index 50% rename from app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx rename to app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions/index.tsx index 9b1a53447d6..ceb8ccea93b 100644 --- a/app/screens/global_drafts/components/global_drafts_list/DraftSwipeActions/index.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions/index.tsx @@ -1,10 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; -import {Animated, DeviceEventEmitter, TouchableWithoutFeedback} from 'react-native'; -import {Swipeable} from 'react-native-gesture-handler'; +import {DeviceEventEmitter, Pressable, View} from 'react-native'; +import {GestureHandlerRootView} from 'react-native-gesture-handler'; +import ReanimatedSwipeable, {type SwipeableMethods} from 'react-native-gesture-handler/ReanimatedSwipeable'; +import Reanimated, {useAnimatedStyle, useSharedValue, type SharedValue} from 'react-native-reanimated'; import CompassIcon from '@components/compass_icon'; import Draft from '@components/draft'; @@ -27,12 +29,15 @@ type Props = { const getStyles = makeStyleSheetFromTheme((theme) => { return { deleteContainer: { + backgroundColor: theme.dndIndicator, + }, + pressableContainer: { display: 'flex', alignItems: 'center', justifyContent: 'center', - backgroundColor: theme.dndIndicator, paddingHorizontal: 24, paddingVertical: 16, + height: '100%', }, deleteText: { color: theme.sidebarText, @@ -45,18 +50,62 @@ const getStyles = makeStyleSheetFromTheme((theme) => { }; }); +function RightAction({deleteDraft, drag}: { deleteDraft: () => void; drag: SharedValue }) { + const theme = useTheme(); + const styles1 = getStyles(theme); + const containerWidth = useSharedValue(0); + const [isReady, setIsReady] = useState(false); // flag is use to prevent the jerky animation before calculating the container width + const styleAnimation = useAnimatedStyle(() => { + return { + transform: [{translateX: drag.value + containerWidth.value}], + opacity: isReady ? 1 : 0, + }; + }); + + const handleLayout = (event: { nativeEvent: { layout: { width: number } } }) => { + const width = event.nativeEvent.layout.width; + containerWidth.value = width; + setIsReady(true); + }; + + return ( + + + + + + + + + ); +} + const DraftSwipeActions: React.FC = ({ item, location, layoutWidth, }) => { - const swipeable = useRef(null); - const theme = useTheme(); + const swipeable = useRef(null); const intl = useIntl(); - const styles = getStyles(theme); const serverUrl = useServerUrl(); - const onSwipeableWillOpen = useCallback(() => { + const onSwipeableOpenStartDrag = useCallback(() => { DeviceEventEmitter.emit(Events.DRAFT_SWIPEABLE, item.id); }, [item.id]); @@ -70,33 +119,6 @@ const DraftSwipeActions: React.FC = ({ }); }, [intl, item.channelId, item.rootId, serverUrl]); - const renderAction = useCallback((progress: Animated.AnimatedInterpolation) => { - const trans = progress.interpolate({ - inputRange: [0, 1], - outputRange: [layoutWidth + 40, 0], - extrapolate: 'clamp', - }); - - return ( - - - - - - - ); - }, [deleteDraft, layoutWidth, styles.deleteContainer, styles.deleteText, theme.sidebarText]); - useEffect(() => { const listener = DeviceEventEmitter.addListener(Events.DRAFT_SWIPEABLE, (draftId: string) => { if (item.id !== draftId) { @@ -108,22 +130,29 @@ const DraftSwipeActions: React.FC = ({ }, [item.id]); return ( - - - + + ( + + )} + ref={swipeable} + onSwipeableOpenStartDrag={onSwipeableOpenStartDrag} + testID='draft_swipeable' + > + + + ); }; diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 3683deba691..9c9041b4881 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -15,7 +15,7 @@ import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; -import DraftSwipeActions from './DraftSwipeActions'; +import DraftSwipeActions from './draft_swipe_actions'; import DraftTooltip from './tooltip'; import type DraftModel from '@typings/database/models/servers/draft'; diff --git a/app/utils/draft/index.ts b/app/utils/draft/index.ts index d16daebd317..31519b6d7ab 100644 --- a/app/utils/draft/index.ts +++ b/app/utils/draft/index.ts @@ -9,7 +9,7 @@ import {CODE_REGEX} from '@constants/autocomplete'; import {t} from '@i18n'; import type {IntlShape, MessageDescriptor} from 'react-intl'; -import type {Swipeable} from 'react-native-gesture-handler'; +import type {SwipeableMethods} from 'react-native-gesture-handler/lib/typescript/components/ReanimatedSwipeable'; type AlertCallback = (value?: string) => void; @@ -198,7 +198,7 @@ export function deleteDraftConfirmation({intl, serverUrl, channelId, rootId, swi serverUrl: string; channelId: string; rootId: string; - swipeable?: React.RefObject; + swipeable?: React.RefObject; }) { const deleteDraft = async () => { removeDraft(serverUrl, channelId, rootId); From a5e683dc432a0f1bb8cf9e96c721e1fc4d14bdcf Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 18:44:08 +0530 Subject: [PATCH 082/111] Added comment and disabled eslint rule for showing warning --- .../components/global_drafts_list/global_drafts_list.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 9c9041b4881..b1a5d1fd6e4 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -77,6 +77,9 @@ const GlobalDraftsList: React.FC = ({ InteractionManager.runAfterInteractions(() => { setTooltipVisible(true); }); + + // This effect is intended to run only on the first mount, so dependencies are omitted intentionally + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const collapse = useCallback(() => { From b2960c5a7db84bb03ce6a473e995688767c579cd Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 19:15:15 +0530 Subject: [PATCH 083/111] fixed component file name --- .../draft_post/{draft_message/index.tsx => draft_message.tsx} | 0 app/components/draft_post_header/draft_post_header.tsx | 2 +- .../{ProfileAvatar/index.tsx => profile_avatar.tsx} | 0 app/components/drafts_buttton/{index.tsx => drafts_button.tsx} | 0 app/components/post_draft/send_handler/send_handler.tsx | 2 +- .../draft_options/{delete_draft/index.tsx => delete_draft.tsx} | 0 .../draft_options/{edit_draft/index.tsx => edit_draft.tsx} | 0 app/screens/draft_options/{send_draft => }/send_draft.tsx | 0 .../index.tsx => draft_empty_component.tsx} | 0 .../{draft_swipe_actions/index.tsx => draft_swipe_actions.tsx} | 0 .../global_drafts_list/{tooltip.tsx => draft_tooltip.tsx} | 0 .../components/global_drafts_list/global_drafts_list.tsx | 2 +- .../home/channel_list/categories_list/categories_list.tsx | 2 +- 13 files changed, 4 insertions(+), 4 deletions(-) rename app/components/draft/draft_post/{draft_message/index.tsx => draft_message.tsx} (100%) rename app/components/draft_post_header/{ProfileAvatar/index.tsx => profile_avatar.tsx} (100%) rename app/components/drafts_buttton/{index.tsx => drafts_button.tsx} (100%) rename app/screens/draft_options/{delete_draft/index.tsx => delete_draft.tsx} (100%) rename app/screens/draft_options/{edit_draft/index.tsx => edit_draft.tsx} (100%) rename app/screens/draft_options/{send_draft => }/send_draft.tsx (100%) rename app/screens/global_drafts/components/{draft_empty_component/index.tsx => draft_empty_component.tsx} (100%) rename app/screens/global_drafts/components/global_drafts_list/{draft_swipe_actions/index.tsx => draft_swipe_actions.tsx} (100%) rename app/screens/global_drafts/components/global_drafts_list/{tooltip.tsx => draft_tooltip.tsx} (100%) diff --git a/app/components/draft/draft_post/draft_message/index.tsx b/app/components/draft/draft_post/draft_message.tsx similarity index 100% rename from app/components/draft/draft_post/draft_message/index.tsx rename to app/components/draft/draft_post/draft_message.tsx diff --git a/app/components/draft_post_header/draft_post_header.tsx b/app/components/draft_post_header/draft_post_header.tsx index 89c2282b4e3..466664f75f6 100644 --- a/app/components/draft_post_header/draft_post_header.tsx +++ b/app/components/draft_post_header/draft_post_header.tsx @@ -5,7 +5,7 @@ import React, {type ReactNode} from 'react'; import {Text, View} from 'react-native'; import CompassIcon from '@components/compass_icon'; -import ProfileAvatar from '@components/draft_post_header/ProfileAvatar'; +import ProfileAvatar from '@components/draft_post_header/profile_avatar'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; import {General} from '@constants'; diff --git a/app/components/draft_post_header/ProfileAvatar/index.tsx b/app/components/draft_post_header/profile_avatar.tsx similarity index 100% rename from app/components/draft_post_header/ProfileAvatar/index.tsx rename to app/components/draft_post_header/profile_avatar.tsx diff --git a/app/components/drafts_buttton/index.tsx b/app/components/drafts_buttton/drafts_button.tsx similarity index 100% rename from app/components/drafts_buttton/index.tsx rename to app/components/drafts_buttton/drafts_button.tsx diff --git a/app/components/post_draft/send_handler/send_handler.tsx b/app/components/post_draft/send_handler/send_handler.tsx index 848724b7a4e..1bb9772e435 100644 --- a/app/components/post_draft/send_handler/send_handler.tsx +++ b/app/components/post_draft/send_handler/send_handler.tsx @@ -7,7 +7,7 @@ import {updateDraftPriority} from '@actions/local/draft'; import {PostPriorityType} from '@constants/post'; import {useServerUrl} from '@context/server'; import {useHandleSendMessage} from '@hooks/handle_send_message'; -import SendDraft from '@screens/draft_options/send_draft/send_draft'; +import SendDraft from '@screens/draft_options/send_draft'; import DraftInput from '../draft_input'; diff --git a/app/screens/draft_options/delete_draft/index.tsx b/app/screens/draft_options/delete_draft.tsx similarity index 100% rename from app/screens/draft_options/delete_draft/index.tsx rename to app/screens/draft_options/delete_draft.tsx diff --git a/app/screens/draft_options/edit_draft/index.tsx b/app/screens/draft_options/edit_draft.tsx similarity index 100% rename from app/screens/draft_options/edit_draft/index.tsx rename to app/screens/draft_options/edit_draft.tsx diff --git a/app/screens/draft_options/send_draft/send_draft.tsx b/app/screens/draft_options/send_draft.tsx similarity index 100% rename from app/screens/draft_options/send_draft/send_draft.tsx rename to app/screens/draft_options/send_draft.tsx diff --git a/app/screens/global_drafts/components/draft_empty_component/index.tsx b/app/screens/global_drafts/components/draft_empty_component.tsx similarity index 100% rename from app/screens/global_drafts/components/draft_empty_component/index.tsx rename to app/screens/global_drafts/components/draft_empty_component.tsx diff --git a/app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions/index.tsx b/app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions.tsx similarity index 100% rename from app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions/index.tsx rename to app/screens/global_drafts/components/global_drafts_list/draft_swipe_actions.tsx diff --git a/app/screens/global_drafts/components/global_drafts_list/tooltip.tsx b/app/screens/global_drafts/components/global_drafts_list/draft_tooltip.tsx similarity index 100% rename from app/screens/global_drafts/components/global_drafts_list/tooltip.tsx rename to app/screens/global_drafts/components/global_drafts_list/draft_tooltip.tsx diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index b1a5d1fd6e4..0d418ed96c3 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -16,7 +16,7 @@ import {popTopScreen} from '@screens/navigation'; import DraftEmptyComponent from '../draft_empty_component'; import DraftSwipeActions from './draft_swipe_actions'; -import DraftTooltip from './tooltip'; +import DraftTooltip from './draft_tooltip'; import type DraftModel from '@typings/database/models/servers/draft'; diff --git a/app/screens/home/channel_list/categories_list/categories_list.tsx b/app/screens/home/channel_list/categories_list/categories_list.tsx index 5265fd72cdc..e7d6f88aa5f 100644 --- a/app/screens/home/channel_list/categories_list/categories_list.tsx +++ b/app/screens/home/channel_list/categories_list/categories_list.tsx @@ -5,7 +5,7 @@ import React, {useEffect, useMemo} from 'react'; import {useWindowDimensions} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import DraftsButton from '@components/drafts_buttton'; +import DraftsButton from '@components/drafts_buttton/drafts_button'; import ThreadsButton from '@components/threads_button'; import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; import {useTheme} from '@context/theme'; From 949089f4308058f1b8680bccd35120b574117199 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 19:18:59 +0530 Subject: [PATCH 084/111] minor' --- .../components/global_drafts_list/global_drafts_list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 0d418ed96c3..5b38b006767 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -78,7 +78,7 @@ const GlobalDraftsList: React.FC = ({ setTooltipVisible(true); }); - // This effect is intended to run only on the first mount, so dependencies are omitted intentionally + // This effect is intended to run only on the first mount, so dependencies are omitted intentionally. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 9abea9fdf6b9af892d2b7935e516138a352ef930 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 19:41:01 +0530 Subject: [PATCH 085/111] Removed deprecated Animated createAnimatedComponent flatlist --- .../components/global_drafts_list/global_drafts_list.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx index 5b38b006767..0b7e93da693 100644 --- a/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx +++ b/app/screens/global_drafts/components/global_drafts_list/global_drafts_list.tsx @@ -1,7 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {FlatList} from '@stream-io/flat-list-mvcp'; import React, {useCallback, useEffect, useState} from 'react'; import {InteractionManager, StyleSheet, View, type LayoutChangeEvent, type ListRenderItemInfo} from 'react-native'; import Animated from 'react-native-reanimated'; @@ -52,7 +51,7 @@ const styles = StyleSheet.create({ }, }); -const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); +const AnimatedFlatList = Animated.FlatList; const keyExtractor = (item: DraftModel) => item.id; const GlobalDraftsList: React.FC = ({ From 74d815dd86862828c9c58fc54ac02731efa0772b Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Jan 2025 19:53:46 +0530 Subject: [PATCH 086/111] added test for missing protocol check --- app/utils/url/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/utils/url/index.test.ts b/app/utils/url/index.test.ts index efd93bdd866..ea75b52ada0 100644 --- a/app/utils/url/index.test.ts +++ b/app/utils/url/index.test.ts @@ -40,6 +40,7 @@ describe('isParsableUrl', () => { it('should return false for invalid URLs', () => { expect(isParsableUrl('example')).toBe(false); + expect(isParsableUrl('example.com')).toBe(false); // Missing protocol expect(isParsableUrl('://example.com')).toBe(false); expect(isParsableUrl('http//example.com')).toBe(false); expect(isParsableUrl('')).toBe(false); From abc7f4cd1a8be40e0c00ce2f024836fa31286f39 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 10 Jan 2025 10:47:56 +0530 Subject: [PATCH 087/111] import change for SwipeableMethod --- app/utils/draft/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/draft/index.ts b/app/utils/draft/index.ts index 31519b6d7ab..1344a0795c0 100644 --- a/app/utils/draft/index.ts +++ b/app/utils/draft/index.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {Alert, type AlertButton} from 'react-native'; +import {type SwipeableMethods} from 'react-native-gesture-handler/ReanimatedSwipeable'; import {parseMarkdownImages, removeDraft, updateDraftMarkdownImageMetadata, updateDraftMessage} from '@actions/local/draft'; import {General} from '@constants'; @@ -9,7 +10,6 @@ import {CODE_REGEX} from '@constants/autocomplete'; import {t} from '@i18n'; import type {IntlShape, MessageDescriptor} from 'react-intl'; -import type {SwipeableMethods} from 'react-native-gesture-handler/lib/typescript/components/ReanimatedSwipeable'; type AlertCallback = (value?: string) => void; From 4cfef6b401cac7327bfd32b901c5c416d8e509cb Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 13 Jan 2025 12:44:41 +0530 Subject: [PATCH 088/111] active tab for tablet view --- .../drafts_buttton/drafts_button.tsx | 9 ++--- .../threads_button/threads_button.tsx | 5 ++- app/constants/events.ts | 1 + app/constants/screens.ts | 1 + .../categories/body/category_body.tsx | 22 ++++++++++--- .../categories_list/categories/categories.tsx | 5 ++- .../categories_list/categories_list.tsx | 33 ++++++++++++++----- .../categories_list/index.test.tsx | 5 --- .../channel_list/categories_list/index.tsx | 3 +- 9 files changed, 59 insertions(+), 25 deletions(-) diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index b07b79a3a27..e8486c90509 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback, useMemo} from 'react'; -import {Text, TouchableOpacity, View} from 'react-native'; +import {DeviceEventEmitter, Text, TouchableOpacity, View} from 'react-native'; import {switchToGlobalDrafts} from '@actions/local/draft'; import { @@ -11,6 +11,8 @@ import { } from '@components/channel_item/channel_item'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; +import {Events} from '@constants'; +import {DRAFT} from '@constants/screens'; import {HOME_PADDING} from '@constants/view'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; @@ -19,7 +21,6 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; type DraftListProps = { - currentChannelId: string; shouldHighlightActive?: boolean; draftsCount: number; }; @@ -56,7 +57,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ })); const DraftsButton: React.FC = ({ - currentChannelId, shouldHighlightActive = false, draftsCount, }) => { @@ -66,10 +66,11 @@ const DraftsButton: React.FC = ({ const isTablet = useIsTablet(); const handlePress = useCallback(preventDoubleTap(() => { + DeviceEventEmitter.emit(Events.ACTIVE_SCREEN, DRAFT); switchToGlobalDrafts(); }), []); - const isActive = isTablet && shouldHighlightActive && !currentChannelId; + const isActive = isTablet && shouldHighlightActive; const [containerStyle, iconStyle, textStyle] = useMemo(() => { const container = [ diff --git a/app/components/threads_button/threads_button.tsx b/app/components/threads_button/threads_button.tsx index 4bf53b7ffb1..54854527a5a 100644 --- a/app/components/threads_button/threads_button.tsx +++ b/app/components/threads_button/threads_button.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback, useMemo} from 'react'; -import {TouchableOpacity, View} from 'react-native'; +import {DeviceEventEmitter, TouchableOpacity, View} from 'react-native'; import {switchToGlobalThreads} from '@actions/local/thread'; import Badge from '@components/badge'; @@ -13,6 +13,8 @@ import { } from '@components/channel_item/channel_item'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; +import {Events} from '@constants'; +import {THREAD} from '@constants/screens'; import {HOME_PADDING} from '@constants/view'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; @@ -65,6 +67,7 @@ const ThreadsButton = ({ const customStyles = getStyleSheet(theme); const handlePress = useCallback(preventDoubleTap(() => { + DeviceEventEmitter.emit(Events.ACTIVE_SCREEN, THREAD); if (onPress) { onPress(); } else { diff --git a/app/constants/events.ts b/app/constants/events.ts index 13c73a2d943..657c9c96452 100644 --- a/app/constants/events.ts +++ b/app/constants/events.ts @@ -32,4 +32,5 @@ export default keyMirror({ CRT_TOGGLED: null, JOIN_CALL_BAR_VISIBLE: null, DRAFT_SWIPEABLE: null, + ACTIVE_SCREEN: null, }); diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 2b981d340e9..23bc9ea89f8 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -21,6 +21,7 @@ export const CREATE_OR_EDIT_CHANNEL = 'CreateOrEditChannel'; export const CREATE_TEAM = 'CreateTeam'; export const CUSTOM_STATUS = 'CustomStatus'; export const CUSTOM_STATUS_CLEAR_AFTER = 'CustomStatusClearAfter'; +export const DRAFT = 'Draft'; export const DRAFT_OPTIONS = 'DraftOptions'; export const EDIT_POST = 'EditPost'; export const EDIT_PROFILE = 'EditProfile'; diff --git a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx index cd4bd15be2b..597b2bf893f 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx +++ b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx @@ -1,13 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo} from 'react'; -import {FlatList} from 'react-native'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {DeviceEventEmitter, FlatList} from 'react-native'; import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {fetchDirectChannelsInfo} from '@actions/remote/channel'; import ChannelItem from '@components/channel_item'; import {ROW_HEIGHT as CHANNEL_ROW_HEIGHT} from '@components/channel_item/channel_item'; +import {Events} from '@constants'; +import {DRAFT, THREAD} from '@constants/screens'; import {useServerUrl} from '@context/server'; import {isDMorGM} from '@utils/channel'; @@ -26,6 +28,18 @@ const extractKey = (item: ChannelModel) => item.id; const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChannelSwitch}: Props) => { const serverUrl = useServerUrl(); + const [isChannelScreenActive, setChannelScreenActive] = useState(true); + + useEffect(() => { + const listener = DeviceEventEmitter.addListener(Events.ACTIVE_SCREEN, (screen: string) => { + setChannelScreenActive(screen !== DRAFT && screen !== THREAD); + }); + + return () => { + listener.remove(); + }; + }, []); + const ids = useMemo(() => { const filteredChannels = unreadsOnTop ? sortedChannels.filter((c) => !unreadIds.has(c.id)) : sortedChannels; @@ -47,12 +61,12 @@ const CategoryBody = ({sortedChannels, unreadIds, unreadsOnTop, category, onChan onPress={onChannelSwitch} key={item.id} testID={`channel_list.category.${category.displayName.replace(/ /g, '_').toLocaleLowerCase()}.channel_item`} - shouldHighlightActive={true} + shouldHighlightActive={isChannelScreenActive} shouldHighlightState={true} isOnHome={true} /> ); - }, [onChannelSwitch]); + }, [category.displayName, isChannelScreenActive, onChannelSwitch]); const sharedValue = useSharedValue(category.collapsed); diff --git a/app/screens/home/channel_list/categories_list/categories/categories.tsx b/app/screens/home/channel_list/categories_list/categories/categories.tsx index 6e2e19fe32b..b696e0beb60 100644 --- a/app/screens/home/channel_list/categories_list/categories/categories.tsx +++ b/app/screens/home/channel_list/categories_list/categories/categories.tsx @@ -3,10 +3,12 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; -import {FlatList, StyleSheet, View} from 'react-native'; +import {DeviceEventEmitter, FlatList, StyleSheet, View} from 'react-native'; import {switchToChannelById} from '@actions/remote/channel'; import Loading from '@components/loading'; +import {Events} from '@constants'; +import {CHANNEL} from '@constants/screens'; import {useServerUrl} from '@context/server'; import {useIsTablet} from '@hooks/device'; import {useTeamSwitch} from '@hooks/team_switch'; @@ -69,6 +71,7 @@ const Categories = ({ const [initiaLoad, setInitialLoad] = useState(!categoriesToShow.length); const onChannelSwitch = useCallback(async (c: Channel | ChannelModel) => { + DeviceEventEmitter.emit(Events.ACTIVE_SCREEN, CHANNEL); PerformanceMetricsManager.startMetric('mobile_channel_switch'); switchToChannelById(serverUrl, c.id); }, [serverUrl]); diff --git a/app/screens/home/channel_list/categories_list/categories_list.tsx b/app/screens/home/channel_list/categories_list/categories_list.tsx index e7d6f88aa5f..3d26d7540fb 100644 --- a/app/screens/home/channel_list/categories_list/categories_list.tsx +++ b/app/screens/home/channel_list/categories_list/categories_list.tsx @@ -1,12 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useEffect, useMemo} from 'react'; -import {useWindowDimensions} from 'react-native'; +import React, {useEffect, useMemo, useState} from 'react'; +import {DeviceEventEmitter, useWindowDimensions} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import DraftsButton from '@components/drafts_buttton/drafts_button'; import ThreadsButton from '@components/threads_button'; +import {Events} from '@constants'; +import {CHANNEL, DRAFT, THREAD} from '@constants/screens'; import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; @@ -30,7 +32,6 @@ type ChannelListProps = { iconPad?: boolean; isCRTEnabled?: boolean; moreThanOneTeam: boolean; - currentChannelId: string; draftsCount: number; }; @@ -38,12 +39,13 @@ const getTabletWidth = (moreThanOneTeam: boolean) => { return TABLET_SIDEBAR_WIDTH - (moreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0); }; +type ScreenType = typeof DRAFT | typeof THREAD | typeof CHANNEL; + const CategoriesList = ({ hasChannels, iconPad, isCRTEnabled, moreThanOneTeam, - currentChannelId, draftsCount, }: ChannelListProps) => { const theme = useTheme(); @@ -51,6 +53,7 @@ const CategoriesList = ({ const {width} = useWindowDimensions(); const isTablet = useIsTablet(); const tabletWidth = useSharedValue(isTablet ? getTabletWidth(moreThanOneTeam) : 0); + const [activeScreen, setActiveScreen] = useState(CHANNEL); useEffect(() => { if (isTablet) { @@ -61,6 +64,20 @@ const CategoriesList = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isTablet, moreThanOneTeam]); + useEffect(() => { + const listener = DeviceEventEmitter.addListener(Events.ACTIVE_SCREEN, (screen: string) => { + if (screen === DRAFT || screen === THREAD) { + setActiveScreen(screen); + } else { + setActiveScreen(CHANNEL); + } + }); + + return () => { + listener.remove(); + }; + }, []); + const tabletStyle = useAnimatedStyle(() => { if (!isTablet) { return { @@ -79,23 +96,23 @@ const CategoriesList = ({ return ( ); - }, [isCRTEnabled]); + }, [activeScreen, isCRTEnabled]); const draftsButtonComponent = useMemo(() => { if (draftsCount > 0) { return ( ); } return null; - }, [currentChannelId, draftsCount]); + }, [activeScreen, draftsCount]); const content = useMemo(() => { if (!hasChannels) { diff --git a/app/screens/home/channel_list/categories_list/index.test.tsx b/app/screens/home/channel_list/categories_list/index.test.tsx index d1da0605e65..3f89d5fd58c 100644 --- a/app/screens/home/channel_list/categories_list/index.test.tsx +++ b/app/screens/home/channel_list/categories_list/index.test.tsx @@ -36,7 +36,6 @@ describe('components/categories_list', () => { moreThanOneTeam={false} hasChannels={true} draftsCount={0} - currentChannelId='' />, {database}, ); @@ -51,7 +50,6 @@ describe('components/categories_list', () => { moreThanOneTeam={false} hasChannels={true} draftsCount={0} - currentChannelId='' />, {database}, ); @@ -69,7 +67,6 @@ describe('components/categories_list', () => { moreThanOneTeam={false} hasChannels={true} draftsCount={1} - currentChannelId='' />, {database}, ); @@ -88,7 +85,6 @@ describe('components/categories_list', () => { moreThanOneTeam={false} hasChannels={true} draftsCount={0} - currentChannelId='' />, {database}, ); @@ -112,7 +108,6 @@ describe('components/categories_list', () => { moreThanOneTeam={true} hasChannels={false} draftsCount={0} - currentChannelId='' />, {database}, ); diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index e62a5bc4e66..9c4d9950724 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -5,7 +5,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; import {switchMap} from 'rxjs/operators'; import {observeDraftCount} from '@queries/servers/drafts'; -import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system'; +import {observeCurrentTeamId} from '@queries/servers/system'; import CategoriesList from './categories_list'; @@ -15,7 +15,6 @@ const enchanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentTeamId = observeCurrentTeamId(database); const draftsCount = currentTeamId.pipe(switchMap((teamId) => observeDraftCount(database, teamId))); // Observe draft count return { - currentChannelId: observeCurrentChannelId(database), draftsCount, }; }); From fd0d5ff5108b3970544c156cda507228c9468285 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 12 Nov 2024 18:08:52 +0530 Subject: [PATCH 089/111] addressed review comments --- assets/base/i18n/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index e127ccbb8bd..93ed68997c5 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -167,6 +167,8 @@ "channel_info.copy_link": "Copy Link", "channel_info.copy_purpose_text": "Copy Purpose Text", "channel_info.custom_status": "Custom status:", + "channel_info.draft_in_channel": "In:", + "channel_info.draft_to_user": "To:", "channel_info.edit_header": "Edit Header", "channel_info.error_close": "Close", "channel_info.favorite": "Favorite", From 89b6c3c9ba8e78f37d2b092d3b08ea6f6789d977 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 13 Nov 2024 14:33:17 +0530 Subject: [PATCH 090/111] linter fixes --- .../xcshareddata/swiftpm/Package.resolved | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9be5f0f2633..62cc6b8f771 100644 --- a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,24 +46,6 @@ "version": "1.9.200" } }, - { - "package": "OpenGraph", - "repositoryURL": "https://github.com/satoshi-takano/OpenGraph.git", - "state": { - "branch": null, - "revision": "382972f1963580eeabafd88ad012e66576b4d213", - "version": "1.4.1" - } - }, - { - "package": "Sentry", - "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", - "state": { - "branch": null, - "revision": "5575af93efb776414f243e93d6af9f6258dc539a", - "version": "8.36.0" - } - }, { "package": "SQLite.swift", "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", From 3556a0e3f9b0484e5c06b945fa8133235c19b090 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 18 Nov 2024 15:30:04 +0530 Subject: [PATCH 091/111] added test for app/actions/local/draft.ts --- .../draft_post_header/index.test.tsx | 69 +++++++++++++++++++ .../draft_post_header/profile_avatar.tsx | 2 + 2 files changed, 71 insertions(+) create mode 100644 app/components/draft_post_header/index.test.tsx diff --git a/app/components/draft_post_header/index.test.tsx b/app/components/draft_post_header/index.test.tsx new file mode 100644 index 00000000000..eecad576ad1 --- /dev/null +++ b/app/components/draft_post_header/index.test.tsx @@ -0,0 +1,69 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {render} from '@testing-library/react-native'; +import React from 'react'; + +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; + +import ProfileAvatar from './profile_avatar'; + +import type UserModel from '@typings/database/models/servers/user'; + +jest.mock('@actions/remote/user', () => ({ + buildProfileImageUrlFromUser: jest.fn(), +})); + +jest.mock('@actions/remote/file', () => ({ + buildAbsoluteUrl: (serverUrl: string, uri: string) => `${serverUrl}${uri}`, +})); + +jest.mock('@utils/theme', () => ({ + changeOpacity: (color: string, opacity: number) => `rgba(${color}, ${opacity})`, +})); + +jest.mock('@context/server', () => ({ + useServerUrl: jest.fn(), +})); + +describe('Avatar Component', () => { + const mockServerUrl = 'mock.server.url'; + const mockAuthor = { + id: 'user123', + username: 'testuser', + email: 'testuser@example.com', + firstName: 'Test', + lastName: 'User', + nickname: 'test', + locale: 'en', + lastPictureUpdate: 123456789, + updateAt: 123456789, + deleteAt: 0, + } as UserModel; + + beforeEach(() => { + jest.resetAllMocks(); + require('@context/server').useServerUrl.mockReturnValue(mockServerUrl); + }); + + it('renders the user profile image if available', () => { + (buildProfileImageUrlFromUser as jest.Mock).mockReturnValue('/profile/image/url'); + + const {getByTestId} = render( + , + ); + + const image = getByTestId('avatar-image'); + expect(image.props.source).toEqual({uri: `${mockServerUrl}/profile/image/url`}); + }); + + it('renders the default icon if profile image URL is not available', () => { + (buildProfileImageUrlFromUser as jest.Mock).mockReturnValue(''); + + const {getByTestId} = render( + , + ); + + const icon = getByTestId('avatar-icon'); + expect(icon.props.name).toBe('account-outline'); + }); +}); diff --git a/app/components/draft_post_header/profile_avatar.tsx b/app/components/draft_post_header/profile_avatar.tsx index db36bad66bf..f1513ac1fc2 100644 --- a/app/components/draft_post_header/profile_avatar.tsx +++ b/app/components/draft_post_header/profile_avatar.tsx @@ -43,6 +43,7 @@ const ProfileAvatar = ({ if (uri) { picture = ( @@ -50,6 +51,7 @@ const ProfileAvatar = ({ } else { picture = ( Date: Tue, 19 Nov 2024 00:27:51 +0530 Subject: [PATCH 092/111] added test for app/components/channel_info/avatar/index.tsx --- .../draft_post_header/index.test.tsx | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/app/components/draft_post_header/index.test.tsx b/app/components/draft_post_header/index.test.tsx index eecad576ad1..f3a136fdd3b 100644 --- a/app/components/draft_post_header/index.test.tsx +++ b/app/components/draft_post_header/index.test.tsx @@ -3,8 +3,6 @@ import {render} from '@testing-library/react-native'; import React from 'react'; -import {buildProfileImageUrlFromUser} from '@actions/remote/user'; - import ProfileAvatar from './profile_avatar'; import type UserModel from '@typings/database/models/servers/user'; @@ -14,56 +12,71 @@ jest.mock('@actions/remote/user', () => ({ })); jest.mock('@actions/remote/file', () => ({ - buildAbsoluteUrl: (serverUrl: string, uri: string) => `${serverUrl}${uri}`, -})); - -jest.mock('@utils/theme', () => ({ - changeOpacity: (color: string, opacity: number) => `rgba(${color}, ${opacity})`, + buildAbsoluteUrl: jest.fn(), })); jest.mock('@context/server', () => ({ useServerUrl: jest.fn(), })); -describe('Avatar Component', () => { - const mockServerUrl = 'mock.server.url'; - const mockAuthor = { - id: 'user123', - username: 'testuser', - email: 'testuser@example.com', - firstName: 'Test', - lastName: 'User', - nickname: 'test', - locale: 'en', - lastPictureUpdate: 123456789, - updateAt: 123456789, - deleteAt: 0, - } as UserModel; +const mockBuildAbsoluteUrl = require('@actions/remote/file').buildAbsoluteUrl; +const mockBuildProfileImageUrlFromUser = require('@actions/remote/user').buildProfileImageUrlFromUser; +const mockUseServerUrl = require('@context/server').useServerUrl; +describe('Avatar Component', () => { beforeEach(() => { - jest.resetAllMocks(); - require('@context/server').useServerUrl.mockReturnValue(mockServerUrl); + jest.clearAllMocks(); }); - it('renders the user profile image if available', () => { - (buildProfileImageUrlFromUser as jest.Mock).mockReturnValue('/profile/image/url'); + it('renders the avatar image when URI is available', () => { + const mockServerUrl = 'base.url.com'; + const mockUri = '/api/v4/users/mock-user-id/image'; + const mockAuthor = {id: 'mock-user-id'} as UserModel; - const {getByTestId} = render( - , - ); + mockUseServerUrl.mockReturnValue(mockServerUrl); + mockBuildProfileImageUrlFromUser.mockImplementation((_: string, author: UserModel) => { + return author ? mockUri : ''; + }); + mockBuildAbsoluteUrl.mockImplementation((serverUrl: string, uri: string) => `${serverUrl}${uri}`); - const image = getByTestId('avatar-image'); - expect(image.props.source).toEqual({uri: `${mockServerUrl}/profile/image/url`}); + const {queryByTestId} = render(); + + expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor); + expect(mockBuildAbsoluteUrl).toHaveBeenCalledWith(mockServerUrl, mockUri); + + expect(queryByTestId('avatar-icon')).toBeNull(); }); - it('renders the default icon if profile image URL is not available', () => { - (buildProfileImageUrlFromUser as jest.Mock).mockReturnValue(''); + it('renders the fallback icon when URI is not available', () => { + const mockServerUrl = 'base.url.com'; + const mockAuthor = {id: 'mock-user-id'} as UserModel; + + mockUseServerUrl.mockReturnValue(mockServerUrl); + mockBuildProfileImageUrlFromUser.mockReturnValue(''); + mockBuildAbsoluteUrl.mockReturnValue(''); + + const {getByTestId, queryByTestId} = render(); + + expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor); + expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled(); + + const icon = getByTestId('avatar-icon'); + expect(icon.props.name).toBe('account-outline'); + expect(queryByTestId('avatar-image')).toBeNull(); + }); + + it('renders the fallback icon when author is not provided', () => { + const mockServerUrl = 'base.url.com'; + + mockUseServerUrl.mockReturnValue(mockServerUrl); + + const {getByTestId, queryByTestId} = render(); - const {getByTestId} = render( - , - ); + expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalled(); + expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled(); const icon = getByTestId('avatar-icon'); expect(icon.props.name).toBe('account-outline'); + expect(queryByTestId('avatar-image')).toBeNull(); }); }); From 1c4661b6238b799c97682212f5948945593ec021 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 19 Nov 2024 14:55:00 +0530 Subject: [PATCH 093/111] added test for app/components/channel_info/channel_info.tsx --- .../draft_post_header/channel_info.test.tsx | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 app/components/draft_post_header/channel_info.test.tsx diff --git a/app/components/draft_post_header/channel_info.test.tsx b/app/components/draft_post_header/channel_info.test.tsx new file mode 100644 index 00000000000..d636ded65d8 --- /dev/null +++ b/app/components/draft_post_header/channel_info.test.tsx @@ -0,0 +1,159 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {render} from '@testing-library/react-native'; +import React from 'react'; + +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; +import FormattedTime from '@components/formatted_time'; +import {General} from '@constants'; +import {useTheme} from '@context/theme'; +import {getUserTimezone} from '@utils/user'; + +import Avatar from './avatar'; +import ChannelInfo from './channel_info'; + +import type ChannelModel from '@typings/database/models/servers/channel'; +import type UserModel from '@typings/database/models/servers/user'; + +jest.mock('@context/theme', () => ({ + useTheme: jest.fn(), +})); + +jest.mock('@components/formatted_text', () => jest.fn(() => null)); +jest.mock('@components/formatted_time', () => jest.fn(() => null)); +jest.mock('./avatar', () => jest.fn(() => null)); +jest.mock('@components/compass_icon', () => jest.fn(() => null)); +jest.mock('@utils/user', () => ({ + getUserTimezone: jest.fn(), +})); + +describe('ChannelInfo Component', () => { + const mockTheme = { + centerChannelColor: '#000000', + }; + + beforeEach(() => { + jest.clearAllMocks(); + (useTheme as jest.Mock).mockReturnValue(mockTheme); + (getUserTimezone as jest.Mock).mockReturnValue('UTC'); + }); + + it('renders correctly for a DM channel', () => { + const baseProps = { + channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + draftReceiverUser: undefined, + updateAt: 1633024800000, + rootId: undefined, + testID: 'channel-info', + currentUser: {timezone: 'UTC'} as unknown as UserModel, + isMilitaryTime: true, + }; + + render(); + expect(CompassIcon).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'globe', + }), + expect.anything(), + ); + expect(FormattedTime).toHaveBeenCalledWith( + expect.objectContaining({ + timezone: 'UTC', + isMilitaryTime: true, + value: 1633024800000, + }), + expect.anything(), + ); + }); + + it('renders correctly for a public channel', () => { + const baseProps = { + channel: {type: General.OPEN_CHANNEL, displayName: 'Public Channel', createAt: 0, creatorId: '', deleteAt: 0, updateAt: 0} as ChannelModel, + draftReceiverUser: undefined, + updateAt: 1633024800000, + rootId: undefined, + testID: 'channel-info', + currentUser: {timezone: 'UTC'} as unknown as UserModel, + isMilitaryTime: true, + }; + render(); + expect(FormattedText).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'channel_info.draft_in_channel', + defaultMessage: 'In:', + }), + expect.anything(), + ); + expect(CompassIcon).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'globe', + }), + expect.anything(), + ); + }); + + it('renders correctly for a thread', () => { + const baseProps = { + channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + draftReceiverUser: undefined, + updateAt: 1633024800000, + rootId: 'root-post-id', + testID: 'channel-info', + currentUser: {timezone: 'UTC'} as unknown as UserModel, + isMilitaryTime: true, + }; + const {getByTestId} = render(); + + expect(useTheme).toHaveBeenCalled(); + expect(getByTestId('channel-info')).toBeTruthy(); + expect(FormattedText).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'channel_info.thread_in', + defaultMessage: 'Thread in:', + }), + expect.anything(), + ); + }); + + it('renders the Avatar when draftReceiverUser is provided', () => { + const baseProps = { + channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + draftReceiverUser: {id: 'user-id', username: 'JohnDoe'} as UserModel, + updateAt: 1633024800000, + rootId: undefined, + testID: 'channel-info', + currentUser: {timezone: 'UTC'} as unknown as UserModel, + isMilitaryTime: true, + }; + render(); + + expect(Avatar).toHaveBeenCalledWith( + expect.objectContaining({ + author: baseProps.draftReceiverUser, + }), + expect.anything(), + ); + }); + + it('renders CompassIcon when draftReceiverUser is not provided', () => { + const baseProps = { + channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + draftReceiverUser: undefined, + updateAt: 1633024800000, + rootId: undefined, + testID: 'channel-info', + currentUser: {timezone: 'UTC'} as unknown as UserModel, + isMilitaryTime: true, + }; + render(); + + expect(CompassIcon).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'globe', + }), + expect.anything(), + ); + }); +}); From b23bd9e982e04ea5b771544c82aa431e69366233 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 22 Nov 2024 15:50:11 +0530 Subject: [PATCH 094/111] added unit test for app/components/draft/draft.tsx --- app/components/draft/draft.test.tsx | 181 ++++++++++++++++++++++++++++ app/components/files/files.tsx | 1 + 2 files changed, 182 insertions(+) create mode 100644 app/components/draft/draft.test.tsx diff --git a/app/components/draft/draft.test.tsx b/app/components/draft/draft.test.tsx new file mode 100644 index 00000000000..93599108d46 --- /dev/null +++ b/app/components/draft/draft.test.tsx @@ -0,0 +1,181 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; +import {General} from '@constants'; +import {renderWithEverything} from '@test/intl-test-helper'; +import TestHelper from '@test/test_helper'; + +import Draft from './draft'; + +import type {Database} from '@nozbe/watermelondb'; +import type ChannelModel from '@typings/database/models/servers/channel'; +import type DraftModel from '@typings/database/models/servers/draft'; + +jest.mock('@components/formatted_text', () => jest.fn(() => null)); +jest.mock('@components/formatted_time', () => jest.fn(() => null)); +jest.mock('@components/compass_icon', () => jest.fn(() => null)); + +describe('Draft', () => { + let database: Database; + + beforeAll(async () => { + const server = await TestHelper.setupServerDatabase(); + database = server.database; + }); + it('should render the draft with channel info and draft message', () => { + const props = { + channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + location: 'channel', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + isPostPriorityEnabled: false, + }; + const {getByText} = renderWithEverything( + + , {database}, + ); + + expect(FormattedText).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'channel_info.draft_in_channel', + defaultMessage: 'In:', + }), + expect.anything(), + ); + expect(CompassIcon).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'globe', + }), + expect.anything(), + ); + expect(getByText('Hello, World!')).toBeTruthy(); + }); + + it('should match the file count', () => { + const props = { + channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + location: 'channel', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [{ + has_preview_image: false, + height: 0, + name: 'file1.txt', + extension: 'txt', + size: 64, + }, { + has_preview_image: false, + height: 0, + name: 'file2.pdf', + extension: 'txt', + size: 64, + }], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + isPostPriorityEnabled: false, + }; + const {getAllByTestId} = renderWithEverything( + + , {database}, + ); + expect(getAllByTestId('file_attachment')).toHaveLength(2); + }); + + it('should render the draft with channel info and draft message for a thread', () => { + const props = { + channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + location: 'thread', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: 'root_id', + files: [], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + isPostPriorityEnabled: false, + }; + const {getByText} = renderWithEverything( + + , {database}, + ); + + expect(FormattedText).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'channel_info.thread_in', + defaultMessage: 'Thread in:', + }), + expect.anything(), + ); + + expect(CompassIcon).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'globe', + }), + expect.anything(), + ); + expect(getByText('Hello, World!')).toBeTruthy(); + }); + + it('should render the draft with post priority', () => { + const props = { + channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel, + location: 'thread', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: 'root_id', + files: [], + metadata: {priority: {priority: 'important', requested_ack: false}}, + } as unknown as DraftModel, + layoutWidth: 100, + isPostPriorityEnabled: true, + }; + const {getByText} = renderWithEverything( + + , {database}, + ); + expect(getByText('IMPORTANT')).toBeTruthy(); + }); +}); diff --git a/app/components/files/files.tsx b/app/components/files/files.tsx index 1006393637c..ffdece6f1e0 100644 --- a/app/components/files/files.tsx +++ b/app/components/files/files.tsx @@ -92,6 +92,7 @@ const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, l Date: Tue, 26 Nov 2024 16:42:43 +0530 Subject: [PATCH 095/111] added test for app/components/draft/draft_post/draft_message/index.tsx --- .../__snapshots__/draft_message.test.tsx.snap | 62 +++++++++++++++++++ .../draft/draft_post/draft_message.test.tsx | 59 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap create mode 100644 app/components/draft/draft_post/draft_message.test.tsx diff --git a/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap b/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap new file mode 100644 index 00000000000..2f517d71851 --- /dev/null +++ b/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draft Message should match snapshot 1`] = ` + + + + + + + + Hello, World! + + + + + + + +`; diff --git a/app/components/draft/draft_post/draft_message.test.tsx b/app/components/draft/draft_post/draft_message.test.tsx new file mode 100644 index 00000000000..e0da756feec --- /dev/null +++ b/app/components/draft/draft_post/draft_message.test.tsx @@ -0,0 +1,59 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {renderWithEverything} from '@test/intl-test-helper'; +import TestHelper from '@test/test_helper'; + +import DraftMessage from '.'; + +import type {Database} from '@nozbe/watermelondb'; +import type DraftModel from '@typings/database/models/servers/draft'; + +describe('Draft Message', () => { + let database: Database; + + beforeAll(async () => { + const server = await TestHelper.setupServerDatabase(); + database = server.database; + }); + it('should render the message', () => { + const props = { + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + location: 'draft', + }; + const {getByText} = renderWithEverything( + , {database}, + ); + expect(getByText('Hello, World!')).toBeTruthy(); + }); + + it('should match snapshot', () => { + const props = { + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + location: 'draft', + }; + const wrapper = renderWithEverything( + , {database}, + ); + + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From 976c9ebf6d5566480c63a501e3c1f95d3e861ca0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 16:51:44 +0530 Subject: [PATCH 096/111] added comparing snapshot for previously added test --- .../__snapshots__/channel_info.test.tsx.snap | 324 +++++ .../avatar/__snapshots__/index.test.tsx.snap | 102 ++ .../draft/__snapshots__/draft.test.tsx.snap | 1090 +++++++++++++++++ app/components/draft/draft.test.tsx | 17 +- .../draft_post_header/channel_info.test.tsx | 17 +- .../draft_post_header/index.test.tsx | 14 +- 6 files changed, 1549 insertions(+), 15 deletions(-) create mode 100644 app/components/channel_info/__snapshots__/channel_info.test.tsx.snap create mode 100644 app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap create mode 100644 app/components/draft/__snapshots__/draft.test.tsx.snap diff --git a/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap b/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap new file mode 100644 index 00000000000..d4854e14eb7 --- /dev/null +++ b/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap @@ -0,0 +1,324 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChannelInfo Component renders CompassIcon when draftReceiverUser is not provided 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`ChannelInfo Component renders correctly for a DM channel 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`ChannelInfo Component renders correctly for a public channel 1`] = ` + + + + + + + + + Public Channel + + + +`; + +exports[`ChannelInfo Component renders correctly for a thread 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`ChannelInfo Component renders the Avatar when draftReceiverUser is provided 1`] = ` + + + + + + + Direct Message Channel + + + +`; diff --git a/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap b/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..9f3b0100261 --- /dev/null +++ b/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Avatar Component renders the avatar image when URI is available 1`] = ` + + + +`; + +exports[`Avatar Component renders the fallback icon when URI is not available 1`] = ` + + + +`; + +exports[`Avatar Component renders the fallback icon when author is not provided 1`] = ` + + + +`; diff --git a/app/components/draft/__snapshots__/draft.test.tsx.snap b/app/components/draft/__snapshots__/draft.test.tsx.snap new file mode 100644 index 00000000000..9f92e76848c --- /dev/null +++ b/app/components/draft/__snapshots__/draft.test.tsx.snap @@ -0,0 +1,1090 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draft should match the file count 1`] = ` + + + + + + + + + + + Direct Message Channel + + + + + + + + + + + + Hello, World! + + + + + + + + + + + + + + + + + + + file1.txt + + + + + 64 B + + + + + + + + + + + + + + + + + + file2.pdf + + + + + 64 B + + + + + + + + + + + +`; + +exports[`Draft should render the draft with channel info and draft message 1`] = ` + + + + + + + + + + + Direct Message Channel + + + + + + + + + + + + Hello, World! + + + + + + + + + + +`; + +exports[`Draft should render the draft with channel info and draft message for a thread 1`] = ` + + + + + + + + + + + Direct Message Channel + + + + + + + + + + + + Hello, World! + + + + + + + + + + +`; + +exports[`Draft should render the draft with post priority 1`] = ` + + + + + + + + + + + Direct Message Channel + + + + + + + + IMPORTANT + + + + + + + + + + + + + Hello, World! + + + + + + + + + + +`; diff --git a/app/components/draft/draft.test.tsx b/app/components/draft/draft.test.tsx index 93599108d46..42b579d8838 100644 --- a/app/components/draft/draft.test.tsx +++ b/app/components/draft/draft.test.tsx @@ -41,7 +41,7 @@ describe('Draft', () => { layoutWidth: 100, isPostPriorityEnabled: false, }; - const {getByText} = renderWithEverything( + const wrapper = renderWithEverything( { , {database}, ); + const {getByText} = wrapper; + expect(FormattedText).toHaveBeenCalledWith( expect.objectContaining({ id: 'channel_info.draft_in_channel', @@ -66,6 +68,7 @@ describe('Draft', () => { expect.anything(), ); expect(getByText('Hello, World!')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('should match the file count', () => { @@ -95,7 +98,7 @@ describe('Draft', () => { layoutWidth: 100, isPostPriorityEnabled: false, }; - const {getAllByTestId} = renderWithEverything( + const wrapper = renderWithEverything( { /> , {database}, ); + const {getAllByTestId} = wrapper; expect(getAllByTestId('file_attachment')).toHaveLength(2); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('should render the draft with channel info and draft message for a thread', () => { @@ -123,7 +128,7 @@ describe('Draft', () => { layoutWidth: 100, isPostPriorityEnabled: false, }; - const {getByText} = renderWithEverything( + const wrapper = renderWithEverything( { , {database}, ); + const {getByText} = wrapper; expect(FormattedText).toHaveBeenCalledWith( expect.objectContaining({ id: 'channel_info.thread_in', @@ -149,6 +155,7 @@ describe('Draft', () => { expect.anything(), ); expect(getByText('Hello, World!')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('should render the draft with post priority', () => { @@ -166,7 +173,7 @@ describe('Draft', () => { layoutWidth: 100, isPostPriorityEnabled: true, }; - const {getByText} = renderWithEverything( + const wrapper = renderWithEverything( { /> , {database}, ); + const {getByText} = wrapper; expect(getByText('IMPORTANT')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/draft_post_header/channel_info.test.tsx b/app/components/draft_post_header/channel_info.test.tsx index d636ded65d8..5b5479607df 100644 --- a/app/components/draft_post_header/channel_info.test.tsx +++ b/app/components/draft_post_header/channel_info.test.tsx @@ -51,7 +51,7 @@ describe('ChannelInfo Component', () => { isMilitaryTime: true, }; - render(); + const wrapper = render(); expect(CompassIcon).toHaveBeenCalledWith( expect.objectContaining({ name: 'globe', @@ -66,6 +66,7 @@ describe('ChannelInfo Component', () => { }), expect.anything(), ); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders correctly for a public channel', () => { @@ -78,7 +79,7 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - render(); + const wrapper = render(); expect(FormattedText).toHaveBeenCalledWith( expect.objectContaining({ id: 'channel_info.draft_in_channel', @@ -92,6 +93,7 @@ describe('ChannelInfo Component', () => { }), expect.anything(), ); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders correctly for a thread', () => { @@ -104,7 +106,8 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - const {getByTestId} = render(); + const wrapper = render(); + const {getByTestId} = wrapper; expect(useTheme).toHaveBeenCalled(); expect(getByTestId('channel-info')).toBeTruthy(); @@ -115,6 +118,7 @@ describe('ChannelInfo Component', () => { }), expect.anything(), ); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders the Avatar when draftReceiverUser is provided', () => { @@ -127,7 +131,7 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - render(); + const wrapper = render(); expect(Avatar).toHaveBeenCalledWith( expect.objectContaining({ @@ -135,6 +139,7 @@ describe('ChannelInfo Component', () => { }), expect.anything(), ); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders CompassIcon when draftReceiverUser is not provided', () => { @@ -147,13 +152,13 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - render(); - + const wrapper = render(); expect(CompassIcon).toHaveBeenCalledWith( expect.objectContaining({ name: 'globe', }), expect.anything(), ); + expect(wrapper.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/draft_post_header/index.test.tsx b/app/components/draft_post_header/index.test.tsx index f3a136fdd3b..4f6dcc9b40d 100644 --- a/app/components/draft_post_header/index.test.tsx +++ b/app/components/draft_post_header/index.test.tsx @@ -39,12 +39,14 @@ describe('Avatar Component', () => { }); mockBuildAbsoluteUrl.mockImplementation((serverUrl: string, uri: string) => `${serverUrl}${uri}`); - const {queryByTestId} = render(); + const wrapper = render(); + const {queryByTestId} = wrapper; expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor); expect(mockBuildAbsoluteUrl).toHaveBeenCalledWith(mockServerUrl, mockUri); expect(queryByTestId('avatar-icon')).toBeNull(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders the fallback icon when URI is not available', () => { @@ -54,8 +56,8 @@ describe('Avatar Component', () => { mockUseServerUrl.mockReturnValue(mockServerUrl); mockBuildProfileImageUrlFromUser.mockReturnValue(''); mockBuildAbsoluteUrl.mockReturnValue(''); - - const {getByTestId, queryByTestId} = render(); + const wrapper = render(); + const {getByTestId, queryByTestId} = wrapper; expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor); expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled(); @@ -63,14 +65,15 @@ describe('Avatar Component', () => { const icon = getByTestId('avatar-icon'); expect(icon.props.name).toBe('account-outline'); expect(queryByTestId('avatar-image')).toBeNull(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); it('renders the fallback icon when author is not provided', () => { const mockServerUrl = 'base.url.com'; mockUseServerUrl.mockReturnValue(mockServerUrl); - - const {getByTestId, queryByTestId} = render(); + const wrapper = render(); + const {getByTestId, queryByTestId} = wrapper; expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalled(); expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled(); @@ -78,5 +81,6 @@ describe('Avatar Component', () => { const icon = getByTestId('avatar-icon'); expect(icon.props.name).toBe('account-outline'); expect(queryByTestId('avatar-image')).toBeNull(); + expect(wrapper.toJSON()).toMatchSnapshot(); }); }); From c6478c3534195f8c97bade5b737ca5ac5d33c24a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 16:59:13 +0530 Subject: [PATCH 097/111] Added unit test for app/components/draft/draft_post/index.tsx --- .../__snapshots__/draft_post.test.tsx.snap | 70 +++++++++++++++++++ .../draft/draft_post/draft_post.test.tsx | 40 +++++++++++ 2 files changed, 110 insertions(+) create mode 100644 app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap create mode 100644 app/components/draft/draft_post/draft_post.test.tsx diff --git a/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap b/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap new file mode 100644 index 00000000000..cb8ffe18034 --- /dev/null +++ b/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draft Post should match the snapshot 1`] = ` + + + + + + + + + Hello, World! + + + + + + + + +`; diff --git a/app/components/draft/draft_post/draft_post.test.tsx b/app/components/draft/draft_post/draft_post.test.tsx new file mode 100644 index 00000000000..0151e62912e --- /dev/null +++ b/app/components/draft/draft_post/draft_post.test.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {renderWithEverything} from '@test/intl-test-helper'; +import TestHelper from '@test/test_helper'; + +import DraftPost from '.'; + +import type {Database} from '@nozbe/watermelondb'; +import type DraftModel from '@typings/database/models/servers/draft'; + +describe('Draft Post', () => { + let database: Database; + + beforeAll(async () => { + const server = await TestHelper.setupServerDatabase(); + database = server.database; + }); + it('should match the snapshot', () => { + const props = { + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + layoutWidth: 100, + location: 'draft', + }; + const wrapper = renderWithEverything( + , {database}, + ); + + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From 0ee8296c75fba2a51503375bdfb95ca0306255e0 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 17:30:12 +0530 Subject: [PATCH 098/111] Added jest unit test from app/components/drafts_buttton/drafts_button.tsx --- .../__snapshots__/drafts_button.test.tsx.snap | 381 ++++++++++++++++++ .../drafts_buttton/drafts_button.test.tsx | 67 +++ 2 files changed, 448 insertions(+) create mode 100644 app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap create mode 100644 app/components/drafts_buttton/drafts_button.test.tsx diff --git a/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap new file mode 100644 index 00000000000..cf6c8b6b195 --- /dev/null +++ b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap @@ -0,0 +1,381 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Drafts Button calls switchToGlobalDrafts when pressed 1`] = ` + + + + + Drafts + + + + + 5 + + + + +`; + +exports[`Drafts Button does not render if draftsCount is 0 1`] = `null`; + +exports[`Drafts Button should render the drafts button component 1`] = ` + + + + + Drafts + + + + + 1 + + + + +`; + +exports[`Drafts Button should render the drafts button component with drafts count 1`] = ` + + + + + Drafts + + + + + 23 + + + + +`; diff --git a/app/components/drafts_buttton/drafts_button.test.tsx b/app/components/drafts_buttton/drafts_button.test.tsx new file mode 100644 index 00000000000..b0bc18d7a97 --- /dev/null +++ b/app/components/drafts_buttton/drafts_button.test.tsx @@ -0,0 +1,67 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {fireEvent} from '@testing-library/react-native'; +import React from 'react'; + +import {switchToGlobalDrafts} from '@actions/local/draft'; +import {renderWithIntl} from '@test/intl-test-helper'; + +import DraftsButton from './drafts_button'; + +jest.mock('@actions/local/draft', () => ({ + switchToGlobalDrafts: jest.fn(), +})); +describe('Drafts Button', () => { + it('should render the drafts button component', () => { + const wrapper = renderWithIntl( + , + ); + + const {getByText} = wrapper; + expect(getByText('Drafts')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('should render the drafts button component with drafts count', () => { + const wrapper = renderWithIntl( + , + ); + + const {getByText} = wrapper; + expect(getByText('Drafts')).toBeTruthy(); + expect(getByText('23')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('calls switchToGlobalDrafts when pressed', () => { + const wrapper = renderWithIntl( + , + ); + const {getByTestId} = wrapper; + fireEvent.press(getByTestId('channel_list.drafts.button')); + expect(switchToGlobalDrafts).toHaveBeenCalledTimes(1); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('does not render if draftsCount is 0', () => { + const wrapper = renderWithIntl( + , + ); + const {queryByTestId} = wrapper; + expect(queryByTestId('channel_list.drafts.button')).toBeNull(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From a45371eaa0a831e59e567537222c7d50b35b6530 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 17:56:17 +0530 Subject: [PATCH 099/111] added test for app/screens/draft_options/delete_draft/index.tsx --- .../__snapshots__/delete_draft.test.tsx.snap | 125 ++++++++++++++++++ .../draft_options/delete_draft.test.tsx | 44 ++++++ app/screens/draft_options/delete_draft.tsx | 1 + 3 files changed, 170 insertions(+) create mode 100644 app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap create mode 100644 app/screens/draft_options/delete_draft.test.tsx diff --git a/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap new file mode 100644 index 00000000000..68a84ed9cea --- /dev/null +++ b/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap @@ -0,0 +1,125 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DeleteDraft calls draftDeleteHandler when pressed 1`] = ` + + + + Delete draft + + +`; + +exports[`DeleteDraft should render the component 1`] = ` + + + + Delete draft + + +`; diff --git a/app/screens/draft_options/delete_draft.test.tsx b/app/screens/draft_options/delete_draft.test.tsx new file mode 100644 index 00000000000..9a90c598feb --- /dev/null +++ b/app/screens/draft_options/delete_draft.test.tsx @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {Screens} from '@constants'; +import {dismissBottomSheet} from '@screens/navigation'; +import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; + +import DeleteDraft from '.'; + +jest.mock('@screens/navigation', () => ({ + dismissBottomSheet: jest.fn(), +})); + +describe('DeleteDraft', () => { + it('should render the component', () => { + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByText, getByTestId} = wrapper; + expect(getByText('Delete draft')).toBeTruthy(); + expect(getByTestId('trash-can-outline-icon')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('calls draftDeleteHandler when pressed', () => { + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByTestId} = wrapper; + fireEvent.press(getByTestId('trash-can-outline-icon')); + expect(dismissBottomSheet).toHaveBeenCalledTimes(1); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/app/screens/draft_options/delete_draft.tsx b/app/screens/draft_options/delete_draft.tsx index 3b1bdfd0a75..f7a4ca27ad1 100644 --- a/app/screens/draft_options/delete_draft.tsx +++ b/app/screens/draft_options/delete_draft.tsx @@ -66,6 +66,7 @@ const DeleteDraft: React.FC = ({ > From 6a4ad10b9dd2e903087be30f5348b6066a62afdb Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 18:34:09 +0530 Subject: [PATCH 100/111] added test case from edit_draft and send_draft --- ...test.tsx.snap => edit_draft.test.tsx.snap} | 16 +- .../__snapshots__/send_draft.test.tsx.snap | 187 ++++++++++++++++++ app/screens/draft_options/edit_draft.test.tsx | 54 +++++ app/screens/draft_options/edit_draft.tsx | 1 + app/screens/draft_options/send_draft.test.tsx | 75 +++++++ 5 files changed, 325 insertions(+), 8 deletions(-) rename app/screens/draft_options/__snapshots__/{delete_draft.test.tsx.snap => edit_draft.test.tsx.snap} (88%) create mode 100644 app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap create mode 100644 app/screens/draft_options/edit_draft.test.tsx create mode 100644 app/screens/draft_options/send_draft.test.tsx diff --git a/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap similarity index 88% rename from app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap rename to app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap index 68a84ed9cea..bc7b23cf8c9 100644 --- a/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap +++ b/app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DeleteDraft calls draftDeleteHandler when pressed 1`] = ` +exports[`Edit Draft Should call editHandler when pressed 1`] = ` - Delete draft + Edit draft `; -exports[`DeleteDraft should render the component 1`] = ` +exports[`Edit Draft Should render the Edit draft component 1`] = ` - Delete draft + Edit draft `; diff --git a/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap new file mode 100644 index 00000000000..64d2346a020 --- /dev/null +++ b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap @@ -0,0 +1,187 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Send Draft should call dismissBottmSheet after sending the draft 1`] = ` + + + + Send draft + + +`; + +exports[`Send Draft should call sendDraft when pressed 1`] = ` + + + + Send draft + + +`; + +exports[`Send Draft should render the component 1`] = ` + + + + Send draft + + +`; diff --git a/app/screens/draft_options/edit_draft.test.tsx b/app/screens/draft_options/edit_draft.test.tsx new file mode 100644 index 00000000000..834bf159075 --- /dev/null +++ b/app/screens/draft_options/edit_draft.test.tsx @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {Screens} from '@constants'; +import {dismissBottomSheet} from '@screens/navigation'; +import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; + +import EditDraft from '.'; + +import type ChannelModel from '@typings/database/models/servers/channel'; + +jest.mock('@screens/navigation', () => ({ + dismissBottomSheet: jest.fn(), +})); + +describe('Edit Draft', () => { + it('Should render the Edit draft component', () => { + const props = { + bottomSheetId: Screens.DRAFT_OPTIONS, + channel: { + id: 'channel_id', + teamId: 'team_id', + } as ChannelModel, + rootId: 'root_id', + }; + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByTestId, getByText} = wrapper; + expect(getByTestId('pencil-outline-icon')).toBeTruthy(); + expect(getByText('Edit draft')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('Should call editHandler when pressed', () => { + const props = { + bottomSheetId: Screens.DRAFT_OPTIONS, + channel: { + id: 'channel_id', + teamId: 'team_id', + } as ChannelModel, + rootId: 'root_id', + }; + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByTestId} = wrapper; + fireEvent.press(getByTestId('pencil-outline-icon')); + expect(dismissBottomSheet).toHaveBeenCalledTimes(1); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/app/screens/draft_options/edit_draft.tsx b/app/screens/draft_options/edit_draft.tsx index 7062b85fa5e..0ead831a239 100644 --- a/app/screens/draft_options/edit_draft.tsx +++ b/app/screens/draft_options/edit_draft.tsx @@ -65,6 +65,7 @@ const EditDraft: React.FC = ({ > diff --git a/app/screens/draft_options/send_draft.test.tsx b/app/screens/draft_options/send_draft.test.tsx new file mode 100644 index 00000000000..ef5931663b8 --- /dev/null +++ b/app/screens/draft_options/send_draft.test.tsx @@ -0,0 +1,75 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {General, Screens} from '@constants'; +import {dismissBottomSheet} from '@screens/navigation'; +import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; + +import SendDraft from './send_draft'; + +jest.mock('@screens/navigation', () => ({ + dismissBottomSheet: jest.fn(), +})); + +describe('Send Draft', () => { + it('should render the component', () => { + const props = { + channelId: 'channel_id', + channelName: 'channel_name', + rootId: '', + channelType: General.OPEN_CHANNEL, + bottomSheetId: Screens.DRAFT_OPTIONS, + currentUserId: 'current_user_id', + maxMessageLength: 4000, + useChannelMentions: true, + userIsOutOfOffice: false, + customEmojis: [], + value: 'value', + files: [], + postPriority: '' as unknown as PostPriority, + persistentNotificationInterval: 0, + persistentNotificationMaxRecipients: 0, + draftReceiverUserName: undefined, + }; + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByText} = wrapper; + expect(getByText('Send draft')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('should call dismissBottmSheet after sending the draft', () => { + const props = { + channelId: 'channel_id', + channelName: 'channel_name', + rootId: '', + channelType: General.OPEN_CHANNEL, + bottomSheetId: Screens.DRAFT_OPTIONS, + currentUserId: 'current_user_id', + maxMessageLength: 4000, + useChannelMentions: true, + userIsOutOfOffice: false, + customEmojis: [], + value: 'value', + files: [], + postPriority: '' as unknown as PostPriority, + persistentNotificationInterval: 0, + persistentNotificationMaxRecipients: 0, + draftReceiverUserName: undefined, + }; + const wrapper = renderWithIntlAndTheme( + , + ); + const {getByTestId} = wrapper; + fireEvent.press(getByTestId('send-draft-button')); + expect(dismissBottomSheet).toHaveBeenCalledTimes(1); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From 34a142e463c34ddd0899212a394167b307ff48cc Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 18:46:48 +0530 Subject: [PATCH 101/111] added test from app/screens/draft_options/index.tsx --- .../__snapshots__/draft_options.test.tsx.snap | 789 ++++++++++++++++++ .../draft_options/draft_options.test.tsx | 71 ++ 2 files changed, 860 insertions(+) create mode 100644 app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap create mode 100644 app/screens/draft_options/draft_options.test.tsx diff --git a/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap b/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap new file mode 100644 index 00000000000..7b5b905797c --- /dev/null +++ b/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap @@ -0,0 +1,789 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draft Options should render the draft options 1`] = ` +[ + , + + + + + + + + + Draft actions + + + + + Edit draft + + + + + + Send draft + + + + + + Delete draft + + + + + + + + + + + + + , +] +`; + +exports[`Draft Options should render the draft options component 1`] = ` +[ + , + + + + + + + + + Draft actions + + + + + Edit draft + + + + + + Send draft + + + + + + Delete draft + + + + + + + + + + + + + , +] +`; diff --git a/app/screens/draft_options/draft_options.test.tsx b/app/screens/draft_options/draft_options.test.tsx new file mode 100644 index 00000000000..2f7a43bdc80 --- /dev/null +++ b/app/screens/draft_options/draft_options.test.tsx @@ -0,0 +1,71 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {renderWithEverything} from '@test/intl-test-helper'; +import TestHelper from '@test/test_helper'; + +import DraftOptions from '.'; + +import type {Database} from '@nozbe/watermelondb'; +import type ChannelModel from '@typings/database/models/servers/channel'; +import type DraftModel from '@typings/database/models/servers/draft'; +describe('Draft Options', () => { + let database: Database; + + beforeAll(async () => { + const server = await TestHelper.setupServerDatabase(); + database = server.database; + }); + it('should render the draft options component', () => { + const props = { + channel: { + id: 'channel_id', + teamId: 'team_id', + } as unknown as ChannelModel, + rootId: '', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + draftReceiverUserName: undefined, + }; + const wrapper = renderWithEverything( + , {database}, + ); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('should render the draft options', () => { + const props = { + channel: { + id: 'channel_id', + teamId: 'team_id', + } as unknown as ChannelModel, + rootId: '', + draft: { + updateAt: 1633024800000, + message: 'Hello, World!', + channelId: 'channel_id', + rootId: '', + files: [], + metadata: {}, + } as unknown as DraftModel, + draftReceiverUserName: 'username', + }; + const wrapper = renderWithEverything( + , {database}, + ); + const {getByText} = wrapper; + expect(getByText('Draft actions')).toBeTruthy(); + expect(getByText('Edit draft')).toBeTruthy(); + expect(getByText('Send draft')).toBeTruthy(); + expect(getByText('Delete draft')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From 10ec08b152137d365a51cafc3612352f5c467b4d Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 18:51:39 +0530 Subject: [PATCH 102/111] Added unit test for app/screens/global_drafts/components/draft_empty_component/index.tsx --- .../draft_empty_component.test.tsx.snap | 133 ++++++++++++++++++ .../components/draft_empty_component.test.tsx | 26 ++++ 2 files changed, 159 insertions(+) create mode 100644 app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap create mode 100644 app/screens/global_drafts/components/draft_empty_component.test.tsx diff --git a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap new file mode 100644 index 00000000000..ca8799492b2 --- /dev/null +++ b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Draft Empty Component should match the snapshot 1`] = ` + + + + No drafts at the moment + + + Any message you have started will show here. + + +`; + +exports[`Draft Empty Component should render empty draft message 1`] = ` + + + + No drafts at the moment + + + Any message you have started will show here. + + +`; + +exports[`Draft Empty Component should render the draft empty component 1`] = ` + + + + No drafts at the moment + + + Any message you have started will show here. + + +`; diff --git a/app/screens/global_drafts/components/draft_empty_component.test.tsx b/app/screens/global_drafts/components/draft_empty_component.test.tsx new file mode 100644 index 00000000000..8fbb2288fbe --- /dev/null +++ b/app/screens/global_drafts/components/draft_empty_component.test.tsx @@ -0,0 +1,26 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {renderWithIntlAndTheme} from '@test/intl-test-helper'; + +import DraftEmptyComponent from '.'; + +describe('Draft Empty Component', () => { + it('should match the snapshot', () => { + const wrapper = renderWithIntlAndTheme( + , + ); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + + it('should render empty draft message', () => { + const wrapper = renderWithIntlAndTheme( + , + ); + expect(wrapper.getByText('No drafts at the moment')).toBeTruthy(); + expect(wrapper.getByText('Any message you have started will show here.')).toBeTruthy(); + expect(wrapper.toJSON()).toMatchSnapshot(); + }); +}); From 96be469d42b3ae1a3927eee8d06add8aa750cf85 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 19:01:07 +0530 Subject: [PATCH 103/111] updated snapshot --- .../__snapshots__/send_draft.test.tsx.snap | 62 ------------------- .../draft_empty_component.test.tsx.snap | 44 ------------- 2 files changed, 106 deletions(-) diff --git a/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap index 64d2346a020..6e7a31c7a00 100644 --- a/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap +++ b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap @@ -62,68 +62,6 @@ exports[`Send Draft should call dismissBottmSheet after sending the draft 1`] = `; -exports[`Send Draft should call sendDraft when pressed 1`] = ` - - - - Send draft - - -`; - exports[`Send Draft should render the component 1`] = ` `; - -exports[`Draft Empty Component should render the draft empty component 1`] = ` - - - - No drafts at the moment - - - Any message you have started will show here. - - -`; From 76c6b31aab2a7dbcc22de0aab210aeabab3b6b7b Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 26 Nov 2024 19:28:45 +0530 Subject: [PATCH 104/111] udpated snapshot --- .../draft/__snapshots__/draft.test.tsx.snap | 12 ++ .../__snapshots__/draft_message.test.tsx.snap | 1 + .../__snapshots__/draft_post.test.tsx.snap | 2 + .../__snapshots__/draft_options.test.tsx.snap | 8 +- .../__snapshots__/edit_draft.test.tsx.snap | 2 + .../__snapshots__/send_draft.test.tsx.snap | 4 +- .../__snapshots__/delete_draft.test.tsx.snap | 127 ++++++++++++++++++ app/screens/draft_options/send_draft.test.tsx | 2 +- .../draft_empty_component.test.tsx.snap | 2 + 9 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 app/screens/draft_options/delete_draft/__snapshots__/delete_draft.test.tsx.snap diff --git a/app/components/draft/__snapshots__/draft.test.tsx.snap b/app/components/draft/__snapshots__/draft.test.tsx.snap index 9f92e76848c..a9d994adfe5 100644 --- a/app/components/draft/__snapshots__/draft.test.tsx.snap +++ b/app/components/draft/__snapshots__/draft.test.tsx.snap @@ -19,6 +19,7 @@ exports[`Draft should match the file count 1`] = ` onResponderTerminate={[Function]} onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} + testID="draft_post" > + + + Delete draft + + +`; + +exports[`DeleteDraft should render the component 1`] = ` + + + + Delete draft + + +`; diff --git a/app/screens/draft_options/send_draft.test.tsx b/app/screens/draft_options/send_draft.test.tsx index ef5931663b8..ccf94cba55d 100644 --- a/app/screens/draft_options/send_draft.test.tsx +++ b/app/screens/draft_options/send_draft.test.tsx @@ -68,7 +68,7 @@ describe('Send Draft', () => { />, ); const {getByTestId} = wrapper; - fireEvent.press(getByTestId('send-draft-button')); + fireEvent.press(getByTestId('send_draft_button')); expect(dismissBottomSheet).toHaveBeenCalledTimes(1); expect(wrapper.toJSON()).toMatchSnapshot(); }); diff --git a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap index 6ca2dae4f41..27c9ad1cf1c 100644 --- a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap +++ b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap @@ -9,6 +9,7 @@ exports[`Draft Empty Component should match the snapshot 1`] = ` "justifyContent": "center", } } + testID="draft_empty_component" > Date: Fri, 10 Jan 2025 12:59:38 +0530 Subject: [PATCH 105/111] fix test and updated snapshot --- .../draft/__snapshots__/draft.test.tsx.snap | 8 +- .../draft/draft_post/draft_message.test.tsx | 2 +- .../__snapshots__/channel_info.test.tsx.snap | 324 ++++++++++++++++++ .../profile_avatar.test.tsx.snap | 102 ++++++ .../draft_post_header/channel_info.test.tsx | 20 +- ...index.test.tsx => profile_avatar.test.tsx} | 2 +- .../__snapshots__/delete_draft.test.tsx.snap | 127 +++++++ .../draft_options/delete_draft.test.tsx | 2 +- app/screens/draft_options/edit_draft.test.tsx | 2 +- .../draft_empty_component.test.tsx.snap | 60 +++- .../components/draft_empty_component.test.tsx | 2 +- 11 files changed, 628 insertions(+), 23 deletions(-) create mode 100644 app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap create mode 100644 app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap rename app/components/draft_post_header/{index.test.tsx => profile_avatar.test.tsx} (98%) create mode 100644 app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap diff --git a/app/components/draft/__snapshots__/draft.test.tsx.snap b/app/components/draft/__snapshots__/draft.test.tsx.snap index a9d994adfe5..46ae0cc8410 100644 --- a/app/components/draft/__snapshots__/draft.test.tsx.snap +++ b/app/components/draft/__snapshots__/draft.test.tsx.snap @@ -11,7 +11,7 @@ exports[`Draft should match the file count 1`] = ` } } accessible={true} - focusable={false} + focusable={true} onClick={[Function]} onResponderGrant={[Function]} onResponderMove={[Function]} @@ -553,7 +553,7 @@ exports[`Draft should render the draft with channel info and draft message 1`] = } } accessible={true} - focusable={false} + focusable={true} onClick={[Function]} onResponderGrant={[Function]} onResponderMove={[Function]} @@ -721,7 +721,7 @@ exports[`Draft should render the draft with channel info and draft message for a } } accessible={true} - focusable={false} + focusable={true} onClick={[Function]} onResponderGrant={[Function]} onResponderMove={[Function]} @@ -889,7 +889,7 @@ exports[`Draft should render the draft with post priority 1`] = ` } } accessible={true} - focusable={false} + focusable={true} onClick={[Function]} onResponderGrant={[Function]} onResponderMove={[Function]} diff --git a/app/components/draft/draft_post/draft_message.test.tsx b/app/components/draft/draft_post/draft_message.test.tsx index e0da756feec..445d2c13e41 100644 --- a/app/components/draft/draft_post/draft_message.test.tsx +++ b/app/components/draft/draft_post/draft_message.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import {renderWithEverything} from '@test/intl-test-helper'; import TestHelper from '@test/test_helper'; -import DraftMessage from '.'; +import DraftMessage from './draft_message'; import type {Database} from '@nozbe/watermelondb'; import type DraftModel from '@typings/database/models/servers/draft'; diff --git a/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap b/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap new file mode 100644 index 00000000000..d7e92523bf0 --- /dev/null +++ b/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap @@ -0,0 +1,324 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DraftPostHeader Component renders CompassIcon when draftReceiverUser is not provided 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`DraftPostHeader Component renders correctly for a DM channel 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`DraftPostHeader Component renders correctly for a public channel 1`] = ` + + + + + + + + + Public Channel + + + +`; + +exports[`DraftPostHeader Component renders correctly for a thread 1`] = ` + + + + + + + + + Direct Message Channel + + + +`; + +exports[`DraftPostHeader Component renders the Avatar when draftReceiverUser is provided 1`] = ` + + + + + + + Direct Message Channel + + + +`; diff --git a/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap b/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap new file mode 100644 index 00000000000..706c7472ecf --- /dev/null +++ b/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProfileAvatar Component renders the avatar image when URI is available 1`] = ` + + + +`; + +exports[`ProfileAvatar Component renders the fallback icon when URI is not available 1`] = ` + + + +`; + +exports[`ProfileAvatar Component renders the fallback icon when author is not provided 1`] = ` + + + +`; diff --git a/app/components/draft_post_header/channel_info.test.tsx b/app/components/draft_post_header/channel_info.test.tsx index 5b5479607df..2c1dab5d576 100644 --- a/app/components/draft_post_header/channel_info.test.tsx +++ b/app/components/draft_post_header/channel_info.test.tsx @@ -11,8 +11,8 @@ import {General} from '@constants'; import {useTheme} from '@context/theme'; import {getUserTimezone} from '@utils/user'; -import Avatar from './avatar'; -import ChannelInfo from './channel_info'; +import DraftPostHeader from './draft_post_header'; +import ProfileAvatar from './profile_avatar'; import type ChannelModel from '@typings/database/models/servers/channel'; import type UserModel from '@typings/database/models/servers/user'; @@ -23,13 +23,13 @@ jest.mock('@context/theme', () => ({ jest.mock('@components/formatted_text', () => jest.fn(() => null)); jest.mock('@components/formatted_time', () => jest.fn(() => null)); -jest.mock('./avatar', () => jest.fn(() => null)); +jest.mock('./profile_avatar', () => jest.fn(() => null)); jest.mock('@components/compass_icon', () => jest.fn(() => null)); jest.mock('@utils/user', () => ({ getUserTimezone: jest.fn(), })); -describe('ChannelInfo Component', () => { +describe('DraftPostHeader Component', () => { const mockTheme = { centerChannelColor: '#000000', }; @@ -51,7 +51,7 @@ describe('ChannelInfo Component', () => { isMilitaryTime: true, }; - const wrapper = render(); + const wrapper = render(); expect(CompassIcon).toHaveBeenCalledWith( expect.objectContaining({ name: 'globe', @@ -79,7 +79,7 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - const wrapper = render(); + const wrapper = render(); expect(FormattedText).toHaveBeenCalledWith( expect.objectContaining({ id: 'channel_info.draft_in_channel', @@ -106,7 +106,7 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - const wrapper = render(); + const wrapper = render(); const {getByTestId} = wrapper; expect(useTheme).toHaveBeenCalled(); @@ -131,9 +131,9 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - const wrapper = render(); + const wrapper = render(); - expect(Avatar).toHaveBeenCalledWith( + expect(ProfileAvatar).toHaveBeenCalledWith( expect.objectContaining({ author: baseProps.draftReceiverUser, }), @@ -152,7 +152,7 @@ describe('ChannelInfo Component', () => { currentUser: {timezone: 'UTC'} as unknown as UserModel, isMilitaryTime: true, }; - const wrapper = render(); + const wrapper = render(); expect(CompassIcon).toHaveBeenCalledWith( expect.objectContaining({ name: 'globe', diff --git a/app/components/draft_post_header/index.test.tsx b/app/components/draft_post_header/profile_avatar.test.tsx similarity index 98% rename from app/components/draft_post_header/index.test.tsx rename to app/components/draft_post_header/profile_avatar.test.tsx index 4f6dcc9b40d..d33e3d6e5d1 100644 --- a/app/components/draft_post_header/index.test.tsx +++ b/app/components/draft_post_header/profile_avatar.test.tsx @@ -23,7 +23,7 @@ const mockBuildAbsoluteUrl = require('@actions/remote/file').buildAbsoluteUrl; const mockBuildProfileImageUrlFromUser = require('@actions/remote/user').buildProfileImageUrlFromUser; const mockUseServerUrl = require('@context/server').useServerUrl; -describe('Avatar Component', () => { +describe('ProfileAvatar Component', () => { beforeEach(() => { jest.clearAllMocks(); }); diff --git a/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap new file mode 100644 index 00000000000..dbfc3b2b033 --- /dev/null +++ b/app/screens/draft_options/__snapshots__/delete_draft.test.tsx.snap @@ -0,0 +1,127 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DeleteDraft calls draftDeleteHandler when pressed 1`] = ` + + + + Delete draft + + +`; + +exports[`DeleteDraft should render the component 1`] = ` + + + + Delete draft + + +`; diff --git a/app/screens/draft_options/delete_draft.test.tsx b/app/screens/draft_options/delete_draft.test.tsx index 9a90c598feb..d892a990706 100644 --- a/app/screens/draft_options/delete_draft.test.tsx +++ b/app/screens/draft_options/delete_draft.test.tsx @@ -7,7 +7,7 @@ import {Screens} from '@constants'; import {dismissBottomSheet} from '@screens/navigation'; import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; -import DeleteDraft from '.'; +import DeleteDraft from './delete_draft'; jest.mock('@screens/navigation', () => ({ dismissBottomSheet: jest.fn(), diff --git a/app/screens/draft_options/edit_draft.test.tsx b/app/screens/draft_options/edit_draft.test.tsx index 834bf159075..15d4d09ae9a 100644 --- a/app/screens/draft_options/edit_draft.test.tsx +++ b/app/screens/draft_options/edit_draft.test.tsx @@ -7,7 +7,7 @@ import {Screens} from '@constants'; import {dismissBottomSheet} from '@screens/navigation'; import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; -import EditDraft from '.'; +import EditDraft from './edit_draft'; import type ChannelModel from '@typings/database/models/servers/channel'; diff --git a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap index 27c9ad1cf1c..7f816ebca46 100644 --- a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap +++ b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap @@ -11,8 +11,34 @@ exports[`Draft Empty Component should match the snapshot 1`] = ` } testID="draft_empty_component" > - - { it('should match the snapshot', () => { From 3f3928b7e60b04f9ffa2e0d2b4eee4bd6b06f917 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 10 Jan 2025 13:03:50 +0530 Subject: [PATCH 106/111] remove a from drafts_button for checking to render is draft count is 0 --- app/components/drafts_buttton/drafts_button.test.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/components/drafts_buttton/drafts_button.test.tsx b/app/components/drafts_buttton/drafts_button.test.tsx index b0bc18d7a97..8fa5428bf22 100644 --- a/app/components/drafts_buttton/drafts_button.test.tsx +++ b/app/components/drafts_buttton/drafts_button.test.tsx @@ -52,16 +52,4 @@ describe('Drafts Button', () => { expect(switchToGlobalDrafts).toHaveBeenCalledTimes(1); expect(wrapper.toJSON()).toMatchSnapshot(); }); - - it('does not render if draftsCount is 0', () => { - const wrapper = renderWithIntl( - , - ); - const {queryByTestId} = wrapper; - expect(queryByTestId('channel_list.drafts.button')).toBeNull(); - expect(wrapper.toJSON()).toMatchSnapshot(); - }); }); From b3b17b4591fb0bbd03fe7b3a19ad99823a9bb91d Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 10 Jan 2025 13:06:16 +0530 Subject: [PATCH 107/111] snapshot updates --- .../__snapshots__/channel_info.test.tsx.snap | 324 ------------------ .../avatar/__snapshots__/index.test.tsx.snap | 102 ------ .../__snapshots__/drafts_button.test.tsx.snap | 2 - .../__snapshots__/delete_draft.test.tsx.snap | 127 ------- 4 files changed, 555 deletions(-) delete mode 100644 app/components/channel_info/__snapshots__/channel_info.test.tsx.snap delete mode 100644 app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap delete mode 100644 app/screens/draft_options/delete_draft/__snapshots__/delete_draft.test.tsx.snap diff --git a/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap b/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap deleted file mode 100644 index d4854e14eb7..00000000000 --- a/app/components/channel_info/__snapshots__/channel_info.test.tsx.snap +++ /dev/null @@ -1,324 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ChannelInfo Component renders CompassIcon when draftReceiverUser is not provided 1`] = ` - - - - - - - - - Direct Message Channel - - - -`; - -exports[`ChannelInfo Component renders correctly for a DM channel 1`] = ` - - - - - - - - - Direct Message Channel - - - -`; - -exports[`ChannelInfo Component renders correctly for a public channel 1`] = ` - - - - - - - - - Public Channel - - - -`; - -exports[`ChannelInfo Component renders correctly for a thread 1`] = ` - - - - - - - - - Direct Message Channel - - - -`; - -exports[`ChannelInfo Component renders the Avatar when draftReceiverUser is provided 1`] = ` - - - - - - - Direct Message Channel - - - -`; diff --git a/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap b/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 9f3b0100261..00000000000 --- a/app/components/channel_info/avatar/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,102 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Avatar Component renders the avatar image when URI is available 1`] = ` - - - -`; - -exports[`Avatar Component renders the fallback icon when URI is not available 1`] = ` - - - -`; - -exports[`Avatar Component renders the fallback icon when author is not provided 1`] = ` - - - -`; diff --git a/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap index cf6c8b6b195..94f2ce54ebf 100644 --- a/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap +++ b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap @@ -126,8 +126,6 @@ exports[`Drafts Button calls switchToGlobalDrafts when pressed 1`] = ` `; -exports[`Drafts Button does not render if draftsCount is 0 1`] = `null`; - exports[`Drafts Button should render the drafts button component 1`] = ` - - - Delete draft - - -`; - -exports[`DeleteDraft should render the component 1`] = ` - - - - Delete draft - - -`; From b42ce63d1205cc1aa60ba11c186b2662dca3fa5f Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 10 Jan 2025 13:11:46 +0530 Subject: [PATCH 108/111] intl fixes --- assets/base/i18n/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 93ed68997c5..e127ccbb8bd 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -167,8 +167,6 @@ "channel_info.copy_link": "Copy Link", "channel_info.copy_purpose_text": "Copy Purpose Text", "channel_info.custom_status": "Custom status:", - "channel_info.draft_in_channel": "In:", - "channel_info.draft_to_user": "To:", "channel_info.edit_header": "Edit Header", "channel_info.error_close": "Close", "channel_info.favorite": "Favorite", From 83fda81dd4abebafd14d87ec4c6d8347570ace4d Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 13 Jan 2025 13:29:34 +0530 Subject: [PATCH 109/111] reverted unneccessary changes --- .../xcshareddata/swiftpm/Package.resolved | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved index 62cc6b8f771..9be5f0f2633 100644 --- a/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Mattermost.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -46,6 +46,24 @@ "version": "1.9.200" } }, + { + "package": "OpenGraph", + "repositoryURL": "https://github.com/satoshi-takano/OpenGraph.git", + "state": { + "branch": null, + "revision": "382972f1963580eeabafd88ad012e66576b4d213", + "version": "1.4.1" + } + }, + { + "package": "Sentry", + "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", + "state": { + "branch": null, + "revision": "5575af93efb776414f243e93d6af9f6258dc539a", + "version": "8.36.0" + } + }, { "package": "SQLite.swift", "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", From 95c154943e2d8a79e56ff5c1a62acd84afdaecb5 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 13 Jan 2025 13:33:02 +0530 Subject: [PATCH 110/111] Updated test --- app/components/drafts_buttton/drafts_button.test.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/components/drafts_buttton/drafts_button.test.tsx b/app/components/drafts_buttton/drafts_button.test.tsx index 8fa5428bf22..e6c38f9bdd2 100644 --- a/app/components/drafts_buttton/drafts_button.test.tsx +++ b/app/components/drafts_buttton/drafts_button.test.tsx @@ -16,7 +16,6 @@ describe('Drafts Button', () => { it('should render the drafts button component', () => { const wrapper = renderWithIntl( , ); @@ -29,7 +28,6 @@ describe('Drafts Button', () => { it('should render the drafts button component with drafts count', () => { const wrapper = renderWithIntl( , ); @@ -43,7 +41,6 @@ describe('Drafts Button', () => { it('calls switchToGlobalDrafts when pressed', () => { const wrapper = renderWithIntl( , ); From 6ddf0d1dc6ee85a010d85ca35494f1ab786851fd Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 14 Jan 2025 20:36:09 +0530 Subject: [PATCH 111/111] updated snapshot --- .../__snapshots__/drafts_button.test.tsx.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap index 94f2ce54ebf..e4904535363 100644 --- a/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap +++ b/app/components/drafts_buttton/__snapshots__/drafts_button.test.tsx.snap @@ -56,7 +56,7 @@ exports[`Drafts Button calls switchToGlobalDrafts when pressed 1`] = ` } >