mattermost-mobile-upstream/app/screens/scheduled_post_options/scheduled_post_picker.tsx
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

163 lines
5.7 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo, useState} from 'react';
import {Platform, View} from 'react-native';
import FormattedText from '@components/formatted_text';
import {Screens} from '@constants';
import {useTheme} from '@context/theme';
import {usePreventDoubleTap} from '@hooks/utils';
import BottomSheet from '@screens/bottom_sheet';
import {dismissBottomSheet} from '@screens/navigation';
import {FOOTER_HEIGHT} from '@screens/post_priority_picker/footer';
import ScheduledPostCoreOptions from '@screens/scheduled_post_options/core_options';
import CallbackStore from '@store/callback_store';
import {logDebug} from '@utils/log';
import {showScheduledPostCreationErrorSnackbar} from '@utils/snack_bar';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {getTimezone} from '@utils/user';
import ScheduledPostFooter from './footer';
import type {BottomSheetFooterProps} from '@gorhom/bottom-sheet';
const OPTIONS_PADDING = 12;
const OPTIONS_SEPARATOR_HEIGHT = 1;
const TITLE_HEIGHT = 54;
const ITEM_HEIGHT = 48;
const ERROR_HEIGHT = 16;
export const SCHEDULED_POST_OPTIONS_BUTTON = 'close-scheduled-post-options';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
backgroundColor: theme.centerChannelBg,
height: 200,
},
titleContainer: {
alignItems: 'center',
flexDirection: 'row',
},
title: {
color: theme.centerChannelColor,
...typography('Heading', 600, 'SemiBold'),
},
optionsContainer: {
paddingTop: OPTIONS_PADDING,
},
optionsSeparator: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
height: OPTIONS_SEPARATOR_HEIGHT,
},
errorText: {
color: theme.errorTextColor,
...typography('Heading', 25),
},
}));
type ScheduledPostOptionsProps = {
currentUserTimezone?: UserTimezone | null;
}
export function ScheduledPostOptions({currentUserTimezone}: ScheduledPostOptionsProps) {
const theme = useTheme();
const [isScheduling, setIsScheduling] = useState(false);
const [selectedTime, setSelectedTime] = useState<string | null>(null);
const [customTimeSelected, setCustomTimeSelected] = useState(false);
const [isError, setIsError] = useState(false);
const userTimezone = getTimezone(currentUserTimezone);
const style = getStyleSheet(theme);
const snapPoints = useMemo(() => {
const bottomSheetAdjust = Platform.select({ios: 0, default: 20});
// 9 items to display inline date-time picker, 4 items otherwise
const iosNumberOfItems = customTimeSelected ? 9 : 4;
const andriodNumberOfItems = customTimeSelected ? 5 : 4;
const errorHeight = isError ? ERROR_HEIGHT : 0;
const numberOfItems = Platform.select({ios: iosNumberOfItems, default: andriodNumberOfItems});
const componentHeight = TITLE_HEIGHT + (numberOfItems * ITEM_HEIGHT) + FOOTER_HEIGHT + errorHeight + bottomSheetAdjust;
return [1, componentHeight];
}, [customTimeSelected, isError]);
const onSelectTime = useCallback((selectedValue: string) => {
setIsError(false);
setSelectedTime(selectedValue);
}, []);
const handleOnSchedule = usePreventDoubleTap(useCallback(async () => {
if (!selectedTime) {
setIsError(true);
logDebug('ScheduledPostOptions', 'No time selected');
return;
}
setIsError(false);
setIsScheduling(true);
const schedulingInfo: SchedulingInfo = {
scheduled_at: parseInt(selectedTime, 10),
};
const onSchedule = CallbackStore.getCallback<((schedulingInfo: SchedulingInfo) => Promise<void | {data?: boolean; error?: unknown}>)>();
const response = await onSchedule?.(schedulingInfo);
setIsScheduling(false);
if (response?.error) {
const errorMessage = response.error as string;
showScheduledPostCreationErrorSnackbar(errorMessage);
return;
}
CallbackStore.removeCallback();
dismissBottomSheet();
}, [selectedTime]));
const renderContent = () => {
return (
<View style={style.container}>
<View style={style.titleContainer}>
<FormattedText
id='scheduled_post.picker.title'
defaultMessage='Schedule draft'
style={style.title}
/>
</View>
<View style={style.optionsContainer}>
<ScheduledPostCoreOptions
userTimezone={userTimezone}
onSelectOption={onSelectTime}
onCustomTimeSelected={setCustomTimeSelected}
/>
</View>
{isError &&
<FormattedText
id='scheduled_post_no_select_time'
defaultMessage={'Please select the time'}
style={style.errorText}
/>
}
</View>
);
};
const renderFooter = (props: BottomSheetFooterProps) => (
<ScheduledPostFooter
{...props}
onSchedule={handleOnSchedule}
isScheduling={isScheduling}
/>
);
return (
<BottomSheet
renderContent={renderContent}
screen={Screens.SCHEDULED_POST_OPTIONS}
snapPoints={snapPoints}
testID='scheduled_post_options_bottom_sheet'
footerComponent={renderFooter}
/>
);
}