diff --git a/App.jsx b/App.jsx index 6c5f735..c234d2d 100644 --- a/App.jsx +++ b/App.jsx @@ -69,7 +69,7 @@ import * as Clipboard from "expo-clipboard"; import PrivatyModal from "./components/PrivatyModal"; import * as Sentry from "@sentry/react-native"; import { ImageViewerProvider } from "./context/ImageViewProvider"; - +import WebSocketComponent from "./components/Websocket"; import store from "./store"; import { Provider as ReduxProvider } from "react-redux"; @@ -122,7 +122,6 @@ const App = () => { isSignin: false, userToken: null, }); - const authContext = useMemo( () => ({ signIn: async (data, mobilePhone, regionCode) => { @@ -158,6 +157,7 @@ const App = () => { dispatch({ type: "SIGN_OUT" }); } }, + state, inviterCode: inviterCode, checked: checked, setChecked: setChecked, @@ -228,7 +228,25 @@ const App = () => { } prepare(); }, []); - + const handleGetWebsocketData = (data) => { + if (data.d.unread_cnt > 0) { + const { n_type, title } = data.d.notif; + Toast.show({ + type: "info", + text1: `🔔 收到一条${ + n_type === 0 + ? "系统" + : n_type === 1 + ? "审核" + : n_type === 2 + ? "付费" + : "活动" + }通知`, + text2: title, + topOffset: 60, + }); + } + }; //获取用户剪贴板内容 const getClipboardContent = useCallback(async () => { const content = await Clipboard.getStringAsync(); @@ -242,7 +260,6 @@ const App = () => { } setInviterCode(null); }, []); - //登录后获取用户剪贴板内容 useEffect(() => { if (!state.isSignin) return; @@ -911,6 +928,10 @@ const App = () => { setVisible={setIsUpdateModalVisible} data={versionData} /> + diff --git a/components/Websocket/index.jsx b/components/Websocket/index.jsx index a3c1530..24ab903 100644 --- a/components/Websocket/index.jsx +++ b/components/Websocket/index.jsx @@ -2,86 +2,116 @@ import React, { useEffect, useState } from "react"; import { View } from "react-native"; import baseRequest from "../../utils/baseRequest"; import { get } from "../../utils/storeInfo"; +import { getNoticeCount } from "../../store/reducer"; +import { connect } from "react-redux"; import "text-encoding"; -const WebSocketComponent = ({ getData }) => { +var retryInterval = 1000; +const maxInterval = 60000; +let interval = null; +let timer = null; +var messageQueue = []; +let socket = null; +const WebSocketComponent = ({ getData, state, changeCount }) => { const [messages, setMessages] = useState([]); - useEffect(() => { - let socket = null; - let interval = null; - async function connect_socket() { - const appInfo = await get("app_info"); - const base = await baseRequest(); - if (socket && socket.readyState === 1) return; - // 创建WebSocket连接 - if (!socket) { - socket = new WebSocket( - `${process.env.EXPO_PUBLIC_WEBSOCKET_URL}/ws?b_mid=${base.b_mid}&b_did=${base.b_did}&b_dt=1&b_token=${base.b_token}&b_ch${appInfo.b_ch}` - ); - // console.log("------------++++++++++++++++", socket); - socket.onopen = () => { - console.log("WebSocket connected."); - // 可以在这里发送消息到服务器,例如:socket.send('Hello Server!'); - socket.send(JSON.stringify({ t: 1 })); - }; - - // 处理收到的消息 - socket.onmessage = (event) => { - if ( - Object.prototype.toString.call(event.data) === - "[object ArrayBuffer]" - ) { - let enc = new TextDecoder("utf-8"); - const view = new Uint8Array(event.data); - let temp = enc.decode(view); - try { - const data = JSON.parse(temp); - if (data.t === 2 && data.msg.ping_interval) { - socket.send("ping"); - interval = setInterval(() => { - // 发送 ping 给服务器 - socket.send("ping"); - // 响应服务器的 ping - // socket.on("ping", () => { - // socket.senrd("pong"); - // }); - }, data.msg.ping_interval * 1000); - } - if (data.t === 3) { - getData(data.msg); - setMessages((prevMessages) => [...prevMessages, data]); - } - } catch (error) {} - } - }; - - // 连接关闭时触发 - socket.onclose = () => { - clearInterval(interval); - // connect_socket(); - console.log("WebSocket disconnected."); - }; - - // 连接错误时触发 - socket.onerror = (error) => { - console.error("WebSocket error:", error); - }; - } - // 响应服务器的 ping - // socket.on("ping", () => { - // socket.send("pong"); - // }); - } - - connect_socket(); - // 组件卸载时关闭WebSocket连接 return () => { + console.log("卸载了socket"); socket?.readyState === WebSocket.OPEN && socket?.close(); clearInterval(interval); }; }, []); // 空依赖数组表示这个effect只在组件挂载时运行一次 + useEffect(() => { + socket?.close(); + if (state.isSignin) { + connect_socket(); + } else { + // changeCount(0); + clearInterval(interval); + clearTimeout(timer); + socket?.close(); + socket = null; + } + }, [state]); // 空依赖数组表示这个effect只在组件挂载时运行一次 + async function connect_socket() { + const appInfo = await get("app_info"); + const base = await baseRequest(); + if (socket && socket.readyState === 1) return; + // 创建WebSocket连接 + socket = new WebSocket( + `${process.env.EXPO_PUBLIC_WEBSOCKET_URL}/ws?b_mid=${base.b_mid}&b_did=${base.b_did}&b_dt=1&b_token=${base.b_token}&b_ch${appInfo.b_ch}` + ); + socket.onopen = () => { + console.log("WebSocket connected."); + retryInterval = 1000; + // 可以在这里发送消息到服务器,例如:socket.send('Hello Server!'); + if (socket.readyState == WebSocket.OPEN) + socket.send(JSON.stringify({ t: 1 })); + }; + // 处理收到的消息 + socket.onmessage = (event) => { + if ( + Object.prototype.toString.call(event.data) === "[object ArrayBuffer]" + ) { + let enc = new TextDecoder("utf-8"); + const view = new Uint8Array(event.data); + let temp = enc.decode(view); + try { + const data = JSON.parse(temp); + if (data.t === 2 && data.msg.ping_interval) { + if (socket.readyState == WebSocket.OPEN) socket.send("ping"); + interval = setInterval(() => { + // 发送 ping 给服务器 + if (socket.readyState == WebSocket.OPEN) socket.send("ping"); + // 响应服务器的 ping + // socket.on("ping", () => { + // socket.senrd("pong"); + // }); + }, data.msg.ping_interval * 1000); + } + if (data.t === 3) { + getData(data.msg); + changeCount(data.msg.d.unread_cnt); + setMessages((prevMessages) => [...prevMessages, data]); + } + } catch (error) {} + } + }; + + // 连接关闭时触发 + socket.onclose = async () => { + clearTimeout(timer); + const account = await get("account"); + if (state.isSignin && typeof account?.mid === "number") { + timer = setTimeout(() => { + if (!state.isSignin) { + clearTimeout(timer); + return; + } + retryInterval = Math.min(retryInterval * 2, maxInterval); + try { + connect_socket(); + } catch (error) { + console.log(error); + } + }, retryInterval); + } + // changeCount(0); + console.log("WebSocket disconnected."); + }; + + // 连接错误时触发 + socket.onerror = (error) => { + clearTimeout(timer); + socket?.close(); + console.error("WebSocket error:", error); + }; + // 响应服务器的 ping + // socket.on("ping", () => { + // socket.send("pong"); + // }); + } return ( {/* WebSocket Messages @@ -94,4 +124,13 @@ const WebSocketComponent = ({ getData }) => { ); }; -export default WebSocketComponent; +const mapDispatchFromProps = (dispatch) => { + return { + changeCount: (num) => { + // 调用dispatch方法,传递actions + dispatch(getNoticeCount(num)); + }, + }; +}; + +export default connect(null, mapDispatchFromProps)(WebSocketComponent); diff --git a/screeens/HomeTab/index.jsx b/screeens/HomeTab/index.jsx index 17b908f..32272bc 100644 --- a/screeens/HomeTab/index.jsx +++ b/screeens/HomeTab/index.jsx @@ -9,18 +9,15 @@ import Posts from "../Posts"; import Space from "../Space"; import { get } from "../../utils/storeInfo"; import CreatePostModal from "../../components/CreatePostModal"; -import WebSocketComponent from "../../components/Websocket"; +import { connect } from "react-redux"; import Toast from "react-native-toast-message"; import baseRequest from "../../utils/baseRequest"; import { generateSignature } from "../../utils/crypto"; -import { useSelector, useDispatch } from "react-redux"; import { getNoticeCount } from "../../store/reducer"; const Tab = createBottomTabNavigator(); -export default function HomeTab({ navigation, route }) { +function HomeTab({ changeCount, noticeCount, authInfo }) { const [isCreatePostTabVisible, setIsCreatePostTabVisible] = useState(false); - const noticeCount = useSelector((state) => state.noticeCount); - const dispatch = useDispatch(); useEffect(() => { const checkRole = async () => { const account = await get("account"); @@ -51,10 +48,10 @@ export default function HomeTab({ navigation, route }) { }); return; } - dispatch(getNoticeCount(_data.data.total)); + changeCount(_data.data.total); }; checkRole(); - }, [noticeCount]); + }, [noticeCount, authInfo]); const CustomTabBarButton = useCallback(({ children }) => { const [visible, setVisible] = useState(false); return ( @@ -80,26 +77,6 @@ export default function HomeTab({ navigation, route }) { ); }, []); - const handleGetWebsocketData = (data) => { - if (data.d.unread_cnt > 0) { - dispatch(getNoticeCount(data.d.unread_cnt)); - const { n_type, title } = data.d.notif; - Toast.show({ - type: "info", - text1: `🔔 收到一条${ - n_type === 0 - ? "系统" - : n_type === 1 - ? "审核" - : n_type === 2 - ? "付费" - : "活动" - }通知`, - text2: title, - topOffset: 60, - }); - } - }; const EmptyComponent = () => <>; return ( @@ -281,7 +258,26 @@ export default function HomeTab({ navigation, route }) { }} /> - ); } + +// 获取store中的数据的方法 +const mapstateFromProps = (state) => { + // state: store仓库中的数据 + return { + noticeCount: state.noticeCount, + authInfo: state.authInfo, + }; +}; +// 修改store中的数据的方法 +const mapDispatchFromProps = (dispatch) => { + return { + changeCount: (num) => { + // 调用dispatch方法,传递actions + dispatch(getNoticeCount(num)); + }, + }; +}; + +export default connect(mapstateFromProps, mapDispatchFromProps)(HomeTab); diff --git a/screeens/NoticeDetail/components/MessageList/index.jsx b/screeens/NoticeDetail/components/MessageList/index.jsx index 3552f4f..0eaf17c 100644 --- a/screeens/NoticeDetail/components/MessageList/index.jsx +++ b/screeens/NoticeDetail/components/MessageList/index.jsx @@ -19,7 +19,8 @@ import { useDispatch } from "react-redux"; import { getNoticeCount } from "../../../../store/reducer"; import requireAPI from "../../../../utils/requireAPI"; import { Image } from "expo-image"; -const MessageList = forwardRef(({ navigation }, ref) => { +import { connect } from "react-redux"; +const MessageList = ({ navigation, noticeCount, refInstance }) => { const dispatch = useDispatch(); const [data, setData] = useState([]); const [scollNotice, setScollNotice] = useState(""); @@ -59,7 +60,7 @@ const MessageList = forwardRef(({ navigation }, ref) => { icon: require(`../../../../assets/icon/others/m_active.png`), }, ]); - useImperativeHandle(ref, () => ({ + useImperativeHandle(refInstance, () => ({ readAllMsg: async (types) => { try { const body = { @@ -98,11 +99,9 @@ const MessageList = forwardRef(({ navigation }, ref) => { }, })); useEffect(() => { - navigation.addListener("focus", () => { - getData(); - getActiveNotice(); - }); - }, []); + getData(); + getActiveNotice(); + }, [noticeCount]); const getData = async () => { try { const base = await baseRequest(); @@ -339,6 +338,17 @@ const MessageList = forwardRef(({ navigation }, ref) => { ); -}); +}; -export default MessageList; +// 获取store中的数据的方法 +const mapstateFromProps = (state) => { + // state: store仓库中的数据 + return { + noticeCount: state.noticeCount, + }; +}; + +const Component = connect(mapstateFromProps, null)(MessageList); +export default forwardRef((props, ref) => ( + +)); diff --git a/screeens/Setting/SwitchAccount/index.jsx b/screeens/Setting/SwitchAccount/index.jsx index 231f4d6..f4ff00f 100644 --- a/screeens/Setting/SwitchAccount/index.jsx +++ b/screeens/Setting/SwitchAccount/index.jsx @@ -25,8 +25,9 @@ import baseRequest from "../../../utils/baseRequest"; import { generateSignature } from "../../../utils/crypto"; import MyModal from "../../../components/MyModal"; import Toast from "react-native-toast-message"; - -export default function SwitchAccount({ navigation, route }) { +import { handleLogin } from "../../../store/reducer"; +import { connect } from "react-redux"; +function SwitchAccount({ changeLogin }) { const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk"; const tailwind = useTailwind(); @@ -115,6 +116,12 @@ export default function SwitchAccount({ navigation, route }) { login_time: new Date().getTime(), }); signIn(_data, data.mobile_phone_origion, data.region_code); + changeLogin({ + isSignin: true, + userToken: data.token, + recommendMid: null, + inviter: null, + }); setAccountList((pre) => pre.map((item) => ({ ...item, @@ -162,6 +169,12 @@ export default function SwitchAccount({ navigation, route }) { await remove(key); if (online) { signOut(); + changeLogin({ + isSignin: false, + userToken: null, + recommendMid: null, + inviter: null, + }); return; } setAccountList((pre) => pre.filter((item) => item.key !== key)); @@ -316,3 +329,14 @@ export default function SwitchAccount({ navigation, route }) { ); } + +const mapDispatchFromProps = (dispatch) => { + return { + changeLogin: (data) => { + // 调用dispatch方法,传递actions + dispatch(handleLogin(data)); + }, + }; +}; + +export default connect(null, mapDispatchFromProps)(SwitchAccount); diff --git a/store/reducer.js b/store/reducer.js index d5d5d8d..42bf64f 100644 --- a/store/reducer.js +++ b/store/reducer.js @@ -5,14 +5,23 @@ export const appSlice = createSlice({ initialState: { noticeCount: 0, }, + authInfo: { + isSignin: false, + userToken: null, + recommendMid: null, + inviter: null, + }, reducers: { getNoticeCount: (state, { payload }) => { state.noticeCount = payload; }, + handleLogin: (state, { payload }) => { + state.authInfo = payload; + }, }, }); // Action creators are generated for each case reducer function -export const { getNoticeCount } = appSlice.actions; +export const { getNoticeCount, handleLogin } = appSlice.actions; export default appSlice.reducer;