ui: fix os platform detection for iPad and layout tweaks for iPad (#416)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user