Page MenuHomePhorge

D6658.1768384305.diff
No OneTemporary

Size
22 KB
Referenced Files
None
Subscribers
None

D6658.1768384305.diff

diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js
--- a/native/chat/multimedia-message-tooltip-button.react.js
+++ b/native/chat/multimedia-message-tooltip-button.react.js
@@ -1,15 +1,15 @@
// @flow
import * as React from 'react';
-import Animated, { type SharedValue } from 'react-native-reanimated';
+import Animated from 'react-native-reanimated';
import EmojiPicker from 'rn-emoji-keyboard';
import { localIDPrefix } from 'lib/shared/message-utils';
import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils';
-import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { useSelector } from '../redux/redux-utils';
+import { useTooltipActions } from '../tooltip/tooltip-hooks';
import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerMultimediaMessage } from './inner-multimedia-message.react';
@@ -33,17 +33,9 @@
+route: TooltipRoute<'MultimediaMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
};
function MultimediaMessageTooltipButton(props: Props): React.Node {
- const {
- navigation,
- progress,
- isOpeningSidebar,
- setHideTooltip,
- showEmojiKeyboard,
- } = props;
+ const { navigation, route, progress, isOpeningSidebar } = props;
const windowWidth = useSelector(state => state.dimensions.width);
@@ -55,12 +47,7 @@
setSidebarInputBarHeight(height);
}, []);
- const {
- item,
- verticalBounds,
- initialCoordinates,
- margin,
- } = props.route.params;
+ const { item, verticalBounds, initialCoordinates, margin } = route.params;
const { style: messageContainerStyle } = useAnimatedMessageTooltipButton({
sourceMessage: item,
@@ -138,6 +125,11 @@
margin,
});
+ const [emojiPickerOpen, setEmojiPickerOpen] = React.useState<boolean>(false);
+ const openEmojiPicker = React.useCallback(() => {
+ setEmojiPickerOpen(true);
+ }, []);
+
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
@@ -145,8 +137,9 @@
return (
<ReactionSelectionPopover
- setHideTooltip={setHideTooltip}
- showEmojiKeyboard={showEmojiKeyboard}
+ navigation={navigation}
+ route={route}
+ openEmojiPicker={openEmojiPicker}
reactionSelectionPopoverContainerStyle={
reactionSelectionPopoverPosition
}
@@ -154,26 +147,25 @@
/>
);
}, [
+ navigation,
+ route,
+ openEmojiPicker,
canCreateReactionFromMessage,
reactionSelectionPopoverPosition,
sendReaction,
- setHideTooltip,
- showEmojiKeyboard,
]);
+ const tooltipRouteKey = route.key;
+ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
+
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
- setHideTooltip(true);
+ dismissTooltip();
},
- [sendReaction, setHideTooltip],
+ [sendReaction, dismissTooltip],
);
- const onCloseEmojiPicker = React.useCallback(() => {
- showEmojiKeyboard.value = false;
- navigation.goBackOnce();
- }, [navigation, showEmojiKeyboard]);
-
return (
<>
<Animated.View style={messageContainerStyle}>
@@ -190,8 +182,8 @@
</Animated.View>
<EmojiPicker
onEmojiSelected={onEmojiSelected}
- open={showEmojiKeyboard.value}
- onClose={onCloseEmojiPicker}
+ open={emojiPickerOpen}
+ onClose={dismissTooltip}
/>
</>
);
diff --git a/native/chat/reaction-selection-popover.react.js b/native/chat/reaction-selection-popover.react.js
--- a/native/chat/reaction-selection-popover.react.js
+++ b/native/chat/reaction-selection-popover.react.js
@@ -2,27 +2,30 @@
import * as React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
-import type { SharedValue } from 'react-native-reanimated';
-
-import type { SetState } from 'lib/types/hook-types';
import SWMansionIcon from '../components/swmansion-icon.react';
+import type { AppNavigationProp } from '../navigation/app-navigator.react';
+import type { TooltipModalParamList } from '../navigation/route-names';
import { useStyles } from '../themes/colors';
+import { useTooltipActions } from '../tooltip/tooltip-hooks';
+import type { TooltipRoute } from '../tooltip/tooltip.react';
import type { ViewStyle } from '../types/styles';
-type ReactionSelectionPopoverProps = {
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
+type Props<RouteName: $Keys<TooltipModalParamList>> = {
+ +navigation: AppNavigationProp<RouteName>,
+ +route: TooltipRoute<RouteName>,
+ +openEmojiPicker: () => mixed,
+reactionSelectionPopoverContainerStyle: ViewStyle,
+sendReaction: (reaction: string) => mixed,
};
-function ReactionSelectionPopover(
- props: ReactionSelectionPopoverProps,
+function ReactionSelectionPopover<RouteName: $Keys<TooltipModalParamList>>(
+ props: Props<RouteName>,
): React.Node {
const {
- setHideTooltip,
- showEmojiKeyboard,
+ navigation,
+ route,
+ openEmojiPicker,
reactionSelectionPopoverContainerStyle,
sendReaction,
} = props;
@@ -40,18 +43,24 @@
],
);
+ const tooltipRouteKey = route.key;
+ const { hideTooltip, dismissTooltip } = useTooltipActions(
+ navigation,
+ tooltipRouteKey,
+ );
+
const onPressDefaultEmoji = React.useCallback(
(emoji: string) => {
sendReaction(emoji);
- setHideTooltip(true);
+ dismissTooltip();
},
- [sendReaction, setHideTooltip],
+ [sendReaction, dismissTooltip],
);
const onPressEmojiKeyboardButton = React.useCallback(() => {
- showEmojiKeyboard.value = true;
- setHideTooltip(true);
- }, [setHideTooltip, showEmojiKeyboard]);
+ openEmojiPicker();
+ hideTooltip();
+ }, [openEmojiPicker, hideTooltip]);
const defaultEmojis = React.useMemo(() => {
const defaultEmojisData = ['❤️', '😆', '😮', '😠', '👍'];
diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js
--- a/native/chat/robotext-message-tooltip-button.react.js
+++ b/native/chat/robotext-message-tooltip-button.react.js
@@ -1,15 +1,15 @@
// @flow
import * as React from 'react';
-import Animated, { type SharedValue } from 'react-native-reanimated';
+import Animated from 'react-native-reanimated';
import EmojiPicker from 'rn-emoji-keyboard';
import { localIDPrefix } from 'lib/shared/message-utils';
import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils';
-import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { useSelector } from '../redux/redux-utils';
+import { useTooltipActions } from '../tooltip/tooltip-hooks';
import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerRobotextMessage } from './inner-robotext-message.react';
@@ -31,17 +31,9 @@
+route: TooltipRoute<'RobotextMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
};
function RobotextMessageTooltipButton(props: Props): React.Node {
- const {
- navigation,
- progress,
- isOpeningSidebar,
- setHideTooltip,
- showEmojiKeyboard,
- } = props;
+ const { navigation, route, progress, isOpeningSidebar } = props;
const windowWidth = useSelector(state => state.dimensions.width);
@@ -53,12 +45,7 @@
setSidebarInputBarHeight(height);
}, []);
- const {
- item,
- verticalBounds,
- initialCoordinates,
- margin,
- } = props.route.params;
+ const { item, verticalBounds, initialCoordinates, margin } = route.params;
const { style: messageContainerStyle } = useAnimatedMessageTooltipButton({
sourceMessage: item,
@@ -122,6 +109,11 @@
margin,
});
+ const [emojiPickerOpen, setEmojiPickerOpen] = React.useState<boolean>(false);
+ const openEmojiPicker = React.useCallback(() => {
+ setEmojiPickerOpen(true);
+ }, []);
+
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
@@ -129,8 +121,9 @@
return (
<ReactionSelectionPopover
- setHideTooltip={setHideTooltip}
- showEmojiKeyboard={showEmojiKeyboard}
+ navigation={navigation}
+ route={route}
+ openEmojiPicker={openEmojiPicker}
reactionSelectionPopoverContainerStyle={
reactionSelectionPopoverPosition
}
@@ -138,26 +131,25 @@
/>
);
}, [
+ navigation,
+ route,
+ openEmojiPicker,
canCreateReactionFromMessage,
reactionSelectionPopoverPosition,
sendReaction,
- setHideTooltip,
- showEmojiKeyboard,
]);
+ const tooltipRouteKey = route.key;
+ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
+
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
- setHideTooltip(true);
+ dismissTooltip();
},
- [sendReaction, setHideTooltip],
+ [sendReaction, dismissTooltip],
);
- const onCloseEmojiPicker = React.useCallback(() => {
- showEmojiKeyboard.value = false;
- navigation.goBackOnce();
- }, [navigation, showEmojiKeyboard]);
-
return (
<>
<Animated.View style={messageContainerStyle}>
@@ -174,8 +166,8 @@
</Animated.View>
<EmojiPicker
onEmojiSelected={onEmojiSelected}
- open={showEmojiKeyboard.value}
- onClose={onCloseEmojiPicker}
+ open={emojiPickerOpen}
+ onClose={dismissTooltip}
/>
</>
);
diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js
--- a/native/chat/text-message-tooltip-button.react.js
+++ b/native/chat/text-message-tooltip-button.react.js
@@ -1,15 +1,15 @@
// @flow
import * as React from 'react';
-import Animated, { type SharedValue } from 'react-native-reanimated';
+import Animated from 'react-native-reanimated';
import EmojiPicker from 'rn-emoji-keyboard';
import { localIDPrefix } from 'lib/shared/message-utils';
import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils';
-import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { useSelector } from '../redux/redux-utils';
+import { useTooltipActions } from '../tooltip/tooltip-hooks';
import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerTextMessage } from './inner-text-message.react';
@@ -33,17 +33,9 @@
+route: TooltipRoute<'TextMessageTooltipModal'>,
+progress: Node,
+isOpeningSidebar: boolean,
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
};
function TextMessageTooltipButton(props: Props): React.Node {
- const {
- navigation,
- progress,
- isOpeningSidebar,
- setHideTooltip,
- showEmojiKeyboard,
- } = props;
+ const { navigation, route, progress, isOpeningSidebar } = props;
const windowWidth = useSelector(state => state.dimensions.width);
@@ -55,12 +47,7 @@
setSidebarInputBarHeight(height);
}, []);
- const {
- item,
- verticalBounds,
- initialCoordinates,
- margin,
- } = props.route.params;
+ const { item, verticalBounds, initialCoordinates, margin } = route.params;
const {
style: messageContainerStyle,
@@ -137,6 +124,11 @@
margin,
});
+ const [emojiPickerOpen, setEmojiPickerOpen] = React.useState<boolean>(false);
+ const openEmojiPicker = React.useCallback(() => {
+ setEmojiPickerOpen(true);
+ }, []);
+
const reactionSelectionPopover = React.useMemo(() => {
if (!canCreateReactionFromMessage) {
return null;
@@ -144,8 +136,9 @@
return (
<ReactionSelectionPopover
- setHideTooltip={setHideTooltip}
- showEmojiKeyboard={showEmojiKeyboard}
+ navigation={navigation}
+ route={route}
+ openEmojiPicker={openEmojiPicker}
reactionSelectionPopoverContainerStyle={
reactionSelectionPopoverPosition
}
@@ -153,26 +146,25 @@
/>
);
}, [
+ navigation,
+ route,
+ openEmojiPicker,
canCreateReactionFromMessage,
reactionSelectionPopoverPosition,
sendReaction,
- setHideTooltip,
- showEmojiKeyboard,
]);
+ const tooltipRouteKey = route.key;
+ const { dismissTooltip } = useTooltipActions(navigation, tooltipRouteKey);
+
const onEmojiSelected = React.useCallback(
emoji => {
sendReaction(emoji.emoji);
- setHideTooltip(true);
+ dismissTooltip();
},
- [sendReaction, setHideTooltip],
+ [sendReaction, dismissTooltip],
);
- const onCloseEmojiPicker = React.useCallback(() => {
- showEmojiKeyboard.value = false;
- navigation.goBackOnce();
- }, [navigation, showEmojiKeyboard]);
-
return (
<MessageListContextProvider threadID={threadID}>
<SidebarInputBarHeightMeasurer
@@ -198,8 +190,8 @@
</Animated.View>
<EmojiPicker
onEmojiSelected={onEmojiSelected}
- open={showEmojiKeyboard.value}
- onClose={onCloseEmojiPicker}
+ open={emojiPickerOpen}
+ onClose={dismissTooltip}
/>
</MessageListContextProvider>
);
diff --git a/native/tooltip/tooltip-context.react.js b/native/tooltip/tooltip-context.react.js
--- a/native/tooltip/tooltip-context.react.js
+++ b/native/tooltip/tooltip-context.react.js
@@ -24,6 +24,7 @@
+maxOptionsToDisplay: number,
+visibleEntryIDs: ?$ReadOnlyArray<string>,
+cancel: () => mixed,
+ +hideTooltip: () => mixed,
+children: React.Node,
};
function TooltipContextProvider(props: ProviderProps): React.Node {
@@ -72,9 +73,11 @@
optionsRef.current = optionsRef.current.filter(option => option.id !== id);
}, []);
- const { cancel } = props;
+ const { cancel, hideTooltip } = props;
const { showActionSheetWithOptions } = useActionSheet();
const showActionSheet = React.useCallback(() => {
+ hideTooltip();
+
const options = optionsRef.current;
const optionsToDisplay = options.filter(option =>
@@ -131,6 +134,7 @@
onPressAction,
);
}, [
+ hideTooltip,
maxOptionsToDisplay,
visibleEntryIDsSet,
cancel,
diff --git a/native/tooltip/tooltip-hooks.js b/native/tooltip/tooltip-hooks.js
new file mode 100644
--- /dev/null
+++ b/native/tooltip/tooltip-hooks.js
@@ -0,0 +1,39 @@
+// @flow
+
+import * as React from 'react';
+
+import type { AppNavigationProp } from '../navigation/app-navigator.react';
+import type { TooltipModalParamList } from '../navigation/route-names';
+
+type TooltipActions = {
+ // Hiding will keep the Tooltip ReactNav screen open, which means that the
+ // background will still be dimmed. But it will hide the actual tooltip menu.
+ +hideTooltip: () => void,
+ // Dismiss the tooltip will dismiss the ReactNav screen. This will start the
+ // OverlayNavigator animation to dismiss the screen.
+ +dismissTooltip: () => void,
+};
+function useTooltipActions<RouteName: $Keys<TooltipModalParamList>>(
+ navigation: AppNavigationProp<RouteName>,
+ tooltipRouteKey: string,
+): TooltipActions {
+ const { clearOverlayModals, setRouteParams } = navigation;
+
+ const hideTooltip = React.useCallback(() => {
+ setRouteParams(tooltipRouteKey, { hideTooltip: true });
+ }, [setRouteParams, tooltipRouteKey]);
+
+ const dismissTooltip = React.useCallback(() => {
+ clearOverlayModals([tooltipRouteKey]);
+ }, [clearOverlayModals, tooltipRouteKey]);
+
+ return React.useMemo(
+ () => ({
+ hideTooltip,
+ dismissTooltip,
+ }),
+ [hideTooltip, dismissTooltip],
+ );
+}
+
+export { useTooltipActions };
diff --git a/native/tooltip/tooltip-item.react.js b/native/tooltip/tooltip-item.react.js
--- a/native/tooltip/tooltip-item.react.js
+++ b/native/tooltip/tooltip-item.react.js
@@ -20,7 +20,7 @@
type Props = {
...TooltipItemBaseProps,
+containerStyle?: ViewStyle,
- +closeTooltip: () => mixed,
+ +closeTooltip?: () => mixed,
};
function TooltipItem(props: Props): React.Node {
const tooltipContext = React.useContext(TooltipContext);
@@ -45,7 +45,7 @@
const onPress = React.useCallback(() => {
onPressItem();
- closeTooltip();
+ closeTooltip?.();
}, [onPressItem, closeTooltip]);
if (!shouldRender) {
diff --git a/native/tooltip/tooltip.react.js b/native/tooltip/tooltip.react.js
--- a/native/tooltip/tooltip.react.js
+++ b/native/tooltip/tooltip.react.js
@@ -10,15 +10,7 @@
Platform,
Keyboard,
} from 'react-native';
-import Animated, {
- SlideInDown,
- SlideOutDown,
- runOnJS,
- useSharedValue,
- type SharedValue,
-} from 'react-native-reanimated';
-
-import type { SetState } from 'lib/types/hook-types';
+import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated';
import { ChatContext, type ChatContextType } from '../chat/chat-context';
import SWMansionIcon from '../components/swmansion-icon.react';
@@ -59,6 +51,7 @@
+margin?: number,
+visibleEntryIDs?: $ReadOnlyArray<string>,
+chatInputBarHeight?: number,
+ +hideTooltip?: boolean,
};
export type TooltipRoute<RouteName: $Keys<TooltipModalParamList>> = RouteProp<
TooltipModalParamList,
@@ -73,8 +66,6 @@
...Base,
+progress: Node,
+isOpeningSidebar: boolean,
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
};
type TooltipProps<Base> = {
...Base,
@@ -82,11 +73,6 @@
+dimensions: DimensionsInfo,
+overlayContext: ?OverlayContextType,
+chatContext: ?ChatContextType,
- +actionSheetShown: SharedValue<boolean>,
- +hideTooltip: boolean,
- +setHideTooltip: SetState<boolean>,
- +showEmojiKeyboard: SharedValue<boolean>,
- +exitAnimationWorklet: (finished: boolean) => void,
+styles: typeof unboundStyles,
+tooltipContext: TooltipContextType,
+closeTooltip: () => mixed,
@@ -301,11 +287,6 @@
dimensions,
overlayContext,
chatContext,
- actionSheetShown,
- hideTooltip,
- setHideTooltip,
- showEmojiKeyboard,
- exitAnimationWorklet,
styles,
tooltipContext,
closeTooltip,
@@ -335,7 +316,6 @@
onPress={this.onPressMore}
renderIcon={this.renderMoreIcon}
containerStyle={tooltipContainerStyle}
- closeTooltip={this.props.closeTooltip}
key="more"
/>,
);
@@ -376,16 +356,13 @@
...navAndRouteForFlow,
progress: position,
isOpeningSidebar,
- setHideTooltip,
- showEmojiKeyboard,
};
const itemsStyles = [styles.items, styles.itemsFixed];
const animationDelay = Platform.OS === 'ios' ? 200 : 500;
const enterAnimation = SlideInDown.delay(animationDelay);
-
- const exitAnimation = SlideOutDown.withCallback(exitAnimationWorklet);
+ const exitAnimation = SlideOutDown;
let tooltip = null;
@@ -402,8 +379,7 @@
);
} else if (
this.tooltipLocation === 'fixed' &&
- !hideTooltip &&
- !showEmojiKeyboard.value
+ !this.props.route.params.hideTooltip
) {
tooltip = (
<AnimatedView
@@ -438,8 +414,6 @@
onPressMore = () => {
Keyboard.dismiss();
- this.props.actionSheetShown.value = true;
- this.props.setHideTooltip(true);
this.props.tooltipContext.showActionSheet();
};
@@ -467,45 +441,24 @@
}
};
}
- function ConnectedTooltip(props: BaseTooltipPropsType) {
+ function ConnectedTooltip(props) {
const dimensions = useSelector(state => state.dimensions);
const overlayContext = React.useContext(OverlayContext);
const chatContext = React.useContext(ChatContext);
- const actionSheetShown = useSharedValue(false);
- const [hideTooltip, setHideTooltip] = React.useState<boolean>(false);
-
- const showEmojiKeyboard = useSharedValue(false);
-
- const { goBackOnce } = props.navigation;
- const goBackCallback = React.useCallback(() => {
- if (!actionSheetShown.value) {
- goBackOnce();
- }
- }, [actionSheetShown.value, goBackOnce]);
-
- const exitAnimationWorklet = React.useCallback(
- finished => {
- 'worklet';
- if (finished) {
- runOnJS(goBackCallback)();
- }
- },
- [goBackCallback],
- );
-
const { params } = props.route;
const { tooltipLocation } = params;
const isFixed = tooltipLocation === 'fixed';
+ const { hideTooltip, ...rest } = props;
+
+ const { goBackOnce } = props.navigation;
const closeTooltip = React.useCallback(() => {
- if (isFixed && !actionSheetShown.value) {
- setHideTooltip(true);
- } else {
- goBackOnce();
+ goBackOnce();
+ if (isFixed) {
+ hideTooltip();
}
- showEmojiKeyboard.value = false;
- }, [isFixed, actionSheetShown.value, goBackOnce, showEmojiKeyboard]);
+ }, [isFixed, hideTooltip, goBackOnce]);
const styles = useStyles(unboundStyles);
const boundTooltipItem = React.useCallback(
@@ -528,15 +481,10 @@
invariant(tooltipContext, 'TooltipContext should be set in Tooltip');
return (
<Tooltip
- {...props}
+ {...rest}
dimensions={dimensions}
overlayContext={overlayContext}
chatContext={chatContext}
- actionSheetShown={actionSheetShown}
- hideTooltip={hideTooltip}
- setHideTooltip={setHideTooltip}
- showEmojiKeyboard={showEmojiKeyboard}
- exitAnimationWorklet={exitAnimationWorklet}
styles={styles}
tooltipContext={tooltipContext}
closeTooltip={closeTooltip}
@@ -547,13 +495,21 @@
function MemoizedTooltip(props: BaseTooltipPropsType) {
const { visibleEntryIDs } = props.route.params;
const { goBackOnce } = props.navigation;
+
+ const { setParams } = props.navigation;
+ const hideTooltip = React.useCallback(() => {
+ const paramsUpdate: any = { hideTooltip: true };
+ setParams(paramsUpdate);
+ }, [setParams]);
+
return (
<TooltipContextProvider
maxOptionsToDisplay={4}
visibleEntryIDs={visibleEntryIDs}
cancel={goBackOnce}
+ hideTooltip={hideTooltip}
>
- <ConnectedTooltip {...props} />
+ <ConnectedTooltip {...props} hideTooltip={hideTooltip} />
</TooltipContextProvider>
);
}

File Metadata

Mime Type
text/plain
Expires
Wed, Jan 14, 9:51 AM (4 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5930760
Default Alt Text
D6658.1768384305.diff (22 KB)

Event Timeline