This commit is contained in:
2026-03-01 11:23:48 +01:00
parent eeeef2674a
commit 9b418a11c2
5 changed files with 414 additions and 40 deletions

View File

@@ -1,9 +1,24 @@
import * as ImagePicker from "expo-image-picker";
import * as Minio from "minio";
import { useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import { TextInput } from "react-native";
import { ActivityIndicator, Button, Image, StyleSheet, Text, TextInput, View } from "react-native";
const minioClient = new Minio.Client({
endPoint: "content2.reversed.dev",
port: 443,
useSSL: true,
accessKey: "tv-control",
secretKey: "0gEjOlnVAECrqJx5Nd77y7Hhouc5faVf0WttjcRH",
});
const bucket = "tv-control";
const BASE_URL =
"https://shsf-api.reversed.dev/api/exec/15/1c44d8b5-4065-4a54-b259-748561021329";
export function IndexPage() {
const [text, setText] = useState("");
const [uploading, setUploading] = useState(false);
const [previewUri, setPreviewUri] = useState<string | null>(null);
const handleSend = () => {
if (text.trim() === "") {
@@ -11,14 +26,12 @@ export function IndexPage() {
return;
}
fetch("https://shsf-api.reversed.dev/api/exec/15/1c44d8b5-4065-4a54-b259-748561021329/push_text", {
fetch(`${BASE_URL}/push_text`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
})
.then((response) => response.json())
.then((r) => r.json())
.then((data) => {
if (data.status === "success") {
alert("Text sent successfully!");
@@ -32,18 +45,100 @@ export function IndexPage() {
});
};
const handlePullText = () => {
fetch(`${BASE_URL}/pull_text`)
.then((r) => r.json())
.then((data) => {
alert(data.text ?? "No text received.");
})
.catch((error) => {
console.error("Error pulling text:", error);
alert("Error pulling text.");
});
};
const handleTakePhoto = async () => {
const { granted } = await ImagePicker.requestCameraPermissionsAsync();
if (!granted) {
alert("Camera permission is required to take photos.");
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: "images",
quality: 0.8,
});
if (result.canceled) return;
const asset = result.assets[0];
setPreviewUri(asset.uri);
setUploading(true);
try {
const fileName = `tv-image-${Date.now()}.jpg`;
// Get a presigned URL for PUT upload (pure signing — no HTTP call)
const presignedUrl = await minioClient.presignedPutObject(
bucket,
fileName,
60 * 60
);
// Upload the image blob using native fetch
const blob = await fetch(asset.uri).then((r) => r.blob());
await fetch(presignedUrl, {
method: "PUT",
body: blob,
headers: { "Content-Type": "image/jpeg" },
});
const imageUrl = `https://content2.reversed.dev/${bucket}/${fileName}`;
// Push the public URL to the server
await fetch(`${BASE_URL}/push_image_url`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ image_url: imageUrl }),
});
alert("Image uploaded and sent to TV!");
} catch (error) {
console.error("Upload error:", error);
alert("Failed to upload image.");
} finally {
setUploading(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Update Title</Text>
<Text style={styles.subtitle}>You typed "{text}"</Text>
<Text style={styles.title}>TV Control</Text>
<View>
<View style={styles.section}>
<Text style={styles.sectionLabel}>Text</Text>
<TextInput
style={styles.input}
placeholder="Type something..."
value={text}
onChangeText={setText}
/>
<Button title="Send" onPress={handleSend} />
<View style={styles.row}>
<Button title="Send Text" onPress={handleSend} />
<Button title="Pull Text" onPress={handlePullText} />
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionLabel}>Image</Text>
{previewUri && (
<Image source={{ uri: previewUri }} style={styles.preview} />
)}
{uploading ? (
<ActivityIndicator size="large" style={{ marginTop: 12 }} />
) : (
<Button title="Take Photo & Send to TV" onPress={handleTakePhoto} />
)}
</View>
</View>
);
@@ -55,14 +150,41 @@ const styles = StyleSheet.create({
alignItems: "center",
justifyContent: "center",
backgroundColor: "#f9f9f9",
padding: 24,
gap: 24,
},
title: {
fontSize: 28,
fontWeight: "700",
marginBottom: 8,
},
subtitle: {
section: {
width: "100%",
gap: 8,
},
sectionLabel: {
fontSize: 14,
fontWeight: "600",
color: "#555",
textTransform: "uppercase",
letterSpacing: 0.5,
},
input: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 8,
padding: 10,
fontSize: 16,
color: "#666",
backgroundColor: "#fff",
},
row: {
flexDirection: "row",
gap: 8,
},
preview: {
width: "100%",
height: 200,
borderRadius: 8,
resizeMode: "cover",
},
});