import { createRouter, Todo } from "@<@var(context.project.name)>/rpc" import { useEffect, useState } from "react" import { Platform, PlatformColor, ScrollView, useColorScheme } from "react-native" import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated" import { SafeAreaView } from "react-native-safe-area-context" import { Button, Checkbox, Host, Column, TextInput, Text, useNativeState, Row, Icon } from "@expo/ui"; import * as Crypto from "expo-crypto" import { background, colorInvert, controlSize, padding as iosPadding, shapes } from "@expo/ui/swift-ui/modifiers"; // No numeric width() on Android: @expo/ui 57 can't convert it natively and crashes. import { fillMaxWidth, padding, weight } from "@expo/ui/jetpack-compose/modifiers"; // The iOS simulator shares the host's network; devenv runs `adb reverse` so Android devices do too. const router = createRouter("http://127.0.0.1:8080") // Changes made elsewhere (another device, the web app) show up within this interval. const syncIntervalMs = 2000 // SwiftUI clips a border to its corner radius, cutting off the corners, so on iOS the // border is an outer rounded fill showing 1pt around an inner one. const iosInputBorder = Platform.OS === "ios" ? [ iosPadding({ all: 10 }), background(PlatformColor("secondarySystemGroupedBackground"), shapes.roundedRectangle({ cornerRadius: 9, roundedCornerStyle: "continuous" })), iosPadding({ all: 1 }), background(PlatformColor("separator"), shapes.roundedRectangle({ cornerRadius: 10, roundedCornerStyle: "continuous" })), ] : [] // Animates its position when the list re-sorts. The Host's measured height goes through state: // on Android a layout transition ignores the size matchContents sets natively, collapsing the row. function AnimatedRow({ children }: { children: React.ReactNode }) { const [height, setHeight] = useState() return ( setHeight(e.nativeEvent.height)} > {children} ) } export default function Index() { const [todos, setTodos] = useState>([]) const [offline, setOffline] = useState(false) const todoToCreateTask = useNativeState("") const colorScheme = useColorScheme() const fetchTodos = () => { router.todos.listTodos({}).then((r) => { setOffline(false) // Polling returns the same list most of the time; keep the old array to skip re-rendering. setTodos((current) => JSON.stringify(current) === JSON.stringify(r.todos) ? current : r.todos) }).catch(() => setOffline(true)) } useEffect(() => { fetchTodos() const interval = setInterval(fetchTodos, syncIntervalMs) return () => clearInterval(interval) }, []) const setTodoDone = (id: string, task: string) => { return (done: boolean) => { router.todos.updateTodo({ todo: { id, task, done } }).then(fetchTodos, () => setOffline(true)) } } const deleteTodo = (id: string) => { return () => { router.todos.deleteTodo({ todo: { id } }).then(fetchTodos, () => setOffline(true)) } } const createTodo = () => { const task = todoToCreateTask.value.trim() if (!task) return router.todos.createTodo({ todo: { id: Crypto.randomUUID(), task } }).then(() => { todoToCreateTask.value = "" fetchTodos() }, () => setOffline(true)) } const updateTodoToCreateTask = (task: string) => { todoToCreateTask.value = task } return ( Create Todo {offline && Can't reach the API, retrying…}