diff --git a/apps/mobile/components/ModelSelector.tsx b/apps/mobile/components/ModelSelector.tsx index ee26ce3..8b9866a 100644 --- a/apps/mobile/components/ModelSelector.tsx +++ b/apps/mobile/components/ModelSelector.tsx @@ -3,13 +3,18 @@ import { useMemo, useState } from "react"; import { ActivityIndicator, FlatList, + Keyboard, + KeyboardAvoidingView, Modal, + Platform, Pressable, StyleSheet, Text, TextInput, + useWindowDimensions, View, } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { colors, monoFont } from "@/lib/theme"; import type { ModelRef, ModelSummary } from "@/lib/types"; @@ -39,6 +44,10 @@ export function ModelSelector({ }: ModelSelectorProps) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); + const { height: windowHeight } = useWindowDimensions(); + const insets = useSafeAreaInsets(); + const sheetHeight = Math.round(windowHeight * 0.78); + const filteredModels = useMemo(() => { const needle = query.trim().toLowerCase(); if (!needle) { @@ -55,6 +64,17 @@ export function ModelSelector({ ? models.find((model) => model.provider === selected.provider && model.id === selected.id) : undefined; + const close = () => { + Keyboard.dismiss(); + setQuery(""); + setOpen(false); + }; + + const handleSelect = (model: ModelRef) => { + onSelect(model); + close(); + }; + return ( <> - setOpen(false)}> - - + + + + Choose model - setOpen(false)} hitSlop={10}> + @@ -94,48 +126,53 @@ export function ModelSelector({ style={styles.search} autoCorrect={false} autoCapitalize="none" + returnKeyType="search" /> {error ? {error} : null} - {models.length === 0 ? ( - {loading ? "Loading models…" : "No available models"} - ) : ( - `${model.provider}:${model.id}`} - keyboardShouldPersistTaps="handled" - renderItem={({ item }) => { - const isSelected = - item.provider === selected?.provider && item.id === selected?.id; - return ( - [ - styles.option, - pressed && styles.pressed, - isSelected && styles.selected, - ]} - onPress={() => { - onSelect({ provider: item.provider, id: item.id }); - setOpen(false); - }} - > - - {item.id} - - {item.providerDisplayName ?? item.provider} - {item.name && item.name !== item.id ? ` · ${item.name}` : ""} - - - {isSelected ? ( - - ) : null} - - ); - }} - ListEmptyComponent={No matching models} - /> - )} + + {models.length === 0 ? ( + {loading ? "Loading models…" : "No available models"} + ) : ( + `${model.provider}:${model.id}`} + keyboardShouldPersistTaps="handled" + keyboardDismissMode="on-drag" + contentContainerStyle={ + filteredModels.length === 0 ? styles.listEmptyContent : styles.listContent + } + renderItem={({ item }) => { + const isSelected = + item.provider === selected?.provider && item.id === selected?.id; + return ( + [ + styles.option, + pressed && styles.pressed, + isSelected && styles.selected, + ]} + onPress={() => handleSelect({ provider: item.provider, id: item.id })} + > + + {item.id} + + {item.providerDisplayName ?? item.provider} + {item.name && item.name !== item.id ? ` · ${item.name}` : ""} + + + {isSelected ? ( + + ) : null} + + ); + }} + ListEmptyComponent={No matching models} + /> + )} + - + ); @@ -160,14 +197,15 @@ const styles = StyleSheet.create({ pressed: { backgroundColor: colors.surface }, disabled: { opacity: 0.45 }, overlay: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0, 0, 0, 0.65)" }, + backdrop: { ...StyleSheet.absoluteFillObject }, sheet: { - maxHeight: "78%", backgroundColor: colors.surface, borderTopLeftRadius: 16, borderTopRightRadius: 16, borderWidth: 1, borderColor: colors.border, - padding: 16, + paddingHorizontal: 16, + paddingTop: 16, gap: 12, }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, @@ -183,6 +221,21 @@ const styles = StyleSheet.create({ fontFamily: monoFont, fontSize: 13, }, + listRegion: { + flex: 1, + minHeight: 0, + }, + list: { + flex: 1, + }, + listContent: { + paddingBottom: 8, + }, + listEmptyContent: { + flexGrow: 1, + justifyContent: "flex-start", + paddingTop: 8, + }, option: { flexDirection: "row", alignItems: "center",