update for onboarding
This commit is contained in:
parent
ab7b1d78bf
commit
8ee4ada38e
1 changed files with 304 additions and 107 deletions
|
|
@ -2934,6 +2934,76 @@ export default function DictationScreen() {
|
||||||
: onboardingProgress >= 1
|
: onboardingProgress >= 1
|
||||||
? "Model ready in background"
|
? "Model ready in background"
|
||||||
: "Downloading model in background"
|
: "Downloading model in background"
|
||||||
|
const onboardingStepCount = 4
|
||||||
|
const clampedOnboardingStep = Math.max(0, Math.min(onboardingStep, onboardingStepCount - 1))
|
||||||
|
const onboardingTitle =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? "Allow mic access."
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? "Turn on notifications."
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? "Enable local network."
|
||||||
|
: "Pair your computer."
|
||||||
|
const onboardingBody =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? "Control only listens while you hold the record button."
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? "Get alerts when your OpenCode run finishes, fails, or needs your attention."
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? "This lets Control discover your machine on the same network."
|
||||||
|
: "Start `opencode serve` on your computer, then scan the QR code to pair."
|
||||||
|
const onboardingPrimaryLabel =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? microphonePermissionState === "pending"
|
||||||
|
? "Requesting microphone..."
|
||||||
|
: "Allow microphone"
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? notificationPermissionState === "pending"
|
||||||
|
? "Requesting notifications..."
|
||||||
|
: "Allow notifications"
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? localNetworkPermissionState === "pending"
|
||||||
|
? "Requesting local network..."
|
||||||
|
: "Allow local network"
|
||||||
|
: "Scan OpenCode QR"
|
||||||
|
const onboardingPrimaryDisabled =
|
||||||
|
(clampedOnboardingStep === 0 && microphonePermissionState === "pending") ||
|
||||||
|
(clampedOnboardingStep === 1 && notificationPermissionState === "pending") ||
|
||||||
|
(clampedOnboardingStep === 2 && localNetworkPermissionState === "pending")
|
||||||
|
const onboardingSecondaryLabel =
|
||||||
|
clampedOnboardingStep === onboardingStepCount - 1 ? "I will do this later" : "Continue without granting"
|
||||||
|
const onboardingVisualTag =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? "MIC"
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? "PUSH"
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? "LAN"
|
||||||
|
: "PAIR"
|
||||||
|
const onboardingVisualSurfaceStyle =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? styles.onboardingVisualSurfaceMic
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? styles.onboardingVisualSurfaceNotifications
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? styles.onboardingVisualSurfaceNetwork
|
||||||
|
: styles.onboardingVisualSurfacePair
|
||||||
|
const onboardingVisualOrbStyle =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? styles.onboardingVisualOrbMic
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? styles.onboardingVisualOrbNotifications
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? styles.onboardingVisualOrbNetwork
|
||||||
|
: styles.onboardingVisualOrbPair
|
||||||
|
const onboardingVisualTagStyle =
|
||||||
|
clampedOnboardingStep === 0
|
||||||
|
? styles.onboardingVisualTagMic
|
||||||
|
: clampedOnboardingStep === 1
|
||||||
|
? styles.onboardingVisualTagNotifications
|
||||||
|
: clampedOnboardingStep === 2
|
||||||
|
? styles.onboardingVisualTagNetwork
|
||||||
|
: styles.onboardingVisualTagPair
|
||||||
const onboardingSafeStyle = useMemo(
|
const onboardingSafeStyle = useMemo(
|
||||||
() => [styles.onboardingRoot, { paddingTop: insets.top + 8, paddingBottom: Math.max(insets.bottom, 16) }],
|
() => [styles.onboardingRoot, { paddingTop: insets.top + 8, paddingBottom: Math.max(insets.bottom, 16) }],
|
||||||
[insets.bottom, insets.top],
|
[insets.bottom, insets.top],
|
||||||
|
|
@ -2952,102 +3022,98 @@ export default function DictationScreen() {
|
||||||
<SafeAreaView style={onboardingSafeStyle} edges={["left", "right"]}>
|
<SafeAreaView style={onboardingSafeStyle} edges={["left", "right"]}>
|
||||||
<StatusBar style="light" />
|
<StatusBar style="light" />
|
||||||
|
|
||||||
<View style={styles.onboardingModelRow}>
|
<View style={styles.onboardingShell}>
|
||||||
<Text style={styles.onboardingModelText}>{onboardingModelStatus}</Text>
|
<View style={styles.onboardingTopRail}>
|
||||||
<View style={styles.onboardingModelTrack}>
|
<View style={styles.onboardingModelRow}>
|
||||||
<View
|
<Text style={styles.onboardingModelText}>{onboardingModelStatus}</Text>
|
||||||
style={[
|
<View style={styles.onboardingModelTrack}>
|
||||||
styles.onboardingModelFill,
|
<View
|
||||||
{ width: `${Math.max(onboardingProgressPct, onboardingProgress > 0 ? 6 : 0)}%` },
|
style={[
|
||||||
]}
|
styles.onboardingModelFill,
|
||||||
/>
|
{ width: `${Math.max(onboardingProgressPct, onboardingProgress > 0 ? 6 : 0)}%` },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.onboardingContent}>
|
<View style={styles.onboardingContent}>
|
||||||
{onboardingStep === 0 ? (
|
<View style={[styles.onboardingVisualSurface, onboardingVisualSurfaceStyle]}>
|
||||||
<View style={styles.onboardingStep}>
|
<View style={[styles.onboardingVisualOrb, styles.onboardingVisualOrbOne, onboardingVisualOrbStyle]} />
|
||||||
<Text style={styles.onboardingTitle}>Allow microphone</Text>
|
<View style={[styles.onboardingVisualOrb, styles.onboardingVisualOrbTwo, onboardingVisualOrbStyle]} />
|
||||||
<Text style={styles.onboardingBody}>Enable microphone access so Control can record dictation.</Text>
|
<View style={[styles.onboardingVisualTag, onboardingVisualTagStyle]}>
|
||||||
<Pressable
|
<Text style={styles.onboardingVisualTagText}>{onboardingVisualTag}</Text>
|
||||||
onPress={() => {
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.onboardingCopyBlock}>
|
||||||
|
<Text
|
||||||
|
style={styles.onboardingEyebrow}
|
||||||
|
>{`STEP ${clampedOnboardingStep + 1} OF ${onboardingStepCount}`}</Text>
|
||||||
|
<Text style={styles.onboardingTitle}>{onboardingTitle}</Text>
|
||||||
|
<Text style={styles.onboardingBody}>{onboardingBody}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.onboardingFooter}>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => {
|
||||||
|
if (clampedOnboardingStep === 0) {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await handleRequestMicrophonePermission()
|
await handleRequestMicrophonePermission()
|
||||||
setOnboardingStep(1)
|
setOnboardingStep(1)
|
||||||
})()
|
})()
|
||||||
}}
|
return
|
||||||
style={({ pressed }) => [styles.onboardingPrimaryButton, pressed && styles.clearButtonPressed]}
|
}
|
||||||
disabled={microphonePermissionState === "pending"}
|
|
||||||
>
|
if (clampedOnboardingStep === 1) {
|
||||||
<Text style={styles.onboardingPrimaryButtonText}>
|
|
||||||
{microphonePermissionState === "pending" ? "Requesting..." : "Allow microphone"}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable onPress={() => setOnboardingStep(1)}>
|
|
||||||
<Text style={styles.onboardingSecondaryText}>Continue</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
) : onboardingStep === 1 ? (
|
|
||||||
<View style={styles.onboardingStep}>
|
|
||||||
<Text style={styles.onboardingTitle}>Allow notifications</Text>
|
|
||||||
<Text style={styles.onboardingBody}>Get session updates when your run completes.</Text>
|
|
||||||
<Pressable
|
|
||||||
onPress={() => {
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await handleRequestNotificationPermission()
|
await handleRequestNotificationPermission()
|
||||||
setOnboardingStep(2)
|
setOnboardingStep(2)
|
||||||
})()
|
})()
|
||||||
}}
|
return
|
||||||
style={({ pressed }) => [styles.onboardingPrimaryButton, pressed && styles.clearButtonPressed]}
|
}
|
||||||
disabled={notificationPermissionState === "pending"}
|
|
||||||
>
|
if (clampedOnboardingStep === 2) {
|
||||||
<Text style={styles.onboardingPrimaryButtonText}>
|
|
||||||
{notificationPermissionState === "pending" ? "Requesting..." : "Allow notifications"}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable onPress={() => setOnboardingStep(2)}>
|
|
||||||
<Text style={styles.onboardingSecondaryText}>Continue</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
) : onboardingStep === 2 ? (
|
|
||||||
<View style={styles.onboardingStep}>
|
|
||||||
<Text style={styles.onboardingTitle}>Allow local network</Text>
|
|
||||||
<Text style={styles.onboardingBody}>This lets Control find your computer on your network.</Text>
|
|
||||||
<Pressable
|
|
||||||
onPress={() => {
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await handleRequestLocalNetworkPermission()
|
await handleRequestLocalNetworkPermission()
|
||||||
setOnboardingStep(3)
|
setOnboardingStep(3)
|
||||||
})()
|
})()
|
||||||
}}
|
return
|
||||||
style={({ pressed }) => [styles.onboardingPrimaryButton, pressed && styles.clearButtonPressed]}
|
}
|
||||||
disabled={localNetworkPermissionState === "pending"}
|
|
||||||
>
|
completeOnboarding(true)
|
||||||
<Text style={styles.onboardingPrimaryButtonText}>
|
}}
|
||||||
{localNetworkPermissionState === "pending" ? "Requesting..." : "Allow local network"}
|
style={({ pressed }) => [
|
||||||
</Text>
|
styles.onboardingPrimaryButton,
|
||||||
</Pressable>
|
onboardingPrimaryDisabled && styles.onboardingPrimaryButtonDisabled,
|
||||||
<Pressable onPress={() => setOnboardingStep(3)}>
|
pressed && styles.clearButtonPressed,
|
||||||
<Text style={styles.onboardingSecondaryText}>Continue</Text>
|
]}
|
||||||
</Pressable>
|
disabled={onboardingPrimaryDisabled}
|
||||||
</View>
|
>
|
||||||
) : (
|
<Text style={styles.onboardingPrimaryButtonText}>{onboardingPrimaryLabel}</Text>
|
||||||
<View style={styles.onboardingStep}>
|
<SymbolView
|
||||||
<Text style={styles.onboardingTitle}>Connect your computer</Text>
|
name={{ ios: "arrow.right", android: "arrow_forward", web: "arrow_forward" }}
|
||||||
<Text style={styles.onboardingBody}>
|
size={20}
|
||||||
Start `opencode serve` on your computer, then scan the QR code to pair.
|
weight="semibold"
|
||||||
</Text>
|
tintColor="#FFFFFF"
|
||||||
<Pressable
|
/>
|
||||||
onPress={() => completeOnboarding(true)}
|
</Pressable>
|
||||||
style={({ pressed }) => [styles.onboardingPrimaryButton, pressed && styles.clearButtonPressed]}
|
|
||||||
>
|
<Pressable
|
||||||
<Text style={styles.onboardingPrimaryButtonText}>Scan OpenCode QR</Text>
|
onPress={() => {
|
||||||
</Pressable>
|
if (clampedOnboardingStep < onboardingStepCount - 1) {
|
||||||
<Pressable onPress={() => completeOnboarding(false)}>
|
setOnboardingStep((step) => Math.min(step + 1, onboardingStepCount - 1))
|
||||||
<Text style={styles.onboardingSecondaryText}>I will do this later</Text>
|
return
|
||||||
</Pressable>
|
}
|
||||||
</View>
|
|
||||||
)}
|
completeOnboarding(false)
|
||||||
|
}}
|
||||||
|
style={({ pressed }) => [styles.onboardingSecondaryButton, pressed && styles.clearButtonPressed]}
|
||||||
|
>
|
||||||
|
<Text style={styles.onboardingSecondaryText}>{onboardingSecondaryLabel}</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
)
|
)
|
||||||
|
|
@ -3601,23 +3667,31 @@ const styles = StyleSheet.create({
|
||||||
onboardingRoot: {
|
onboardingRoot: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: "#121212",
|
backgroundColor: "#121212",
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 16,
|
||||||
|
},
|
||||||
|
onboardingShell: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
onboardingTopRail: {
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
onboardingContent: {
|
onboardingContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
alignItems: "stretch",
|
||||||
onboardingStep: {
|
gap: 22,
|
||||||
gap: 14,
|
paddingHorizontal: 2,
|
||||||
},
|
},
|
||||||
onboardingModelRow: {
|
onboardingModelRow: {
|
||||||
gap: 6,
|
gap: 6,
|
||||||
marginBottom: 12,
|
|
||||||
},
|
},
|
||||||
onboardingModelText: {
|
onboardingModelText: {
|
||||||
color: "#C3C3C3",
|
color: "#A9A9A9",
|
||||||
fontSize: 12,
|
fontSize: 11,
|
||||||
fontWeight: "600",
|
fontWeight: "700",
|
||||||
|
letterSpacing: 0.35,
|
||||||
|
textTransform: "uppercase",
|
||||||
},
|
},
|
||||||
onboardingModelTrack: {
|
onboardingModelTrack: {
|
||||||
height: 4,
|
height: 4,
|
||||||
|
|
@ -3631,37 +3705,160 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
backgroundColor: "#FF5B47",
|
backgroundColor: "#FF5B47",
|
||||||
},
|
},
|
||||||
onboardingTitle: {
|
onboardingVisualSurface: {
|
||||||
color: "#F1F1F1",
|
width: "100%",
|
||||||
fontSize: 20,
|
minHeight: 176,
|
||||||
fontWeight: "700",
|
borderRadius: 26,
|
||||||
},
|
borderWidth: 1,
|
||||||
onboardingBody: {
|
|
||||||
color: "#A3A3A3",
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
onboardingPrimaryButton: {
|
|
||||||
marginTop: 6,
|
|
||||||
height: 44,
|
|
||||||
borderRadius: 12,
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
backgroundColor: "#4B2620",
|
overflow: "hidden",
|
||||||
|
backgroundColor: "#171717",
|
||||||
|
borderColor: "#2B2B2B",
|
||||||
|
},
|
||||||
|
onboardingVisualSurfaceMic: {
|
||||||
|
backgroundColor: "#1A2118",
|
||||||
|
borderColor: "#2F3D2D",
|
||||||
|
},
|
||||||
|
onboardingVisualSurfaceNotifications: {
|
||||||
|
backgroundColor: "#1A1D2A",
|
||||||
|
borderColor: "#303A5A",
|
||||||
|
},
|
||||||
|
onboardingVisualSurfaceNetwork: {
|
||||||
|
backgroundColor: "#1A2218",
|
||||||
|
borderColor: "#344930",
|
||||||
|
},
|
||||||
|
onboardingVisualSurfacePair: {
|
||||||
|
backgroundColor: "#1F1A27",
|
||||||
|
borderColor: "#413157",
|
||||||
|
},
|
||||||
|
onboardingVisualOrb: {
|
||||||
|
position: "absolute",
|
||||||
|
borderRadius: 999,
|
||||||
|
opacity: 0.22,
|
||||||
|
},
|
||||||
|
onboardingVisualOrbOne: {
|
||||||
|
width: 130,
|
||||||
|
height: 130,
|
||||||
|
top: -28,
|
||||||
|
left: -22,
|
||||||
|
},
|
||||||
|
onboardingVisualOrbTwo: {
|
||||||
|
width: 160,
|
||||||
|
height: 160,
|
||||||
|
bottom: -52,
|
||||||
|
right: -44,
|
||||||
|
},
|
||||||
|
onboardingVisualOrbMic: {
|
||||||
|
backgroundColor: "#61C372",
|
||||||
|
},
|
||||||
|
onboardingVisualOrbNotifications: {
|
||||||
|
backgroundColor: "#4A6EE0",
|
||||||
|
},
|
||||||
|
onboardingVisualOrbNetwork: {
|
||||||
|
backgroundColor: "#78B862",
|
||||||
|
},
|
||||||
|
onboardingVisualOrbPair: {
|
||||||
|
backgroundColor: "#9B6CDC",
|
||||||
|
},
|
||||||
|
onboardingVisualTag: {
|
||||||
|
borderRadius: 20,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingVertical: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: "#70372D",
|
shadowColor: "#000000",
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.12,
|
||||||
|
shadowRadius: 16,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
onboardingVisualTagMic: {
|
||||||
|
backgroundColor: "#253A25",
|
||||||
|
borderColor: "#3A5C3A",
|
||||||
|
},
|
||||||
|
onboardingVisualTagNotifications: {
|
||||||
|
backgroundColor: "#223561",
|
||||||
|
borderColor: "#38518C",
|
||||||
|
},
|
||||||
|
onboardingVisualTagNetwork: {
|
||||||
|
backgroundColor: "#284122",
|
||||||
|
borderColor: "#3D6835",
|
||||||
|
},
|
||||||
|
onboardingVisualTagPair: {
|
||||||
|
backgroundColor: "#3B2859",
|
||||||
|
borderColor: "#5A3D86",
|
||||||
|
},
|
||||||
|
onboardingVisualTagText: {
|
||||||
|
color: "#F6F7F8",
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: "800",
|
||||||
|
letterSpacing: 1.8,
|
||||||
|
},
|
||||||
|
onboardingCopyBlock: {
|
||||||
|
alignItems: "flex-start",
|
||||||
|
gap: 10,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
onboardingEyebrow: {
|
||||||
|
color: "#7F7F7F",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: "700",
|
||||||
|
letterSpacing: 1.3,
|
||||||
|
},
|
||||||
|
onboardingTitle: {
|
||||||
|
color: "#F1F1F1",
|
||||||
|
fontSize: 34,
|
||||||
|
fontWeight: "800",
|
||||||
|
textAlign: "left",
|
||||||
|
letterSpacing: -1,
|
||||||
|
lineHeight: 38,
|
||||||
|
},
|
||||||
|
onboardingBody: {
|
||||||
|
color: "#B4B4B4",
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 25,
|
||||||
|
textAlign: "left",
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
},
|
||||||
|
onboardingFooter: {
|
||||||
|
gap: 10,
|
||||||
|
paddingTop: 6,
|
||||||
|
},
|
||||||
|
onboardingPrimaryButton: {
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 14,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: "#1D6FF4",
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "#1557C3",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 10,
|
||||||
|
shadowColor: "#000000",
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 18,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
onboardingPrimaryButtonDisabled: {
|
||||||
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
onboardingPrimaryButtonText: {
|
onboardingPrimaryButtonText: {
|
||||||
color: "#FFD9D2",
|
color: "#FFFFFF",
|
||||||
fontSize: 14,
|
fontSize: 17,
|
||||||
fontWeight: "700",
|
fontWeight: "700",
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
},
|
||||||
|
onboardingSecondaryButton: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 2,
|
||||||
},
|
},
|
||||||
onboardingSecondaryText: {
|
onboardingSecondaryText: {
|
||||||
color: "#A8A8A8",
|
color: "#959CAA",
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
textAlign: "center",
|
textAlign: "left",
|
||||||
paddingVertical: 4,
|
|
||||||
},
|
},
|
||||||
dismissOverlay: {
|
dismissOverlay: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue