mattermost-mobile-upstream/app/utils/scheduled_post/index.ts
Elias Nahum b6fc85385e
Migrate from RNN to Expo router (#9402)
* Expo-Router partial migration

* Full migration to ExpoRouter

* i18n

* add tests

* Merge branch 'main' into expo-router

* align package-lock.json

* i18n

* feedback review

* feedback review

* android 16kb patch

* Refactored emoji picker keyboard using a state machine

* set android launch_screen in drawables instead of layout

* no conditional hooks

* remove duplicate call in test

* address feedback

* fix tests and address more feedback

* small corrections

* small corrections

* small corrections

* fix draft test

* fix draft test 2

* remove navigateToScreen in draff test

* fix nit in draft test

* remove doc file

* Feedback review

* fix snapshots

* improve test coverage

* fix i18n

* Upgrade t o RN 0.83, other dependencies and new architecture (#9737)

* library hardware-keyboard android new arch support fix

* library rnshare android new arch support fix

* library rnutils android new arch support fix

* library secure-pdf-viewer add new arch support

* Enable new arch on iOS and Android

* update emm and network client with new-arch

* update intune to use new-arch

* performance improvements

* fix tests

* fixes post input on new-arch

* fix android emoji picker

* remove doc

* run tests in parallel

* upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture

Bumps core dependencies to their latest versions:
- React Native 0.77.3 → 0.83.9 (New Architecture enabled)
- React 18.3.1 → 19.2.5
- Expo SDK 52 → 55
- TypeScript 5.8.3 → 6.0.3
- Node.js 22.16.0 → 24.15.0
- react-native-reanimated 3.17.3 → 4.3.0
- Various other dependency upgrades

Migrates iOS AppDelegate from Objective-C to Swift, updates Android
build tooling and Gradle wrapper, refreshes all patches and font
assets, and adapts app code, tests, and CI workflows throughout for
API changes introduced by these upgrades.

* remove unecessary dismissKeyboard in notifications

* feedback review

* update intune to use new-arch

* update @mattermost deps

* missing i18n string

* additional fixes

* update fastlane
2026-05-13 17:44:55 +08:00

236 lines
8.0 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {defineMessages, type IntlShape} from 'react-intl';
import {Alert} from 'react-native';
import {deleteScheduledPost} from '@actions/remote/scheduled_post';
import {SNACK_BAR_TYPE} from '@constants/snack_bar';
import DatabaseManager from '@database/manager';
import {getPostById} from '@queries/servers/post';
import {getErrorMessage} from '@utils/errors';
import {showSnackBar} from '@utils/snack_bar';
import type ScheduledPostModel from '@typings/database/models/servers/scheduled_post';
import type {ScheduledPostErrorCode} from '@typings/utils/scheduled_post';
import type {SwipeableMethods} from 'react-native-gesture-handler/lib/typescript/components/ReanimatedSwipeable';
export function deleteScheduledPostConfirmation({
intl,
serverUrl,
scheduledPostId,
swipeable,
}: {
intl: IntlShape;
serverUrl: string;
scheduledPostId: string;
swipeable?: React.RefObject<SwipeableMethods | null>;
}) {
const deleteScheduledPostOnConfirm = async () => {
const res = await deleteScheduledPost(serverUrl, scheduledPostId);
if (res?.error) {
showSnackBar({
barType: SNACK_BAR_TYPE.DELETE_SCHEDULED_POST_ERROR,
customMessage: getErrorMessage(res.error),
type: 'error',
});
}
};
const onDismiss = () => {
swipeable?.current?.close();
};
Alert.alert(
intl.formatMessage({
id: 'schedule_post.options.delete.title',
defaultMessage: 'Delete scheduled post',
}),
intl.formatMessage({
id: 'scheduled_post.options.delete.confirmation',
defaultMessage: 'Are you sure you want to delete this scheduled post?',
}),
[
{
text: intl.formatMessage({
id: 'scheduled_post.options.delete.cancel',
defaultMessage: 'Cancel',
}),
style: 'cancel',
onPress: onDismiss,
},
{
text: intl.formatMessage({
id: 'scheduled_post.options.delete.confirm',
defaultMessage: 'Delete',
}),
onPress: deleteScheduledPostOnConfirm,
},
],
);
}
export const hasScheduledPostError = (scheduledPosts: ScheduledPostModel[]) =>
scheduledPosts.some((post) => post.errorCode !== '');
const errorCodeToErrorMessage = defineMessages<ScheduledPostErrorCode>({
unknown: {
id: 'scheduled_post.error_code.unknown_error',
defaultMessage: 'Unknown Error',
},
channel_archived: {
id: 'scheduled_post.error_code.channel_archived',
defaultMessage: 'Channel Archived',
},
channel_not_found: {
id: 'scheduled_post.error_code.channel_removed',
defaultMessage: 'Channel Removed',
},
user_missing: {
id: 'scheduled_post.error_code.user_missing',
defaultMessage: 'User Deleted',
},
user_deleted: {
id: 'scheduled_post.error_code.user_deleted',
defaultMessage: 'User Deleted',
},
no_channel_permission: {
id: 'scheduled_post.error_code.no_channel_permission',
defaultMessage: 'Missing Permission',
},
no_channel_member: {
id: 'scheduled_post.error_code.no_channel_member',
defaultMessage: 'Not In Channel',
},
thread_deleted: {
id: 'scheduled_post.error_code.thread_deleted',
defaultMessage: 'Thread Deleted',
},
unable_to_send: {
id: 'scheduled_post.error_code.unable_to_send',
defaultMessage: 'Unable to Send',
},
invalid_post: {
id: 'scheduled_post.error_code.invalid_post',
defaultMessage: 'Invalid Post',
},
post_send_success_delete_failed: {
id: 'scheduled_post.error_code.post_send_success_delete_failed',
defaultMessage: 'Please delete as this was posted',
},
});
export function getErrorStringFromCode(intl: IntlShape, errorCode: ScheduledPostErrorCode = 'unknown') {
const textDefinition = errorCodeToErrorMessage[errorCode] ?? errorCodeToErrorMessage.unknown;
return intl.formatMessage(textDefinition).toUpperCase();
}
export const isScheduledPostModel = (obj: ScheduledPostModel | ScheduledPost): obj is ScheduledPostModel => {
return 'toApi' in obj && typeof (obj as ScheduledPostModel).toApi === 'function';
};
type CanPostDraftInChannelOrThreadProps = {
intl: IntlShape;
serverUrl: string;
rootId?: string;
canPost?: boolean;
channelIsArchived?: boolean;
channelIsReadOnly?: boolean;
deactivatedChannel?: boolean;
}
export async function canPostDraftInChannelOrThread({
serverUrl,
rootId,
intl,
canPost,
channelIsArchived,
channelIsReadOnly,
deactivatedChannel,
}: CanPostDraftInChannelOrThreadProps) {
if (rootId) {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const rootPost = await getPostById(database, rootId);
if (!rootPost) {
Alert.alert(
intl.formatMessage({id: 'scheduled_post.root_post_not_exist', defaultMessage: 'Sending post failed'}),
intl.formatMessage({
id: 'scheduled_post.root_post_not_exist.message',
defaultMessage: 'Someone delete the message on which you tried to post a comment.',
}),
[{
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
},
], {cancelable: false},
);
return false;
}
}
if (channelIsArchived) {
Alert.alert(
intl.formatMessage({id: 'scheduled_post.channel_archived', defaultMessage: 'Sending post failed'}),
intl.formatMessage({
id: 'scheduled_post.channel_archived.message',
defaultMessage: 'You cannot post to an archived channel.',
}),
[{
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
},
], {cancelable: false},
);
return false;
}
if (channelIsReadOnly) {
Alert.alert(
intl.formatMessage({id: 'scheduled_post.channel_read_only', defaultMessage: 'Sending post failed'}),
intl.formatMessage({
id: 'scheduled_post.channel_read_only.message',
defaultMessage: 'You cannot post to a read-only channel.',
}),
[{
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
},
], {cancelable: false},
);
return false;
}
if (deactivatedChannel) {
Alert.alert(
intl.formatMessage({id: 'scheduled_post.channel_deactivated', defaultMessage: 'Sending post failed'}),
intl.formatMessage({
id: 'scheduled_post.channel_deactivated.message',
defaultMessage: 'You cannot post to a deactivated channel.',
}),
[{
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
},
], {cancelable: false},
);
return false;
}
if (!canPost) {
Alert.alert(
intl.formatMessage({id: 'scheduled_post.no_permission', defaultMessage: 'Sending post failed'}),
intl.formatMessage({
id: 'scheduled_post.no_permission.message',
defaultMessage: 'You do not have permission to post in this channel.',
}),
[{
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
},
], {cancelable: false},
);
return false;
}
return true;
}