From d474ec60ca86e791c1f6f1d0d37bef7a8942b131 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Thu, 26 Sep 2024 17:06:27 +0300 Subject: [PATCH 1/4] refactor: logic unite --- src/common/Platform/Platform.tsx | 66 ++++++++++++++++++++++++++------ src/common/platform.js | 36 ----------------- src/routes/Settings/Settings.js | 5 +-- 3 files changed, 57 insertions(+), 50 deletions(-) delete mode 100644 src/common/platform.js diff --git a/src/common/Platform/Platform.tsx b/src/common/Platform/Platform.tsx index 13e8592ad..2dee98bff 100644 --- a/src/common/Platform/Platform.tsx +++ b/src/common/Platform/Platform.tsx @@ -1,26 +1,70 @@ import React, { createContext, useContext } from 'react'; -import useShell from './useShell'; import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS'; +import useShell from './useShell'; +import Bowser from 'bowser'; interface PlatformContext { - openExternal: (url: string) => void, -}; + name: string; + isMobile: () => boolean; + openExternal: (url: string) => void; +} const PlatformContext = createContext(null); type Props = { - children: JSX.Element, + children: JSX.Element; }; const PlatformProvider = ({ children }: Props) => { const shell = useShell(); + // this detects ipad properly in safari + // while bowser does not + const iOS = () => { + return ( + [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod', + ].includes(navigator.platform) || + (navigator.userAgent.includes('Mac') && 'ontouchend' in document) + ); + }; + + // Edge case: iPad is included in this function + // Keep in mind maxTouchPoints for Vision Pro might change in the future + const isVisionProUser = () => { + const isMacintosh = navigator.userAgent.includes('Macintosh'); + const hasFiveTouchPoints = navigator.maxTouchPoints === 5; + return isMacintosh && hasFiveTouchPoints; + }; + + const browser = Bowser.getParser(window.navigator?.userAgent || ''); + const osName = browser.getOSName().toLowerCase(); + + const name = isVisionProUser() + ? 'visionos' + : iOS() + ? 'ios' + : osName || 'unknown'; + + const isMobile = () => { + return name === 'ios' || name === 'android'; + }; + const openExternal = (url: string) => { try { const { hostname } = new URL(url); - const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => hostname.endsWith(host)); - const finalUrl = !isWhitelisted ? 'https://www.stremio.com/warning#' + encodeURIComponent(url) : url; - + const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => + hostname.endsWith(host) + ); + const finalUrl = !isWhitelisted + ? 'https://www.stremio.com/warning#' + encodeURIComponent(url) + : url; + if (shell.active) { shell.send('open-external', finalUrl); } else { @@ -28,11 +72,11 @@ const PlatformProvider = ({ children }: Props) => { } } catch (e) { console.error('Failed to parse external url:', e); - } + } }; return ( - + {children} ); @@ -42,7 +86,7 @@ const usePlatform = () => { return useContext(PlatformContext); }; -export { +export { PlatformProvider, - usePlatform, + usePlatform }; diff --git a/src/common/platform.js b/src/common/platform.js deleted file mode 100644 index 7e423489a..000000000 --- a/src/common/platform.js +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2017-2024 Smart code 203358507 - -// this detects ipad properly in safari -// while bowser does not -const iOS = () => { - return [ - 'iPad Simulator', - 'iPhone Simulator', - 'iPod Simulator', - 'iPad', - 'iPhone', - 'iPod' - ].includes(navigator.platform) - || (navigator.userAgent.includes('Mac') && 'ontouchend' in document); -}; - -const Bowser = require('bowser'); - -const browser = Bowser.parse(window.navigator?.userAgent || ''); - -// Edge case: iPad is included in this function -// Keep in mind maxTouchPoints for Vision Pro might change in the future -const isVisionProUser = () => { - const isMacintosh = navigator.userAgent.includes('Macintosh'); - const hasFiveTouchPoints = navigator.maxTouchPoints === 5; - return isMacintosh && hasFiveTouchPoints; -}; - -const name = isVisionProUser() ? 'visionos' : (iOS() ? 'ios' : (browser?.os?.name || 'unknown').toLowerCase()); - -module.exports = { - name, - isMobile: () => { - return name === 'ios' || name === 'android'; - } -}; diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 3e0c4cf8e..0d9199225 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -11,7 +11,6 @@ const { Button, Checkbox, MainNavBars, Multiselect, ColorInput, TextInput, Modal const useProfileSettingsInputs = require('./useProfileSettingsInputs'); const useStreamingServerSettingsInputs = require('./useStreamingServerSettingsInputs'); const useDataExport = require('./useDataExport'); -const { name: platformName } = require('stremio/common/platform'); const styles = require('./styles'); const GENERAL_SECTION = 'general'; @@ -104,12 +103,12 @@ const Settings = () => { } }, [isTraktAuthenticated, profile.auth]); const subscribeCalendarOnClick = React.useCallback(() => { - const protocol = platformName === 'ios' ? 'webcal' : 'https'; + const protocol = platform.name === 'ios' ? 'webcal' : 'https'; const url = `${protocol}://www.strem.io/calendar/${profile.auth.user._id}.ics`; platform.openExternal(url); toast.show({ type: 'success', - title: platformName === 'ios' ? t('SETTINGS_SUBSCRIBE_CALENDAR_IOS_TOAST') : t('SETTINGS_SUBSCRIBE_CALENDAR_TOAST'), + title: platform.name === 'ios' ? t('SETTINGS_SUBSCRIBE_CALENDAR_IOS_TOAST') : t('SETTINGS_SUBSCRIBE_CALENDAR_TOAST'), timeout: 25000 }); // Stremio 4 emits not documented event subscribeCalendar From ff662d087282bf6665885e4daaa4535a4b52b369 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 27 Sep 2024 09:05:02 +0200 Subject: [PATCH 2/4] refactor: platform device logic --- src/common/Platform/Platform.tsx | 49 +++----------------------------- src/common/Platform/device.ts | 31 ++++++++++++++++++++ src/common/index.js | 2 -- 3 files changed, 35 insertions(+), 47 deletions(-) create mode 100644 src/common/Platform/device.ts diff --git a/src/common/Platform/Platform.tsx b/src/common/Platform/Platform.tsx index 2dee98bff..69ff3e8c3 100644 --- a/src/common/Platform/Platform.tsx +++ b/src/common/Platform/Platform.tsx @@ -1,11 +1,11 @@ import React, { createContext, useContext } from 'react'; import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS'; import useShell from './useShell'; -import Bowser from 'bowser'; +import { name, isMobile } from './device'; interface PlatformContext { name: string; - isMobile: () => boolean; + isMobile: boolean; openExternal: (url: string) => void; } @@ -18,52 +18,11 @@ type Props = { const PlatformProvider = ({ children }: Props) => { const shell = useShell(); - // this detects ipad properly in safari - // while bowser does not - const iOS = () => { - return ( - [ - 'iPad Simulator', - 'iPhone Simulator', - 'iPod Simulator', - 'iPad', - 'iPhone', - 'iPod', - ].includes(navigator.platform) || - (navigator.userAgent.includes('Mac') && 'ontouchend' in document) - ); - }; - - // Edge case: iPad is included in this function - // Keep in mind maxTouchPoints for Vision Pro might change in the future - const isVisionProUser = () => { - const isMacintosh = navigator.userAgent.includes('Macintosh'); - const hasFiveTouchPoints = navigator.maxTouchPoints === 5; - return isMacintosh && hasFiveTouchPoints; - }; - - const browser = Bowser.getParser(window.navigator?.userAgent || ''); - const osName = browser.getOSName().toLowerCase(); - - const name = isVisionProUser() - ? 'visionos' - : iOS() - ? 'ios' - : osName || 'unknown'; - - const isMobile = () => { - return name === 'ios' || name === 'android'; - }; - const openExternal = (url: string) => { try { const { hostname } = new URL(url); - const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => - hostname.endsWith(host) - ); - const finalUrl = !isWhitelisted - ? 'https://www.stremio.com/warning#' + encodeURIComponent(url) - : url; + const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => hostname.endsWith(host)); + const finalUrl = !isWhitelisted ? `https://www.stremio.com/warning#${encodeURIComponent(url)}` : url; if (shell.active) { shell.send('open-external', finalUrl); diff --git a/src/common/Platform/device.ts b/src/common/Platform/device.ts new file mode 100644 index 000000000..68ca72b32 --- /dev/null +++ b/src/common/Platform/device.ts @@ -0,0 +1,31 @@ +import Bowser from 'bowser'; + +const APPLE_MOBILE_DEVICES = [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod', +]; + +const { userAgent, platform, maxTouchPoints } = globalThis.navigator; + +// this detects ipad properly in safari +// while bowser does not +const isIOS = APPLE_MOBILE_DEVICES.includes(platform) || (userAgent.includes('Mac') && 'ontouchend' in document); + +// Edge case: iPad is included in this function +// Keep in mind maxTouchPoints for Vision Pro might change in the future +const isVisionOS = userAgent.includes('Macintosh') || maxTouchPoints === 5; + +const bowser = Bowser.getParser(userAgent); +const os = bowser.getOSName().toLowerCase(); + +const name = isVisionOS ? 'visionos' : isIOS ? 'ios' : os || 'unknown'; +const isMobile = ['ios', 'android'].includes(name); + +export { + name, + isMobile, +}; \ No newline at end of file diff --git a/src/common/index.js b/src/common/index.js index 1188b8c61..5adef3e60 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -46,7 +46,6 @@ const useProfile = require('./useProfile'); const useStreamingServer = require('./useStreamingServer'); const useTorrent = require('./useTorrent'); const useTranslate = require('./useTranslate'); -const platform = require('./platform'); const EventModal = require('./EventModal'); module.exports = { @@ -101,6 +100,5 @@ module.exports = { useStreamingServer, useTorrent, useTranslate, - platform, EventModal, }; From 3c517f6a32a2b29e8b789e23771378916096cc45 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 27 Sep 2024 10:59:48 +0200 Subject: [PATCH 3/4] fix: replace platform by usePlatform --- src/routes/MetaDetails/StreamsList/Stream/Stream.js | 3 ++- src/routes/Settings/useProfileSettingsInputs.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/Stream/Stream.js b/src/routes/MetaDetails/StreamsList/Stream/Stream.js index 40ccb2f07..e4829ece0 100644 --- a/src/routes/MetaDetails/StreamsList/Stream/Stream.js +++ b/src/routes/MetaDetails/StreamsList/Stream/Stream.js @@ -5,7 +5,7 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); const { t } = require('i18next'); -const { Button, Image, useProfile, platform, useToast, Popup, useBinaryState } = require('stremio/common'); +const { Button, Image, useProfile, usePlatform, useToast, Popup, useBinaryState } = require('stremio/common'); const { useServices } = require('stremio/services'); const { useRouteFocused } = require('stremio-router'); const StreamPlaceholder = require('./StreamPlaceholder'); @@ -14,6 +14,7 @@ const styles = require('./styles'); const Stream = ({ className, videoId, videoReleased, addonName, name, description, thumbnail, progress, deepLinks, ...props }) => { const profile = useProfile(); const toast = useToast(); + const platform = usePlatform(); const { core } = useServices(); const routeFocused = useRouteFocused(); diff --git a/src/routes/Settings/useProfileSettingsInputs.js b/src/routes/Settings/useProfileSettingsInputs.js index 939e5e7cf..a90d0d483 100644 --- a/src/routes/Settings/useProfileSettingsInputs.js +++ b/src/routes/Settings/useProfileSettingsInputs.js @@ -3,11 +3,12 @@ const React = require('react'); const { useTranslation } = require('react-i18next'); const { useServices } = require('stremio/services'); -const { CONSTANTS, interfaceLanguages, languageNames, platform } = require('stremio/common'); +const { CONSTANTS, usePlatform, interfaceLanguages, languageNames } = require('stremio/common'); const useProfileSettingsInputs = (profile) => { const { t } = useTranslation(); const { core } = useServices(); + const platform = usePlatform(); // TODO combine those useMemo in one const interfaceLanguageSelect = React.useMemo(() => ({ options: interfaceLanguages.map(({ name, codes }) => ({ From 0a2ad7b6aa4331f20ae48b8c82f43b60405366d9 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 27 Sep 2024 11:20:57 +0200 Subject: [PATCH 4/4] fix(Settings): check for profile.auth in subscribeCalendarOnClick --- src/routes/Settings/Settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 0d9199225..ed4f45e16 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -103,6 +103,8 @@ const Settings = () => { } }, [isTraktAuthenticated, profile.auth]); const subscribeCalendarOnClick = React.useCallback(() => { + if (!profile.auth) return; + const protocol = platform.name === 'ios' ? 'webcal' : 'https'; const url = `${protocol}://www.strem.io/calendar/${profile.auth.user._id}.ics`; platform.openExternal(url); @@ -112,7 +114,7 @@ const Settings = () => { timeout: 25000 }); // Stremio 4 emits not documented event subscribeCalendar - }, [profile.auth.user._id]); + }, [profile.auth]); const exportDataOnClick = React.useCallback(() => { loadDataExport(); }, []);