diff --git a/src/satori/protocol.ts b/src/satori/protocol.ts index 539b1a8..832dfab 100644 --- a/src/satori/protocol.ts +++ b/src/satori/protocol.ts @@ -1,3 +1,4 @@ +import { Alert } from 'react-native' import { ConnectionInfo } from './connection' import Element from './element' import { Dict } from 'cosmokit' @@ -209,6 +210,11 @@ const fixArrays = (obj: object) => { } } +const satoriError = (message: string) => { + Alert.alert('Satori Error', message) + // throw new Error(message) +} + /* HTTP API 这是一套 HTTP RPC 风格的 API,所有 URL 的形式均为 /{path}/{version}/{resource}.{method}。其中,path 为部署路径 (可以为空),version 为 API 的版本号,resource 是资源类型,method 为方法名。 @@ -234,11 +240,11 @@ export const callMethodAsync = (method: string, args: object, connectionInfo: Co // Verify fields const methodInfo = Methods[method] if (!methodInfo) { - throw new Error(`Unknown method ${method}`) + satoriError(`Unknown method ${method}`) } for (const key in args) { if (!methodInfo.fields.some(field => field.name === key)) { - throw new Error(`Redundant field ${key} in args`) + satoriError(`Redundant field ${key} in args`) } } @@ -256,7 +262,7 @@ export const callMethodAsync = (method: string, args: object, connectionInfo: Co console.log('call satori', method) return fetch(url, { method: 'POST', headers, body }).then(res => { if (!res.ok) { - throw new Error(`HTTP ${res.status}: ${res.statusText} (${httpCodeTips[res.status]})`) + satoriError(`HTTP ${res.status}: ${res.statusText} (${httpCodeTips[res.status]})`) } return res.text() }).then(v => { @@ -264,7 +270,7 @@ export const callMethodAsync = (method: string, args: object, connectionInfo: Co console.log(method, '=> response', v) return fixArrays(convertSnakeObjectToCamel(JSON.parse(v))) } catch (e) { - throw new Error(`Failed to parse response: ${v}`) + satoriError(`Failed to parse response: ${v}`) } }) } diff --git a/src/screens/Chat.tsx b/src/screens/Chat.tsx index c180504..5ae9398 100644 --- a/src/screens/Chat.tsx +++ b/src/screens/Chat.tsx @@ -11,6 +11,7 @@ import { elementRendererMap, renderElement, elementToObject, toPreviewString } f import React from "react" import Clipboard from "@react-native-clipboard/clipboard" import { create } from "zustand" +import Animated, { Easing, FadeIn, Keyframe, LinearTransition, useSharedValue, withTiming } from "react-native-reanimated" const useReplyTo = create<{ replyTo: SaMessage | null, @@ -23,7 +24,7 @@ const useReplyTo = create<{ const Message = memo(({ message }: { message: SaMessage }) => { const content = useMemo(() => Element.parse(message.content).map(elementToObject), [message]); const login = useLogin() - const isSelf = login.selfId === message.user?.id + const isSelf = login?.selfId === message.user?.id const [menuVisible, setMenuVisible] = useState(false) const [menuAnchor, setMenuAnchor] = useState<{ x: number, @@ -34,81 +35,104 @@ const Message = memo(({ message }: { message: SaMessage }) => { const { setReplyTo } = useReplyTo() - return { - setMenuAnchor({ - x: e.nativeEvent.pageX, - y: e.nativeEvent.pageY - }) - setMenuVisible(true) - }}> - <> + const animMaxHeight = useSharedValue(0); - - {message.user ? <> - {message.user.name ?? message.user.id} : Unknown user} - - - { - content.map(renderElement) - } - - - setMenuVisible(false)} - > - { - setMenuVisible(false) - - await satori.bot.createMessage(message.channel.id, message.content, message.guild.id) - }} title="+1" /> - { - setMenuVisible(false) - - const content = Element.parse(message.content) - const text = content.map(v => v.attrs?.text).filter(v => v).join(' ') - - Clipboard.setString(text) - ToastAndroid.show('已复制', ToastAndroid.SHORT) - }} title="复制" /> - { - setMenuVisible(false) - - setMsgStore(msgStore => { - msgStore[message.channel.id] = msgStore[message.channel.id].filter(v => v.id !== message.id) - return { ...msgStore } - }) - - ToastAndroid.show('已删除', ToastAndroid.SHORT) - }} title="删除" /> - { - setMenuVisible(false) - - setReplyTo(message) - }} title="回复" /> - { - setMenuVisible(false) - const inspect = v => JSON.stringify(v, null, 4) - Alert.alert('消息信息', - `Sender ${inspect(message.user)} + useEffect(() => { + if ((Date.now() - message.timestamp * 1000) < 1000) { + animMaxHeight.value = withTiming(500, { + duration: 300, + easing: Easing.linear + }) + + setTimeout(() => { + animMaxHeight.value = 2000 + }, 1000) + } + else + animMaxHeight.value = 2000 + }, [message]) + + return + { + setMenuAnchor({ + x: e.nativeEvent.pageX, + y: e.nativeEvent.pageY + }) + setMenuVisible(true) + }}> + <> + + {message.user ? <> + {message.user.name ?? message.user.id} : Unknown user} + + + { + content.map(renderElement) + } + + + setMenuVisible(false)} + > + { + setMenuVisible(false) + + await satori.bot.createMessage(message.channel.id, message.content, message.guild.id) + }} title="+1" /> + { + setMenuVisible(false) + + const content = Element.parse(message.content) + const text = content.map(v => v.attrs?.text).filter(v => v).join(' ') + + Clipboard.setString(text) + ToastAndroid.show('已复制', ToastAndroid.SHORT) + }} title="复制" /> + { + setMenuVisible(false) + + setMsgStore(msgStore => { + msgStore[message.channel.id] = msgStore[message.channel.id].filter(v => v.id !== message.id) + return { ...msgStore } + }) + + ToastAndroid.show('已删除', ToastAndroid.SHORT) + }} title="删除" /> + { + setMenuVisible(false) + + setReplyTo(message) + }} title="回复" /> + { + setMenuVisible(false) + const inspect = v => JSON.stringify(v, null, 4) + Alert.alert('消息信息', + `Sender ${inspect(message.user)} Channel ${inspect(message.channel)} Content ${inspect(content)}`) - }} title="详细信息" /> - - - + }} title="详细信息" /> + + + + }) export const Chat = ({