mobile v0.1 with deployment for ios (#191)

* stage 1: project init

* stage 2: skeleton with navigation

* step 2.5: streams list

* step 4: stream playback experience

* step 5-6: compose experience

* fix: broken record

* transcode media particles to mp4

* build: reproducible go generate

* build: rename skaffold module for particle processor worker

* infra: increase particle processor worker resources

Was dealing with OOM errors

* tweaks to mobile

* log transcode work

* view on desktop placeholder

* tweak padding

* cap video resolution to save on memory

* infra: bump memory limits as insurance

* ux improvements

* update bundle id for mobile

* config for mobile
This commit was merged in pull request #191.
This commit is contained in:
Arjun Patel
2026-04-29 17:39:11 -07:00
committed by GitHub
parent 3a11a82cd3
commit e3461dd5cd
110 changed files with 14682 additions and 22 deletions
@@ -0,0 +1,193 @@
import { useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Pressable,
Text,
TextInput,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useAuthStore } from "@/stores/auth-store";
type Step = "email" | "code";
export function SignInScreen() {
const [step, setStep] = useState<Step>("email");
const [email, setEmail] = useState("");
return (
<SafeAreaView className="flex-1 bg-background">
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
className="flex-1"
>
<View className="flex-1 justify-center px-6">
{step === "email" ? (
<EmailStep
onCodeSent={(submittedEmail) => {
setEmail(submittedEmail);
setStep("code");
}}
/>
) : (
<CodeStep email={email} onBack={() => setStep("email")} />
)}
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
function EmailStep({ onCodeSent }: { onCodeSent: (email: string) => void }) {
const [email, setEmail] = useState("");
const isRequestingCode = useAuthStore((s) => s.isRequestingCode);
const error = useAuthStore((s) => s.error);
const requestCode = useAuthStore((s) => s.requestCode);
const clearError = useAuthStore((s) => s.clearError);
const submit = async () => {
try {
await requestCode(email);
onCodeSent(email);
} catch {
// Error surfaced via the store
}
};
const disabled = isRequestingCode || email.trim().length === 0;
return (
<View className="gap-5">
<View className="gap-1">
<Text className="text-foreground text-3xl font-semibold">Sign in</Text>
<Text className="text-muted-foreground text-base">
Enter your email to receive a sign-in code.
</Text>
</View>
<View className="gap-2">
<Text className="text-foreground text-sm font-medium">Email</Text>
<TextInput
value={email}
onChangeText={(text) => {
setEmail(text);
if (error) clearError();
}}
placeholder="[email protected]"
placeholderTextColor="#878787"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
autoComplete="email"
textContentType="emailAddress"
autoFocus
returnKeyType="go"
onSubmitEditing={submit}
className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base"
/>
</View>
{error ? (
<Text className="text-destructive text-sm">{error}</Text>
) : null}
<Pressable
onPress={submit}
disabled={disabled}
className={`rounded-lg px-4 py-3.5 items-center ${
disabled ? "bg-muted" : "bg-primary"
}`}
>
<Text
className={`text-base font-semibold ${
disabled ? "text-muted-foreground" : "text-primary-foreground"
}`}
>
{isRequestingCode ? "Sending..." : "Continue"}
</Text>
</Pressable>
</View>
);
}
function CodeStep({ email, onBack }: { email: string; onBack: () => void }) {
const [code, setCode] = useState("");
const isSigningIn = useAuthStore((s) => s.isSigningIn);
const error = useAuthStore((s) => s.error);
const signIn = useAuthStore((s) => s.signIn);
const clearError = useAuthStore((s) => s.clearError);
const submit = async () => {
try {
await signIn(email, code);
} catch {
// Error surfaced via the store; keep the screen visible
}
};
const disabled = isSigningIn || code.trim().length === 0;
return (
<View className="gap-5">
<View className="gap-1">
<Text className="text-foreground text-3xl font-semibold">
Check your email
</Text>
<Text className="text-muted-foreground text-base">
We sent a code to{" "}
<Text className="text-foreground font-medium">{email}</Text>.
</Text>
</View>
<View className="gap-2">
<Text className="text-foreground text-sm font-medium">Code</Text>
<TextInput
value={code}
onChangeText={(text) => {
setCode(text);
if (error) clearError();
}}
placeholder="Enter code"
placeholderTextColor="#878787"
keyboardType="number-pad"
autoFocus
returnKeyType="go"
textContentType="oneTimeCode"
onSubmitEditing={submit}
className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base tracking-widest"
/>
</View>
{error ? (
<Text className="text-destructive text-sm">{error}</Text>
) : null}
<View className="gap-2">
<Pressable
onPress={submit}
disabled={disabled}
className={`rounded-lg px-4 py-3.5 items-center ${
disabled ? "bg-muted" : "bg-primary"
}`}
>
<Text
className={`text-base font-semibold ${
disabled ? "text-muted-foreground" : "text-primary-foreground"
}`}
>
{isSigningIn ? "Signing in..." : "Sign in"}
</Text>
</Pressable>
<Pressable
onPress={onBack}
className="rounded-lg px-4 py-3.5 items-center"
>
<Text className="text-muted-foreground text-base font-medium">
Back
</Text>
</Pressable>
</View>
</View>
);
}