Skip to content

Commit

Permalink
⚡️ add native search screen for places autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-bierman committed Dec 21, 2024
1 parent 2b9f3e8 commit 436e2a4
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 195 deletions.
121 changes: 108 additions & 13 deletions apps/expo/app/(app)/(drawer)/(tabs)/index/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,109 @@
import React from 'react';
import { Platform, View } from 'react-native';
import { Redirect, Stack } from 'expo-router';
import { theme } from 'app/theme';
import { DashboardScreen } from 'app/modules/dashboard';
import { useOfflineStore } from 'app/atoms';
import { SearchResults } from 'app/components/SearchInput/SearchResults';
import { useRouter } from 'app/hooks/router';
import useTheme from 'app/hooks/useTheme';
import { useAuthUser, LoginScreen } from 'app/modules/auth';
import { LoginScreen, useAuthUser } from 'app/modules/auth';
import { DashboardScreen } from 'app/modules/dashboard';
import { theme } from 'app/theme';
import { Redirect, Stack } from 'expo-router';
import Head from 'expo-router/head';
import { useOfflineStore } from 'app/atoms';
import { useAtom } from 'jotai';
import React, { useState } from 'react';
import { Platform, View } from 'react-native';

import { PlaceItem } from 'app/components/PlacesAutocomplete/PlacesAutocomplete';
import {
placesAutocompleteSearchAtom,
usePlacesAutoComplete,
} from 'app/components/PlacesAutocomplete/usePlacesAutoComplete';

interface SearchResult {
properties: {
osm_id: number;
osm_type: string;
name: string;
};
geometry: {
coordinates: [number, number];
};
}

function NativeHomeScreen() {
const user = useAuthUser();
const router = useRouter();
const { currentTheme } = useTheme();
const [searchQuery, setSearchQuery] = useAtom(placesAutocompleteSearchAtom);

const handleSearchSelect = async (selectedResult: SearchResult) => {
try {
const { osm_id, osm_type, name } = selectedResult.properties;

const coordinates = selectedResult.geometry.coordinates;

if (!osm_id || !osm_type) {
console.error(
'No OSM ID or OSM type found in the selected search result',
);
} else {
router.push({
pathname: '/destination/query',
query: {
osmType: osm_type,
osmId: osm_id,
name,
},
});
}
} catch (error) {
console.error('errorrrrrr', error);
}
};
const {
data: searchResults,
handleSelect,
search,
setSearch,
} = usePlacesAutoComplete(handleSearchSelect);

const [isFocused, setIsFocused] = useState(false); // Track focus state for input

console.log('searchResults', searchResults);

const mutualStyles = {
backgroundColor: currentTheme.colors.background,
flex: 1,
};

const DashboardWithNativeSearch = () => {
if (searchQuery) {
return (
<SearchResults
results={searchResults}
onResultClick={(result) => {
handleSelect(result); // Handle the selection logic
setSearchQuery(''); // Clear search query
}}
resultItemComponent={<PlaceItem />} // Custom item rendering
isVisible={true}
/>
);
}
return <DashboardScreen />;
};

return (
<View style={mutualStyles}>
{!user ? (
<LoginScreen />
) : (
<>
{/* Dashboard or Search Results */}
<DashboardWithNativeSearch />
</>
)}
</View>
);
}

export default function HomeScreen() {
const {
Expand All @@ -25,6 +122,8 @@ export default function HomeScreen() {
flex: 1,
};

const [searchQuery, setSearchQuery] = useAtom(placesAutocompleteSearchAtom);

return (
<>
{Platform.OS === 'web' && (
Expand All @@ -39,15 +138,11 @@ export default function HomeScreen() {
placeholder: 'Search by park, city, or trail',
hideWhenScrolling: false,
inputType: 'text',
// onChangeText: (e) => setSearchQuery(e.nativeEvent.text),
onChangeText: (e) => setSearchQuery(e.nativeEvent.text),
},
}}
/>
{connectionStatus === 'connected' && (
<View style={mutualStyles}>
{!user ? <LoginScreen /> : <DashboardScreen />}
</View>
)}
{connectionStatus === 'connected' && <NativeHomeScreen />}
{connectionStatus === 'offline' && <Redirect href="offline/maps" />}
</>
);
Expand Down
50 changes: 23 additions & 27 deletions packages/app/components/PlacesAutocomplete/PlacesAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { Chip } from '@packrat/ui/src/Bento/elements/chips/components/chipsParts';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import { type TextInput } from 'react-native';
import { Text, XStack } from 'tamagui'; // Ensure proper imports based on imports.md
import { SearchInput } from '../SearchInput';
import { RStack as OriginalRStack, RText as OriginalRText } from '@packrat/ui';
import useTheme from 'app/hooks/useTheme';

import { usePlacesAutoComplete } from './usePlacesAutoComplete';

const RStack: any = OriginalRStack;
const RText: any = OriginalRText;

interface PlacesAutocompleteProps {
onSelect?: (geoJSON: any) => void;
placeholder?: string;
Expand All @@ -31,6 +27,7 @@ export const PlacesAutocomplete = forwardRef<any, PlacesAutocompleteProps>(

return (
<SearchInput
onCreate={() => {}}
onSelect={handleSelect}
placeholder={placeholder}
results={data}
Expand All @@ -43,28 +40,27 @@ export const PlacesAutocomplete = forwardRef<any, PlacesAutocompleteProps>(
},
);

const PlaceItem = ({ item }: any) => {
const { currentTheme } = useTheme();
export const PlaceItem = ({ item, onPress = () => {} }: any) => {
return (
<RStack style={{ flexDirection: 'row' }}>
<RText fontWeight="400" color="red">
<XStack
ai="center"
p="$3"
br="$4"
bg="$background"
hoverStyle={{
bg: '$gray2',
}}
pressStyle={{
bg: '$gray3',
}}
onPress={onPress}
>
<Text color="$color" fontSize="$4" flex={1}>
{item.properties.name}
</RText>
<RText
style={{
backgroundColor: '#e6e6e6',
fontWeight: 600,
color: '#000000',
borderRadius: 8,
paddingLeft: 10,
paddingRight: 10,
width: 'auto',
fontSize: 12,
textAlign: 'center',
}}
>
{item.properties.osm_value}
</RText>
</RStack>
</Text>
<Chip rounded theme="blue" size="small" key="blue">
<Chip.Text>{item.properties.osm_value}</Chip.Text>
</Chip>
</XStack>
);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { usePhotonDetail } from 'app/hooks/photonDetail';
import { useState } from 'react';
import { useDebouncedValue } from 'app/hooks/common';
import { usePhotonDetail } from 'app/hooks/photonDetail';
import { atom, useAtom } from 'jotai';

export const placesAutocompleteSearchAtom = atom('');

export const usePlacesAutoComplete = (onSelect) => {
const [search, setSearch] = useState('');
const [search, setSearch] = useAtom(placesAutocompleteSearchAtom);
const debouncedSearch = useDebouncedValue(search, 200);
const { data } = usePhotonDetail(debouncedSearch, !!debouncedSearch);

Expand Down
31 changes: 7 additions & 24 deletions packages/app/components/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '@packrat/ui';
import { View, Pressable as OriginalPressable } from 'react-native';
import { Adapt, Popover as OriginalPopover, Button } from 'tamagui';
import { SearchResults } from './SearchResults';

const Popover = OriginalPopover;
const RStack = OriginalRStack;
Expand Down Expand Up @@ -217,30 +218,12 @@ export const SearchInput = forwardRef<TextInput, SearchInputProps>(
</RIconButton>
)}
</RStack>
<RStack
style={{
position: 'relative',
display: isVisible ? 'block' : 'none',
}}
>
{showSearchResults && (
<RScrollView keyboardShouldPersistTaps="handled">
<View role="list" style={{ width: '100%' }}>
{options.map((result, i) => (
<Pressable
key={`result + ${i}`}
role="listitem"
onPress={() => handleSearchResultClick(result)}
paddingHorizontal={16}
paddingVertical={8}
>
{cloneElement(ResultItemComponent, { item: result })}
</Pressable>
))}
</View>
</RScrollView>
)}
</RStack>
<SearchResults
results={options}
onResultClick={handleSearchResultClick}
resultItemComponent={ResultItemComponent}
isVisible={isVisible}
/>
</RStack>
);
}
Expand Down
74 changes: 74 additions & 0 deletions packages/app/components/SearchInput/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SearchResults.tsx
import { RScrollView, RStack, YStack } from '@packrat/ui';
import useTheme from 'app/hooks/useTheme';
import React, { cloneElement } from 'react';
import { Platform, Pressable, SafeAreaView } from 'react-native';

interface SearchResultsProps {
results: any[];
onResultClick: (result: any, index: number) => void;
resultItemComponent: React.ReactElement;
isVisible: boolean;
containerWidth?: number; // For web width adjustments
}

export const SearchResults: React.FC<SearchResultsProps> = ({
results,
onResultClick,
resultItemComponent: ResultItemComponent,
isVisible,
containerWidth,
}) => {
const { isDark, currentTheme } = useTheme();

if (!isVisible) return null;

const content = (
<YStack
role="list"
style={{
width: containerWidth || '100%',
maxHeight: Platform.OS === 'web' ? 200 : undefined,
overflow: Platform.OS === 'web' ? 'scroll' : 'hidden',
}}
gap="$2"
pt="$2"
>
{results &&
Array.isArray(results) &&
results.map((result, index) => (
<Pressable
key={`result-${index}`}
role="listitem"
onPress={() => onResultClick(result, index)}
>
{cloneElement(ResultItemComponent, {
item: result,
key: `item-${index}`,
onPress: () => onResultClick(result, index),
})}
</Pressable>
))}
</YStack>
);

return Platform.OS === 'web' ? (
<RStack
style={{
borderWidth: 1,
borderRadius: 8,
padding: 8,
backgroundColor: isDark
? currentTheme.colors.black
: currentTheme.colors.white,
width: containerWidth,
}}
>
{content}
</RStack>
) : (
<SafeAreaView style={{ flex: 1 }}>
<RScrollView keyboardShouldPersistTaps="handled">{content}</RScrollView>
</SafeAreaView>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { FeedPreview } from 'app/modules/feed';
import { Button, Stack } from 'tamagui';
import { useRouter } from '@packrat/crosspath';

export const DashboardScreen = () => {
const DashboardScreenInner = () => {
const styles = useCustomStyles(loadStyles);
const router = useRouter();

Expand Down Expand Up @@ -61,6 +61,10 @@ export const DashboardScreen = () => {
);
};

export const DashboardScreen = () => {
return <DashboardScreenInner />;
};

const loadStyles = (theme) => {
const { currentTheme } = theme;
const { screenWidth } = useScreenWidth();
Expand Down
Loading

0 comments on commit 436e2a4

Please sign in to comment.