ui: fix os platform detection for iPad and layout tweaks for iPad (#416)

This commit is contained in:
Huang Xin
2025-02-21 01:11:33 +01:00
committed by GitHub
parent 66866372ea
commit 3ada880155
17 changed files with 41 additions and 33 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@readest/readest-app",
"version": "0.9.14",
"version": "0.9.15",
"private": true,
"scripts": {
"dev": "dotenv -e .env.tauri -- next dev",
+5 -7
View File
@@ -21,7 +21,6 @@ import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oauth';
import { openUrl } from '@tauri-apps/plugin-opener';
import { handleAuthCallback } from '@/helpers/auth';
import { getOSPlatform } from '@/utils/misc';
import { getAppleIdAuth, Scope } from './utils/appleIdAuth';
type OAuthProvider = 'google' | 'apple' | 'azure' | 'github';
@@ -66,14 +65,13 @@ export default function AuthPage() {
const [port, setPort] = useState<number | null>(null);
const isOAuthServerRunning = useRef(false);
const [isMounted, setIsMounted] = useState(false);
const osPlatform = getOSPlatform();
const getTauriRedirectTo = () => {
return process.env.NODE_ENV === 'production'
? ['android', 'ios'].includes(osPlatform)
return process.env.NODE_ENV === 'production' || appService?.isMobile
? appService?.isMobile
? WEB_AUTH_CALLBACK
: DEEPLINK_CALLBACK
: `http://localhost:${port}`;
: `http://localhost:${port}`; // only for development env on Desktop
};
const tauriSignInApple = async () => {
@@ -133,7 +131,7 @@ export default function AuthPage() {
const startTauriOAuth = async () => {
try {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production' || appService?.isMobile) {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
const currentWindow = getCurrentWindow();
currentWindow.listen('single-instance', ({ event, payload }) => {
@@ -310,7 +308,7 @@ export default function AuthPage() {
/>
<ProviderLogin
provider='apple'
handleSignIn={getOSPlatform() === 'ios' ? tauriSignInApple : tauriSignIn}
handleSignIn={appService?.isIOSApp ? tauriSignInApple : tauriSignIn}
Icon={FaApple}
label={_('Sign in with Apple')}
/>
@@ -114,14 +114,16 @@ const Bookshelf: React.FC<BookshelfProps> = ({
};
const confirmDelete = async () => {
for (const id of selectedBooks) {
const book = libraryBooks.find((b) => b.hash === id || b.groupId === id);
if (book) {
await handleBookDelete(book);
selectedBooks.forEach((id) => {
for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) {
if (book && !book.deletedAt) {
handleBookDelete(book);
}
}
}
});
setSelectedBooks([]);
setShowDeleteAlert(false);
setShowSelectModeActions(true);
};
const deleteSelectedBooks = () => {
+2 -3
View File
@@ -323,8 +323,7 @@ const LibraryPage = () => {
let files;
if (isTauriAppPlatform()) {
const { type } = await import('@tauri-apps/plugin-os');
if (['android', 'ios'].includes(type())) {
if (appService?.isMobile) {
files = (await selectFilesWeb()) as [File];
} else {
files = (await selectFilesTauri()) as [string];
@@ -382,7 +381,7 @@ const LibraryPage = () => {
</div>
)}
{libraryLoaded &&
(libraryBooks.length > 0 ? (
(libraryBooks.some((book) => !book.deletedAt) ? (
<div
ref={containerRef}
className={clsx(
@@ -31,7 +31,7 @@ import DeepLPopup from './DeepLPopup';
const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const _ = useTranslation();
const { envConfig } = useEnv();
const { envConfig, appService } = useEnv();
const { settings } = useSettingsStore();
const { getConfig, saveConfig, getBookData, updateBooknotes } = useBookDataStore();
const { getProgress, getView, getViewsById, getViewSettings } = useReaderStore();
@@ -143,7 +143,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
// Disable the default context menu on mobile devices,
// although it should but doesn't work on iOS
if (osPlatform === 'android' || osPlatform === 'ios') {
if (appService?.isMobile) {
detail.doc?.addEventListener('contextmenu', (event: Event) => {
event.preventDefault();
event.stopPropagation();
@@ -21,7 +21,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
return (
<div
tabIndex={0}
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 w-44 border shadow-2xl'
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 border shadow-2xl'
>
<button
className='hover:bg-base-200 text-base-content flex w-full items-center justify-between rounded-md p-2'
@@ -37,7 +37,7 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
isFontLayoutSettingsGlobal ? _('Apply to All Books') : _('Apply to This Book')
}
>
<span className='ml-2'>{_('Global Settings')}</span>
<span className='ml-2 whitespace-nowrap'>{_('Global Settings')}</span>
</div>
</div>
</button>
@@ -135,7 +135,10 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
>
<div className='relative p-1'>
<textarea
className='textarea textarea-ghost h-48 w-full border-0 p-3 text-base !outline-none sm:text-sm'
className={clsx(
'textarea textarea-ghost h-48 w-full border-0 p-3 text-base !outline-none sm:text-sm',
'placeholder:text-base-content/70',
)}
placeholder={_('Enter your custom CSS here...')}
spellCheck='false'
value={draftStylesheet}
@@ -64,7 +64,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
isOpen={true}
onClose={handleClose}
className='modal-open'
boxClassName='sm:w-1/2 sm:min-w-[480px]'
boxClassName='sm:w-1/2 sm:min-w-[480px] sm:h-[65%]'
header={
<div className='flex w-full items-center justify-between'>
<button
@@ -94,7 +94,7 @@ const TTSControl = () => {
setShowIndicator(true);
try {
if (getOSPlatform() === 'ios') {
if (getOSPlatform() === 'ios' || appService?.isIOSApp) {
unblockAudio();
}
const ttsController = new TTSController(view);
@@ -1,16 +1,17 @@
import { useEffect } from 'react';
import { FoliateView } from '@/types/view';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import { tauriGetWindowLogicalPosition } from '@/utils/window';
import { getOSPlatform } from '@/utils/misc';
export const useClickEvent = (
bookKey: string,
viewRef: React.MutableRefObject<FoliateView | null>,
containerRef: React.RefObject<HTMLDivElement>,
) => {
const { appService } = useEnv();
const { getViewSettings } = useReaderStore();
const { hoveredBookKey, setHoveredBookKey } = useReaderStore();
const handleTurnPage = async (
@@ -27,7 +28,7 @@ export const useClickEvent = (
let windowStartX;
// Currently for tauri APP the window.screenX is always 0
if (isTauriAppPlatform()) {
if (['android', 'ios'].includes(getOSPlatform())) {
if (appService?.isMobile) {
windowStartX = 0;
} else {
const windowPosition = await tauriGetWindowLogicalPosition();
@@ -33,7 +33,7 @@ export const AboutWindow = () => {
isOpen={false}
title={_('About Readest')}
onClose={() => setAboutDialogVisible(false)}
boxClassName='sm:!w-96'
boxClassName='sm:!w-96 sm:h-auto'
>
<div className='about-content flex h-full flex-col items-center justify-center'>
<div className='flex flex-col items-center px-8'>
@@ -73,7 +73,7 @@ export const AboutWindow = () => {
. You are free to use, modify, and distribute this software under the terms of the
AGPL v3 license. Please see the license for more details.
</p>
<p className='text-neutral-content mt-2 text-xs'>
<p className='text-neutral-content my-2 text-xs'>
Source code is available at{' '}
<a
href='https://github.com/readest/readest'
@@ -83,7 +83,7 @@ const BookDetailModal = ({ book, isOpen, onClose }: BookDetailModalProps) => {
isOpen={isOpen}
onClose={handleClose}
className='!bg-[rgba(0,0,0,0.5)]'
boxClassName='sm:min-w-[480px]'
boxClassName='sm:min-w-[480px] sm:h-auto'
contentClassName='!px-6 !py-2'
>
<div className='flex w-full select-text items-center justify-center'>
+3 -3
View File
@@ -54,9 +54,9 @@ const Dialog: React.FC<DialogProps> = ({
>
<div
className={clsx(
'modal-box settings-content flex h-full max-h-full w-full max-w-full flex-col rounded-none p-0 sm:rounded-2xl',
'sm:h-[60%] sm:w-[65%] sm:max-w-[600px]',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
'modal-box settings-content flex flex-col rounded-none p-0 sm:rounded-2xl',
'h-full max-h-full w-full max-w-full sm:w-[65%] sm:max-w-[600px]',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)] sm:pt-0',
boxClassName,
)}
>
+1 -1
View File
@@ -40,11 +40,11 @@ import { BOOK_FILE_NOT_FOUND_ERROR } from './errors';
export abstract class BaseAppService implements AppService {
osPlatform: string = getOSPlatform();
isMobile: boolean = ['android', 'ios'].includes(getOSPlatform());
localBooksDir: string = '';
abstract fs: FileSystem;
abstract appPlatform: AppPlatform;
abstract isAppDataSandbox: boolean;
abstract isMobile: boolean;
abstract isAndroidApp: boolean;
abstract isIOSApp: boolean;
abstract hasTrafficLight: boolean;
@@ -118,6 +118,7 @@ export class NativeAppService extends BaseAppService {
fs = nativeFileSystem;
appPlatform = 'tauri' as AppPlatform;
isAppDataSandbox = ['android', 'ios'].includes(OS_TYPE);
isMobile = ['android', 'ios'].includes(OS_TYPE);
isAndroidApp = OS_TYPE === 'android';
isIOSApp = OS_TYPE === 'ios';
hasTrafficLight = OS_TYPE === 'macos';
@@ -1,7 +1,7 @@
import { Book } from '@/types/book';
import { ToastType, FileSystem, BaseDir, AppPlatform } from '@/types/system';
import { getCoverFilename } from '@/utils/book';
import { isValidURL } from '@/utils/misc';
import { getOSPlatform, isValidURL } from '@/utils/misc';
import { isPWA } from './environment';
import { BaseAppService } from './appService';
@@ -180,6 +180,7 @@ export class WebAppService extends BaseAppService {
fs = indexedDBFileSystem;
appPlatform = 'web' as AppPlatform;
isAppDataSandbox = false;
isMobile = ['android', 'ios'].includes(getOSPlatform());
isAndroidApp = false;
isIOSApp = false;
hasTrafficLight = false;
+3
View File
@@ -44,6 +44,9 @@ export const getUserLocale = (lang: string): string | undefined => {
return filteredLocales.length > 0 ? filteredLocales[0] : undefined;
};
// Note that iPad may have a user agent string like a desktop browser
// when possible please use appService.isIOSApp || getOSPlatform() === 'ios'
// to check if the app is running on iOS
export const getOSPlatform = () => {
const userAgent = navigator.userAgent.toLowerCase();