294 lines
7.6 KiB
TypeScript
294 lines
7.6 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Modal,
|
|
Platform,
|
|
ScrollView,
|
|
TextInput,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useThings } from "@/state/use-thing";
|
|
import { useI18n } from "@/hooks/use-i18n";
|
|
import { useThemeContext } from "@/hooks/use-theme-context";
|
|
|
|
interface ShipSelectorProps {
|
|
selectedShipId: string;
|
|
onChange: (shipId: string) => void;
|
|
}
|
|
|
|
export default function ShipSelector({
|
|
selectedShipId,
|
|
onChange,
|
|
}: ShipSelectorProps) {
|
|
const { t } = useI18n();
|
|
const { colors } = useThemeContext();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [searchText, setSearchText] = useState("");
|
|
|
|
const { things } = useThings();
|
|
|
|
// Convert things to ship options
|
|
const shipOptions =
|
|
things
|
|
?.filter((thing) => thing.id != null)
|
|
.map((thing) => ({
|
|
id: thing.id as string,
|
|
shipName: thing.metadata?.ship_name || "",
|
|
})) || [];
|
|
|
|
// Filter ships based on search text
|
|
const filteredShips = shipOptions.filter((ship) => {
|
|
const searchLower = searchText.toLowerCase();
|
|
return ship.shipName.toLowerCase().includes(searchLower);
|
|
});
|
|
|
|
const handleSelect = (shipId: string) => {
|
|
onChange(shipId);
|
|
setIsOpen(false);
|
|
setSearchText("");
|
|
};
|
|
|
|
const selectedShip = shipOptions.find((ship) => ship.id === selectedShipId);
|
|
const displayValue = selectedShip
|
|
? selectedShip.shipName
|
|
: t("diary.selectShip");
|
|
|
|
const themedStyles = {
|
|
label: { color: colors.text },
|
|
selector: { backgroundColor: colors.card, borderColor: colors.border },
|
|
selectorText: { color: colors.text },
|
|
placeholder: { color: colors.textSecondary },
|
|
modalContent: { backgroundColor: colors.card },
|
|
searchContainer: {
|
|
backgroundColor: colors.backgroundSecondary,
|
|
borderColor: colors.border,
|
|
},
|
|
searchInput: { color: colors.text },
|
|
option: { borderBottomColor: colors.separator },
|
|
selectedOption: { backgroundColor: colors.backgroundSecondary },
|
|
optionText: { color: colors.text },
|
|
emptyText: { color: colors.textSecondary },
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={[styles.label, themedStyles.label]}>
|
|
{t("diary.shipSelector")}
|
|
<Text style={styles.required}> *</Text>
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[styles.selector, themedStyles.selector]}
|
|
onPress={() => setIsOpen(true)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.selectorText,
|
|
themedStyles.selectorText,
|
|
!selectedShipId && themedStyles.placeholder,
|
|
]}
|
|
>
|
|
{displayValue}
|
|
</Text>
|
|
<Ionicons
|
|
name="ellipsis-horizontal"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
<Modal
|
|
visible={isOpen}
|
|
transparent
|
|
animationType="fade"
|
|
onRequestClose={() => setIsOpen(false)}
|
|
>
|
|
<TouchableOpacity
|
|
style={styles.modalOverlay}
|
|
activeOpacity={1}
|
|
onPress={() => setIsOpen(false)}
|
|
>
|
|
<View
|
|
style={[styles.modalContent, themedStyles.modalContent]}
|
|
onStartShouldSetResponder={() => true}
|
|
>
|
|
{/* Search Input */}
|
|
<View
|
|
style={[styles.searchContainer, themedStyles.searchContainer]}
|
|
>
|
|
<Ionicons
|
|
name="search"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
style={styles.searchIcon}
|
|
/>
|
|
<TextInput
|
|
style={[styles.searchInput, themedStyles.searchInput]}
|
|
placeholder={t("diary.searchShip")}
|
|
placeholderTextColor={colors.textSecondary}
|
|
value={searchText}
|
|
onChangeText={setSearchText}
|
|
autoCapitalize="none"
|
|
/>
|
|
{searchText.length > 0 && (
|
|
<TouchableOpacity onPress={() => setSearchText("")}>
|
|
<Ionicons
|
|
name="close-circle"
|
|
size={20}
|
|
color={colors.textSecondary}
|
|
/>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
|
|
<ScrollView style={styles.optionsList}>
|
|
{/* Filtered ship options */}
|
|
{filteredShips.length > 0 ? (
|
|
filteredShips.map((ship) => (
|
|
<TouchableOpacity
|
|
key={ship.id}
|
|
style={[
|
|
styles.option,
|
|
themedStyles.option,
|
|
selectedShipId === ship.id && themedStyles.selectedOption,
|
|
]}
|
|
onPress={() => handleSelect(ship.id)}
|
|
>
|
|
<Text style={[styles.optionText, themedStyles.optionText]}>
|
|
{ship.shipName}
|
|
</Text>
|
|
{selectedShipId === ship.id && (
|
|
<Ionicons
|
|
name="checkmark"
|
|
size={20}
|
|
color={colors.primary}
|
|
/>
|
|
)}
|
|
</TouchableOpacity>
|
|
))
|
|
) : (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={[styles.emptyText, themedStyles.emptyText]}>
|
|
{t("diary.noShipsFound")}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 20,
|
|
},
|
|
label: {
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
marginBottom: 8,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
required: {
|
|
color: "#EF4444",
|
|
},
|
|
selector: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
borderWidth: 1,
|
|
borderRadius: 8,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
},
|
|
selectorText: {
|
|
fontSize: 16,
|
|
flex: 1,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
modalContent: {
|
|
borderRadius: 12,
|
|
width: "85%",
|
|
maxHeight: "70%",
|
|
overflow: "hidden",
|
|
shadowColor: "#000",
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 4,
|
|
},
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 8,
|
|
elevation: 8,
|
|
},
|
|
searchContainer: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
borderBottomWidth: 1,
|
|
},
|
|
searchIcon: {
|
|
marginRight: 8,
|
|
},
|
|
searchInput: {
|
|
flex: 1,
|
|
fontSize: 16,
|
|
padding: 0,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
optionsList: {
|
|
maxHeight: 350,
|
|
},
|
|
option: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 16,
|
|
borderBottomWidth: 1,
|
|
},
|
|
optionText: {
|
|
fontSize: 16,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
emptyContainer: {
|
|
paddingVertical: 24,
|
|
alignItems: "center",
|
|
},
|
|
emptyText: {
|
|
fontSize: 14,
|
|
fontFamily: Platform.select({
|
|
ios: "System",
|
|
android: "Roboto",
|
|
default: "System",
|
|
}),
|
|
},
|
|
});
|