Files
JKVideo/components/LoginModal.tsx

136 lines
3.9 KiB
TypeScript
Raw Normal View History

2026-03-17 22:18:05 +08:00
import React, { useEffect, useState, useRef } from "react";
import {
Modal,
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
ActivityIndicator,
} from "react-native";
import { generateQRCode, pollQRCode, getUserInfo } from "../services/bilibili";
import { useAuthStore } from "../store/authStore";
interface Props {
visible: boolean;
onClose: () => void;
}
export function LoginModal({ visible, onClose }: Props) {
const [qrUrl, setQrUrl] = useState<string | null>(null);
const [qrKey, setQrKey] = useState<string | null>(null);
2026-03-17 22:18:05 +08:00
const [status, setStatus] = useState<
"loading" | "waiting" | "scanned" | "done" | "error"
>("loading");
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
2026-03-17 22:18:05 +08:00
const login = useAuthStore((s) => s.login);
const setProfile = useAuthStore((s) => s.setProfile);
useEffect(() => {
if (!visible) return;
2026-03-17 22:18:05 +08:00
setStatus("loading");
setQrUrl(null);
setQrKey(null);
2026-03-17 22:18:05 +08:00
generateQRCode()
.then((data) => {
setQrUrl(
`https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(data.url)}&size=200x200`,
);
setQrKey(data.qrcode_key);
setStatus("waiting");
})
.catch(() => setStatus("error"));
2026-03-17 22:18:05 +08:00
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
}, [visible]);
useEffect(() => {
2026-03-17 22:18:05 +08:00
if (!qrKey || status !== "waiting") return;
pollRef.current = setInterval(async () => {
const result = await pollQRCode(qrKey);
2026-03-17 22:18:05 +08:00
if (result.code === 86038) {
setStatus("error");
clearInterval(pollRef.current!);
}
if (result.code === 86090) setStatus("scanned");
if (result.code === 0 && result.cookie) {
clearInterval(pollRef.current!);
2026-03-14 18:25:13 +08:00
try {
2026-03-17 22:18:05 +08:00
await login(result.cookie, "", "");
setStatus("done");
2026-03-14 18:25:13 +08:00
// 登录后异步拉取用户头像和昵称
const info = await getUserInfo();
setProfile(info.face, info.uname, String(info.mid));
} catch {
2026-03-17 22:18:05 +08:00
setStatus("error");
2026-03-14 18:25:13 +08:00
}
onClose();
}
}, 2000);
2026-03-17 22:18:05 +08:00
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
}, [qrKey, status]);
return (
2026-03-17 22:18:05 +08:00
<Modal
visible={visible}
transparent
animationType="none"
onRequestClose={onClose}
>
<View style={styles.overlay}>
<View style={styles.sheet}>
<Text style={styles.title}></Text>
2026-03-17 22:18:05 +08:00
{status === "loading" && (
<ActivityIndicator
size="large"
color="#00AEEC"
style={styles.loader}
/>
)}
{(status === "waiting" || status === "scanned") && qrUrl && (
<>
<Image source={{ uri: qrUrl }} style={styles.qr} />
2026-03-17 22:18:05 +08:00
<Text style={styles.hint}>
{status === "scanned"
? "扫描成功,请在手机确认"
: "使用 B站 APP 扫一扫"}
</Text>
</>
)}
2026-03-17 22:18:05 +08:00
{status === "error" && (
<Text style={styles.hint}></Text>
)}
<TouchableOpacity style={styles.closeBtn} onPress={onClose}>
<Text style={styles.closeTxt}></Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
2026-03-17 22:18:05 +08:00
overlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "flex-end",
},
sheet: {
backgroundColor: "#fff",
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
padding: 24,
alignItems: "center",
},
title: { fontSize: 18, fontWeight: "600", marginBottom: 20 },
loader: { marginVertical: 40 },
qr: { width: 200, height: 200, marginBottom: 12 },
2026-03-17 22:18:05 +08:00
hint: { fontSize: 13, color: "#666", marginBottom: 20 },
closeBtn: { padding: 12 },
2026-03-17 22:18:05 +08:00
closeTxt: { fontSize: 14, color: "#00AEEC" },
});