import * as React from 'react' ; import { StyleSheet , View , ScrollView } from 'react-native' ; import { TabBarIcon } from '@frogpond/navigation-tabs' ; import * as c from '@frogpond/colors' ; import { updateCourseData , loadCourseDataIntoMemory , } from '../../../redux/parts/courses' ; import { areAnyTermsCached } from '../../../lib/course-search' ; import type { ReduxState } from '../../../redux' ; import type { TopLevelViewPropsType } from '../../types' ; import { useDispatch , useSelector } from 'react-redux' ; import { NoticeView , LoadingView } from '@frogpond/notice' ; import { AnimatedSearchBar } from '@frogpond/searchbar' ; import { RecentItemsList } from '../components/recents-list' ; import { Separator } from '@frogpond/separator' ; import { buildFilters } from './lib/build-filters' ; import type { FilterComboType } from './lib/format-filter-combo' ; import fromPairs from 'lodash/fromPairs' ; const PROMPT_TEXT = 'We need to download the courses from the server. This will take a few seconds.' ; const NETWORK_WARNING = '(Please make sure that you are connected to the Internet before downloading the courses).' ; type ReactProps = TopLevelViewPropsType ; type ReduxStateProps = { courseDataState : string ; recentFilters : FilterComboType [ ] ; recentSearches : string [ ] ; } ; type ReduxDispatchProps = { updateCourseData : ( ) => Promise < any > ; loadCourseDataIntoMemory : ( ) => Promise < any > ; } ; type Props = ReactProps & ReduxStateProps & ReduxDispatchProps ; type State = { mode : 'loading' | 'pending' | 'ready' ; isSearchbarActive : boolean ; typedQuery : string ; } ; class CourseSearchView extends React . Component < Props , State > { state = { mode : 'pending' , isSearchbarActive : false , typedQuery : '' , } ; componentDidMount ( ) { this . loadData ( { userInitiated : false } ) ; } loadData = async ( { userInitiated = true } = { } ) => { let hasCache = await areAnyTermsCached ( ) ; if ( ! hasCache && ! userInitiated ) { this . setState ( ( ) => ( { mode : 'pending' } ) ) ; return ; } this . setState ( ( ) => ( { mode : 'loading' } ) ) ; if ( this . props . courseDataState !== 'ready' ) { await this . props . loadCourseDataIntoMemory ( ) ; if ( hasCache ) { this . setState ( ( ) => ( { mode : 'ready' } ) ) ; } await this . props . updateCourseData ( ) ; } else { await this . props . updateCourseData ( ) ; } this . setState ( ( ) => ( { mode : 'ready' } ) ) ; } ; handleSearchSubmit = ( ) => { this . props . navigation . push ( 'CourseSearchResultsView' , { initialQuery : this . state . typedQuery , } ) ; this . setState ( ( ) => ( { isSearchbarActive : false , typedQuery : '' } ) ) ; } ; handleSearchCancel = ( ) => { this . setState ( ( ) => ( { typedQuery : '' , isSearchbarActive : false } ) ) ; } ; handleSearchChange = ( value ) => { this . setState ( ( ) => ( { typedQuery : value } ) ) ; } ; handleSearchFocus = ( ) => { this . setState ( ( ) => ( { isSearchbarActive : true } ) ) ; } ; browseAll = ( ) => { this . props . navigation . push ( 'CourseSearchResultsView' , { initialQuery : '' } ) ; } ; onRecentSearchPress = ( text ) => { this . props . navigation . push ( 'CourseSearchResultsView' , { initialQuery : text } ) ; } ; onRecentFilterPress = async ( text ) => { let { recentFilters } = this . props ; let selectedFilterCombo = recentFilters . find ( ( f ) => f . description === text ) ; let freshFilters = await buildFilters ( ) ; let selectedFilters = freshFilters ; if ( selectedFilterCombo ) { let filterLookup = fromPairs ( selectedFilterCombo . filters . map ( ( f ) => [ f . key , f ] ) ) ; selectedFilters = freshFilters . map ( ( f ) => filterLookup [ f . key ] || f ) ; } this . props . navigation . push ( 'CourseSearchResultsView' , { initialFilters : selectedFilters , } ) ; } ; render ( ) { let { typedQuery , mode , isSearchbarActive } = this . state ; if ( mode === 'loading' ) { return < LoadingView text = "Loading Course Data…" / > ; } if ( this . props . courseDataState === 'not-loaded' ) { let msg = PROMPT_TEXT + '\n\n' + NETWORK_WARNING ; return ( < NoticeView buttonText = "Download" header = "Almost there…" onPress = { this . loadData } text = { msg } / > ) ; } let recentFilterDescriptions = this . props . recentFilters . map ( ( f ) => f . description ) ; return ( < View style = { [ styles . container , styles . common ] } > < AnimatedSearchBar active = { isSearchbarActive } onCancel = { this . handleSearchCancel } onChange = { this . handleSearchChange } onFocus = { this . handleSearchFocus } onSubmit = { this . handleSearchSubmit } placeholder = "Search Class & Lab" title = "Search Courses" value = { typedQuery } / > < Separator /> < ScrollView keyboardDismissMode = "interactive" style = { [ styles . common , styles . bottomContainer ] } > < RecentItemsList emptyHeader = "No recent searches" emptyText = "Your recent searches will appear here." items = { this . props . recentSearches } onItemPress = { this . onRecentSearchPress } title = "Recent" / > < RecentItemsList actionLabel = "Browse All" emptyHeader = "No recent filter combinations" emptyText = "Your recent filter combinations will appear here." items = { recentFilterDescriptions } onAction = { this . browseAll } onItemPress = { this . onRecentFilterPress } title = "Browse" / > < /ScrollView> < /View>); } } export function ConnectedCourseSearchView ( props ) { let dispatch = useDispatch ( ) ; let courseDataState = useSelector ( ( state ) => state . courses ?. readyState || '' ) ; let recentFilters = useSelector ( ( state ) => state . courses ?. recentFilters || [ ] ) ; let recentSearches = useSelector ( ( state ) => state . courses ?. recentSearches || [ ] ) ; let _loadCourseDataIntoMemory = React . useCallback ( ( ) => dispatch ( loadCourseDataIntoMemory ( ) ) , [ dispatch ] ) ; let _updateCourseData = React . useCallback ( ( ) => dispatch ( updateCourseData ( ) ) , [ dispatch ] ) ; return ( < CourseSearchView { ... props } courseDataState = { courseDataState } loadCourseDataIntoMemory = { _loadCourseDataIntoMemory } recentFilters = { recentFilters } recentSearches = { recentSearches } updateCourseData = { _updateCourseData } / > ) ; } ConnectedCourseSearchView . navigationOptions = { tabBarLabel : 'Course Search' , tabBarIcon : TabBarIcon ( 'search' ) , title : 'SIS' , } ; let styles = StyleSheet . create ( { bottomContainer : { paddingTop : 12 , } , container : { flex : 1 , } , common : { backgroundColor : c . white , } , } ) ;