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
|
|
@ -8,7 +8,7 @@
|
||||||
"scheme": "mobilevoice",
|
"scheme": "mobilevoice",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
"ios": {
|
"ios": {
|
||||||
"icon": "./assets/expo.icon",
|
"icon": "./assets/images/icon.png",
|
||||||
"bundleIdentifier": "com.anomalyco.mobilevoice",
|
"bundleIdentifier": "com.anomalyco.mobilevoice",
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"NSMicrophoneUsageDescription": "This app needs microphone access for live speech-to-text dictation.",
|
"NSMicrophoneUsageDescription": "This app needs microphone access for live speech-to-text dictation.",
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 780 KiB After Width: | Height: | Size: 1.3 MiB |
|
|
@ -5,7 +5,6 @@ import {
|
||||||
View,
|
View,
|
||||||
Pressable,
|
Pressable,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
TextInput,
|
|
||||||
Modal,
|
Modal,
|
||||||
Alert,
|
Alert,
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
|
|
@ -120,6 +119,11 @@ type Scan = {
|
||||||
data: string
|
data: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cam = {
|
||||||
|
CameraView: (typeof import("expo-camera"))["CameraView"]
|
||||||
|
requestCameraPermissionsAsync: (typeof import("expo-camera"))["Camera"]["requestCameraPermissionsAsync"]
|
||||||
|
}
|
||||||
|
|
||||||
function parsePair(input: string): Pair | undefined {
|
function parsePair(input: string): Pair | undefined {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(input)
|
const data = JSON.parse(input)
|
||||||
|
|
@ -158,14 +162,7 @@ function pickHost(list: string[]): string | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DictationScreen() {
|
export default function DictationScreen() {
|
||||||
const [camera, setCamera] = useState<{
|
const [camera, setCamera] = useState<Cam | null>(null)
|
||||||
CameraView: React.ComponentType<{
|
|
||||||
style?: unknown
|
|
||||||
barcodeScannerSettings?: { barcodeTypes?: string[] }
|
|
||||||
onBarcodeScanned?: (event: Scan) => void
|
|
||||||
}>
|
|
||||||
requestCameraPermissionsAsync: () => Promise<{ granted: boolean | undefined }>
|
|
||||||
} | null>(null)
|
|
||||||
const [modelReset, setModelReset] = useState(false)
|
const [modelReset, setModelReset] = useState(false)
|
||||||
const model = useSpeechToText({
|
const model = useSpeechToText({
|
||||||
model: WHISPER_BASE_EN,
|
model: WHISPER_BASE_EN,
|
||||||
|
|
@ -184,10 +181,6 @@ export default function DictationScreen() {
|
||||||
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState)
|
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState)
|
||||||
const [dropdownMode, setDropdownMode] = useState<DropdownMode>("none")
|
const [dropdownMode, setDropdownMode] = useState<DropdownMode>("none")
|
||||||
const [dropdownRenderMode, setDropdownRenderMode] = useState<Exclude<DropdownMode, "none">>("server")
|
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 [scanOpen, setScanOpen] = useState(false)
|
||||||
const [camGranted, setCamGranted] = useState(false)
|
const [camGranted, setCamGranted] = useState(false)
|
||||||
const [servers, setServers] = useState<ServerItem[]>([
|
const [servers, setServers] = useState<ServerItem[]>([
|
||||||
|
|
@ -974,7 +967,7 @@ export default function DictationScreen() {
|
||||||
const menuRows =
|
const menuRows =
|
||||||
effectiveDropdownMode === "server" ? Math.max(servers.length, 1) : Math.max(activeServer?.sessions.length ?? 0, 1)
|
effectiveDropdownMode === "server" ? Math.max(servers.length, 1) : Math.max(activeServer?.sessions.length ?? 0, 1)
|
||||||
const expandedRowsHeight = Math.min(menuRows, DROPDOWN_VISIBLE_ROWS) * 42
|
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 expandedHeaderHeight = 51 + 12 + expandedRowsHeight + addServerExtraHeight
|
||||||
|
|
||||||
const animatedHeaderStyle = useAnimatedStyle(() => ({
|
const animatedHeaderStyle = useAnimatedStyle(() => ({
|
||||||
|
|
@ -1135,7 +1128,6 @@ export default function DictationScreen() {
|
||||||
|
|
||||||
const toggleServerMenu = useCallback(() => {
|
const toggleServerMenu = useCallback(() => {
|
||||||
Haptics.selectionAsync().catch(() => {})
|
Haptics.selectionAsync().catch(() => {})
|
||||||
setIsAddingServer(false)
|
|
||||||
setDropdownMode((prev) => {
|
setDropdownMode((prev) => {
|
||||||
const next = prev === "server" ? "none" : "server"
|
const next = prev === "server" ? "none" : "server"
|
||||||
if (next === "server") {
|
if (next === "server") {
|
||||||
|
|
@ -1195,18 +1187,6 @@ export default function DictationScreen() {
|
||||||
[activeServerId, devicePushToken],
|
[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(
|
const addServer = useCallback(
|
||||||
(serverURL: string, relayURL: string, relaySecretRaw: string) => {
|
(serverURL: string, relayURL: string, relaySecretRaw: string) => {
|
||||||
const raw = serverURL.trim()
|
const raw = serverURL.trim()
|
||||||
|
|
@ -1242,8 +1222,6 @@ export default function DictationScreen() {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
setActiveServerId(existing.id)
|
setActiveServerId(existing.id)
|
||||||
setActiveSessionId(null)
|
setActiveSessionId(null)
|
||||||
setIsAddingServer(false)
|
|
||||||
setServerDraftRelaySecret("")
|
|
||||||
setDropdownMode("none")
|
setDropdownMode("none")
|
||||||
refreshServerStatusAndSessions(existing.id)
|
refreshServerStatusAndSessions(existing.id)
|
||||||
return true
|
return true
|
||||||
|
|
@ -1264,8 +1242,6 @@ export default function DictationScreen() {
|
||||||
])
|
])
|
||||||
setActiveServerId(id)
|
setActiveServerId(id)
|
||||||
setActiveSessionId(null)
|
setActiveSessionId(null)
|
||||||
setIsAddingServer(false)
|
|
||||||
setServerDraftRelaySecret("")
|
|
||||||
setDropdownMode("none")
|
setDropdownMode("none")
|
||||||
refreshServerStatusAndSessions(id)
|
refreshServerStatusAndSessions(id)
|
||||||
return true
|
return true
|
||||||
|
|
@ -1273,10 +1249,6 @@ export default function DictationScreen() {
|
||||||
[refreshServerStatusAndSessions],
|
[refreshServerStatusAndSessions],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleConfirmAddServer = useCallback(() => {
|
|
||||||
addServer(serverDraftURL, serverDraftRelayURL, serverDraftRelaySecret)
|
|
||||||
}, [addServer, serverDraftRelaySecret, serverDraftRelayURL, serverDraftURL])
|
|
||||||
|
|
||||||
const handleStartScan = useCallback(async () => {
|
const handleStartScan = useCallback(async () => {
|
||||||
scanLockRef.current = false
|
scanLockRef.current = false
|
||||||
const current =
|
const current =
|
||||||
|
|
@ -1550,55 +1522,9 @@ export default function DictationScreen() {
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{effectiveDropdownMode === "server" ? (
|
{effectiveDropdownMode === "server" ? (
|
||||||
isAddingServer ? (
|
<Pressable onPress={() => void handleStartScan()} style={styles.addServerButton}>
|
||||||
<View style={styles.addServerComposer}>
|
<Text style={styles.addServerButtonText}>Add server by scanning QR code</Text>
|
||||||
<Pressable onPress={() => void handleStartScan()} style={styles.scanButton}>
|
</Pressable>
|
||||||
<Text style={styles.scanButtonText}>Scan server QR</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}
|
) : null}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -1878,52 +1804,6 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: "600",
|
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: {
|
statusLeft: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
|
||||||