diff --git a/.eslintrc.js b/.eslintrc.js index dcf0be08..8f087f9d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,9 +7,13 @@ module.exports = { { files: ['*.ts', '*.tsx'], rules: { - '@typescript-eslint/no-shadow': ['error'], + 'no-spaced-func': 'off', 'no-shadow': 'off', 'no-undef': 'off', + '@typescript-eslint/no-unused-vars': ['warn', {varsIgnorePattern: '_'}], + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-native/no-inline-styles': 'off', }, }, ], diff --git a/package.json b/package.json index cebff443..e7379b50 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint . --ext .ts,.tsx", - "typecheck": "tsc --noUnusedLocals", + "typecheck": "tsc", "prettier": "prettier --write src/**/*", "release": "appcenter codepush release-react -a robertying/learnX-iOS && appcenter codepush release-react -a robertying/learnX-Android" }, @@ -67,7 +67,7 @@ "path": "0.12.7", "postinstall-postinstall": "2.1.0", "prettier": "2.6.2", - "react": "18.1.0", + "react": "17.0.2", "react-native": "0.68.1", "react-native-code-push": "7.0.4", "react-native-device-info": "8.7.0", diff --git a/src/App.tsx b/src/App.tsx index 51b10f35..f6b8edaf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,7 +30,7 @@ import { DarkTheme as PaperDarkTheme, } from 'react-native-paper'; import codePush from 'react-native-code-push'; -import {Provider as StoreProvider, useDispatch} from 'react-redux'; +import {Provider as StoreProvider} from 'react-redux'; import {PersistGate} from 'redux-persist/integration/react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import dayjs from 'dayjs'; @@ -71,7 +71,7 @@ import Styles from 'constants/Styles'; import Colors from 'constants/Colors'; import DeviceInfo from 'constants/DeviceInfo'; import {setSetting} from 'data/actions/settings'; -import {persistor, store, useTypedSelector} from 'data/store'; +import {persistor, store, useAppDispatch, useAppSelector} from 'data/store'; import {Notice, Assignment, File, Course} from 'data/types/state'; import {login} from 'data/actions/auth'; import {getAllSemesters, getCurrentSemester} from 'data/actions/semesters'; @@ -370,16 +370,16 @@ const SettingStack = () => ( const MainTab = () => { const theme = useTheme(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const auth = useTypedSelector(state => state.auth); - const currentSemester = useTypedSelector(state => state.semesters.current); - const semesters = useTypedSelector(state => state.semesters.items); - const newChangelog = useTypedSelector( + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const auth = useAppSelector(state => state.auth); + const currentSemester = useAppSelector(state => state.semesters.current); + const semesters = useAppSelector(state => state.semesters.items); + const newChangelog = useAppSelector( state => state.settings.lastShowChangelogVersion !== packageJson.version, ); - const newUpdate = useTypedSelector(state => state.settings.newUpdate); - const courseInformationSharingBadgeShown = useTypedSelector( + const newUpdate = useAppSelector(state => state.settings.newUpdate); + const courseInformationSharingBadgeShown = useAppSelector( state => state.settings.courseInformationSharingBadgeShown, ); @@ -638,11 +638,11 @@ const Container = () => { const colorScheme = useColorScheme(); const toast = useToast(); - const dispatch = useDispatch(); - const loggingIn = useTypedSelector(state => state.auth.loggingIn); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const loginError = useTypedSelector(state => state.auth.error); - const auth = useTypedSelector(state => state.auth); + const dispatch = useAppDispatch(); + const loggingIn = useAppSelector(state => state.auth.loggingIn); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const loginError = useAppSelector(state => state.auth.error); + const auth = useAppSelector(state => state.auth); const windowSize = useWindowDimensions(); const [appState, setAppState] = useState(AppState.currentState); diff --git a/src/components/AssignmentCard.tsx b/src/components/AssignmentCard.tsx index 068907e3..71880e4e 100644 --- a/src/components/AssignmentCard.tsx +++ b/src/components/AssignmentCard.tsx @@ -21,7 +21,9 @@ export interface AssignmentCardProps extends CardWrapperProps { hideCourseName?: boolean; } -const AssignmentCard: React.FC = ({ +const AssignmentCard: React.FC< + React.PropsWithChildren +> = ({ data: { courseName, courseTeacherName, diff --git a/src/components/AutoHeightWebView.tsx b/src/components/AutoHeightWebView.tsx index 520c19e7..6bfb29f7 100644 --- a/src/components/AutoHeightWebView.tsx +++ b/src/components/AutoHeightWebView.tsx @@ -6,7 +6,9 @@ import { WebViewNavigation, } from 'react-native-webview/lib/WebViewTypes'; -const AutoHeightWebView: React.FC = props => { +const AutoHeightWebView: React.FC< + React.PropsWithChildren +> = props => { const [height, setHeight] = useState(0); const webViewRef = useRef(null); diff --git a/src/components/CardWrapper.tsx b/src/components/CardWrapper.tsx index 8a656fab..74a3d627 100644 --- a/src/components/CardWrapper.tsx +++ b/src/components/CardWrapper.tsx @@ -21,7 +21,9 @@ export interface CardWrapperProps { const buttonWidth = 80; -const CardWrapper: React.FC> = ({ +const CardWrapper: React.FC< + React.PropsWithChildren> +> = ({ fav, onFav, archived, diff --git a/src/components/CourseCard.tsx b/src/components/CourseCard.tsx index c51249da..205c6500 100644 --- a/src/components/CourseCard.tsx +++ b/src/components/CourseCard.tsx @@ -14,7 +14,7 @@ export interface CourseCardProps extends CardWrapperProps { }; } -const CourseCard: React.FC = ({ +const CourseCard: React.FC> = ({ data: { name, englishName, diff --git a/src/components/Empty.tsx b/src/components/Empty.tsx index 1fd738c2..dc22a460 100644 --- a/src/components/Empty.tsx +++ b/src/components/Empty.tsx @@ -4,7 +4,7 @@ import Icon from 'react-native-vector-icons/MaterialIcons'; import Styles from 'constants/Styles'; import {t} from 'helpers/i18n'; -const Empty: React.FC = () => { +const Empty: React.FC> = () => { const theme = useTheme(); return ( diff --git a/src/components/FileCard.tsx b/src/components/FileCard.tsx index fc61c529..69dee8b4 100644 --- a/src/components/FileCard.tsx +++ b/src/components/FileCard.tsx @@ -19,7 +19,7 @@ export interface FileCardProps extends CardWrapperProps { hideCourseName?: boolean; } -const FileCard: React.FC = ({ +const FileCard: React.FC> = ({ data: { title, description, diff --git a/src/components/Filter.tsx b/src/components/Filter.tsx index f08cbbf4..12c1975c 100644 --- a/src/components/Filter.tsx +++ b/src/components/Filter.tsx @@ -26,7 +26,7 @@ export interface FilterProps { unfinishedCount?: number; } -const Filter: React.FC = ({ +const Filter: React.FC> = ({ visible, selected, onSelectionChange, @@ -70,13 +70,15 @@ const Filter: React.FC = ({ } }, [visible, position]); - const ListItem: React.FC<{ - name: FilterSelection; - text: string; - count?: number; - color?: string; - badgeColor?: string; - }> = ({name, text, count, color, badgeColor}) => ( + const ListItem: React.FC< + React.PropsWithChildren<{ + name: FilterSelection; + text: string; + count?: number; + color?: string; + badgeColor?: string; + }> + > = ({name, text, count, color, badgeColor}) => ( = ({ return ( - + { fav?: T[]; archived?: T[]; hidden: T[]; - itemComponent: React.FC>; + itemComponent: React.FC>>; navigation: NativeStackNavigationProp< ScreenParams, 'Notices' | 'Assignments' | 'Files' | 'Courses' @@ -71,16 +70,16 @@ const FilterList = ({ refreshing, onRefresh, }: FilterListProps) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const toast = useToast(); const detailNavigator = useDetailNavigator(); - const tabFilterSelections = useTypedSelector( + const tabFilterSelections = useAppSelector( state => state.settings.tabFilterSelections, ); - const filterSelected = useTypedSelector( + const filterSelected = useAppSelector( state => state.settings.tabFilterSelections[type] ?? defaultSelected ?? 'all', ); diff --git a/src/components/HeaderTitle.tsx b/src/components/HeaderTitle.tsx index 226e51ee..ae331403 100644 --- a/src/components/HeaderTitle.tsx +++ b/src/components/HeaderTitle.tsx @@ -6,7 +6,10 @@ export interface HeaderTitleProps { subtitle?: string; } -const HeaderTitle: React.FC = ({title, subtitle}) => { +const HeaderTitle: React.FC> = ({ + title, + subtitle, +}) => { return ( = ({ +const NoticeCard: React.FC> = ({ data: { title, content, diff --git a/src/components/SafeArea.tsx b/src/components/SafeArea.tsx index d7a84493..b0b6aea9 100644 --- a/src/components/SafeArea.tsx +++ b/src/components/SafeArea.tsx @@ -1,7 +1,9 @@ import {SafeAreaView, SafeAreaViewProps} from 'react-native-safe-area-context'; import Styles from 'constants/Styles'; -const SafeArea: React.FC = props => { +const SafeArea: React.FC< + React.PropsWithChildren +> = props => { return ( ); diff --git a/src/components/Skeleton.tsx b/src/components/Skeleton.tsx index 30eeeda8..c0d51f7a 100644 --- a/src/components/Skeleton.tsx +++ b/src/components/Skeleton.tsx @@ -2,7 +2,7 @@ import {useEffect, useCallback, useRef} from 'react'; import {StyleSheet, View, Animated} from 'react-native'; import {useTheme} from 'react-native-paper'; -const Skeleton: React.FC = () => { +const Skeleton: React.FC> = () => { const theme = useTheme(); const opacity = useRef(new Animated.Value(1)); diff --git a/src/components/Splash.tsx b/src/components/Splash.tsx index bd51f057..7e9e2025 100644 --- a/src/components/Splash.tsx +++ b/src/components/Splash.tsx @@ -1,6 +1,6 @@ import {Image, StyleSheet, useColorScheme, View} from 'react-native'; -const Splash: React.FC = () => { +const Splash: React.FC> = () => { const colorScheme = useColorScheme(); return ( diff --git a/src/components/SplitView.tsx b/src/components/SplitView.tsx index 4127dfeb..a5934e62 100644 --- a/src/components/SplitView.tsx +++ b/src/components/SplitView.tsx @@ -25,7 +25,7 @@ export interface SplitViewProps { showDetail: boolean; } -const SplitViewProvider: React.FC = ({ +const SplitViewProvider: React.FC> = ({ splitEnabled, detailNavigationContainerRef, showDetail, diff --git a/src/components/TableCell.tsx b/src/components/TableCell.tsx index 514a407f..8e12c5e1 100644 --- a/src/components/TableCell.tsx +++ b/src/components/TableCell.tsx @@ -28,7 +28,7 @@ export interface TableCellProps extends ViewProps { badge?: boolean; } -const TableCell: React.FC = ({ +const TableCell: React.FC> = ({ type, onPress, primaryText, diff --git a/src/components/TextButton.tsx b/src/components/TextButton.tsx index 51b9bb09..56cf46a2 100644 --- a/src/components/TextButton.tsx +++ b/src/components/TextButton.tsx @@ -8,7 +8,7 @@ export interface TextButtonProps extends TextProps { onPress?: TouchableOpacityProps['onPress']; } -const TextButton: React.FC = ({ +const TextButton: React.FC> = ({ containerStyle, onPress, style, diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index b6cb9bcf..72893ede 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -15,7 +15,9 @@ const ToastContext = createContext<{ toggleToast: () => {}, }); -const ToastProvider: React.FC = ({children}) => { +const ToastProvider: React.FC> = ({ + children, +}) => { const [toastText, setToastText] = useState(''); const [toastDuration, setToastDuration] = useState(3000); diff --git a/src/data/store.ts b/src/data/store.ts index 7771f23d..5caa6f54 100644 --- a/src/data/store.ts +++ b/src/data/store.ts @@ -1,15 +1,21 @@ -import {applyMiddleware, compose, createStore} from 'redux'; -import {TypedUseSelectorHook, useSelector} from 'react-redux'; -import {PersistConfig, persistReducer, persistStore} from 'redux-persist'; +import {TypedUseSelectorHook, useDispatch, useSelector} from 'react-redux'; +import { + persistStore, + persistReducer, + FLUSH, + REHYDRATE, + PAUSE, + PERSIST, + PURGE, + REGISTER, + PersistConfig, +} from 'redux-persist'; import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import thunk from 'redux-thunk'; +import {configureStore} from '@reduxjs/toolkit'; import {rootReducer} from 'data/reducers/root'; import {AppState, PersistAppState} from 'data/types/state'; -declare const window: any; -const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - const rootPersistConfig: PersistConfig = { key: 'root', storage: AsyncStorage, @@ -17,12 +23,19 @@ const rootPersistConfig: PersistConfig = { blacklist: ['auth', 'settings'], }; -const store = createStore( - persistReducer(rootPersistConfig, rootReducer), - composeEnhancers(applyMiddleware(thunk)), -); -const persistor = persistStore(store); - -const useTypedSelector: TypedUseSelectorHook = useSelector; +export const store = configureStore({ + reducer: persistReducer(rootPersistConfig, rootReducer), + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], + }, + }) as any, +}); +export const persistor = persistStore(store); -export {store, persistor, useTypedSelector}; +export type AppDispatch = typeof store.dispatch; +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook< + ReturnType +> = useSelector; diff --git a/src/screens/About.tsx b/src/screens/About.tsx index ea5449ea..49c43ea9 100644 --- a/src/screens/About.tsx +++ b/src/screens/About.tsx @@ -10,7 +10,7 @@ import {ScreenParams} from './types'; import packageJson from '../../package.json'; const About: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = props => { useNavigationAnimation(props); diff --git a/src/screens/AssignmentDetail.tsx b/src/screens/AssignmentDetail.tsx index ad2d5592..d1bc29f0 100644 --- a/src/screens/AssignmentDetail.tsx +++ b/src/screens/AssignmentDetail.tsx @@ -25,7 +25,9 @@ import {getLocale, t} from 'helpers/i18n'; import {File} from 'data/types/state'; const AssignmentDetail: React.FC< - NativeStackScreenProps + React.PropsWithChildren< + NativeStackScreenProps + > > = ({route, navigation}) => { const theme = useTheme(); diff --git a/src/screens/AssignmentSubmission.tsx b/src/screens/AssignmentSubmission.tsx index b66bb323..86445e74 100644 --- a/src/screens/AssignmentSubmission.tsx +++ b/src/screens/AssignmentSubmission.tsx @@ -19,7 +19,6 @@ import { TextInput, useTheme, } from 'react-native-paper'; -import {useDispatch} from 'react-redux'; import dayjs from 'dayjs'; import SafeArea from 'components/SafeArea'; import TextButton from 'components/TextButton'; @@ -32,9 +31,12 @@ import {submitAssignment} from 'data/source'; import {getAssignmentsForCourse} from 'data/actions/assignments'; import useToast from 'hooks/useToast'; import {ScreenParams} from './types'; +import {useAppDispatch} from 'data/store'; const AssignmentSubmission: React.FC< - NativeStackScreenProps + React.PropsWithChildren< + NativeStackScreenProps + > > = ({navigation, route}) => { const theme = useTheme(); const toast = useToast(); @@ -50,7 +52,7 @@ const AssignmentSubmission: React.FC< submittedContent, } = route.params; - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [content, setContent] = useState( (removeTags(submittedContent || '') ?? '').replace('-->', ''), diff --git a/src/screens/Assignments.tsx b/src/screens/Assignments.tsx index 20e4b11f..d48bec09 100644 --- a/src/screens/Assignments.tsx +++ b/src/screens/Assignments.tsx @@ -1,13 +1,12 @@ import {useCallback, useEffect, useMemo} from 'react'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {StackActions} from '@react-navigation/native'; -import {useDispatch} from 'react-redux'; import dayjs from 'dayjs'; import {ScreenParams} from 'screens/types'; import FilterList from 'components/FilterList'; import AssignmentCard from 'components/AssignmentCard'; import SafeArea from 'components/SafeArea'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {getAllAssignmentsForCourses} from 'data/actions/assignments'; import {Assignment} from 'data/types/state'; import useDetailNavigator from 'hooks/useDetailNavigator'; @@ -17,24 +16,22 @@ import {saveAssignmentsToReminderOrCalendar} from 'helpers/event'; import {t} from 'helpers/i18n'; const Assignments: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = ({navigation}) => { const detailNavigator = useDetailNavigator(); const toast = useToast(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const courseIds = useTypedSelector( + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const courseIds = useAppSelector( state => state.courses.items.map(i => i.id), (a, b) => JSON.stringify(a) === JSON.stringify(b), ); - const hiddenCourseIds = useTypedSelector(state => state.courses.hidden); - const assignmentState = useTypedSelector(state => state.assignments); - const fetching = useTypedSelector(state => state.assignments.fetching); - const assignmentSync = useTypedSelector( - state => state.settings.assignmentSync, - ); + const hiddenCourseIds = useAppSelector(state => state.courses.hidden); + const assignmentState = useAppSelector(state => state.assignments); + const fetching = useAppSelector(state => state.assignments.fetching); + const assignmentSync = useAppSelector(state => state.settings.assignmentSync); const [all, _, fav, archived, hidden, unfinished, finished] = useFilteredData( assignmentState.items, diff --git a/src/screens/CalendarEvent.tsx b/src/screens/CalendarEvent.tsx index ebb52a17..2a62c97c 100644 --- a/src/screens/CalendarEvent.tsx +++ b/src/screens/CalendarEvent.tsx @@ -2,11 +2,10 @@ import {useMemo, useState} from 'react'; import {Alert, Platform, ScrollView, StyleSheet} from 'react-native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {Caption} from 'react-native-paper'; -import {useDispatch} from 'react-redux'; import dayjs from 'dayjs'; import TableCell from 'components/TableCell'; import SafeArea from 'components/SafeArea'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {setSetting} from 'data/actions/settings'; import {dataSource} from 'data/source'; import { @@ -21,21 +20,19 @@ import useFilteredData from 'hooks/useFilteredData'; import {ScreenParams} from './types'; const CalendarEvent: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = props => { const toast = useToast(); - const dispatch = useDispatch(); - const assignmentSync = useTypedSelector( - state => state.settings.assignmentSync, - ); - const syncAssignmentsToCalendar = useTypedSelector( + const dispatch = useAppDispatch(); + const assignmentSync = useAppSelector(state => state.settings.assignmentSync); + const syncAssignmentsToCalendar = useAppSelector( state => state.settings.syncAssignmentsToCalendar, ); - const alarms = useTypedSelector(state => state.settings.alarms); - const graduate = useTypedSelector(state => state.settings.graduate); - const hiddenCourseIds = useTypedSelector(state => state.courses.hidden); - const assignmentState = useTypedSelector(state => state.assignments); + const alarms = useAppSelector(state => state.settings.alarms); + const graduate = useAppSelector(state => state.settings.graduate); + const hiddenCourseIds = useAppSelector(state => state.courses.hidden); + const assignmentState = useAppSelector(state => state.assignments); const [all] = useFilteredData( assignmentState.items, diff --git a/src/screens/Changelog.tsx b/src/screens/Changelog.tsx index 2255549b..ce1bf1f4 100644 --- a/src/screens/Changelog.tsx +++ b/src/screens/Changelog.tsx @@ -2,17 +2,17 @@ import {useEffect, useRef} from 'react'; import {Linking} from 'react-native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import WebView, {WebViewNavigation} from 'react-native-webview'; -import {useDispatch} from 'react-redux'; import SafeArea from 'components/SafeArea'; import {setSetting} from 'data/actions/settings'; import useNavigationAnimation from 'hooks/useNavigationAnimation'; import {ScreenParams} from './types'; import packageJson from '../../package.json'; +import {useAppDispatch} from 'data/store'; const Changelog: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = props => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const webViewRef = useRef(null); diff --git a/src/screens/CourseDetail.tsx b/src/screens/CourseDetail.tsx index 7f1bf279..2f3a8d2e 100644 --- a/src/screens/CourseDetail.tsx +++ b/src/screens/CourseDetail.tsx @@ -6,7 +6,6 @@ import { TouchableOpacity, } from 'react-native'; import {Divider, Text, useTheme} from 'react-native-paper'; -import {useDispatch} from 'react-redux'; import { NativeStackNavigationProp, NativeStackScreenProps, @@ -22,7 +21,7 @@ import { import {Scene} from 'react-native-tab-view/lib/typescript/types'; import Icon from 'react-native-vector-icons/MaterialIcons'; import {Assignment, File, Notice} from 'data/types/state'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {getNoticesForCourse} from 'data/actions/notices'; import {getAssignmentsForCourse} from 'data/actions/assignments'; import {getFilesForCourse} from 'data/actions/files'; @@ -38,9 +37,9 @@ import {ScreenParams} from './types'; const Notices = ({courseId, data}: {courseId: string; data: Notice[]}) => { const navigation = useNavigation>(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const fetching = useTypedSelector(state => state.notices.fetching); + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const fetching = useAppSelector(state => state.notices.fetching); const handleRefresh = () => { if (loggedIn) { @@ -80,9 +79,9 @@ const Assignments = ({ }) => { const navigation = useNavigation>(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const fetching = useTypedSelector(state => state.assignments.fetching); + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const fetching = useAppSelector(state => state.assignments.fetching); const handleRefresh = () => { if (loggedIn) { @@ -116,9 +115,9 @@ const Assignments = ({ const Files = ({courseId, data}: {courseId: string; data: File[]}) => { const navigation = useNavigation>(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const fetching = useTypedSelector(state => state.files.fetching); + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const fetching = useAppSelector(state => state.files.fetching); const handleRefresh = () => { if (loggedIn) { @@ -150,7 +149,7 @@ const Files = ({courseId, data}: {courseId: string; data: File[]}) => { }; const CourseDetail: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = ({ navigation, route: { @@ -163,9 +162,9 @@ const CourseDetail: React.FC< const detailNavigator = useDetailNavigator(); - const notices = useTypedSelector(state => state.notices.items); - const assignments = useTypedSelector(state => state.assignments.items); - const files = useTypedSelector(state => state.files.items); + const notices = useAppSelector(state => state.notices.items); + const assignments = useAppSelector(state => state.assignments.items); + const files = useAppSelector(state => state.files.items); const [index, setIndex] = useState(0); const [routes] = useState([ diff --git a/src/screens/CourseInformationSharing.tsx b/src/screens/CourseInformationSharing.tsx index 65b63c3e..9bf238ac 100644 --- a/src/screens/CourseInformationSharing.tsx +++ b/src/screens/CourseInformationSharing.tsx @@ -2,8 +2,7 @@ import {useEffect} from 'react'; import {Alert, Linking, ScrollView, StyleSheet} from 'react-native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {Caption} from 'react-native-paper'; -import {useDispatch} from 'react-redux'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {setSetting} from 'data/actions/settings'; import TableCell from 'components/TableCell'; import SafeArea from 'components/SafeArea'; @@ -13,12 +12,14 @@ import {uploadCourses} from 'helpers/coursex'; import {ScreenParams} from './types'; const CourseInformationSharing: React.FC< - NativeStackScreenProps + React.PropsWithChildren< + NativeStackScreenProps + > > = props => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); - const courses = useTypedSelector(state => state.courses.items); - const courseInformationSharing = useTypedSelector( + const courses = useAppSelector(state => state.courses.items); + const courseInformationSharing = useAppSelector( state => state.settings.courseInformationSharing, ); diff --git a/src/screens/CourseX.tsx b/src/screens/CourseX.tsx index 8e080323..b034ae4b 100644 --- a/src/screens/CourseX.tsx +++ b/src/screens/CourseX.tsx @@ -7,9 +7,9 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'; import SafeArea from 'components/SafeArea'; import {ScreenParams} from './types'; -const CourseX: React.FC> = ({ - route, -}) => { +const CourseX: React.FC< + React.PropsWithChildren> +> = ({route}) => { const courseId = route.params?.id ?? ''; const theme = useTheme(); diff --git a/src/screens/Courses.tsx b/src/screens/Courses.tsx index 058bdae0..b7950de1 100644 --- a/src/screens/Courses.tsx +++ b/src/screens/Courses.tsx @@ -1,12 +1,11 @@ import {useEffect, useMemo} from 'react'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; -import {useDispatch} from 'react-redux'; import {StackActions} from '@react-navigation/native'; import dayjs from 'dayjs'; import CourseCard from 'components/CourseCard'; import FilterList from 'components/FilterList'; import SafeArea from 'components/SafeArea'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {getCoursesForSemester} from 'data/actions/courses'; import {Course} from 'data/types/state'; import useDetailNavigator from 'hooks/useDetailNavigator'; @@ -14,24 +13,24 @@ import {getSemesterTextFromId} from 'helpers/parse'; import {uploadCourses} from 'helpers/coursex'; import {ScreenParams} from './types'; -const Courses: React.FC> = ({ - navigation, -}) => { +const Courses: React.FC< + React.PropsWithChildren> +> = ({navigation}) => { const detailNavigator = useDetailNavigator(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const currentSemesterId = useTypedSelector(state => state.semesters.current); - const courseInformationSharing = useTypedSelector( + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const currentSemesterId = useAppSelector(state => state.semesters.current); + const courseInformationSharing = useAppSelector( state => state.settings.courseInformationSharing, ); - const courses = useTypedSelector(state => state.courses.items); - const hiddenIds = useTypedSelector(state => state.courses.hidden); - const fetching = useTypedSelector(state => state.courses.fetching); + const courses = useAppSelector(state => state.courses.items); + const hiddenIds = useAppSelector(state => state.courses.hidden); + const fetching = useAppSelector(state => state.courses.fetching); - const notices = useTypedSelector(state => state.notices.items); - const assignments = useTypedSelector(state => state.assignments.items); - const files = useTypedSelector(state => state.files.items); + const notices = useAppSelector(state => state.notices.items); + const assignments = useAppSelector(state => state.assignments.items); + const files = useAppSelector(state => state.files.items); const coursesWithCounts = useMemo(() => { return courses.map(course => ({ diff --git a/src/screens/FileCache.tsx b/src/screens/FileCache.tsx index 843f4b9b..82aa449e 100644 --- a/src/screens/FileCache.tsx +++ b/src/screens/FileCache.tsx @@ -1,8 +1,7 @@ import {Alert, ScrollView, StyleSheet} from 'react-native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {Caption} from 'react-native-paper'; -import {useDispatch} from 'react-redux'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {setSetting} from 'data/actions/settings'; import {removeFileDir} from 'helpers/fs'; import {getLocale, t} from 'helpers/i18n'; @@ -13,15 +12,15 @@ import SafeArea from 'components/SafeArea'; import {ScreenParams} from './types'; const FileCache: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = props => { const toast = useToast(); - const dispatch = useDispatch(); - const fileUseDocumentDir = useTypedSelector( + const dispatch = useAppDispatch(); + const fileUseDocumentDir = useAppSelector( state => state.settings.fileUseDocumentDir, ); - const fileOmitCourseName = useTypedSelector( + const fileOmitCourseName = useAppSelector( state => state.settings.fileOmitCourseName, ); diff --git a/src/screens/FileDetail.tsx b/src/screens/FileDetail.tsx index fd936476..c96f668b 100644 --- a/src/screens/FileDetail.tsx +++ b/src/screens/FileDetail.tsx @@ -23,7 +23,7 @@ import {ScreenParams} from './types'; import {SplitViewContext} from 'components/SplitView'; const FileDetail: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = ({route, navigation}) => { const {fileType, disableAnimation} = route.params; diff --git a/src/screens/Files.tsx b/src/screens/Files.tsx index 379e9cea..a08e8b82 100644 --- a/src/screens/Files.tsx +++ b/src/screens/Files.tsx @@ -1,31 +1,30 @@ import {useCallback, useEffect} from 'react'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {StackActions} from '@react-navigation/native'; -import {useDispatch} from 'react-redux'; import FileCard from 'components/FileCard'; import SafeArea from 'components/SafeArea'; import FilterList from 'components/FilterList'; import {getAllFilesForCourses} from 'data/actions/files'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {File} from 'data/types/state'; import useFilteredData from 'hooks/useFilteredData'; import useDetailNavigator from 'hooks/useDetailNavigator'; import {ScreenParams} from './types'; -const Files: React.FC> = ({ - navigation, -}) => { +const Files: React.FC< + React.PropsWithChildren> +> = ({navigation}) => { const detailNavigator = useDetailNavigator(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const courseIds = useTypedSelector( + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const courseIds = useAppSelector( state => state.courses.items.map(i => i.id), (a, b) => JSON.stringify(a) === JSON.stringify(b), ); - const hiddenCourseIds = useTypedSelector(state => state.courses.hidden); - const fileState = useTypedSelector(state => state.files); - const fetching = useTypedSelector(state => state.files.fetching); + const hiddenCourseIds = useAppSelector(state => state.courses.hidden); + const fileState = useAppSelector(state => state.files); + const fetching = useAppSelector(state => state.files.fetching); const [all, unread, fav, archived, hidden] = useFilteredData( fileState.items, diff --git a/src/screens/Help.tsx b/src/screens/Help.tsx index eba5d2ce..dae9665c 100644 --- a/src/screens/Help.tsx +++ b/src/screens/Help.tsx @@ -7,7 +7,9 @@ import useNavigationAnimation from 'hooks/useNavigationAnimation'; import {t} from 'helpers/i18n'; import {ScreenParams} from './types'; -const Help: React.FC> = props => { +const Help: React.FC< + React.PropsWithChildren> +> = props => { useNavigationAnimation(props); return ( diff --git a/src/screens/Login.tsx b/src/screens/Login.tsx index ace1f034..813d12a2 100644 --- a/src/screens/Login.tsx +++ b/src/screens/Login.tsx @@ -17,10 +17,9 @@ import { Text, } from 'react-native-paper'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; -import {useDispatch} from 'react-redux'; import {FailReason} from 'thu-learn-lib-no-native/lib/types'; import useToast from 'hooks/useToast'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {login} from 'data/actions/auth'; import {setMockStore} from 'data/actions/root'; import {setSetting} from 'data/actions/settings'; @@ -30,15 +29,17 @@ import {ScreenParams} from './types'; import env from 'helpers/env'; import {t} from 'helpers/i18n'; -const Login: React.FC> = () => { +const Login: React.FC< + React.PropsWithChildren> +> = () => { const theme = useTheme(); const toast = useToast(); - const dispatch = useDispatch(); - const loggingIn = useTypedSelector(state => state.auth.loggingIn); - const error = useTypedSelector(state => state.auth.error); - const graduate = useTypedSelector(state => state.settings.graduate); + const dispatch = useAppDispatch(); + const loggingIn = useAppSelector(state => state.auth.loggingIn); + const error = useAppSelector(state => state.auth.error); + const graduate = useAppSelector(state => state.settings.graduate); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); diff --git a/src/screens/NoticeDetail.tsx b/src/screens/NoticeDetail.tsx index 1b27a677..efb6d63b 100644 --- a/src/screens/NoticeDetail.tsx +++ b/src/screens/NoticeDetail.tsx @@ -15,7 +15,7 @@ import {stripExtension, getExtension} from 'helpers/fs'; import {File} from 'data/types/state'; const NoticeDetail: React.FC< - NativeStackScreenProps + React.PropsWithChildren> > = ({route, navigation}) => { const theme = useTheme(); diff --git a/src/screens/Notices.tsx b/src/screens/Notices.tsx index ef6cdfaa..d24e8c2c 100644 --- a/src/screens/Notices.tsx +++ b/src/screens/Notices.tsx @@ -1,31 +1,30 @@ import {useCallback, useEffect} from 'react'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import {StackActions} from '@react-navigation/native'; -import {useDispatch} from 'react-redux'; import NoticeCard from 'components/NoticeCard'; import FilterList from 'components/FilterList'; import SafeArea from 'components/SafeArea'; import {getAllNoticesForCourses} from 'data/actions/notices'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import {Notice} from 'data/types/state'; import useFilteredData from 'hooks/useFilteredData'; import useDetailNavigator from 'hooks/useDetailNavigator'; import {ScreenParams} from './types'; -const Notices: React.FC> = ({ - navigation, -}) => { +const Notices: React.FC< + React.PropsWithChildren> +> = ({navigation}) => { const detailNavigator = useDetailNavigator(); - const dispatch = useDispatch(); - const loggedIn = useTypedSelector(state => state.auth.loggedIn); - const courseIds = useTypedSelector( + const dispatch = useAppDispatch(); + const loggedIn = useAppSelector(state => state.auth.loggedIn); + const courseIds = useAppSelector( state => state.courses.items.map(i => i.id), (a, b) => JSON.stringify(a) === JSON.stringify(b), ); - const hiddenCourseIds = useTypedSelector(state => state.courses.hidden); - const noticeState = useTypedSelector(state => state.notices); - const fetching = useTypedSelector(state => state.notices.fetching); + const hiddenCourseIds = useAppSelector(state => state.courses.hidden); + const noticeState = useAppSelector(state => state.notices); + const fetching = useAppSelector(state => state.notices.fetching); const [all, unread, fav, archived, hidden] = useFilteredData( noticeState.items, diff --git a/src/screens/Search.tsx b/src/screens/Search.tsx index 5fc2bf79..838b7eca 100644 --- a/src/screens/Search.tsx +++ b/src/screens/Search.tsx @@ -11,7 +11,7 @@ import {Searchbar, Subheading, useTheme} from 'react-native-paper'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import useSearch from 'hooks/useSearch'; import Styles from 'constants/Styles'; -import {useTypedSelector} from 'data/store'; +import {useAppSelector} from 'data/store'; import {Notice, Assignment, File} from 'data/types/state'; import NoticeCard from 'components/NoticeCard'; import AssignmentCard from 'components/AssignmentCard'; @@ -21,17 +21,16 @@ import SafeArea from 'components/SafeArea'; import {t} from 'helpers/i18n'; import {ScreenParams} from './types'; -const Search: React.FC> = ({ - navigation, - route, -}) => { +const Search: React.FC< + React.PropsWithChildren> +> = ({navigation, route}) => { const theme = useTheme(); const safeAreaInsets = useSafeAreaInsets(); - const notices = useTypedSelector(state => state.notices.items); - const assignments = useTypedSelector(state => state.assignments.items); - const files = useTypedSelector(state => state.files.items); + const notices = useAppSelector(state => state.notices.items); + const assignments = useAppSelector(state => state.assignments.items); + const files = useAppSelector(state => state.files.items); const [searchQuery, setSearchQuery] = useState(route.params?.query ?? ''); diff --git a/src/screens/SemesterSelection.tsx b/src/screens/SemesterSelection.tsx index 53c43046..b9a83ddd 100644 --- a/src/screens/SemesterSelection.tsx +++ b/src/screens/SemesterSelection.tsx @@ -1,10 +1,9 @@ import {useCallback, useEffect} from 'react'; import {FlatList, StyleSheet} from 'react-native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; -import {useDispatch} from 'react-redux'; import TableCell from 'components/TableCell'; import SafeArea from 'components/SafeArea'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import { getAllSemesters, getCurrentSemester, @@ -15,12 +14,14 @@ import useNavigationAnimation from 'hooks/useNavigationAnimation'; import {ScreenParams} from './types'; const SemesterSelection: React.FC< - NativeStackScreenProps + React.PropsWithChildren< + NativeStackScreenProps + > > = props => { - const dispatch = useDispatch(); - const semesters = useTypedSelector(state => state.semesters.items); - const fetching = useTypedSelector(state => state.semesters.fetching); - const currentSemesterId = useTypedSelector(state => state.semesters.current); + const dispatch = useAppDispatch(); + const semesters = useAppSelector(state => state.semesters.items); + const fetching = useAppSelector(state => state.semesters.fetching); + const currentSemesterId = useAppSelector(state => state.semesters.current); const handleSelect = (id: string) => { dispatch(setCurrentSemester(id)); diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx index 866ae40e..84ec23a9 100644 --- a/src/screens/Settings.tsx +++ b/src/screens/Settings.tsx @@ -1,6 +1,5 @@ import {useEffect, useState} from 'react'; import {Alert, Linking, Platform, ScrollView, StyleSheet} from 'react-native'; -import {useDispatch} from 'react-redux'; import {StackActions} from '@react-navigation/native'; import {NativeStackScreenProps} from '@react-navigation/native-stack'; import semverGt from 'semver/functions/gt'; @@ -10,24 +9,24 @@ import Styles from 'constants/Styles'; import {getLatestRelease} from 'helpers/update'; import {setSetting} from 'data/actions/settings'; import {clearStore} from 'data/actions/root'; -import {useTypedSelector} from 'data/store'; +import {useAppDispatch, useAppSelector} from 'data/store'; import useDetailNavigator from 'hooks/useDetailNavigator'; import {t} from 'helpers/i18n'; import {ScreenParams} from './types'; import packageJson from '../../package.json'; -const Settings: React.FC> = ({ - navigation, -}) => { +const Settings: React.FC< + React.PropsWithChildren> +> = ({navigation}) => { const detailNavigator = useDetailNavigator(); - const dispatch = useDispatch(); - const userInfo = useTypedSelector(state => state.user); - const username = useTypedSelector(state => state.auth.username); - const newChangelog = useTypedSelector( + const dispatch = useAppDispatch(); + const userInfo = useAppSelector(state => state.user); + const username = useAppSelector(state => state.auth.username); + const newChangelog = useAppSelector( state => state.settings.lastShowChangelogVersion !== packageJson.version, ); - const courseInformationSharingBadgeShown = useTypedSelector( + const courseInformationSharingBadgeShown = useAppSelector( state => state.settings.courseInformationSharingBadgeShown, ); diff --git a/tsconfig.json b/tsconfig.json index 5ecc92e3..4a708fb9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "resolveJsonModule": true, - "skipLibCheck": true + "skipLibCheck": true, + "noUnusedLocals": true }, "include": ["src/**/*"] } diff --git a/yarn.lock b/yarn.lock index 05b553a7..02a8cb90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3287,9 +3287,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.4.118: - version "1.4.128" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.128.tgz#759dd0bf297183d30b87b0cb46c1b84a7c8f94f1" - integrity sha512-DT8G+2drgywCWN+74jbYJ7yXbdec9wefs+K8C0Tu2Hbhj7K0nod17Iq8hoS21MpuYYrL6oXu2Kd636KmzXVugQ== + version "1.4.129" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz#c675793885721beefff99da50f57c6525c2cd238" + integrity sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ== emoji-regex@^8.0.0: version "8.0.0" @@ -6952,12 +6952,13 @@ react-shallow-renderer@16.14.1: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0" -react@18.1.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890" - integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ== +react@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" + object-assign "^4.1.1" readable-stream@1.1.x: version "1.1.14"