diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association new file mode 100644 index 000000000..54b0dd1bf --- /dev/null +++ b/.well-known/apple-app-site-association @@ -0,0 +1,67 @@ +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "9EWRZ4QP3J.com.stremio.one", + "paths": [ + "/", + "/#/player/*", + "/#/discover/*", + "/#/detail/*", + "/#/library/*", + "/#/addons/*", + "/#/settings", + "/#/search/*" + ], + "components": [ + { + "/": "/", + "#": "/player/*", + "comment": "Matches deep link for player" + }, + { + "/": "/", + "#": "/discover/*", + "comment": "Matches deep link for discover" + }, + { + "/": "/", + "#": "/detail/*", + "comment": "Matches deep link for detail" + }, + { + "/": "/", + "#": "/library/*", + "comment": "Matches deep link for library" + }, + { + "/": "/", + "#": "/addons/*", + "comment": "Matches deep link for addons" + }, + { + "/": "/", + "#": "/settings", + "comment": "Matches deep link for settings" + }, + { + "/": "/", + "#": "/search/*", + "comment": "Matches deep link for search" + } + ] + } + ] + }, + "activitycontinuation": { + "apps": [ + "9EWRZ4QP3J.com.stremio.one" + ] + }, + "webcredentials": { + "apps": [ + "9EWRZ4QP3J.com.stremio.one" + ] + } +} diff --git a/package-lock.json b/package-lock.json index 0824f882f..3fca2228f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@babel/runtime": "7.26.0", "@sentry/browser": "8.42.0", "@stremio/stremio-colors": "5.2.0", - "@stremio/stremio-core-web": "0.49.0", + "@stremio/stremio-core-web": "0.49.2", "@stremio/stremio-icons": "5.4.1", "@stremio/stremio-video": "0.0.53", "a-color-picker": "1.2.1", @@ -36,7 +36,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#62bcc6e8f44258203c7375af59210771efb6f634", + "stremio-translations": "github:Stremio/stremio-translations#4bb1b7e31df274f538b8588c2a2b360d6e14ab27", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -3371,9 +3371,9 @@ "integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==" }, "node_modules/@stremio/stremio-core-web": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.0.tgz", - "integrity": "sha512-oxJRVAE6z6Eh1B0qomdz6L2CVaTkwt70kDNC1TmHyGNo+Hhp2RaMlygqBKvBLXyHUXi82R67Mc11gT/JqlmaMw==", + "version": "0.49.2", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.2.tgz", + "integrity": "sha512-IYU+pdHkq4iEfqZ9G+DFZheIE53nY8XyhI1OJLvZp68/4ntRwssXwfj9InHK2Wau20fH+oV2KD1ZWb0CsTLqPA==", "license": "MIT", "dependencies": { "@babel/runtime": "7.24.1" @@ -13372,9 +13372,9 @@ } }, "node_modules/stremio-translations": { - "version": "1.44.9", - "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#62bcc6e8f44258203c7375af59210771efb6f634", - "integrity": "sha512-8Sc5Qvd4IiObwGXkmj1XFXFavUc15My5po6G48HHDBbp42SVc5I/t7h+1yxW1A81byyBCXbL23a9iU9v49vpQA==", + "version": "1.44.10", + "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#4bb1b7e31df274f538b8588c2a2b360d6e14ab27", + "integrity": "sha512-+RLkoytMyqP90mn9Wkh1MhwB2fxVuvMxsxxceGnFgYlyyEL8fxuHTRnSaBjWBw+xFtsaeMLmDfA1n3l+UEzg4A==", "license": "MIT" }, "node_modules/string_decoder": { diff --git a/package.json b/package.json index 4cab1781f..afed7e885 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@babel/runtime": "7.26.0", "@sentry/browser": "8.42.0", "@stremio/stremio-colors": "5.2.0", - "@stremio/stremio-core-web": "0.49.0", + "@stremio/stremio-core-web": "0.49.2", "@stremio/stremio-icons": "5.4.1", "@stremio/stremio-video": "0.0.53", "a-color-picker": "1.2.1", @@ -40,7 +40,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#62bcc6e8f44258203c7375af59210771efb6f634", + "stremio-translations": "github:Stremio/stremio-translations#4bb1b7e31df274f538b8588c2a2b360d6e14ab27", "url": "0.11.4", "use-long-press": "^3.2.0" }, diff --git a/src/App/App.js b/src/App/App.js index d3a1ce188..803515b09 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -100,14 +100,29 @@ const App = () => { }; }, []); - // Handle shell window visibility changed event + // Handle shell events React.useEffect(() => { const onWindowVisibilityChanged = (state) => { setWindowHidden(state.visible === false && state.visibility === 0); }; + const onOpenMedia = (data) => { + if (data.startsWith('stremio:///')) return; + if (data.startsWith('stremio://')) { + const transportUrl = data.replace('stremio://', 'https://'); + if (URL.canParse(transportUrl)) { + window.location.href = `#/addons?addon=${encodeURIComponent(transportUrl)}`; + } + } + }; + shell.on('win-visibility-changed', onWindowVisibilityChanged); - return () => shell.off('win-visibility-changed', onWindowVisibilityChanged); + shell.on('open-media', onOpenMedia); + + return () => { + shell.off('win-visibility-changed', onWindowVisibilityChanged); + shell.off('open-media', onOpenMedia); + }; }, []); React.useEffect(() => { diff --git a/src/components/MainNavBars/MainNavBars.less b/src/components/MainNavBars/MainNavBars.less index ca816a72f..10538cd7c 100644 --- a/src/components/MainNavBars/MainNavBars.less +++ b/src/components/MainNavBars/MainNavBars.less @@ -34,7 +34,7 @@ bottom: 0; left: var(--vertical-nav-bar-size); z-index: 0; - overflow: scroll; + overflow: hidden; } } diff --git a/src/components/Slider/Slider.js b/src/components/Slider/Slider.js index 0e5c96c97..d0d4721b8 100644 --- a/src/components/Slider/Slider.js +++ b/src/components/Slider/Slider.js @@ -31,14 +31,18 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl const retainThumb = React.useCallback(() => { window.addEventListener('blur', onBlur); window.addEventListener('mouseup', onMouseUp); + window.addEventListener('touchend', onTouchEnd); window.addEventListener('mousemove', onMouseMove); + window.addEventListener('touchmove', onTouchMove); document.documentElement.className = classnames(document.documentElement.className, styles['active-slider-within']); }, []); const releaseThumb = React.useCallback(() => { cancelThumbAnimation(); window.removeEventListener('blur', onBlur); window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('touchend', onTouchEnd); window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('touchmove', onTouchMove); const classList = document.documentElement.className.split(' '); const classIndex = classList.indexOf(styles['active-slider-within']); if (classIndex !== -1) { @@ -85,6 +89,36 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl retainThumb(); }, []); + const onTouchStart = React.useCallback((event) => { + const touch = event.touches[0]; + const value = calculateValueForMouseX(touch.clientX); + if (typeof onSlideRef.current === 'function') { + onSlideRef.current(value); + } + + retainThumb(); + event.preventDefault(); + }, []); + const onTouchMove = React.useCallback((event) => { + requestThumbAnimation(() => { + const touch = event.touches[0]; + const value = calculateValueForMouseX(touch.clientX); + if (typeof onSlideRef.current === 'function') { + onSlideRef.current(value); + } + }); + + event.preventDefault(); + }, []); + const onTouchEnd = React.useCallback((event) => { + const touch = event.changedTouches[0]; + const value = calculateValueForMouseX(touch.clientX); + if (typeof onCompleteRef.current === 'function') { + onCompleteRef.current(value); + } + + releaseThumb(); + }, []); React.useLayoutEffect(() => { if (!routeFocused || disabled) { releaseThumb(); @@ -98,7 +132,7 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl const thumbPosition = Math.max(0, Math.min(1, (valueRef.current - minimumValueRef.current) / (maximumValueRef.current - minimumValueRef.current))); const bufferedPosition = Math.max(0, Math.min(1, (bufferedRef.current - minimumValueRef.current) / (maximumValueRef.current - minimumValueRef.current))); return ( -
+
diff --git a/src/components/Slider/styles.less b/src/components/Slider/styles.less index 2bdbbfe72..41478924e 100644 --- a/src/components/Slider/styles.less +++ b/src/components/Slider/styles.less @@ -46,7 +46,8 @@ html.active-slider-within { width: 100%; height: var(--track-size); border-radius: var(--track-size); - background-color: var(--overlay-color); + background-color: var(--primary-accent-color); + opacity: 0.2; &.audio-boost { opacity: 0.3; diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js index 0bcb569a5..94fbefcc7 100644 --- a/src/components/Video/Video.js +++ b/src/components/Video/Video.js @@ -8,11 +8,13 @@ const { useRouteFocused } = require('stremio-router'); const { default: Icon } = require('@stremio/stremio-icons/react'); const { Button, Image, Popup } = require('stremio/components'); const useBinaryState = require('stremio/common/useBinaryState'); +const useProfile = require('stremio/common/useProfile'); const VideoPlaceholder = require('./VideoPlaceholder'); const styles = require('./styles'); const Video = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => { const routeFocused = useRouteFocused(); + const profile = useProfile(); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); const popupLabelOnMouseUp = React.useCallback((event) => { if (!event.nativeEvent.togglePopupPrevented) { @@ -66,13 +68,14 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc } }, [deepLinks]); const renderLabel = React.useMemo(() => function renderLabel({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, children, ...props }) { + const blurThumbnail = profile.settings.hideSpoilers && season && episode && !watched; return ( - + { + !platform.isMobile ? + + : null + }
-
App Version: {process.env.VERSION}
+
+ App Version: {process.env.VERSION} +
+
+ Build Version: {process.env.COMMIT_HASH} +
{ streamingServer.settings !== null && streamingServer.settings.type === 'Ready' ?
Server Version: {streamingServer.settings.content.serverVersion}
@@ -336,12 +342,22 @@ const Settings = () => { />
} +
+
+
{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }
+
+ +
{ t('SETTINGS_NAV_PLAYER') }
-
{t('SETTINGS_CLOSE_WINDOW')}
+
{t('SETTINGS_SECTION_SUBTITLES')}
@@ -722,6 +738,18 @@ const Settings = () => {
+
+
+
+ Build Version +
+
+
+
+ {process.env.COMMIT_HASH} +
+
+
{ streamingServer.settings !== null && streamingServer.settings.type === 'Ready' ?
diff --git a/src/routes/Settings/URLsManager/URLsManager.tsx b/src/routes/Settings/URLsManager/URLsManager.tsx index b43232f4e..46e57020d 100644 --- a/src/routes/Settings/URLsManager/URLsManager.tsx +++ b/src/routes/Settings/URLsManager/URLsManager.tsx @@ -46,7 +46,7 @@ const URLsManager = () => { }
- diff --git a/src/routes/Settings/styles.less b/src/routes/Settings/styles.less index de37d17c2..072dd32a3 100644 --- a/src/routes/Settings/styles.less +++ b/src/routes/Settings/styles.less @@ -64,8 +64,11 @@ .version-info-label { flex: 0 1 auto; margin: 0.5rem 0; + white-space: nowrap; + text-overflow: ellipsis; color: var(--primary-foreground-color); opacity: 0.3; + overflow: hidden; } } @@ -242,6 +245,8 @@ flex-shrink: 1; flex-basis: auto; line-height: 1.5rem; + white-space: nowrap; + text-overflow: ellipsis; color: var(--primary-foreground-color); } diff --git a/src/routes/Settings/useProfileSettingsInputs.js b/src/routes/Settings/useProfileSettingsInputs.js index c193c6eaf..afad298ed 100644 --- a/src/routes/Settings/useProfileSettingsInputs.js +++ b/src/routes/Settings/useProfileSettingsInputs.js @@ -32,6 +32,22 @@ const useProfileSettingsInputs = (profile) => { } }), [profile.settings]); + const hideSpoilersToggle = React.useMemo(() => ({ + checked: profile.settings.hideSpoilers, + onClick: () => { + core.transport.dispatch({ + action: 'Ctx', + args: { + action: 'UpdateSettings', + args: { + ...profile.settings, + hideSpoilers: !profile.settings.hideSpoilers + } + } + }); + } + }), [profile.settings]); + const quitOnCloseToggle = React.useMemo(() => ({ checked: profile.settings.quitOnClose, onClick: () => { @@ -325,6 +341,7 @@ const useProfileSettingsInputs = (profile) => { }), [profile.settings]); return { interfaceLanguageSelect, + hideSpoilersToggle, subtitlesLanguageSelect, subtitlesSizeSelect, subtitlesTextColorInput, diff --git a/src/types/models/Ctx.d.ts b/src/types/models/Ctx.d.ts index a86fd60fa..47f18749f 100644 --- a/src/types/models/Ctx.d.ts +++ b/src/types/models/Ctx.d.ts @@ -21,6 +21,7 @@ type Settings = { hardwareDecoding: boolean, escExitFullscreen: boolean, interfaceLanguage: string, + hideSpoilers: boolean, nextVideoNotificationDuration: number, playInBackground: boolean, playerType: string | null, diff --git a/webpack.config.js b/webpack.config.js index 36f6b6e50..ea1685592 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -234,6 +234,7 @@ module.exports = (env, argv) => ({ { from: 'favicons', to: 'favicons' }, { from: 'images', to: 'images' }, { from: 'screenshots/*.webp', to: './' }, + { from: '.well-known', to: '.well-known' }, ] }), new MiniCssExtractPlugin({