diff --git a/app/_layout.tsx b/app/_layout.tsx index 6d137c9..5f95632 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -30,6 +30,7 @@ import * as Sentry from '@sentry/react-native'; import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated'; import { LogBox } from 'react-native'; import * as ImagePicker from 'expo-image-picker'; +import { setupCarPlay } from '@/relisten/carplay'; // c.f. https://github.com/meliorence/react-native-render-html/issues/661#issuecomment-2453476566 LogBox.ignoreLogs([/Support for defaultProps will be removed/]); @@ -89,6 +90,10 @@ function TabLayout() { } }, [realmRef.current]); + useEffect(() => { + setupCarPlay(); + }, []); + useEffect(() => { if (!navigation?.isReady()) return; diff --git a/package.json b/package.json index b8d0ab8..4946c8e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react-content-loader": "^7.0.2", "react-native": "~0.77.1", "react-native-airplay-button": "^1.1.0", + "react-native-carplay": "^1.2.1", "react-native-awesome-slider": "^2.5.6", "react-native-blob-util": "^0.21.2", "react-native-draggable-flatlist": "^4.0.1", diff --git a/relisten/carplay/index.ts b/relisten/carplay/index.ts new file mode 100644 index 0000000..d268ee6 --- /dev/null +++ b/relisten/carplay/index.ts @@ -0,0 +1,98 @@ +import CarPlay, { ListTemplate, ListItem } from 'react-native-carplay'; +import { RelistenApiClient } from '../api/client'; + +const api = new RelistenApiClient(); + +async function getAllArtists() { + const resp = await api.artists(); + return resp.data || []; +} + +async function getYearsForArtist(artistUuid: string) { + const resp = await api.years(artistUuid); + return resp.data || []; +} + +async function getShowsForYear(artistUuid: string, yearUuid: string) { + const resp = await api.year(artistUuid, yearUuid); + return resp.data?.shows || []; +} + +async function getSourcesForShow(showUuid: string) { + const resp = await api.showWithSources(showUuid); + return resp.data?.sources || []; +} + +export async function setupCarPlay() { + const artists = await getAllArtists(); + CarPlay.setRootTemplate(createArtistsListTemplate(artists), true); +} + +function createArtistsListTemplate(artists: any[]): ListTemplate { + return { + type: 'list', + title: 'Artists', + sections: [ + { + items: artists.map((artist) => ({ + text: artist.name, + onPress: async () => { + const years = await getYearsForArtist(artist.uuid); + CarPlay.pushTemplate(createYearsListTemplate(artist.uuid, years), true); + }, + } as ListItem)), + }, + ], + } as ListTemplate; +} + +function createYearsListTemplate(artistUuid: string, years: any[]): ListTemplate { + return { + type: 'list', + title: 'Years', + sections: [ + { + items: years.map((year) => ({ + text: String(year.year), + onPress: async () => { + const shows = await getShowsForYear(artistUuid, year.uuid); + CarPlay.pushTemplate(createShowsListTemplate(artistUuid, year.uuid, shows), true); + }, + } as ListItem)), + }, + ], + } as ListTemplate; +} + +function createShowsListTemplate(artistUuid: string, yearUuid: string, shows: any[]): ListTemplate { + return { + type: 'list', + title: 'Shows', + sections: [ + { + items: shows.map((show) => ({ + text: show.display_date, + onPress: async () => { + const sources = await getSourcesForShow(show.uuid); + CarPlay.pushTemplate(createSourcesListTemplate(sources), true); + }, + } as ListItem)), + }, + ], + } as ListTemplate; +} + +function createSourcesListTemplate(sources: any[]): ListTemplate { + return { + type: 'list', + title: 'Sources', + sections: [ + { + items: sources.map((source) => ({ + text: source.source, + onPress: () => {}, + } as ListItem)), + }, + ], + } as ListTemplate; +}