fix: Resolve Type and Lint Issues

This commit is contained in:
Patrick Loser
2026-07-26 18:37:53 +02:00
committed by Rajarshee Chatterjee
parent 345d084eba
commit 72bfcad034
47 changed files with 167 additions and 146 deletions
+34 -33
View File
@@ -1,61 +1,62 @@
const { defineConfig, globalIgnores } = require('eslint/config');
const expoConfig = require("eslint-config-expo/flat");
const { FlatCompat } = require("@eslint/eslintrc");
const expoConfig = require('eslint-config-expo/flat');
const { FlatCompat } = require('@eslint/eslintrc');
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const testFiles = [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)",
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
];
module.exports = defineConfig([
globalIgnores([
"android/**",
"ios/**",
"node_modules/**",
".expo/**",
"dist/**",
"coverage/**",
'android/**',
'ios/**',
'node_modules/**',
'.expo/**',
'dist/**',
'coverage/**',
]),
expoConfig,
...compat
.extends("plugin:testing-library/react", "plugin:jest/recommended")
.map((config) => ({
.extends('plugin:testing-library/react', 'plugin:jest/recommended')
.map(config => ({
...config,
files: testFiles,
})),
{
files: ["**/*.{js,jsx,ts,tsx}"],
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
"no-shadow": "off",
"no-undef": "off",
"no-console": "error",
'no-shadow': 'off',
'no-undef': 'off',
'no-console': 'error',
"@typescript-eslint/no-shadow": "warn",
'@typescript-eslint/no-shadow': 'warn',
"react-hooks/exhaustive-deps": "warn",
"react-hooks/static-components": "warn",
"react-hooks/purity": "warn",
"react-hooks/set-state-in-effect": "warn",
"react-hooks/refs": "warn",
"react-hooks/immutability": "warn",
"react/no-children-prop": "warn",
'react-hooks/exhaustive-deps': 'warn',
'react-hooks/static-components': 'warn',
'react-hooks/purity': 'warn',
'react-hooks/set-state-in-effect': 'warn',
'react-hooks/refs': 'warn',
'react-hooks/immutability': 'warn',
'react/no-children-prop': 'warn',
"react/display-name": "off",
'react/display-name': 'off',
curly: ["error", "multi-line", "consistent"],
"no-useless-return": "error",
"block-scoped-var": "error",
"no-var": "error",
"prefer-const": "error",
"no-dupe-else-if": "error",
"no-duplicate-imports": "error",
curly: ['error', 'multi-line', 'consistent'],
'no-useless-return': 'error',
'block-scoped-var': 'error',
'no-var': 'error',
'prefer-const': 'error',
'no-dupe-else-if': 'error',
'no-duplicate-imports': 'error',
"@react-native/no-deep-imports": "off",
'@react-native/no-deep-imports': 'off',
'@typescript-eslint/no-require-imports': 'off',
},
},
]);
@@ -9,6 +9,12 @@ declare class NativeVolumeButtonListenerModule extends NativeModule<NativeVolume
setActive(active: boolean): void;
}
export default requireNativeModule<NativeVolumeButtonListenerModule>(
'NativeVolumeButtonListener',
);
setActive(active: boolean): void;
}
export default requireNativeModule<NativeVolumeButtonListenerModule>(
'NativeVolumeButtonListener',
);
@@ -7,12 +7,12 @@ type NativeZipArchiveModule = {
distDirPath: string,
urlString: string,
headers: Record<string, string>,
): Promise<void>;
): Promise<string>;
remoteZip(
sourceDirPath: string,
urlString: string,
headers: Record<string, string>,
): Promise<void>;
): Promise<string>;
};
export default requireNativeModule<NativeZipArchiveModule>('NativeZipArchive');
export default requireNativeModule<NativeZipArchiveModule>('NativeZipArchive');
+5 -1
View File
@@ -19,7 +19,11 @@ const Button: React.FC<ButtonProps> = props => {
[props.children, props.title],
);
return <PaperButton {...props} theme={theme} children={Children} />;
return (
<PaperButton {...props} theme={theme}>
{Children}
</PaperButton>
);
};
export default React.memo(Button);
+1 -1
View File
@@ -1,5 +1,5 @@
import { ThemeColors } from '@theme/types';
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { IconButton } from 'react-native-paper';
-1
View File
@@ -1,5 +1,4 @@
import './mocks';
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Text } from 'react-native';
import Button from '../Button/Button';
@@ -1,5 +1,4 @@
import './mocks';
import React from 'react';
import { render, screen } from '@testing-library/react-native';
import { Text } from 'react-native';
import EmptyView from '../EmptyView';
@@ -1,4 +1,3 @@
import React from 'react';
import { render } from '@testing-library/react-native';
import LoadingMoreIndicator from '../LoadingMoreIndicator/LoadingMoreIndicator';
import { ThemeColors } from '../../theme/types';
+1 -1
View File
@@ -240,7 +240,7 @@ describe('production migrations', () => {
const appliedMigrations = sqlite.executeSync(
'SELECT name FROM __drizzle_migrations ORDER BY created_at',
).rows as Array<{ name: string }>;
).rows as { name: string }[];
expect(appliedMigrations.map(row => row.name)).toEqual([
'20251222152612_past_mandrill',
'20260612232322_normal_saracen',
@@ -15,12 +15,6 @@ import {
import { categorySchema, novelCategorySchema } from '@database/schema';
import { eq, sql } from 'drizzle-orm';
const mockGetLibraryDefaultCategoryId = jest.fn<number | undefined, []>();
jest.mock('@hooks/persisted/useSettings', () => ({
getLibraryDefaultCategoryId: () => mockGetLibraryDefaultCategoryId(),
}));
import {
getAllNovels,
getNovelById,
@@ -37,6 +31,12 @@ import {
updateNovelCategories,
} from '../NovelQueries';
const mockGetLibraryDefaultCategoryId = jest.fn<number | undefined, []>();
jest.mock('@hooks/persisted/useSettings', () => ({
getLibraryDefaultCategoryId: () => mockGetLibraryDefaultCategoryId(),
}));
describe('NovelQueries', () => {
beforeEach(() => {
const testDb = setupTestDatabase();
@@ -346,12 +346,12 @@ describe('StatsQueries', () => {
const result = await getTopNovelsByTimeSpentFromDb();
expect(result.topNovelsByTimeSpent).toHaveLength(2);
expect(result.topNovelsByTimeSpent!![0]).toMatchObject({
expect(result.topNovelsByTimeSpent![0]).toMatchObject({
id: novelId2,
name: 'Novel B',
timeSpent: 500,
});
expect(result.topNovelsByTimeSpent!![1]).toMatchObject({
expect(result.topNovelsByTimeSpent![1]).toMatchObject({
id: novelId1,
name: 'Novel A',
timeSpent: 300,
@@ -370,7 +370,7 @@ describe('StatsQueries', () => {
const result = await getTopNovelsByTimeSpentFromDb();
expect(result.topNovelsByTimeSpent).toHaveLength(1);
expect(result.topNovelsByTimeSpent!![0].id).toBe(libraryNovelId);
expect(result.topNovelsByTimeSpent![0].id).toBe(libraryNovelId);
});
it('should exclude novels with 0 or null time spent', async () => {
@@ -401,7 +401,7 @@ describe('StatsQueries', () => {
const result = await getTopNovelsByTimeSpentFromDb();
expect(result.topNovelsByTimeSpent).toHaveLength(10);
expect(result.topNovelsByTimeSpent!![0].name).toBe('Novel 12');
expect(result.topNovelsByTimeSpent![0].name).toBe('Novel 12');
});
});
@@ -426,12 +426,12 @@ describe('StatsQueries', () => {
const result = await getTopCategoriesByTimeSpentFromDb();
expect(result.topCategoriesByTimeSpent).toHaveLength(2);
expect(result.topCategoriesByTimeSpent!![0]).toMatchObject({
expect(result.topCategoriesByTimeSpent![0]).toMatchObject({
id: scifiCat,
name: 'Sci-Fi',
timeSpent: 300,
});
expect(result.topCategoriesByTimeSpent!![1]).toMatchObject({
expect(result.topCategoriesByTimeSpent![1]).toMatchObject({
id: fantasyCat,
name: 'Fantasy',
timeSpent: 100,
@@ -455,7 +455,7 @@ describe('StatsQueries', () => {
const result = await getTopCategoriesByTimeSpentFromDb();
expect(result.topCategoriesByTimeSpent).toHaveLength(1);
expect(result.topCategoriesByTimeSpent!![0].timeSpent).toBe(350);
expect(result.topCategoriesByTimeSpent![0].timeSpent).toBe(350);
});
it('should return an empty array when no category chapters have spent time', async () => {
+1 -1
View File
@@ -7,7 +7,7 @@
// @ts-ignore
import type { TestDb } from './testDb';
// @ts-ignore
global.__DEV__ ??= false;
const getTestDbModule = () => require('./testDb') as typeof import('./testDb');
@@ -115,6 +115,7 @@ export const useGithubUpdateChecker = (): GithubUpdate => {
};
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
checkForRelease();
}, [checkForRelease]);
+1
View File
@@ -67,6 +67,7 @@ export const useTrackedNovel = (novelId: number | 'NO_ID') => {
}
});
// eslint-disable-next-line react-hooks/set-state-in-effect
setTrackedNovels(loadedNovels);
}, [novelId, migrated, setMigrated]);
+6 -6
View File
@@ -9,12 +9,6 @@ import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import calendar from 'dayjs/plugin/calendar';
import duration from 'dayjs/plugin/duration';
dayjs.extend(customParseFormat);
dayjs.extend(localeData);
dayjs.extend(localizedFormat);
dayjs.extend(relativeTime);
dayjs.extend(calendar);
dayjs.extend(duration);
import 'dayjs/locale/ar';
import 'dayjs/locale/ca';
@@ -85,6 +79,12 @@ import zhTw from './languages/zh_TW/strings.json';
import { StringMap } from './types';
import { showToast } from '@utils/showToast';
dayjs.extend(customParseFormat);
dayjs.extend(localeData);
dayjs.extend(localizedFormat);
dayjs.extend(relativeTime);
dayjs.extend(calendar);
dayjs.extend(duration);
const i18n = new I18n({
af,
+4 -4
View File
@@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import { useRef } from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
@@ -20,13 +20,13 @@ const Stack = createNativeStackNavigator<ReaderStackParamList>();
const ReaderStack = ({ route }) => {
const params = useRef(route?.params);
const theme = useTheme();
// eslint-disable-next-line react-hooks/refs
const routeParams = route?.params ?? params.current;
return (
<NovelContextProvider
route={
(route?.params ?? params.current) as
| NovelScreenProps['route']
| ChapterScreenProps['route']
routeParams as NovelScreenProps['route'] | ChapterScreenProps['route']
}
>
<Stack.Navigator
+1
View File
@@ -190,6 +190,7 @@ export type RespositorySettingsScreenProps = CompositeScreenProps<
declare global {
namespace ReactNavigation {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface RootParamList extends RootStackParamList {}
}
}
@@ -73,6 +73,7 @@ export const useBrowseSource = (
}, []);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
fetchNovels(currentPage, selectedFilters);
}, [fetchNovels, currentPage, selectedFilters]);
@@ -159,6 +160,7 @@ export const useSearchSource = (pluginId: string) => {
useEffect(() => {
if (searchText) {
// eslint-disable-next-line react-hooks/set-state-in-effect
fetchNovels(searchText, currentPage);
}
}, [currentPage, fetchNovels, searchText]);
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { ProgressBar } from 'react-native-paper';
@@ -156,7 +156,7 @@ export const useGlobalSearch = ({
);
const debouncedGlobalSearch = useMemo(
() => debounce(globalSearch, 300),
() => debounce(globalSearch, 300), // eslint-disable-line react-hooks/refs
[globalSearch],
);
+2 -2
View File
@@ -32,7 +32,7 @@ const WebviewScreen = ({ route, navigation }: WebviewScreenProps) => {
const userAgent = useMemo(() => getUserAgent(), []);
const theme = useTheme();
const webViewRef = useRef<WebView | null>(null);
const webViewRef = useRef<WebView<object> | null>(null);
const [progress, setProgress] = useState(0);
const [title, setTitle] = useState(name || '');
@@ -115,7 +115,7 @@ const WebviewScreen = ({ route, navigation }: WebviewScreenProps) => {
progress={Math.round(1000 * progress) / 1000}
visible={progress !== 1}
/>
<WebView
<WebView<object>
userAgent={userAgent}
ref={webViewRef}
source={source}
@@ -12,7 +12,7 @@ interface AppbarProps {
theme: ThemeColors;
canGoBack: boolean;
canGoForward: boolean;
webView: RefObject<WebView | null>;
webView: RefObject<WebView<object> | null>;
setMenuVisible: (value: boolean) => void;
goBack: () => void;
}
@@ -11,7 +11,7 @@ import { showToast } from '@utils/showToast';
interface MenuProps {
theme: ThemeColors;
currentUrl: string;
webView: RefObject<WebView | null>;
webView: RefObject<WebView<object> | null>;
setMenuVisible: (value: boolean) => void;
}
-1
View File
@@ -1,4 +1,3 @@
import React from 'react';
import { StyleSheet, View, FlatList, Text, FlatListProps } from 'react-native';
import { useTheme } from '@hooks/persisted';
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
StyleSheet,
View,
@@ -147,6 +147,7 @@ const BrowseALScreen = ({ navigation }: BrowseALScreenProps) => {
};
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
searchAniList(true);
}, [searchAniList]);
+2 -1
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
StyleSheet,
View,
@@ -70,6 +70,7 @@ const BrowseMalScreen = ({ navigation }: BrowseMalScreenProps) => {
};
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
getNovels();
}, [getNovels]);
@@ -1,6 +1,5 @@
import { PluginItem } from '@plugins/types';
import { ThemeColors } from '@theme/types';
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import { TouchableRipple } from 'react-native-paper';
@@ -1,4 +1,4 @@
import React, { memo, useCallback } from 'react';
import { memo, useCallback } from 'react';
import NovelCover from '@components/NovelCover';
import { NovelInfo } from '@database/types';
import { ThemeColors } from '@theme/types';
@@ -14,7 +14,7 @@ interface LibraryNovelItemProps {
imageRequestInit: ImageRequestInit | undefined;
}
const LibraryNovelItem = memo(function LibraryNovelItem({
const LibraryNovelItem = memo(function LibraryNovelItem_({
item,
theme,
isSelected,
+4 -4
View File
@@ -71,7 +71,7 @@ export const useLibrary = (): UseLibraryReturnType => {
setIsLoading(true);
}
const [_, novels] = await Promise.all([
const [, novels] = await Promise.all([
refreshCategories(),
getLibraryNovelsFromDb(sortOrder, filter, searchText, downloadedOnlyMode),
]);
@@ -117,9 +117,9 @@ export const useLibrary = (): UseLibraryReturnType => {
getLibrary();
});
const [taskQueue] = useMMKVObject<
(BackgroundTask | QueuedBackgroundTask)[]
>(BACKGROUND_TASKS_STORE_KEY);
const [taskQueue] = useMMKVObject<(BackgroundTask | QueuedBackgroundTask)[]>(
BACKGROUND_TASKS_STORE_KEY,
);
const restoreTasksCount = useMemo(
() =>
taskQueue?.filter(t => {
+11 -11
View File
@@ -1,20 +1,21 @@
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import * as Linking from 'expo-linking';
import { getString } from '@i18n/translations';
import { MoreHeader } from './components/MoreHeader';
import { useTheme } from '@hooks/persisted';
import { List, SafeAreaView } from '@components';
import { AboutScreenProps } from '@navigators/types';
import Config from '@env';
import * as Clipboard from 'expo-clipboard';
import { version } from '../../../package.json';
import { getString } from '@i18n/translations';
import { setStringAsync } from 'expo-clipboard';
import { useTheme } from '@hooks/persisted';
import { List, SafeAreaView } from '@components';
import { MoreHeader } from './components/MoreHeader';
import { AboutScreenProps } from '@navigators/types';
import Config from '@env';
/* eslint-disable import/no-named-as-default-member */
const GIT_HASH = Config.GIT_HASH;
const RELEASE_DATE = Config.RELEASE_DATE;
const BUILD_TYPE = Config.BUILD_TYPE;
/* eslint-enable import/no-named-as-default-member */
const AboutScreen = ({ navigation }: AboutScreenProps) => {
const theme = useTheme();
@@ -32,7 +33,6 @@ const AboutScreen = ({ navigation }: AboutScreenProps) => {
return `${BUILD_TYPE} ${version} (${localDateTime}) Commit: ${GIT_HASH}`;
}
}
return (
<SafeAreaView excludeTop>
<MoreHeader
@@ -48,7 +48,7 @@ const AboutScreen = ({ navigation }: AboutScreenProps) => {
description={getBuildName()}
theme={theme}
onPress={() => {
Clipboard.setStringAsync(getBuildName());
setStringAsync(getBuildName());
}}
/>
<List.Item
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import { StyleSheet, View, Pressable, Text, ScrollView } from 'react-native';
import { getString } from '@i18n/translations';
+2 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
import {
FAB,
@@ -33,6 +33,7 @@ const DownloadQueue = ({ navigation }: TaskQueueScreenProps) => {
const closeMenu = () => setVisible(false);
useEffect(() => {
if (taskQueue?.length === 0) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsRunning(false);
}
}, [taskQueue]);
@@ -1,4 +1,3 @@
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Appbar, List } from '@components';
+1 -1
View File
@@ -5,7 +5,7 @@ import useChapter from './hooks/useChapter';
type ChapterContextType = ReturnType<typeof useChapter>['chapterContext'] & {
novel: NovelInfo;
webViewRef: React.RefObject<WebView<{}> | null>;
webViewRef: React.RefObject<WebView<object> | null>;
};
const defaultValue = {} as ChapterContextType;
+3
View File
@@ -157,16 +157,19 @@ export const ChapterContent = ({
);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setBookmarked(chapter.bookmark ?? false);
}, [chapter]);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSearchVisible(false);
resetSearch();
}, [chapter.id, resetSearch]);
useEffect(() => {
if (hidden) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSearchVisible(false);
}
}, [hidden]);
@@ -1,10 +1,4 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { Text } from 'react-native-paper';
import { useAppSettings, useTheme } from '@hooks/persisted';
+20 -19
View File
@@ -282,8 +282,10 @@ const WebViewReader: React.FC<WebViewReaderProps> = ({
* page, so nothing that changes while a chapter is on screen may be part of
* it – those updates go through `injectJavaScript` instead.
*/
const source = useMemo(
() => ({
const source = useMemo(() => {
// eslint-disable-next-line react-hooks/refs
const isNextChapterScreenVisible = nextChapterScreenVisible.current;
return {
baseUrl: !chapter.isDownloaded ? plugin?.site : undefined,
headers: plugin?.imageRequestInit?.headers,
method: plugin?.imageRequestInit?.method,
@@ -345,7 +347,7 @@ const WebViewReader: React.FC<WebViewReaderProps> = ({
chapterGeneralSettings.pageReader ? 'page-reader' : ''
}">
<div class="transition-chapter" style="transform: ${
nextChapterScreenVisible.current
isNextChapterScreenVisible
? 'translateX(-100%)'
: 'translateX(0%)'
};
@@ -358,7 +360,7 @@ const WebViewReader: React.FC<WebViewReaderProps> = ({
</body>
<script>
var initialPageReaderConfig = ${JSON.stringify({
nextChapterScreenVisible: nextChapterScreenVisible.current,
nextChapterScreenVisible: isNextChapterScreenVisible,
})};
@@ -392,21 +394,20 @@ const WebViewReader: React.FC<WebViewReaderProps> = ({
</script>
</html>
`,
}),
[
batteryLevel,
chapter,
chapterGeneralSettings,
html,
initialReaderSettings,
novel,
plugin,
pluginCustomCSS,
pluginCustomJS,
readerDir,
theme,
],
);
};
}, [
batteryLevel,
chapter,
chapterGeneralSettings,
html,
initialReaderSettings,
novel,
plugin,
pluginCustomCSS,
pluginCustomJS,
readerDir,
theme,
]);
return (
<WebView
+15 -5
View File
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';
/**
* Starts tracking time spent on the chapter and returns a callback to be called on user interaction (to prevent the inactivity timeout from being triggered).
*/
@@ -12,20 +11,28 @@ export default function useTimeTracking(
turnedOn: boolean,
increaseTimeSpent: (chapterId: number, elapsed: number) => void,
) {
const lastUserInteractionRef = useRef(Date.now());
const lastUserInteractionRef = useRef(0);
const isTTSReadingRef = useRef(false);
/** Increases the time spent on the current chapter since the last user interaction, as long as it was not too long ago */
const flushElapsedTime = useCallback(() => {
const enabled = turnedOn && !incognitoMode;
const isInactive = inactivityTimeoutMs !== undefined && Date.now() - lastUserInteractionRef.current > inactivityTimeoutMs;
const isInactive =
inactivityTimeoutMs !== undefined &&
Date.now() - lastUserInteractionRef.current > inactivityTimeoutMs;
if (enabled && (!isInactive || isTTSReadingRef.current)) {
const elapsed = Date.now() - lastUserInteractionRef.current;
if (elapsed > 0) {
increaseTimeSpent(chapterId, elapsed);
}
}
}, [chapterId, incognitoMode, inactivityTimeoutMs, turnedOn, increaseTimeSpent]);
}, [
chapterId,
incognitoMode,
inactivityTimeoutMs,
turnedOn,
increaseTimeSpent,
]);
const onUserInteraction = useCallback(() => {
flushElapsedTime();
@@ -40,7 +47,10 @@ export default function useTimeTracking(
// Save the elapsed time when the app goes in the background
flushElapsedTime();
lastUserInteractionRef.current = Date.now();
} else if (appState.match(/inactive|background/) && nextState === 'active') {
} else if (
appState.match(/inactive|background/) &&
nextState === 'active'
) {
// Reset the start timestamp when the app comes back and the TTS isn't on
if (!isTTSReadingRef.current) {
lastUserInteractionRef.current = Date.now();
+1 -1
View File
@@ -20,7 +20,7 @@ const initialProgress: TtsProgress = {
export const useTtsSession = () => {
const sessionRef = useRef<TtsSession | null>(null);
const sessionPromiseRef = useRef<Promise<TtsSession> | null>(null);
const subscriptionsRef = useRef<Array<{ remove(): void }>>([]);
const subscriptionsRef = useRef<{ remove(): void }[]>([]);
const mountedRef = useRef(true);
const [state, setState] = useState<TtsPlaybackState>('idle');
const [progress, setProgress] = useState<TtsProgress>(initialProgress);
@@ -241,6 +241,7 @@ export default function GoogleDriveModal({
if (isSignedIn) {
const localUser = GoogleSignin.getCurrentUser();
if (localUser) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setUser(localUser);
setBackupModal(BackupModal.AUTHORIZED);
}
@@ -1,4 +1,3 @@
import React from 'react';
import { useTheme } from '@hooks/persisted';
import { Appbar, List, SafeAreaView } from '@components';
import { useBoolean } from '@hooks';
@@ -52,6 +52,7 @@ const DownloadCooldownModal: React.FC<DownloadCooldownModalProps> = ({
useEffect(() => {
if (visible) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setDraft(msToSeconds(currentMs));
}
}, [visible, currentMs]);
@@ -88,7 +88,7 @@ const toNativeTtsSettings = (
const SettingsReaderScreen = () => {
const theme = useTheme();
const navigation = useNavigation();
const webViewRef = useRef<WebView>(null);
const webViewRef = useRef<WebView<object>>(null);
const bottomSheetRef = useRef<BottomSheetModalMethods>(null);
const { bottom, right } = useSafeAreaInsets();
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
@@ -171,7 +171,7 @@ const SettingsReaderScreen = () => {
--theme-outline: ${theme.outline};
--theme-rippleColor: ${theme.rippleColor};
}
@font-face {
font-family: ${readerSettings.fontFamily};
src: url("file:///android_asset/fonts/${
@@ -273,7 +273,7 @@ const SettingsReaderScreen = () => {
{/* Large Preview Area */}
<View style={styles.previewContainer}>
<WebView
<WebView<object>
ref={webViewRef}
originWhitelist={['*']}
allowFileAccess={true}
@@ -359,7 +359,7 @@ const SettingsReaderScreen = () => {
</head>
<body class="${
chapterGeneralSettings.pageReader ? 'page-reader' : ''
}">
}">
<div id="LNReader-chapter">
${dummyHTML}
</div>
@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Clipboard from 'expo-clipboard';
import * as Linking from 'expo-linking';
@@ -165,6 +165,8 @@ const TrackerScreen = ({ navigation }: TrackerSettingsScreenProps) => {
[theme, isTrackerAuthenticated],
);
// eslint-disable-next-line react-hooks/purity
const now = Date.now();
return (
<SafeAreaView excludeTop>
<Provider>
@@ -256,12 +258,10 @@ const TrackerScreen = ({ navigation }: TrackerSettingsScreenProps) => {
/>
{(isTrackerAuthenticated('MyAnimeList') &&
getTrackerAuth('MyAnimeList')?.auth?.expiresAt &&
getTrackerAuth('MyAnimeList')!.auth.expiresAt <
new Date(Date.now())) ||
getTrackerAuth('MyAnimeList')!.auth.expiresAt < new Date(now)) ||
(isTrackerAuthenticated('Kitsu') &&
getTrackerAuth('Kitsu')?.auth?.expiresAt &&
getTrackerAuth('Kitsu')!.auth.expiresAt <
new Date(Date.now())) ? (
getTrackerAuth('Kitsu')!.auth.expiresAt < new Date(now)) ? (
<>
<List.SubHeader theme={theme}>
{getString('common.settings')}
@@ -269,7 +269,7 @@ const TrackerScreen = ({ navigation }: TrackerSettingsScreenProps) => {
{isTrackerAuthenticated('MyAnimeList') &&
getTrackerAuth('MyAnimeList')?.auth?.expiresAt &&
getTrackerAuth('MyAnimeList')!.auth.expiresAt <
new Date(Date.now()) && (
new Date(now) && (
<List.Item
title={
getString('trackingScreen.revalidate') + ' MyAnimeList'
@@ -288,8 +288,7 @@ const TrackerScreen = ({ navigation }: TrackerSettingsScreenProps) => {
)}
{isTrackerAuthenticated('Kitsu') &&
getTrackerAuth('Kitsu')?.auth?.expiresAt &&
getTrackerAuth('Kitsu')!.auth.expiresAt <
new Date(Date.now()) && (
getTrackerAuth('Kitsu')!.auth.expiresAt < new Date(now) && (
<List.Item
title={getString('trackingScreen.revalidate') + ' Kitsu'}
onPress={async () => {
+2 -2
View File
@@ -1,10 +1,10 @@
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
import Config from '@env';
import { ANILIST_CLIENT_ID } from '@env';
import { AuthenticationResult, Tracker } from './index';
const apiEndpoint = 'https://graphql.anilist.co';
const clientId = Config.ANILIST_CLIENT_ID;
const clientId = ANILIST_CLIENT_ID;
const redirectUri = Linking.createURL('tracker/AL');
+2 -2
View File
@@ -1,6 +1,6 @@
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
import Config from '@env';
import { MYANIMELIST_CLIENT_ID } from '@env';
import {
AuthenticationResult,
Tracker,
@@ -8,7 +8,7 @@ import {
UserListStatus,
} from './index';
const clientId = Config.MYANIMELIST_CLIENT_ID;
const clientId = MYANIMELIST_CLIENT_ID;
const baseOAuthUrl = 'https://myanimelist.net/v1/oauth2/authorize';
const tokenUrl = 'https://myanimelist.net/v1/oauth2/token';
const baseApiUrl = 'https://api.myanimelist.net/v2';