mobile: fix attachment downloads failing on mobile

This commit is contained in:
Ammar Ahmed
2024-11-23 16:39:08 +05:00
committed by Ammar Ahmed
parent 48459efdb1
commit 4ee2923181
16 changed files with 113 additions and 656 deletions

View File

@@ -224,6 +224,13 @@ export default async function downloadAttachment(
}
try {
useAttachmentStore.getState().setDownloading({
groupId: options.groupId || attachment.hash,
current: 0,
total: 1,
filename: attachment.filename
});
await db
.fs()
.downloadFile(
@@ -231,6 +238,15 @@ export default async function downloadAttachment(
attachment.hash,
attachment.chunkSize
);
useAttachmentStore.getState().setDownloading({
groupId: options.groupId || attachment.hash,
current: 1,
total: 1,
filename: attachment.filename,
success: true
});
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
@@ -301,6 +317,14 @@ export default async function downloadAttachment(
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
}
useAttachmentStore.getState().setDownloading({
groupId: options.groupId || attachment.hash,
current: 0,
total: 0,
filename: attachment.filename,
success: false
});
DatabaseLogger.error(e);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {

View File

@@ -63,9 +63,11 @@ const Actions = ({
attachment,
close,
setAttachments,
fwdRef
fwdRef,
context
}: {
attachment: Attachment;
context: string;
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
close?: () => void;
fwdRef: RefObject<ActionSheetRef>;
@@ -79,7 +81,6 @@ const Actions = ({
const [loading, setLoading] = useState<{
name?: string;
}>({});
const actions = [
{
name: strings.network.download(),
@@ -88,7 +89,7 @@ const Actions = ({
await db.fs().cancel(attachment.hash);
useAttachmentStore.getState().remove(attachment.hash);
}
downloadAttachment(attachment.hash, false);
downloadAttachment(attachment.hash, context === "global");
fwdRef.current?.hide();
},
icon: "download"
@@ -375,6 +376,7 @@ Actions.present = (
setAttachments={set}
close={close}
attachment={attachment}
context={context || "global"}
/>
)
});

View File

@@ -206,7 +206,7 @@ export const AttachmentDialog = ({
errorOnly={currentFilter === "errors"}
attachments={attachments}
id={index}
context="global"
context={!isSheet ? "global" : "attachments-list"}
/>
);

View File

@@ -19,9 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
//@ts-ignore
import { useThemeColors } from "@notesnook/theme";
import Menu from "react-native-reanimated-material-menu";
import { Menu } from "react-native-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import {
HeaderRightButton,
@@ -97,14 +96,16 @@ export const RightMenus = ({
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -40
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
@@ -116,9 +117,10 @@ export const RightMenus = ({
{headerRightButtons.map((item) => (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain"
buttonType={{
@@ -127,6 +129,7 @@ export const RightMenus = ({
key={item.title}
title={item.title}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();

View File

@@ -26,7 +26,7 @@ import {
Platform,
View
} from "react-native";
import Menu from "react-native-reanimated-material-menu/src/Menu";
import { Menu } from "react-native-material-menu";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -101,11 +101,14 @@ export const SelectionHeader = React.memo(
};
const deleteItem = async () => {
if (!type) return;
presentDialog({
title: strings.doActions.delete[type](selectedItemsList.length),
paragraph: strings.actionConfirmations.delete[type](
selectedItemsList.length
),
title: strings.doActions.delete[
type as keyof typeof strings.doActions.delete
](selectedItemsList.length),
paragraph: strings.actionConfirmations.delete[
type as keyof typeof strings.doActions.delete
](selectedItemsList.length),
positiveText: strings.delete(),
negativeText: strings.cancel(),
positivePress: async () => {
@@ -218,14 +221,16 @@ export const SelectionHeader = React.memo(
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -20
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
@@ -319,9 +324,10 @@ export const SelectionHeader = React.memo(
!item.visible ? null : (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain"
buttonType={{
@@ -331,6 +337,7 @@ export const SelectionHeader = React.memo(
key={item.title}
title={item.title}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();

View File

@@ -43,7 +43,7 @@
"@lingui/react": "4.11.2",
"@lingui/core": "4.11.2",
"react-native-check-version": "^1.3.0",
"react-native-reanimated-material-menu": "github:ammarahm-ed/react-native-reanimated-material-menu"
"react-native-material-menu": "^2.0.0"
},
"sideEffects": false
}

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import { View } from "react-native";
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
import { Menu, MenuItem } from "react-native-material-menu";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { Dialog } from "../../../components/dialog";
import { Pressable } from "../../../components/ui/pressable";
@@ -146,7 +146,7 @@ export function SettingsPicker<T>({
onChange(item);
}
}}
underlayColor={colors.primary.hover}
pressColor={colors.primary.hover}
style={{
backgroundColor: compareValue(currentValue, item)
? colors.selected.background

View File

@@ -123,6 +123,7 @@ export type PresentSheetOptions = {
};
export function presentSheet(data: Partial<PresentSheetOptions>) {
console.log("PRESENTING...");
eSendEvent(eOpenSheet, data);
}

View File

@@ -217,7 +217,7 @@ module.exports = (env) => {
/node_modules(.*[/\\])+@tanstack[/\\]react-query/,
/node_modules(.*[/\\])+@trpc[/\\]react-query/,
/node_modules(.*[/\\])+katex/,
/node_modules(.*[/\\])+mime/,
/node_modules(.*[/\\])+react-native-material-menu/,
/node_modules(.*[/\\])+@notesnook[/\\]core/,
/node_modules(.*[/\\])+whatwg-url-without-unicode/,
/node_modules(.*[/\\])+whatwg-url/,

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.0.21",
"version": "3.0.22",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.0.21",
"version": "3.0.22",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -14,7 +14,6 @@
"app/"
],
"dependencies": {
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/editor": "file:../../packages/editor",
@@ -24,9 +23,9 @@
"@notesnook/theme": "file:../../packages/theme",
"@notesnook/themes-server": "file:../../servers/themes",
"diffblazer": "^1.0.1",
"mime": "^2.6.0",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-actions-sheet": "^0.9.7"
"react-native": "0.74.5"
},
"devDependencies": {
"fonteditor-core": "^2.1.11",
@@ -28912,9 +28911,9 @@
"react-native-format-currency": "0.0.5",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-material-menu": "^2.0.0",
"react-native-progress": "^5.0.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-reanimated-material-menu": "github:ammarahm-ed/react-native-reanimated-material-menu",
"react-native-reanimated-progress-bar": "1.0.1",
"react-native-swiper-flatlist": "3.2.2",
"react-native-wheel-color-picker": "^1.3.1",
@@ -28997,6 +28996,7 @@
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
"@ammarahmed/react-native-share-extension": "^2.6.0",
"@ammarahmed/react-native-sodium": "1.5.6",
"@bam.tech/react-native-image-resizer": "3.0.5",
@@ -41679,7 +41679,8 @@
},
"node_modules/mime": {
"version": "2.6.0",
"license": "MIT",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"bin": {
"mime": "cli.js"
},
@@ -43475,6 +43476,15 @@
"version": "4.0.5",
"license": "MIT"
},
"node_modules/react-native-material-menu": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-native-material-menu/-/react-native-material-menu-2.0.0.tgz",
"integrity": "sha512-SmO9PLE3E469EPbVWZqvdu6JGPPZIm7YjqDcWs2PPoY0k7w2V9tFo3BmmLXNzNZDCVCAi+PPSsL7h/5WkfHcSg==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-mmkv-storage": {
"version": "0.10.2",
"license": "MIT",
@@ -43590,14 +43600,6 @@
"react-native": "*"
}
},
"node_modules/react-native-reanimated-material-menu": {
"version": "2.0.0",
"resolved": "git+ssh://git@github.com/ammarahm-ed/react-native-reanimated-material-menu.git#b1b19ba9e87333c76eb8abc3dc8377fe3ddd8bfc",
"peerDependencies": {
"react": ">= 16.3.0",
"react-native": ">= 0.54.0"
}
},
"node_modules/react-native-reanimated-progress-bar": {
"version": "1.0.1",
"license": "MIT",

View File

@@ -45,7 +45,6 @@
"@notesnook/themes-server": "file:../../servers/themes",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-actions-sheet": "^0.9.7"
"react-native": "0.74.5"
}
}
}

View File

@@ -0,0 +1,19 @@
diff --git a/node_modules/react-native-material-menu/dist/Menu.js b/node_modules/react-native-material-menu/dist/Menu.js
index 64fbf13..c7fcee0 100644
--- a/node_modules/react-native-material-menu/dist/Menu.js
+++ b/node_modules/react-native-material-menu/dist/Menu.js
@@ -132,10 +132,13 @@ class Menu extends react_1.default.Component {
else if (left < SCREEN_INDENT) {
left = SCREEN_INDENT;
}
+ console.log(top, windowHeight - menuHeight - SCREEN_INDENT);
// Flip by Y axis if menu hits bottom screen border
if (top > windowHeight - menuHeight - SCREEN_INDENT) {
+ const diff = top - (windowHeight - menuHeight - SCREEN_INDENT);
+ const fraction = (diff / menuHeight);
transforms.push({
- translateY: react_native_1.Animated.multiply(menuSizeAnimation.y, -1),
+ translateY: react_native_1.Animated.multiply(menuSizeAnimation.y, -(fraction + 1)),
});
top = windowHeight - SCREEN_INDENT;
top = Math.min(windowHeight - SCREEN_INDENT, top + buttonHeight);

View File

@@ -1,600 +0,0 @@
diff --git a/node_modules/react-native-reanimated-material-menu/src/Menu.js b/node_modules/react-native-reanimated-material-menu/src/Menu.js
deleted file mode 100644
index 69065f0..0000000
--- a/node_modules/react-native-reanimated-material-menu/src/Menu.js
+++ /dev/null
@@ -1,268 +0,0 @@
-import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import {
- Dimensions, I18nManager, Modal,
- Platform,
- StatusBar,
- StyleSheet,
- TouchableWithoutFeedback,
- View
-} from 'react-native';
-import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
-
-const STATES = {
- HIDDEN: 'HIDDEN',
- ANIMATING: 'ANIMATING',
- SHOWN: 'SHOWN',
-};
-
-const EASING = Easing.bezier(0.4, 0, 0.2, 1);
-const SCREEN_INDENT = 8;
-
-const Menu = ({ animationDuration = 300, ...props }, ref) => {
- const container = useRef();
- const [state, setState] = useState({
- menuState: STATES.HIDDEN,
-
- top: 0,
- left: 0,
-
- menuWidth: 0,
- menuHeight: 0,
-
- buttonWidth: 0,
- buttonHeight: 0,
- });
- const menuSizeXAnimation = useSharedValue(0);
- const menuSizeYAnimation = useSharedValue(0);
- const opacityAnimation = useSharedValue(0);
-
- const _onMenuLayout = e => {
- if (state.menuState === STATES.ANIMATING) {
- return;
- }
- const { width, height } = e.nativeEvent.layout;
- setState(state => {
- return {
- ...state,
- menuState: STATES.ANIMATING,
- menuWidth: width,
- menuHeight: height,
- }
- }
-
- );
- };
-
- useEffect(() => {
- if (menuState === STATES.ANIMATING) {
-
- menuSizeXAnimation.value = withTiming(state.menuWidth, {
- duration: animationDuration * 2,
- easing: EASING
- })
- menuSizeYAnimation.value = withTiming(state.menuHeight, {
- duration: animationDuration,
- easing: EASING
- })
- opacityAnimation.value = withTiming(1, {
- duration: animationDuration + 25,
- easing: EASING
- })
- }
- }, [state])
-
- const _onDismiss = () => {
- if (props.onHidden) {
- props.onHidden();
- }
- };
- useImperativeHandle(ref, () => {
- return {
- show: () => {
- container.current.measureInWindow((left, top, buttonWidth, buttonHeight) => {
-
- setState(state => {
-
- return {
- ...state,
- buttonHeight,
- buttonWidth,
- left,
- menuState: STATES.SHOWN,
- top,
- }
- });
- });
- },
-
- hide: hide
- }
-
- }, [hide, container])
-
- const hide = onHidden => {
- opacityAnimation.value = withTiming(0, {
- duration: animationDuration,
- easing: EASING
- });
- setTimeout(() => {
- setState(
- state => {
- return {
- ...state,
- menuState: STATES.HIDDEN,
- }
- }
- );
- menuSizeXAnimation.value = 0;
- menuSizeYAnimation.value = 0;
- opacityAnimation.value = 0;
-
- onHidden && onHidden();
- if (Platform.OS !== 'ios' && props.onHidden) {
- props.onHidden();
- }
- }, props.animationDuration)
-
- };
-
- // @@ TODO: Rework this
- const _hide = () => {
- hide();
- };
-
-
- const menuSize = useAnimatedStyle(() => {
- return {
- width: menuSizeXAnimation.value,
- height: menuSizeYAnimation.value,
- }
- });
- const shadowMenuContainerStyle = useAnimatedStyle(() => {
- return {
- opacity: opacityAnimation.value,
- };
- }, [state])
-
- const { isRTL } = I18nManager;
- const dimensions = Dimensions.get('window');
- const { width: windowWidth } = dimensions;
- const windowHeight = dimensions.height - (StatusBar.currentHeight || 0);
- const {
- menuWidth,
- menuHeight,
- buttonWidth,
- buttonHeight,
- } = state;
-
- // Adjust position of menu
- let { left, top } = state;
- const transforms = [];
- if (
- (isRTL && left + buttonWidth - menuWidth > SCREEN_INDENT) ||
- (!isRTL && left + menuWidth > windowWidth - SCREEN_INDENT)
- ) {
- transforms.push({
- translateX: menuSizeXAnimation.value * -1,
- });
-
- left = Math.min(windowWidth - SCREEN_INDENT, left + buttonWidth);
- } else if (left < SCREEN_INDENT) {
- left = SCREEN_INDENT;
- }
-
-
- // Flip by Y axis if menu hits bottom screen border
- if (top > windowHeight - menuHeight - SCREEN_INDENT) {
- transforms.push({
- translateY: menuSizeYAnimation.value * -1,
- });
-
- top = windowHeight - SCREEN_INDENT;
- top = Math.min(windowHeight - SCREEN_INDENT, top + buttonHeight);
- } else if (top < SCREEN_INDENT) {
- top = SCREEN_INDENT;
- }
-
- const extraStyles = {
- transform: transforms,
- top,
-
- // Switch left to right for rtl devices
- ...(isRTL ? { right: left } : { left }),
- }
-
- const { menuState } = state;
- const animationStarted = menuState === STATES.ANIMATING;
- const modalVisible = menuState === STATES.SHOWN || animationStarted;
-
- const { testID, button, style, children } = props;
-
- return <View ref={container} collapsable={false} testID={testID}>
- <View>{button}</View>
-
- <Modal
- visible={modalVisible}
- onRequestClose={_hide}
- supportedOrientations={[
- 'portrait',
- 'portrait-upside-down',
- 'landscape',
- 'landscape-left',
- 'landscape-right',
- ]}
- transparent
- onDismiss={_onDismiss}
- >
- <TouchableWithoutFeedback onPress={_hide} accessible={false}>
- <View style={StyleSheet.absoluteFill}>
- <Animated.View
- onLayout={_onMenuLayout}
- style={[
- styles.shadowMenuContainer,
- shadowMenuContainerStyle,
- extraStyles,
- style,
- ]}
- >
- <Animated.View
- style={[styles.menuContainer, animationStarted && menuSize]}
- >
- {children}
- </Animated.View>
- </Animated.View>
- </View>
- </TouchableWithoutFeedback>
- </Modal>
- </View>
-
-}
-
-
-const styles = StyleSheet.create({
- shadowMenuContainer: {
- position: 'absolute',
- backgroundColor: 'white',
- borderRadius: 4,
- opacity: 0,
- overflow: "hidden",
-
- // Shadow
- ...Platform.select({
- ios: {
- shadowColor: 'black',
- shadowOffset: { width: 0.3 * 5, height: 0.5 * 5 },
- shadowOpacity: 0.2,
- shadowRadius: 0.7 * 5,
- },
- android: {
- elevation: 8,
- },
- }),
- },
- menuContainer: {
- overflow: 'hidden',
- },
-});
-
-export default forwardRef(Menu);
diff --git a/node_modules/react-native-reanimated-material-menu/src/Menu.tsx b/node_modules/react-native-reanimated-material-menu/src/Menu.tsx
new file mode 100644
index 0000000..c3bc1c9
--- /dev/null
+++ b/node_modules/react-native-reanimated-material-menu/src/Menu.tsx
@@ -0,0 +1,303 @@
+import React from 'react';
+
+import {
+ Animated,
+ Dimensions,
+ Easing,
+ I18nManager,
+ LayoutChangeEvent,
+ Modal,
+ Platform,
+ ScrollView,
+ StatusBar,
+ StyleSheet,
+ TouchableWithoutFeedback,
+ View,
+ ViewStyle,
+} from 'react-native';
+
+export interface MenuProps {
+ children?: React.ReactNode;
+ anchor?: React.ReactNode;
+ style?: ViewStyle;
+ onRequestClose?(): void;
+ animationDuration?: number;
+ testID?: string;
+ visible?: boolean;
+}
+
+enum States {
+ Hidden,
+ Animating,
+ Shown,
+}
+
+interface State {
+ buttonHeight: number;
+ buttonWidth: number;
+ left: number;
+ menuHeight: number;
+ menuSizeAnimation: Animated.ValueXY;
+ menuState: States;
+ menuWidth: number;
+ opacityAnimation: Animated.Value;
+ top: number;
+}
+
+const EASING = Easing.bezier(0.4, 0, 0.2, 1);
+const SCREEN_INDENT = 8;
+const SCREEN_INDENT_VERTICAL = 80;
+
+class Menu extends React.Component<MenuProps, State> {
+ _container: View | null = null;
+
+ static defaultProps = {
+ animationDuration: 300,
+ };
+
+ constructor(props: MenuProps) {
+ super(props);
+
+ this.state = {
+ menuState: States.Hidden,
+
+ top: 0,
+ left: 0,
+
+ menuWidth: 0,
+ menuHeight: 0,
+
+ buttonWidth: 0,
+ buttonHeight: 0,
+
+ menuSizeAnimation: new Animated.ValueXY({ x: 0, y: 0 }),
+ opacityAnimation: new Animated.Value(0),
+ };
+ }
+
+ componentDidMount() {
+ if (!this.props.visible) {
+ return;
+ }
+
+ this.show();
+ }
+
+ componentDidUpdate(prevProps: MenuProps) {
+ if (prevProps.visible === this.props.visible) {
+ return;
+ }
+
+ if (this.props.visible) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ }
+
+ private setContainerRef = (ref: View) => {
+ this._container = ref;
+ };
+
+ // Start menu animation
+ private onMenuLayout = (e: LayoutChangeEvent) => {
+ if (this.state.menuState === States.Animating) {
+ return;
+ }
+
+ const { width, height } = e.nativeEvent.layout;
+ let timeout:any = 0;
+ this.setState(
+ {
+ menuState: States.Animating,
+ menuWidth: width,
+ menuHeight: height,
+ },
+ () => {
+ Animated.parallel([
+ Animated.timing(this.state.menuSizeAnimation, {
+ toValue: { x: width, y: height },
+ duration: this.props.animationDuration,
+ easing: EASING,
+ useNativeDriver: false,
+ }),
+ Animated.timing(this.state.opacityAnimation, {
+ toValue: 1,
+ duration: this.props.animationDuration,
+ easing: EASING,
+ useNativeDriver: false,
+ }),
+ ]).start(({finished}) => {
+ if (finished) {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ this.setState({
+ menuState: States.Shown
+ })
+ },20)
+
+ }
+ });
+ },
+ );
+ };
+
+ show = () => {
+ this._container?.measureInWindow((left, top, buttonWidth, buttonHeight) => {
+ this.setState({
+ buttonHeight,
+ buttonWidth,
+ left,
+ menuState: States.Shown,
+ top,
+ });
+ });
+ };
+
+ hide = () => {
+ Animated.timing(this.state.opacityAnimation, {
+ toValue: 0,
+ duration: this.props.animationDuration,
+ easing: EASING,
+ useNativeDriver: false,
+ }).start(() => {
+ // Reset state
+ this.setState({
+ menuState: States.Hidden,
+ menuSizeAnimation: new Animated.ValueXY({ x: 0, y: 0 }),
+ opacityAnimation: new Animated.Value(0),
+ });
+ });
+ };
+
+ private onRequestClose = () => {
+ this.props.onRequestClose?.();
+ };
+
+ render() {
+ const { isRTL } = I18nManager;
+
+ const dimensions = Dimensions.get('window');
+ const { width: windowWidth } = dimensions;
+ const windowHeight = dimensions.height - (StatusBar.currentHeight || 0);
+
+ const {
+ menuSizeAnimation,
+ menuWidth,
+ menuHeight,
+ buttonWidth,
+ buttonHeight,
+ opacityAnimation,
+ } = this.state;
+ const menuSize = {
+ width: menuSizeAnimation.x,
+ height: menuSizeAnimation.y,
+ };
+
+ // Adjust position of menu
+ let { left, top } = this.state;
+ const transforms:any[] = [];
+
+ if (
+ (isRTL && left + buttonWidth - menuWidth > SCREEN_INDENT) ||
+ (!isRTL && left + menuWidth > windowWidth - SCREEN_INDENT)
+ ) {
+ transforms.push({
+ translateX: Animated.multiply(menuSizeAnimation.x, -1),
+ } as never);
+
+ left = Math.min(windowWidth - SCREEN_INDENT, left + buttonWidth);
+ } else if (left < SCREEN_INDENT) {
+ left = SCREEN_INDENT;
+ }
+
+ // Flip by Y axis if menu hits bottom screen border
+ if (top > windowHeight - menuHeight - SCREEN_INDENT_VERTICAL) {
+ transforms.push({
+ translateY: Animated.multiply(menuSizeAnimation.y, -1),
+ } as never);
+
+ top = windowHeight - SCREEN_INDENT_VERTICAL;
+ top = Math.min(windowHeight - SCREEN_INDENT_VERTICAL, top + buttonHeight);
+ } else if (top < SCREEN_INDENT_VERTICAL) {
+ top = SCREEN_INDENT_VERTICAL;
+ }
+
+ const shadowMenuContainerStyle = {
+ opacity: opacityAnimation,
+ transform: transforms,
+ maxHeight: 500,
+ top,
+
+ // Switch left to right for rtl devices
+ ...(isRTL ? { right: left } : { left }),
+ };
+ const { menuState } = this.state;
+ const animationStarted = menuState === States.Animating;
+ const modalVisible = menuState === States.Shown || animationStarted;
+
+ const { testID, anchor, style, children } = this.props;
+
+ return (
+ <View ref={this.setContainerRef} collapsable={false} testID={testID}>
+ {anchor}
+
+ <Modal
+ visible={modalVisible}
+ onRequestClose={this.onRequestClose}
+ supportedOrientations={[
+ 'portrait',
+ 'portrait-upside-down',
+ 'landscape',
+ 'landscape-left',
+ 'landscape-right',
+ ]}
+ transparent
+ >
+ <TouchableWithoutFeedback onPress={this.onRequestClose} accessible={false}>
+ <View style={StyleSheet.absoluteFill}>
+ <Animated.View
+ onLayout={this.onMenuLayout}
+ style={[styles.shadowMenuContainer, shadowMenuContainerStyle, style]}
+ >
+ <Animated.View style={[styles.menuContainer, animationStarted && menuSize]}>
+ <ScrollView showsVerticalScrollIndicator={menuState !== States.Animating} >
+ {children}
+ </ScrollView>
+ </Animated.View>
+ </Animated.View>
+ </View>
+ </TouchableWithoutFeedback>
+ </Modal>
+ </View>
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ shadowMenuContainer: {
+ position: 'absolute',
+ backgroundColor: 'white',
+ borderRadius: 4,
+ opacity: 0,
+
+ // Shadow
+ ...Platform.select({
+ ios: {
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.14,
+ shadowRadius: 2,
+ },
+ android: {
+ elevation: 8,
+ },
+ }),
+ },
+ menuContainer: {
+ overflow: 'hidden',
+ },
+});
+
+
+export default Menu
\ No newline at end of file
diff --git a/node_modules/react-native-reanimated-material-menu/src/MenuItem.js b/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
index 120a870..d6459c0 100644
--- a/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
+++ b/node_modules/react-native-reanimated-material-menu/src/MenuItem.js
@@ -14,6 +14,11 @@ const Touchable =
? TouchableNativeFeedback
: TouchableHighlight;
+/**
+ *
+ * @param {any} param0
+ * @returns
+ */
function MenuItem({
children,
disabled,

View File

@@ -16,6 +16,7 @@
"@readme/data-urls": "^3.0.0",
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"dom-serializer": "^2.0.0",
@@ -28,7 +29,7 @@
"katex": "0.16.2",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime": "^4.0.4",
"mime-db": "^1.53.0",
"prismjs": "^1.29.0",
"qclone": "^1.2.0",
"rfdc": "^1.3.0",
@@ -2266,6 +2267,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/mime-db": {
"version": "1.43.5",
"resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.5.tgz",
"integrity": "sha512-/bfTiIUTNPUBnwnYvUxXAre5MhD88jgagLEQiQtIASjU+bwxd8kS/ASDA4a8ufd8m0Lheu6eeMJHEUpLHoJ28A=="
},
"node_modules/@types/node": {
"version": "18.11.9",
"dev": true,
@@ -3447,19 +3453,12 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"node_modules/mime": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
"funding": [
"https://github.com/sponsors/broofa"
],
"license": "MIT",
"bin": {
"mime": "bin/cli.js"
},
"node_modules/mime-db": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
"integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
"engines": {
"node": ">=16"
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {

View File

@@ -68,6 +68,7 @@
"@readme/data-urls": "^3.0.0",
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"dom-serializer": "^2.0.0",
@@ -80,7 +81,7 @@
"katex": "0.16.2",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime": "^4.0.4",
"mime-db": "^1.53.0",
"prismjs": "^1.29.0",
"qclone": "^1.2.0",
"rfdc": "^1.3.0",

View File

@@ -23,11 +23,11 @@ export async function getFileNameWithExtension(
): Promise<string> {
if (!mime || mime === "application/octet-stream") return filename;
const { default: mimeDB } = await import("mime");
const { default: mimeDB } = await import("mime-db");
const extensions = mimeDB.getAllExtensions(mime);
const { extensions } = mimeDB[mime] || {};
if (!extensions || extensions.size === 0) return filename;
if (!extensions || extensions.length === 0) return filename;
for (const ext of extensions) {
if (filename.endsWith(ext)) return filename;