Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/expo/features/feed/components/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ export const PostCard: React.FC<PostCardProps> = ({ post, onLike, onDelete, curr
showsHorizontalScrollIndicator={false}
style={styles.imageScroll}
>
{post.images.map((img, idx) => (
// biome-ignore lint/suspicious/noArrayIndexKey: images have no stable id
{post.images.map((img) => (
<Image
key={`${img}-${idx}`}
key={img}
source={{ uri: buildPostImageUrl(img) }}
style={[styles.image, { width: SCREEN_WIDTH - 32 }]}
resizeMode="cover"
Expand Down
5 changes: 2 additions & 3 deletions apps/expo/features/feed/screens/PostDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,9 @@ export const PostDetailScreen = ({ post, currentUserId }: PostDetailScreenProps)
showsHorizontalScrollIndicator={false}
style={styles.imageScroll}
>
{post.images.map((img, idx) => (
// biome-ignore lint/suspicious/noArrayIndexKey: images have no stable id
{post.images.map((img) => (
<Image
key={`${img}-${idx}`}
key={img}
source={{ uri: buildPostImageUrl(img) }}
style={styles.image}
resizeMode="cover"
Expand Down
188 changes: 93 additions & 95 deletions apps/expo/features/weather/components/LocationPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,112 +52,110 @@ export function LocationPicker({
};

return (
<>
<Modal visible={open} animationType="slide" presentationStyle="pageSheet">
<SafeAreaView className="flex-1 bg-background">
<View className="px-4 mb-4 py-2 border-b border-border flex-row gap-2 justify-between items-center">
<View className="flex-row flex-1 items-center gap-2">
<TouchableOpacity onPress={onClose}>
<Icon name="close" size={20} color={colors.foreground} />
</TouchableOpacity>
<View>
<Text className="text-lg font-semibold">{displayTitle}</Text>
{subtitle && <Text className="text-xs text-muted-foreground">{subtitle}</Text>}
</View>
</View>
<TouchableOpacity onPress={handleSearchLocation}>
{/* <Icon name="cog-outline" size={20} color={colors.foreground} /> */}
<Icon
materialIcon={{ name: 'search', type: 'MaterialIcons' }}
ios={{ name: 'magnifyingglass' }}
size={20}
color={colors.foreground}
/>
<Modal visible={open} animationType="slide" presentationStyle="pageSheet">
<SafeAreaView className="flex-1 bg-background">
<View className="px-4 mb-4 py-2 border-b border-border flex-row gap-2 justify-between items-center">
<View className="flex-row flex-1 items-center gap-2">
<TouchableOpacity onPress={onClose}>
<Icon name="close" size={20} color={colors.foreground} />
</TouchableOpacity>
<View>
<Text className="text-lg font-semibold">{displayTitle}</Text>
{subtitle && <Text className="text-xs text-muted-foreground">{subtitle}</Text>}
</View>
</View>
<ScrollView contentContainerStyle={!hasLocations && { flex: 1 }}>
{hasLocations ? (
<View className="gap-2 p-4">
{locations.map((location) => (
<Pressable
key={location.id}
onPress={() => setSelectedLocation(location)}
className={cn(
'flex-row items-center rounded-lg p-2 border border-border bg-card',
location.id === selectedLocation?.id && 'border-primary',
<TouchableOpacity onPress={handleSearchLocation}>
{/* <Icon name="cog-outline" size={20} color={colors.foreground} /> */}
<Icon
materialIcon={{ name: 'search', type: 'MaterialIcons' }}
ios={{ name: 'magnifyingglass' }}
size={20}
color={colors.foreground}
/>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={!hasLocations && { flex: 1 }}>
{hasLocations ? (
<View className="gap-2 p-4">
{locations.map((location) => (
<Pressable
key={location.id}
onPress={() => setSelectedLocation(location)}
className={cn(
'flex-row items-center rounded-lg p-2 border border-border bg-card',
location.id === selectedLocation?.id && 'border-primary',
)}
style={({ pressed }) => (pressed ? { opacity: 0.7 } : {})}
>
<View className="mr-3 h-8 w-8 items-center justify-center rounded-full bg-neutral-300 dark:bg-neutral-600">
<Icon name="map-marker-radius-outline" size={18} color={colors.grey} />
</View>
<View className="flex-1">
<Text className="font-medium">{location.name}</Text>
<Text className="text-xs text-muted-foreground">{location.condition}</Text>
</View>
<View className="flex-row gap-2 items-center">
{location.id === activeLocation?.id && (
<>
<Text variant="callout">{t('common.default')}</Text>
<View className="mx-1 h-1 w-1 rounded-full bg-muted-foreground" />
</>
)}
style={({ pressed }) => (pressed ? { opacity: 0.7 } : {})}
>
<View className="mr-3 h-8 w-8 items-center justify-center rounded-full bg-neutral-300 dark:bg-neutral-600">
<Icon name="map-marker-radius-outline" size={18} color={colors.grey} />
</View>
<View className="flex-1">
<Text className="font-medium">{location.name}</Text>
<Text className="text-xs text-muted-foreground">{location.condition}</Text>
</View>
<View className="flex-row gap-2 items-center">
{location.id === activeLocation?.id && (
<>
<Text variant="callout">{t('common.default')}</Text>
<View className="mx-1 h-1 w-1 rounded-full bg-muted-foreground" />
</>
)}
<Text className="text-2xl">{location.temperature}°</Text>
</View>
</Pressable>
))}
<Text className="text-2xl">{location.temperature}°</Text>
</View>
</Pressable>
))}
</View>
) : (
<View className="flex-1 items-center justify-center p-8 gap-4 mt-8">
<View className="h-16 w-16 rounded-full items-center justify-center bg-neutral-300 dark:bg-neutral-600">
<Icon
materialIcon={{
name: 'location-searching',
type: 'MaterialIcons',
}}
ios={{ name: 'location' }}
size={32}
color={colors.grey}
/>
</View>
) : (
<View className="flex-1 items-center justify-center p-8 gap-4 mt-8">
<View className="h-16 w-16 rounded-full items-center justify-center bg-neutral-300 dark:bg-neutral-600">
<Icon
materialIcon={{
name: 'location-searching',
type: 'MaterialIcons',
}}
ios={{ name: 'location' }}
size={32}
color={colors.grey}
/>
</View>
<View className="items-center gap-1">
<Text className="text-base font-semibold">{t('weather.noSavedLocations')}</Text>
<Text className="text-xs text-muted-foreground text-center">
{t('weather.addLocation')}
</Text>
</View>
<Button variant="secondary" onPress={handleSearchLocation}>
<Text>{t('location.searchButton')}</Text>
</Button>
<View className="items-center gap-1">
<Text className="text-base font-semibold">{t('weather.noSavedLocations')}</Text>
<Text className="text-xs text-muted-foreground text-center">
{t('weather.addLocation')}
</Text>
</View>
)}
</ScrollView>
<View className="px-4 pb-4 flex-row self-end items-center gap-2 justify-between">
{onSkip && (
<Button
onPress={() => {
onClose();
onSkip();
}}
variant="secondary"
>
<Text>{skipText}</Text>
<Button variant="secondary" onPress={handleSearchLocation}>
<Text>{t('location.searchButton')}</Text>
</Button>
)}
</View>
)}
</ScrollView>
<View className="px-4 pb-4 flex-row self-end items-center gap-2 justify-between">
{onSkip && (
<Button
onPress={() => {
onClose();
assertNonNull(selectedLocation);
onSelect(selectedLocation);
onSkip();
}}
disabled={!selectedLocation}
variant="tonal"
variant="secondary"
>
<Text>{displaySelectText}</Text>
<Text>{skipText}</Text>
</Button>
</View>
</SafeAreaView>
</Modal>
</>
)}
<Button
onPress={() => {
onClose();
assertNonNull(selectedLocation);
onSelect(selectedLocation);
}}
disabled={!selectedLocation}
variant="tonal"
>
<Text>{displaySelectText}</Text>
</Button>
</View>
</SafeAreaView>
</Modal>
);
}
8 changes: 4 additions & 4 deletions packages/analytics/src/core/spec-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,21 @@ export function parseTempRatingF(text: string): number | null {
// Range: take lower bound ("20/30F" → 20F)
const range = TEMP_RANGE.exec(text);
if (range?.[1] !== undefined && range[2] !== undefined && range[3] !== undefined) {
const lower = Math.min(Number.parseInt(range[1]), Number.parseInt(range[2]));
const lower = Math.min(Number.parseInt(range[1], 10), Number.parseInt(range[2], 10));
return toFahrenheit(lower, range[3]);
}

const single = TEMP_SINGLE.exec(text);
if (single?.[1] !== undefined && single[2] !== undefined) {
return toFahrenheit(Number.parseInt(single[1]), single[2]);
return toFahrenheit(Number.parseInt(single[1], 10), single[2]);
}
return null;
}

export function parseFillPower(text: string): number | null {
const match = FILL_POWER.exec(text);
if (match?.[1] !== undefined) {
const val = Number.parseInt(match[1]);
const val = Number.parseInt(match[1], 10);
return val >= 300 && val <= 1200 ? val : null;
}
return null;
Expand All @@ -134,7 +134,7 @@ export function parseFillPower(text: string): number | null {
export function parseWaterproofRating(text: string): number | null {
const match = WATERPROOF.exec(text);
if (match?.[1] !== undefined) {
const raw = Number.parseInt(match[1].replace(/,/g, ''));
const raw = Number.parseInt(match[1].replace(/,/g, ''), 10);
// If "k" or "K" prefix was captured in the regex (e.g., "20k mm"), multiply by 1000
const hasKMultiplier = /(\d[\d,]*)\s*k\s*mm\b/i.exec(text);
return hasKMultiplier ? raw * 1000 : raw;
Expand Down
Loading