feat: Support remote input

This commit is contained in:
zimplexing
2025-07-10 22:18:34 +08:00
parent eaa783824d
commit 9e4d4ca242
12 changed files with 9691 additions and 9 deletions

View File

@@ -7,6 +7,8 @@ import { Platform } from "react-native";
import Toast from "react-native-toast-message";
import { useSettingsStore } from "@/stores/settingsStore";
import { useRemoteControlStore } from "@/stores/remoteControlStore";
import { remoteControlService } from "@/services/remoteControlService";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
@@ -31,6 +33,23 @@ export default function RootLayout() {
}
}, [loaded, error]);
useEffect(() => {
// Initialize the service with store actions to break require cycle
const { setMessage, hideModal } = useRemoteControlStore.getState();
remoteControlService.init({
onMessage: setMessage,
onHandshake: hideModal,
});
// Start the remote control server on app launch
useRemoteControlStore.getState().startServer();
return () => {
// Stop the server on app close
useRemoteControlStore.getState().stopServer();
};
}, []);
if (!loaded && !error) {
return null;
}

View File

@@ -6,7 +6,7 @@ import { ThemedView } from "@/components/ThemedView";
import { StyledButton } from "@/components/StyledButton";
import { AVPlaybackStatus } from "expo-av";
const M3U_URL = "https://raw.githubusercontent.com/fanmingming/live/refs/heads/main/tv/m3u/ipv6.m3u";
const M3U_URL = "https://raw.githubusercontent.com/sjnhnp/adblock/refs/heads/main/filtered_http_only_valid.m3u";
export default function LiveScreen() {
const [channels, setChannels] = useState<Channel[]>([]);

View File

@@ -4,8 +4,10 @@ import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import VideoCard from "@/components/VideoCard.tv";
import { api, SearchResult } from "@/services/api";
import { Search } from "lucide-react-native";
import { Search, QrCode } from "lucide-react-native";
import { StyledButton } from "@/components/StyledButton";
import { useRemoteControlStore } from "@/stores/remoteControlStore";
import { RemoteControlModal } from "@/components/RemoteControlModal";
export default function SearchScreen() {
const [keyword, setKeyword] = useState("");
@@ -15,6 +17,15 @@ export default function SearchScreen() {
const textInputRef = useRef<TextInput>(null);
const colorScheme = "dark"; // Replace with useColorScheme() if needed
const [isInputFocused, setIsInputFocused] = useState(false);
const { showModal: showRemoteModal, lastMessage } = useRemoteControlStore();
useEffect(() => {
if (lastMessage) {
const realMessage = lastMessage.split("_")[0];
setKeyword(realMessage);
handleSearch(realMessage);
}
}, [lastMessage]);
useEffect(() => {
// Focus the text input when the screen loads
@@ -24,8 +35,9 @@ export default function SearchScreen() {
return () => clearTimeout(timer);
}, []);
const handleSearch = async () => {
if (!keyword.trim()) {
const handleSearch = async (searchText?: string) => {
const term = typeof searchText === "string" ? searchText : keyword;
if (!term.trim()) {
Keyboard.dismiss();
return;
}
@@ -33,7 +45,7 @@ export default function SearchScreen() {
setLoading(true);
setError(null);
try {
const response = await api.searchVideos(keyword);
const response = await api.searchVideos(term);
if (response.results.length > 0) {
setResults(response.results);
} else {
@@ -47,6 +59,8 @@ export default function SearchScreen() {
}
};
const onSearchPress = () => handleSearch();
const renderItem = ({ item }: { item: SearchResult }) => (
<VideoCard
id={item.id.toString()}
@@ -78,12 +92,15 @@ export default function SearchScreen() {
onChangeText={setKeyword}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
onSubmitEditing={handleSearch} // Allow searching with remote 'enter' button
onSubmitEditing={onSearchPress}
returnKeyType="search"
/>
<StyledButton style={styles.searchButton} onPress={handleSearch}>
<StyledButton style={styles.searchButton} onPress={onSearchPress}>
<Search size={24} color={colorScheme === "dark" ? "white" : "black"} />
</StyledButton>
<StyledButton style={styles.qrButton} onPress={showRemoteModal}>
<QrCode size={24} color={colorScheme === "dark" ? "white" : "black"} />
</StyledButton>
</View>
{loading ? (
@@ -108,6 +125,7 @@ export default function SearchScreen() {
}
/>
)}
<RemoteControlModal />
</ThemedView>
);
}
@@ -140,6 +158,11 @@ const styles = StyleSheet.create({
// backgroundColor is now set dynamically
borderRadius: 8,
},
qrButton: {
padding: 12,
borderRadius: 8,
marginLeft: 10,
},
centerContainer: {
flex: 1,
justifyContent: "center",