Skip to content

Commit

Permalink
Search in songs and concerts list
Browse files Browse the repository at this point in the history
  • Loading branch information
belinde committed Apr 18, 2024
1 parent e9eee77 commit b387bd4
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 11 deletions.
24 changes: 21 additions & 3 deletions src/components/ConcertList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FC, useCallback, useState } from "react";
import { FC, useCallback, useEffect, useState } from "react";
import { FlatList } from "react-native";
import { List } from "react-native-paper";
import { useDataContext } from "../hooks/useDataContext";
import { useEffectOnFocus } from "../hooks/useEffectOnFocus";
import { Concert } from "../types";
import { EmptySearchableListElement } from "./EmptySearchableListElement";

export const ConcertList: FC<{ onPress: (concert: Concert) => void }> = (
props
Expand All @@ -12,18 +13,34 @@ export const ConcertList: FC<{ onPress: (concert: Concert) => void }> = (
const [concerts, setConcerts] = useState<Concert[]>();
const [refreshing, setRefreshing] = useState(false);

const applyFilter = useCallback(
(s: Concert[]) => {
const filter = data.search.toLowerCase().trim();
setConcerts(
filter === ""
? s
: s.filter((song) => song.title.toLowerCase().includes(filter))
);
},
[data.search]
);

const refresh = useCallback(() => {
setRefreshing(true);
data.concerts
.load()
.then(setConcerts)
.then(applyFilter)
.finally(() => {
setRefreshing(false);
});
}, [data.concerts]);
}, [applyFilter, data.concerts]);

useEffectOnFocus(refresh);

useEffect(() => {
applyFilter(data.concerts.list());
}, [applyFilter, data.concerts]);

return (
<FlatList
data={concerts}
Expand All @@ -41,6 +58,7 @@ export const ConcertList: FC<{ onPress: (concert: Concert) => void }> = (
/>
)}
keyExtractor={(song) => song.id}
ListEmptyComponent={EmptySearchableListElement}
/>
);
};
18 changes: 18 additions & 0 deletions src/components/EmptySearchableListElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FC } from "react";
import { View } from "react-native";
import { Text } from "react-native-paper";
import { useDataContext } from "../hooks/useDataContext";

export const EmptySearchableListElement: FC = () => {
const { search } = useDataContext();

return (
<View>
<Text variant="labelLarge" style={{ textAlign: "center" }}>
{search
? `Nessun elemento soddisfa la ricerca "${search}"`
: "Questa lista non contiene ancora alcun elemento."}
</Text>
</View>
);
};
60 changes: 60 additions & 0 deletions src/components/SearchMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { FC, useCallback, useEffect, useRef, useState } from "react";
import { TextInput } from "react-native";
import { IconButton, Searchbar } from "react-native-paper";
import { useDataContext } from "../hooks/useDataContext";

export const SearchMenu: FC = () => {
const data = useDataContext();
const [text, setText] = useState(() => data.search);
const ref = useRef<TextInput>(null);

const [visible, setVisible] = useState(() => data.search !== "");

useEffect(() => {
if (visible) {
ref.current?.focus();
} else {
ref.current?.blur();
}
}, [data, visible]);

const reset = useCallback(() => {
ref.current?.blur();
setText("");
setVisible(false);
data.setSearch("");
}, [data]);

const search = useCallback(
(s: string) => {
setText(s);
data.setSearch(s);
},
[data]
);

if (visible) {
return (
<Searchbar
ref={ref}
value={text}
onChangeText={search}
onIconPress={reset}
onClearIconPress={reset}
placeholder="Cerca..."
searchAccessibilityLabel="Cerca..."
aria-label="Cerca..."
style={{ width: 300 }}
/>
);
}

return (
<IconButton
icon="magnify"
onPress={() => setVisible(true)}
accessibilityLabel="Cerca"
aria-label="Cerca"
/>
);
};
2 changes: 2 additions & 0 deletions src/components/SongList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FC, useCallback, useState } from "react";
import { FlatList } from "react-native";
import { List } from "react-native-paper";
import { Song } from "../types";
import { EmptySearchableListElement } from "./EmptySearchableListElement";

export const SongList: FC<{
songs: Song[];
Expand Down Expand Up @@ -34,6 +35,7 @@ export const SongList: FC<{
/>
)}
keyExtractor={(song) => song.id}
ListEmptyComponent={EmptySearchableListElement}
/>
);
};
7 changes: 6 additions & 1 deletion src/hooks/useDataContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ const DataContext = createContext({
songs,
concerts,
settings,
search: "",
setSearch: (_: string) => {},
});

export const DataProvider: FC<{ children: ReactNode }> = (props) => {
const [loaded, setLoaded] = useState(false);
const [search, setSearch] = useState("");
useEffect(() => {
Promise.all([songs.load(), concerts.load(), settings.load()]).then(() =>
setLoaded(true)
Expand All @@ -47,7 +50,9 @@ export const DataProvider: FC<{ children: ReactNode }> = (props) => {
if (!loaded) return null;

return (
<DataContext.Provider value={{ songs, concerts, settings }}>
<DataContext.Provider
value={{ songs, concerts, settings, search, setSearch }}
>
{props.children}
</DataContext.Provider>
);
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Concert/ConcertStack.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { FC } from "react";
import { SearchMenu } from "../../components/SearchMenu";
import { useNativeStackNavigatorOptions } from "../../hooks/useNativeStackNavigatorOptions";
import { CreateConcert } from "./CreateConcert";
import { EditConcert } from "./EditConcert";
Expand Down Expand Up @@ -28,7 +29,7 @@ export const ConcertStack: FC = () => {
<Stack.Screen
name="List"
component={ListConcerts}
options={{ title: "Concerti" }}
options={{ title: "Concerti", headerRight: SearchMenu }}
/>
<Stack.Screen
name="Create"
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Library/LibraryStack.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { FC } from "react";
import { SearchMenu } from "../../components/SearchMenu";
import { useNativeStackNavigatorOptions } from "../../hooks/useNativeStackNavigatorOptions";
import { CreateSong } from "./CreateSong";
import { EditSong } from "./EditSong";
Expand Down Expand Up @@ -27,7 +28,7 @@ export const LibraryStack: FC = () => {
<Stack.Screen
name="List"
component={ListSongs}
options={{ title: "Repertorio" }}
options={{ title: "Repertorio", headerRight: SearchMenu }}
/>
<Stack.Screen
name="Create"
Expand All @@ -53,4 +54,4 @@ export const LibraryStack: FC = () => {
/>
</Stack.Navigator>
);
};
};
26 changes: 22 additions & 4 deletions src/pages/Library/ListSongs.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
import { FC, useCallback, useState } from "react";
import { Button } from "react-native-paper";
import { FC, useCallback, useEffect, useState } from "react";
import { Button, Text } from "react-native-paper";
import { Page } from "../../components/Page";
import { SongList } from "../../components/SongList";
import { useDataContext } from "../../hooks/useDataContext";
import { useEffectOnFocus } from "../../hooks/useEffectOnFocus";
import { Song } from "../../types";
import { LibraryTabScreenProps } from "../types";

export const ListSongs: FC<LibraryTabScreenProps<"List">> = (props) => {
const data = useDataContext();
const [songs, setSongs] = useState(() => data.songs.list());

const applyFilter = useCallback(
(s: Song[]) => {
const filter = data.search.toLowerCase().trim();
setSongs(
filter === ""
? s
: s.filter((song) => song.title.toLowerCase().includes(filter))
);
},
[data.search]
);

const refresh = useCallback(
() => data.songs.load().then(setSongs),
[data.songs]
() => data.songs.load().then(applyFilter),
[applyFilter, data.songs]
);

useEffectOnFocus(refresh);

useEffect(() => {
applyFilter(data.songs.list());
}, [applyFilter, data.songs]);

return (
<Page accessibilityLabel="Repertorio">
<Text>Ricerca: {data.search}</Text>
<SongList
songs={songs}
onRefresh={refresh}
Expand Down

0 comments on commit b387bd4

Please sign in to comment.