feat: use new mobile app icon and QR-only server add flow

Replace Expo icon/adaptive icon assets with the provided image and simplify the server dropdown so adding a server is done by scanning the setup QR code only.
pull/19545/head
Ryan Vogel 2026-03-28 17:30:13 -04:00
parent 0a9fcab56f
commit 52d1ee70a0
11 changed files with 11 additions and 131 deletions

View File

@ -8,7 +8,7 @@
"scheme": "mobilevoice",
"userInterfaceStyle": "automatic",
"ios": {
"icon": "./assets/expo.icon",
"icon": "./assets/images/icon.png",
"bundleIdentifier": "com.anomalyco.mobilevoice",
"infoPlist": {
"NSMicrophoneUsageDescription": "This app needs microphone access for live speech-to-text dictation.",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -5,7 +5,6 @@ import {
View,
Pressable,
ScrollView,
TextInput,
Modal,
Alert,
LayoutChangeEvent,
@ -120,6 +119,11 @@ type Scan = {
data: string
}
type Cam = {
CameraView: (typeof import("expo-camera"))["CameraView"]
requestCameraPermissionsAsync: (typeof import("expo-camera"))["Camera"]["requestCameraPermissionsAsync"]
}
function parsePair(input: string): Pair | undefined {
try {
const data = JSON.parse(input)
@ -158,14 +162,7 @@ function pickHost(list: string[]): string | undefined {
}
export default function DictationScreen() {
const [camera, setCamera] = useState<{
CameraView: React.ComponentType<{
style?: unknown
barcodeScannerSettings?: { barcodeTypes?: string[] }
onBarcodeScanned?: (event: Scan) => void
}>
requestCameraPermissionsAsync: () => Promise<{ granted: boolean | undefined }>
} | null>(null)
const [camera, setCamera] = useState<Cam | null>(null)
const [modelReset, setModelReset] = useState(false)
const model = useSpeechToText({
model: WHISPER_BASE_EN,
@ -184,10 +181,6 @@ export default function DictationScreen() {
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState)
const [dropdownMode, setDropdownMode] = useState<DropdownMode>("none")
const [dropdownRenderMode, setDropdownRenderMode] = useState<Exclude<DropdownMode, "none">>("server")
const [isAddingServer, setIsAddingServer] = useState(false)
const [serverDraftURL, setServerDraftURL] = useState("http://127.0.0.1:4096")
const [serverDraftRelayURL, setServerDraftRelayURL] = useState(DEFAULT_RELAY_URL)
const [serverDraftRelaySecret, setServerDraftRelaySecret] = useState("")
const [scanOpen, setScanOpen] = useState(false)
const [camGranted, setCamGranted] = useState(false)
const [servers, setServers] = useState<ServerItem[]>([
@ -974,7 +967,7 @@ export default function DictationScreen() {
const menuRows =
effectiveDropdownMode === "server" ? Math.max(servers.length, 1) : Math.max(activeServer?.sessions.length ?? 0, 1)
const expandedRowsHeight = Math.min(menuRows, DROPDOWN_VISIBLE_ROWS) * 42
const addServerExtraHeight = effectiveDropdownMode === "server" ? (isAddingServer ? 188 : 38) : 8
const addServerExtraHeight = effectiveDropdownMode === "server" ? 38 : 8
const expandedHeaderHeight = 51 + 12 + expandedRowsHeight + addServerExtraHeight
const animatedHeaderStyle = useAnimatedStyle(() => ({
@ -1135,7 +1128,6 @@ export default function DictationScreen() {
const toggleServerMenu = useCallback(() => {
Haptics.selectionAsync().catch(() => {})
setIsAddingServer(false)
setDropdownMode((prev) => {
const next = prev === "server" ? "none" : "server"
if (next === "server") {
@ -1195,18 +1187,6 @@ export default function DictationScreen() {
[activeServerId, devicePushToken],
)
const handleStartAddServer = useCallback(() => {
setIsAddingServer(true)
setServerDraftRelayURL(DEFAULT_RELAY_URL)
setServerDraftRelaySecret("")
}, [])
const handleCancelAddServer = useCallback(() => {
setIsAddingServer(false)
setServerDraftRelayURL(DEFAULT_RELAY_URL)
setServerDraftRelaySecret("")
}, [])
const addServer = useCallback(
(serverURL: string, relayURL: string, relaySecretRaw: string) => {
const raw = serverURL.trim()
@ -1242,8 +1222,6 @@ export default function DictationScreen() {
if (existing) {
setActiveServerId(existing.id)
setActiveSessionId(null)
setIsAddingServer(false)
setServerDraftRelaySecret("")
setDropdownMode("none")
refreshServerStatusAndSessions(existing.id)
return true
@ -1264,8 +1242,6 @@ export default function DictationScreen() {
])
setActiveServerId(id)
setActiveSessionId(null)
setIsAddingServer(false)
setServerDraftRelaySecret("")
setDropdownMode("none")
refreshServerStatusAndSessions(id)
return true
@ -1273,10 +1249,6 @@ export default function DictationScreen() {
[refreshServerStatusAndSessions],
)
const handleConfirmAddServer = useCallback(() => {
addServer(serverDraftURL, serverDraftRelayURL, serverDraftRelaySecret)
}, [addServer, serverDraftRelaySecret, serverDraftRelayURL, serverDraftURL])
const handleStartScan = useCallback(async () => {
scanLockRef.current = false
const current =
@ -1550,55 +1522,9 @@ export default function DictationScreen() {
</ScrollView>
{effectiveDropdownMode === "server" ? (
isAddingServer ? (
<View style={styles.addServerComposer}>
<Pressable onPress={() => void handleStartScan()} style={styles.scanButton}>
<Text style={styles.scanButtonText}>Scan server QR</Text>
<Pressable onPress={() => void handleStartScan()} style={styles.addServerButton}>
<Text style={styles.addServerButtonText}>Add server by scanning QR code</Text>
</Pressable>
<TextInput
value={serverDraftURL}
onChangeText={setServerDraftURL}
autoCapitalize="none"
autoCorrect={false}
keyboardType="url"
placeholder="https://your-opencode-server"
placeholderTextColor="#6F7686"
style={styles.addServerInput}
/>
<TextInput
value={serverDraftRelayURL}
onChangeText={setServerDraftRelayURL}
autoCapitalize="none"
autoCorrect={false}
keyboardType="url"
placeholder="https://your-relay-server"
placeholderTextColor="#6F7686"
style={styles.addServerInput}
/>
<TextInput
value={serverDraftRelaySecret}
onChangeText={setServerDraftRelaySecret}
autoCapitalize="none"
autoCorrect={false}
placeholder="Relay shared secret"
placeholderTextColor="#6F7686"
secureTextEntry
style={styles.addServerInput}
/>
<View style={styles.addServerActions}>
<Pressable onPress={handleCancelAddServer}>
<Text style={styles.addServerCancelText}>Cancel</Text>
</Pressable>
<Pressable onPress={handleConfirmAddServer}>
<Text style={styles.addServerConfirmText}>Add</Text>
</Pressable>
</View>
</View>
) : (
<Pressable onPress={handleStartAddServer} style={styles.addServerButton}>
<Text style={styles.addServerButtonText}>+ Add server</Text>
</Pressable>
)
) : null}
</Animated.View>
</Animated.View>
@ -1878,52 +1804,6 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: "600",
},
addServerComposer: {
marginTop: 8,
paddingHorizontal: 4,
gap: 8,
},
scanButton: {
height: 38,
borderRadius: 10,
borderWidth: 1,
borderColor: "#2F4D84",
backgroundColor: "#142544",
alignItems: "center",
justifyContent: "center",
},
scanButtonText: {
color: "#A8C7FF",
fontSize: 14,
fontWeight: "700",
letterSpacing: 0.2,
},
addServerInput: {
height: 38,
borderRadius: 10,
borderWidth: 1,
borderColor: "#2A2A33",
backgroundColor: "#151515",
color: "#D6DAE4",
paddingHorizontal: 12,
fontSize: 14,
},
addServerActions: {
flexDirection: "row",
justifyContent: "flex-end",
gap: 16,
paddingHorizontal: 4,
},
addServerCancelText: {
color: "#8C93A3",
fontSize: 14,
fontWeight: "600",
},
addServerConfirmText: {
color: "#FF6A78",
fontSize: 14,
fontWeight: "700",
},
statusLeft: {
flexDirection: "row",
alignItems: "center",