Skip to content
Merged
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
194 changes: 119 additions & 75 deletions source/views/streaming/streams/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {timezone} from '@frogpond/constants'
import * as c from '@frogpond/colors'
import {ListSeparator, ListSectionHeader} from '@frogpond/lists'
import {NoticeView, LoadingView} from '@frogpond/notice'
import {FilterToolbar, ListType} from '@frogpond/filter'
import {StreamRow} from './row'
import toPairs from 'lodash/toPairs'
import groupBy from 'lodash/groupBy'
Expand All @@ -12,7 +13,7 @@ import type {Moment} from 'moment-timezone'
import {toLaxTitleCase as titleCase} from '@frogpond/titlecase'
import type {StreamType} from './types'
import {API} from '@frogpond/api'
import {fetch} from '@frogpond/fetch'
import {useFetch} from 'react-async'

const styles = StyleSheet.create({
listContainer: {
Expand All @@ -23,99 +24,142 @@ const styles = StyleSheet.create({
},
})

export const StreamListView = (): JSX.Element => {
let [error, setError] = React.useState<Error | null>(null)
let [loading, setLoading] = React.useState(true)
let [refreshing, setRefreshing] = React.useState(false)
let [streams, setStreams] = React.useState<
Array<{title: string; data: StreamType[]}>
>([])
const groupStreams = (entries: StreamType[]) => {
let grouped = groupBy(entries, (j) => j.$groupBy)
return toPairs(grouped).map(([title, data]) => ({title, data}))
}

React.useEffect(() => {
try {
getStreams().then(() => {
setLoading(false)
})
} catch (error) {
if (error instanceof Error) {
setError(error)
} else {
setError(new Error('unknown error - not an Error'))
}
return
}
}, [])
const groupStreamsByCategoryAndDate = (stream: StreamType) => {
let date: Moment = moment(stream.starttime)
let dateGroup = date.format('dddd, MMMM Do')

let refresh = async (): Promise<void> => {
setRefreshing(true)
await getStreams(true)
setRefreshing(false)
}
let group = stream.status.toLowerCase() !== 'live' ? dateGroup : 'Live'

let getStreams = async (
reload?: boolean,
date: Moment = moment.tz(timezone()),
) => {
let dateFrom = date.format('YYYY-MM-DD')
let dateTo = date.clone().add(2, 'month').format('YYYY-MM-DD')

let data = await fetch(API('/streams/upcoming'), {
searchParams: {
sort: 'ascending',
dateFrom,
dateTo,
},
delay: reload ? 500 : 0,
}).json<Array<StreamType>>()

data = data
.filter((stream) => stream.category !== 'athletics')
.map((stream) => {
let date: Moment = moment(stream.starttime)
let dateGroup = date.format('dddd, MMMM Do')

let group = stream.status.toLowerCase() !== 'live' ? dateGroup : 'Live'

return {
...stream,
// force title-case on the stream types, to prevent not-actually-duplicate headings
category: titleCase(stream.category),
date: date,
$groupBy: group,
}
})

let grouped = groupBy(data, (j) => j.$groupBy)
let mapped = toPairs(grouped).map(([title, data]) => ({title, data}))

setStreams(mapped)
return {
...stream,
// force title-case on the stream types, to prevent not-actually-duplicate headings
category: titleCase(stream.category),
date: date,
$groupBy: group,
}
}

let keyExtractor = (item: StreamType) => item.eid
const getEnabledCategories = (filters: ListType[]) => {
return filters.flatMap((filter: ListType) => {
let filterSelections: ListType['spec']['selected'] = filter.spec.selected
return filterSelections.flatMap((spec) => spec.title)
})
}

let renderItem = ({item}: {item: StreamType}) => <StreamRow stream={item} />
const filterStreams = (streams: StreamType[], filters: ListType[]) => {
let enabledCategories = getEnabledCategories(filters)

if (loading) {
return <LoadingView />
if (enabledCategories.length === 0) {
return streams
}

return streams.filter((stream) => enabledCategories.includes(stream.category))
}

const useStreams = (date: Moment = moment.tz(timezone())) => {
let dateFrom = date.format('YYYY-MM-DD')
let dateTo = date.clone().add(2, 'month').format('YYYY-MM-DD')

return useFetch<StreamType[]>(
API('/streams/upcoming', {
sort: 'ascending',
dateFrom,
dateTo,
}),
{
headers: {accept: 'application/json'},
},
)
}

export const StreamListView = (): JSX.Element => {
let {data = [], error, reload, isPending, isInitial, isLoading} = useStreams()

let [filters, setFilters] = React.useState<ListType[]>([])

let entries = React.useMemo(() => {
return data.map((stream) => groupStreamsByCategoryAndDate(stream))
}, [data])

React.useEffect(() => {
let allCategories = data.map((stream) => titleCase(stream.category))

if (allCategories.length === 0) {
return
}

let categories = [...new Set(allCategories)].sort()
let filterCategories = categories.map((c) => {
return {title: c}
})

let streamFilters: ListType[] = [
{
type: 'list',
key: 'category',
enabled: true,
spec: {
title: 'Categories',
options: filterCategories,
selected: filterCategories,
mode: 'OR',
displayTitle: true,
},
apply: {key: 'category'},
},
]
setFilters(streamFilters)
}, [data])

if (error) {
return <NoticeView text={`Error: ${error.message}`} />
return (
<NoticeView
buttonText="Try Again"
onPress={reload}
text={`A problem occured while loading the streams. ${error.message}`}
/>
)
}

const header = (
<FilterToolbar
filters={filters}
onPopoverDismiss={(newFilter) => {
let edited = filters.map((f) =>
f.key === newFilter.key ? newFilter : f,
)
setFilters(edited as ListType[])
}}
/>
)

return (
<SectionList
ItemSeparatorComponent={ListSeparator}
ListEmptyComponent={<NoticeView text="No Streams" />}
ListEmptyComponent={
isLoading ? (
<LoadingView />
) : filters.some((f: ListType) => f.spec.selected.length) ? (
<NoticeView text="No streams to show. Try changing the filters." />
) : (
<NoticeView text="No streams." />
)
}
ListHeaderComponent={header}
contentContainerStyle={styles.contentContainer}
keyExtractor={keyExtractor}
onRefresh={refresh}
refreshing={refreshing}
renderItem={renderItem}
keyExtractor={(item: StreamType) => item.eid}
onRefresh={reload}
refreshing={isPending && !isInitial}
renderItem={({item}) => <StreamRow stream={item} />}
renderSectionHeader={({section: {title}}) => (
<ListSectionHeader title={title} />
)}
sections={streams}
sections={groupStreams(filterStreams(entries, filters))}
style={styles.listContainer}
/>
)
Expand Down