show progress during upload

More explicit feedback of the state of their upload. Also prevents
recording more media at the same time or interacting with the stream
which would feel stale to the human since they just created something
and it's yet to be present in the stream.
This commit is contained in:
talksik
2026-04-30 17:23:17 -07:00
parent 3051b1558f
commit aaf9ad4518
2 changed files with 63 additions and 21 deletions
+23 -6
View File
@@ -36,7 +36,12 @@ type ComposeUiState =
uri: string;
durationMs: number;
}
| { kind: "uploading" };
| {
kind: "uploading";
mode: RecordingMode;
uri: string;
durationMs: number;
};
interface SubmitMediaParams {
fileUri: string;
@@ -125,7 +130,12 @@ export function ComposeDock({
const sendReview = useEvent(async () => {
if (ui.kind !== "review" || !userId) return;
const captured = ui;
setUi({ kind: "uploading" });
setUi({
kind: "uploading",
mode: captured.mode,
uri: captured.uri,
durationMs: captured.durationMs,
});
try {
const mimeType =
captured.mode === "audio" ? "audio/mp4" : "video/mp4";
@@ -251,10 +261,17 @@ export function ComposeDock({
) : null}
<ReviewSheet
open={ui.kind === "review"}
uri={ui.kind === "review" ? ui.uri : null}
mode={ui.kind === "review" ? ui.mode : null}
durationMs={ui.kind === "review" ? ui.durationMs : 0}
open={ui.kind === "review" || ui.kind === "uploading"}
uri={
ui.kind === "review" || ui.kind === "uploading" ? ui.uri : null
}
mode={
ui.kind === "review" || ui.kind === "uploading" ? ui.mode : null
}
durationMs={
ui.kind === "review" || ui.kind === "uploading" ? ui.durationMs : 0
}
sending={ui.kind === "uploading"}
onSend={sendReview}
onRetake={retake}
onCancel={cancelReview}
+40 -15
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { Modal, Pressable, Text, View } from "react-native";
import { useEffect } from "react";
import { ActivityIndicator, Modal, Pressable, Text, View } from "react-native";
import {
initialWindowMetrics,
SafeAreaProvider,
@@ -17,6 +17,12 @@ interface ReviewSheetProps {
uri: string | null;
mode: "video" | "audio" | null;
durationMs: number;
/**
* True once the parent has flipped to the uploading state. The sheet stays
* mounted (so playback doesn't tear down) and blocks all interaction behind
* a "Sending..." overlay until the parent clears it.
*/
sending: boolean;
onSend: () => Promise<void>;
onRetake: () => void;
onCancel: () => void;
@@ -32,6 +38,7 @@ export function ReviewSheet({
uri,
mode,
durationMs,
sending,
onSend,
onRetake,
onCancel,
@@ -41,22 +48,22 @@ export function ReviewSheet({
p.muted = false;
p.audioMixingMode = "mixWithOthers";
});
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (open && uri) {
if (!open || !uri) return;
if (sending) {
player.pause();
} else {
player.play();
}
}, [open, uri, player]);
}, [open, uri, sending, player]);
const handleSend = async () => {
if (submitting) return;
setSubmitting(true);
if (sending) return;
try {
await onSend();
} catch (err) {
toast.error(toUserMessage(err));
setSubmitting(false);
}
};
@@ -67,7 +74,7 @@ export function ReviewSheet({
visible={open}
animationType="fade"
transparent={false}
onRequestClose={onCancel}
onRequestClose={sending ? undefined : onCancel}
>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<View className="flex-1 bg-black">
@@ -110,10 +117,18 @@ export function ReviewSheet({
<View className="px-4 pt-3">
<Pressable
onPress={onCancel}
disabled={sending}
hitSlop={12}
accessibilityLabel="Cancel"
>
<Text className="text-white/80 text-base">Cancel</Text>
<Text
className={cn(
"text-base",
sending ? "text-white/30" : "text-white/80",
)}
>
Cancel
</Text>
</Pressable>
</View>
</SafeAreaView>
@@ -125,8 +140,11 @@ export function ReviewSheet({
<View className="flex-row items-center justify-between px-6 pb-4 pt-3">
<Pressable
onPress={onRetake}
disabled={submitting}
className="rounded-full bg-white/15 px-5 py-3"
disabled={sending}
className={cn(
"rounded-full bg-white/15 px-5 py-3",
sending && "opacity-40",
)}
accessibilityLabel="Retake"
>
<Text className="text-white text-base font-medium">Retake</Text>
@@ -134,19 +152,26 @@ export function ReviewSheet({
<Pressable
onPress={handleSend}
disabled={submitting}
disabled={sending}
className={cn(
"rounded-full px-7 py-3",
submitting ? "bg-white/40" : "bg-white",
sending ? "bg-white/40" : "bg-white",
)}
accessibilityLabel="Send"
>
<Text className="text-black text-base font-semibold">
{submitting ? "Sending..." : "Send"}
{sending ? "Sending..." : "Send"}
</Text>
</Pressable>
</View>
</SafeAreaView>
{sending ? (
<View className="absolute inset-0 items-center justify-center bg-black/85">
<ActivityIndicator color="white" />
<Text className="text-white/70 mt-4 text-sm">Sending...</Text>
</View>
) : null}
</View>
</SafeAreaProvider>
</Modal>