fix: Resolve Type and Lint Issues
This commit is contained in:
committed by
Rajarshee Chatterjee
parent
345d084eba
commit
72bfcad034
+34
-33
@@ -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');
|
||||
|
||||
@@ -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,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,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';
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
|
||||
@@ -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,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]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user