添加消息通知功能
This commit is contained in:
parent
6f60c440fd
commit
bc7720f817
13
App.jsx
13
App.jsx
|
@ -67,7 +67,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";
|
||||
const RootStack = createNativeStackNavigator();
|
||||
|
||||
export const AuthContext = createContext("");
|
||||
|
@ -117,6 +117,7 @@ const App = () => {
|
|||
isSignin: false,
|
||||
userToken: null,
|
||||
});
|
||||
|
||||
const authContext = useMemo(
|
||||
() => ({
|
||||
signIn: async (data, mobilePhone, regionCode) => {
|
||||
|
@ -299,12 +300,20 @@ const App = () => {
|
|||
if (!appIsReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleGetWebsocketData = (data) => {
|
||||
console.log("websocketData", data);
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "🔔 收到一条系统通知",
|
||||
topOffset: 60,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<TailwindProvider utilities={utilities}>
|
||||
<AuthContext.Provider value={authContext}>
|
||||
<SafeAreaProvider>
|
||||
<StatusBar style="light" translucent />
|
||||
<WebSocketComponent getData={handleGetWebsocketData} />
|
||||
<View style={{ flex: 1, backgroundColor: "#07050A" }}>
|
||||
<NavigationContainer onReady={onLayoutRootView} theme={MyTheme}>
|
||||
<ImageViewerProvider>
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { View, Text, FlatList } from "react-native";
|
||||
import baseRequest from "../../utils/baseRequest";
|
||||
const WebSocketComponent = ({ getData }) => {
|
||||
const [messages, setMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
let socket = null;
|
||||
async function fn() {
|
||||
const base = await baseRequest();
|
||||
// 创建WebSocket连接
|
||||
socket = new WebSocket(
|
||||
`https://wsdebug.tiefen.fun/ws?b_mid=${base.b_mid}&b_did=${
|
||||
base.b_did
|
||||
}&b_dt=1&b_token=${base.b_token.replace(/\./g, "_")}`
|
||||
); // 注意使用wss协议(如果服务器支持)
|
||||
// 连接打开时触发
|
||||
console.log("WebSocket ccccc.");
|
||||
socket.onopen = () => {
|
||||
console.log("WebSocket connected.");
|
||||
// 可以在这里发送消息到服务器,例如:socket.send('Hello Server!');
|
||||
socket.send(JSON.stringify({ t: 1 }));
|
||||
};
|
||||
|
||||
// 处理收到的消息
|
||||
socket.onmessage = (event) => {
|
||||
// console.log(
|
||||
// "收到消息-----:",
|
||||
// Object.prototype.toString.call(event.data),
|
||||
// JSON.parse(event.data)
|
||||
// );
|
||||
if (
|
||||
Object.prototype.toString.call(event.data) === "[object ArrayBuffer]"
|
||||
) {
|
||||
const view = new Uint8Array(event.data);
|
||||
const str = String.fromCharCode.apply(null, view);
|
||||
console.log(str);
|
||||
try {
|
||||
const data = JSON.parse(str);
|
||||
console.log("收到消息:", data);
|
||||
if (data.t === 2) {
|
||||
socket.send("ping");
|
||||
setInterval(() => {
|
||||
// 发送 ping 给服务器
|
||||
socket.send("ping");
|
||||
// 响应服务器的 ping
|
||||
// socket.on("ping", () => {
|
||||
// socket.send("pong");
|
||||
// });
|
||||
}, data.ping_interval * 1000);
|
||||
getData(data);
|
||||
setMessages((prevMessages) => [...prevMessages, data]);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
};
|
||||
|
||||
// 连接关闭时触发
|
||||
socket.onclose = () => {
|
||||
console.log("WebSocket disconnected.");
|
||||
};
|
||||
|
||||
// 连接错误时触发
|
||||
socket.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
|
||||
// 响应服务器的 ping
|
||||
// socket.on("ping", () => {
|
||||
// socket.send("pong");
|
||||
// });
|
||||
}
|
||||
|
||||
fn();
|
||||
// 组件卸载时关闭WebSocket连接
|
||||
return () => {
|
||||
socket.close();
|
||||
};
|
||||
}, []); // 空依赖数组表示这个effect只在组件挂载时运行一次
|
||||
|
||||
return (
|
||||
<View>
|
||||
{/* <Text style={{ fontSize: 24, fontWeight: 'bold' }}>WebSocket Messages</Text>
|
||||
<FlatList
|
||||
data={messages}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
renderItem={({ item }) => <Text>{item}</Text>}
|
||||
/> */}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebSocketComponent;
|
|
@ -6,13 +6,10 @@ import {
|
|||
Animated,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { useTailwind } from "tailwind-rn";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Badge } from "@rneui/themed";
|
||||
import { Icon } from "@rneui/themed";
|
||||
import baseRequest from "../../../utils/baseRequest";
|
||||
import { generateSignature } from "../../../utils/crypto";
|
||||
import { TabView, SceneMap, TabBar } from "react-native-tab-view";
|
||||
import MessageList from "../components/MessageList";
|
||||
export default function NoticeNav({ navigation }) {
|
||||
|
@ -20,56 +17,21 @@ export default function NoticeNav({ navigation }) {
|
|||
const [openNotices, setOpenNotices] = useState(false);
|
||||
const tailwind = useTailwind();
|
||||
const insets = useSafeAreaInsets();
|
||||
const mesListEl = useRef(null);
|
||||
|
||||
//tab组件相关
|
||||
const layout = useWindowDimensions();
|
||||
const [index, setIndex] = useState(0);
|
||||
const [routes] = useState([{ key: "list", title: "消息" }]);
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
const getData = async (searchValue) => {
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||
try {
|
||||
const base = await baseRequest();
|
||||
const body = {
|
||||
mid: base.b_mid,
|
||||
...base,
|
||||
};
|
||||
console.log(base.b_mid);
|
||||
const signature = await generateSignature(body);
|
||||
const _response = await fetch(
|
||||
`${apiUrl}/api/notification/list_by_mid?signature=${signature}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
const _data = await _response.json();
|
||||
if (_data.ret === -1) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: _data.msg,
|
||||
topOffset: 60,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(_data.data.list);
|
||||
setData(_data.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
const renderScene = useCallback(
|
||||
SceneMap({
|
||||
list: () => <MessageList navigation={navigation} />,
|
||||
list: () => <MessageList navigation={navigation} ref={mesListEl} />,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const handleReadAll = useCallback(() => {
|
||||
mesListEl.current.readAllMsg();
|
||||
}, []);
|
||||
const renderIndicator = useCallback((props) => {
|
||||
const { position, navigationState, getTabWidth } = props;
|
||||
const inputRange = [0, 1];
|
||||
|
@ -125,7 +87,7 @@ export default function NoticeNav({ navigation }) {
|
|||
style={tailwind(
|
||||
"flex items-center justify-center w-9 h-9 mr-4 bg-[#FFFFFF1A] rounded-full"
|
||||
)}
|
||||
onPress={() => navigation.navigate("Search")}
|
||||
onPress={handleReadAll}
|
||||
>
|
||||
<NativeImage
|
||||
source={require("../../../assets/icon/32DP/remove.png")}
|
||||
|
|
|
@ -3,17 +3,41 @@ import React, { useState, useEffect } from "react";
|
|||
import { useTailwind } from "tailwind-rn";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import NoticeItem from "../components/NoticeItem";
|
||||
import { Icon } from "@rneui/themed";
|
||||
import baseRequest from "../../../utils/baseRequest";
|
||||
import { generateSignature } from "../../../utils/crypto";
|
||||
import { Image } from "expo-image";
|
||||
import Empty from "../../../components/Empty";
|
||||
export default function SystemNotice({ navigation, route }) {
|
||||
const tailwind = useTailwind();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [data, setData] = useState([]);
|
||||
useEffect(() => {
|
||||
// handleClearCount();
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
// const handleClearCount = async () => {
|
||||
// const type = route.params["type"];
|
||||
// const total = route.params["total"];
|
||||
// let msgName = "";
|
||||
// switch (type) {
|
||||
// case 0:
|
||||
// msgName = "system_msg";
|
||||
// break;
|
||||
// case 1:
|
||||
// msgName = "pay_msg";
|
||||
// break;
|
||||
// case 2:
|
||||
// msgName = "active_msg";
|
||||
// break;
|
||||
// case 3:
|
||||
// msgName = "exam_msg";
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// await save(msgName, total);
|
||||
// };
|
||||
const getData = async (searchValue) => {
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||
try {
|
||||
|
@ -53,6 +77,7 @@ export default function SystemNotice({ navigation, route }) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
// console.log("_data", _data);
|
||||
const type = route.params["type"];
|
||||
setData(_data.data.list.filter((it) => it.n_type == type));
|
||||
} catch (error) {
|
||||
|
@ -79,6 +104,7 @@ export default function SystemNotice({ navigation, route }) {
|
|||
}
|
||||
/>
|
||||
))}
|
||||
{!data.length && <Empty type="nodata" />}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,18 +3,20 @@ import {
|
|||
TouchableOpacity,
|
||||
Image as NativeImage,
|
||||
Text,
|
||||
Animated,
|
||||
useWindowDimensions,
|
||||
Easing,
|
||||
} from "react-native";
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from "react";
|
||||
import { useTailwind } from "tailwind-rn";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import baseRequest from "../../../../utils/baseRequest";
|
||||
import { generateSignature } from "../../../../utils/crypto";
|
||||
import { formatDate } from "../../../../utils/tools";
|
||||
import ScrollNotice from "../ScrollNotice";
|
||||
export default function MessageList({ navigation }) {
|
||||
const MessageList = forwardRef(({ navigation }, ref) => {
|
||||
const [data, setData] = useState([]);
|
||||
const [scollNotice, setScollNotice] = useState("");
|
||||
const tailwind = useTailwind();
|
||||
|
@ -25,6 +27,7 @@ export default function MessageList({ navigation }) {
|
|||
time: "",
|
||||
subtitle: "展示相关活动推送消息",
|
||||
count: 0,
|
||||
type: "system_msg",
|
||||
icon: require(`../../../../assets/icon/others/m_system.png`),
|
||||
},
|
||||
{
|
||||
|
@ -32,6 +35,7 @@ export default function MessageList({ navigation }) {
|
|||
time: "",
|
||||
subtitle: "展示相关活动推送消息",
|
||||
count: 0,
|
||||
type: "pay_msg",
|
||||
icon: require(`../../../../assets/icon/others/m_pay.png`),
|
||||
},
|
||||
{
|
||||
|
@ -39,6 +43,7 @@ export default function MessageList({ navigation }) {
|
|||
time: "",
|
||||
subtitle: "展示相关活动推送消息",
|
||||
count: 0,
|
||||
type: "active_msg",
|
||||
icon: require(`../../../../assets/icon/others/m_active.png`),
|
||||
},
|
||||
{
|
||||
|
@ -46,15 +51,21 @@ export default function MessageList({ navigation }) {
|
|||
time: "",
|
||||
subtitle: "展示相关活动推送消息",
|
||||
count: 0,
|
||||
type: "exam_msg",
|
||||
icon: require(`../../../../assets/icon/others/m_exam.png`),
|
||||
},
|
||||
]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
readAllMsg: async () => {
|
||||
console.log("清除未读数!");
|
||||
},
|
||||
}));
|
||||
useEffect(() => {
|
||||
getData();
|
||||
getActiveNotice();
|
||||
navigation.addListener("focus", () => {
|
||||
getData();
|
||||
getActiveNotice();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getData = async (searchValue) => {
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||
try {
|
||||
|
@ -161,7 +172,9 @@ export default function MessageList({ navigation }) {
|
|||
return (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
onPress={() => navigation?.navigate("SystemNotice", { type: index })}
|
||||
onPress={() =>
|
||||
navigation?.navigate("SystemNotice", { type: index, total: count })
|
||||
}
|
||||
style={{
|
||||
...tailwind("flex flex-row items-center py-4 mb-2 rounded-xl"),
|
||||
backgroundColor: "#ffffff1a",
|
||||
|
@ -319,4 +332,6 @@ export default function MessageList({ navigation }) {
|
|||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default MessageList;
|
||||
|
|
|
@ -30,6 +30,10 @@ import MyDivider from "../../components/MyDivider/index";
|
|||
import MySlider from "../../components/MySlider";
|
||||
import Picker from "../../components/Picker";
|
||||
import { get } from "../../utils/storeInfo";
|
||||
import { debounce } from "../../utils/tools";
|
||||
const newDebounce = debounce(function (fn) {
|
||||
fn && fn();
|
||||
}, 500);
|
||||
const filterComprehensiveItems = {
|
||||
age: { lower_bound: 18, upper_bound: 60 },
|
||||
fans: { lower_bound: 1, upper_bound: 1000 },
|
||||
|
@ -224,11 +228,11 @@ export default function Search({ navigation, route }) {
|
|||
...filterComprehensiveItems,
|
||||
...filterPriceItems,
|
||||
});
|
||||
const updateSearch = (search) => {
|
||||
setSearch(search);
|
||||
if (!search) return;
|
||||
setIsloading(true);
|
||||
};
|
||||
// const updateSearch = (search) => {
|
||||
// setSearch(search);
|
||||
// if (!search) return;
|
||||
// setIsloading(true);
|
||||
// };
|
||||
|
||||
//进入页面默认focus
|
||||
useEffect(() => {
|
||||
|
@ -269,81 +273,63 @@ export default function Search({ navigation, route }) {
|
|||
getIsMember();
|
||||
}, [])
|
||||
);
|
||||
|
||||
//搜索框文本变化时进行搜索
|
||||
useEffect(() => {
|
||||
if (!search) {
|
||||
setStreamers([]);
|
||||
setZones([]);
|
||||
return;
|
||||
const handleSearch = async (searchValue) => {
|
||||
if (filtersValue.comprehensiveUsed.used || filtersValue.priceUsed.used) {
|
||||
handleResetFiltersValue();
|
||||
handleResetFiltersSearchValue();
|
||||
}
|
||||
const isNumeric = (str) => {
|
||||
return /^\d+$/.test(str);
|
||||
};
|
||||
const getResult = async () => {
|
||||
if (filtersValue.comprehensiveUsed.used || filtersValue.priceUsed.used) {
|
||||
handleResetFiltersValue();
|
||||
handleResetFiltersSearchValue();
|
||||
}
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||
const isSearchInt = isNumeric(search);
|
||||
let api;
|
||||
let querryParams;
|
||||
if (isSearchInt) {
|
||||
api = "/api/streamer/list_ext_fuzzily_by_user_id";
|
||||
querryParams = { user_id: parseInt(search, 10) };
|
||||
} else {
|
||||
api = "/api/streamer/list_ext_fuzzily_by_name";
|
||||
querryParams = { name: search };
|
||||
}
|
||||
try {
|
||||
const base = await baseRequest();
|
||||
const signature = await generateSignature({
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||
const isSearchInt = isNumeric(searchValue);
|
||||
let api;
|
||||
let querryParams;
|
||||
if (isSearchInt) {
|
||||
api = "/api/streamer/list_ext_fuzzily_by_user_id";
|
||||
querryParams = { user_id: parseInt(searchValue, 10) };
|
||||
} else {
|
||||
api = "/api/streamer/list_ext_fuzzily_by_name";
|
||||
querryParams = { name: searchValue };
|
||||
}
|
||||
try {
|
||||
const base = await baseRequest();
|
||||
const signature = await generateSignature({
|
||||
...base,
|
||||
...querryParams,
|
||||
offset: 0,
|
||||
limit: 20,
|
||||
});
|
||||
const response = await fetch(`${apiUrl}${api}?signature=${signature}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...base,
|
||||
...querryParams,
|
||||
offset: 0,
|
||||
limit: 20,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.ret === -1) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: data.msg,
|
||||
topOffset: 60,
|
||||
});
|
||||
const response = await fetch(`${apiUrl}${api}?signature=${signature}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...base,
|
||||
...querryParams,
|
||||
offset: 0,
|
||||
limit: 20,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.ret === -1) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: data.msg,
|
||||
topOffset: 60,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!ignore) {
|
||||
const zonesData = data.data.list.filter(
|
||||
(item) => item.zones.length > 0
|
||||
);
|
||||
setStreamers(data.data.list);
|
||||
setZones(zonesData);
|
||||
setRecommList([]);
|
||||
}
|
||||
setIsloading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ignore = false;
|
||||
getResult();
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [search]);
|
||||
const zonesData = data.data.list.filter((item) => item.zones.length > 0);
|
||||
setStreamers(data.data.list);
|
||||
setZones(zonesData);
|
||||
setRecommList([]);
|
||||
setIsloading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
const getFiltersResult = async (obj) => {
|
||||
if (search != "") {
|
||||
setSearch("");
|
||||
|
@ -733,7 +719,20 @@ export default function Search({ navigation, route }) {
|
|||
clearIcon={() => <></>}
|
||||
searchIcon={() => <></>}
|
||||
showLoading={isloading}
|
||||
onChangeText={updateSearch}
|
||||
onChangeText={(val) => {
|
||||
setSearch((old) => {
|
||||
let test = (e) => {
|
||||
if (val == "") {
|
||||
setStreamers([]);
|
||||
setZones([]);
|
||||
return;
|
||||
}
|
||||
handleSearch(val);
|
||||
};
|
||||
newDebounce(test);
|
||||
return val;
|
||||
});
|
||||
}}
|
||||
value={search}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -187,6 +187,30 @@ export default function Wallet({ navigation, route }) {
|
|||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
navigation.navigate("WebWithHeader", {
|
||||
title: "收支明细",
|
||||
uri:
|
||||
process.env.EXPO_PUBLIC_WEB_URL +
|
||||
"/bill/income/income_querry",
|
||||
})
|
||||
}
|
||||
style={tailwind("flex-row justify-between items-center py-4")}
|
||||
>
|
||||
<View style={tailwind("flex-row items-center")}>
|
||||
<Icon type="ionicon" name="print" size={32} color="#60a5fa" />
|
||||
<Text style={tailwind("text-base text-white font-medium ml-2")}>
|
||||
近一周收益
|
||||
</Text>
|
||||
</View>
|
||||
<Icon
|
||||
type="ionicon"
|
||||
name="chevron-forward-outline"
|
||||
size={28}
|
||||
color="white"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -34,3 +34,22 @@ export function formatDate(timestamp) {
|
|||
return `${hours}:${minutes > 9 ? minutes : "0" + minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖函数
|
||||
export function debounce(fn, delay) {
|
||||
let timer = null;
|
||||
return (fnn) => {
|
||||
//清除上一次的延时器
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
// return;
|
||||
// console.log(timer);
|
||||
}
|
||||
//重新设置新的延时器
|
||||
timer = setTimeout(() => {
|
||||
//修改this指向问题
|
||||
// fn.apply(this,value)
|
||||
fn(fnn);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue