* feat: ai rewrite * feat: allow crating content apart from editing it * feat: feature parity with webapp * feat: feature parity with webapp * chore: fixed padding * map ux to webapp * refactored ai rewrite logic to separate package * chore: tests and lint * Update app/products/ai/rewrite/screens/options/options.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * rewrite post editor animation * i18n * ui feedback, centered icon and less top padding * chore: lint * Consolidate @ai product into @agents Move all rewrite functionality from app/products/ai/ into app/products/agents/ to unify under a single namespace. - Add rewrite types, store, hooks, components, and screens to agents - Merge AI client methods into agents client - Update screen constants (AI_* -> AGENTS_*) - Update all consumer imports - Remove AI state from EphemeralStore - Remove @ai path alias from config - Delete app/products/ai/ directory * refactor: load screens from product package * refactor: move detection logic to the agents pacakge * refactor: remove "backwards compatibility" * refactor: move hooks to proper package * refactor: styles * refactor: remove unneedd position attribute * refactor: remove unneeded cancel animation calls * refactor: "backwards compat" * refactor: optimize renderContent with useCallback for performance * refactor: rename variable to avoid confusion * refactor: update handleRewrite to use async/await for better error handling * refactor: simplify handleRewrite by always dismissing keyboard * refactor: use hook, always all keyboard.dismiss * chore: enhance AgentSelector component with FlatList support * feat: add rewriteMessage function for AI message rewriting and integrate it into useRewrite hook * refactor: simplify message length calculation in useHandleSendMessage hook * refactor: consolidate agent screen constants and integrate with existing screens * fix: update dependency array in useMemo for isUnrevealedPost to include post expiration metadata * refactor: remove unused variable to clean up Typing component * refactor: simplify logic for atDisabled and slashDisabled flags in QuickActions component * feat: add AIRewriteAction component for AI message rewriting functionality * revert: thread.ts changes manually * refactor: integrate useSafeAreaInsets * refactor: cleanup unused methods * chore: add comment to clarify casting * refactor: use_agents * chore: lint and test * fix: trim --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
128 lines
3.3 KiB
TypeScript
128 lines
3.3 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {useCallback, useEffect} from 'react';
|
|
import {
|
|
Easing, runOnJS, useAnimatedRef, useAnimatedStyle,
|
|
useSharedValue,
|
|
withTiming, type WithTimingConfig,
|
|
} from 'react-native-reanimated';
|
|
|
|
import {useGallery} from '@context/gallery';
|
|
|
|
export function diff(context: any, name: string, value: any) {
|
|
'worklet';
|
|
|
|
if (!context.___diffs) {
|
|
context.___diffs = {};
|
|
}
|
|
|
|
if (!context.___diffs[name]) {
|
|
context.___diffs[name] = {
|
|
stash: 0,
|
|
prev: null,
|
|
};
|
|
}
|
|
|
|
const d = context.___diffs[name];
|
|
|
|
d.stash = d.prev === null ? 0 : value - d.prev;
|
|
d.prev = value;
|
|
|
|
return d.stash;
|
|
}
|
|
|
|
export const translateYConfig: WithTimingConfig = {
|
|
duration: 400,
|
|
easing: Easing.bezier(0.33, 0.01, 0, 1),
|
|
};
|
|
|
|
export function useGalleryControls(bottomInset = 0) {
|
|
const headerAndFooterHidden = useSharedValue(false);
|
|
|
|
const headerStyles = useAnimatedStyle(() => ({
|
|
opacity: headerAndFooterHidden.value ? withTiming(0) : withTiming(1),
|
|
transform: [
|
|
{
|
|
translateY: headerAndFooterHidden.value ? withTiming(-100, translateYConfig) : withTiming(0, translateYConfig),
|
|
},
|
|
],
|
|
position: 'absolute',
|
|
top: 0,
|
|
width: '100%',
|
|
zIndex: 1,
|
|
}));
|
|
|
|
const footerStyles = useAnimatedStyle(() => ({
|
|
opacity: headerAndFooterHidden.value ? withTiming(0) : withTiming(1),
|
|
transform: [
|
|
{
|
|
translateY: headerAndFooterHidden.value ? withTiming(100, translateYConfig) : withTiming(0, translateYConfig),
|
|
},
|
|
],
|
|
position: 'absolute',
|
|
bottom: bottomInset,
|
|
width: '100%',
|
|
zIndex: 1,
|
|
}), [bottomInset]);
|
|
|
|
const hideHeaderAndFooter = useCallback((hidden?: boolean) => {
|
|
'worklet';
|
|
|
|
if (hidden == null) {
|
|
// if we don't pass hidden, then we toggle the current value
|
|
headerAndFooterHidden.value = !headerAndFooterHidden.value;
|
|
return;
|
|
}
|
|
|
|
if (headerAndFooterHidden.value === hidden) {
|
|
return;
|
|
}
|
|
|
|
headerAndFooterHidden.value = hidden;
|
|
}, [headerAndFooterHidden]);
|
|
|
|
return {
|
|
headerAndFooterHidden,
|
|
headerStyles,
|
|
footerStyles,
|
|
hideHeaderAndFooter,
|
|
};
|
|
}
|
|
|
|
export function useGalleryItem(
|
|
identifier: string,
|
|
index: number,
|
|
onPress: (identifier: string, itemIndex: number) => void,
|
|
) {
|
|
const gallery = useGallery(identifier);
|
|
const ref = useAnimatedRef<any>();
|
|
const {opacity, activeIndex} = gallery.sharedValues;
|
|
|
|
const styles = useAnimatedStyle(() => {
|
|
return {
|
|
opacity: activeIndex.value === index ? opacity.value : 1,
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
gallery.registerItem(index, ref);
|
|
|
|
// Only register item once on mount - gallery, index, and ref are stable references
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const onGestureEvent = () => {
|
|
'worklet';
|
|
|
|
activeIndex.value = index;
|
|
|
|
runOnJS(onPress)(identifier, index);
|
|
};
|
|
|
|
return {
|
|
ref,
|
|
styles,
|
|
onGestureEvent,
|
|
};
|
|
}
|