Merge pull request #865 from Stremio/feat/settings-blur-unwatched-img

Settings: Blur Unwatched Episodes Images
This commit is contained in:
Tim 2025-03-19 13:40:25 +01:00 committed by GitHub
commit 1347bb3084
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 57 additions and 13 deletions

16
package-lock.json generated
View file

@ -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": {

View file

@ -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"
},

View file

@ -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 (
<Button {...props} className={classnames(className, styles['video-container'])} title={title}>
{
typeof thumbnail === 'string' && thumbnail.length > 0 ?
<div className={styles['thumbnail-container']}>
<Image
className={styles['thumbnail']}
className={classnames(styles['thumbnail'], { [styles['blurred']]: blurThumbnail })}
src={thumbnail}
alt={' '}
renderFallback={() => (

View file

@ -41,6 +41,11 @@
object-position: center;
opacity: 0.9;
background-color: var(--overlay-color);
&.blurred {
filter: blur(0.5rem);
-webkit-filter: blur(0.5rem);
}
}
.placeholder-icon {

View file

@ -4,11 +4,13 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { CONSTANTS } = require('stremio/common');
const { CONSTANTS, useProfile } = require('stremio/common');
const { Button, Image } = require('stremio/components');
const styles = require('./styles');
const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideoRequested }) => {
const profile = useProfile();
const blurPosterImage = profile.settings.hideSpoilers && metaItem.type === 'series';
const watchNowButtonRef = React.useRef(null);
const [animationEnded, setAnimationEnded] = React.useState(false);
const videoName = React.useMemo(() => {
@ -51,7 +53,7 @@ const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideo
<div className={classnames(className, styles['next-video-popup-container'])} onAnimationEnd={onAnimationEnd}>
<div className={styles['poster-container']}>
<Image
className={styles['poster-image']}
className={classnames(styles['poster-image'], { [styles['blurred']]: blurPosterImage })}
src={nextVideo?.thumbnail}
alt={' '}
fallbackSrc={metaItem?.poster}

View file

@ -35,6 +35,11 @@
height: 100%;
object-position: center;
object-fit: cover;
&.blurred {
filter: blur(0.5rem);
-webkit-filter: blur(0.5rem);
}
}
.placeholder-icon {

View file

@ -31,6 +31,7 @@ const Settings = () => {
const toast = useToast();
const {
interfaceLanguageSelect,
hideSpoilersToggle,
subtitlesLanguageSelect,
subtitlesSizeSelect,
subtitlesTextColorInput,
@ -336,6 +337,16 @@ const Settings = () => {
/>
</div>
}
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }</div>
</div>
<Toggle
className={classnames(styles['option-input-container'], styles['toggle-container'])}
tabIndex={-1}
{...hideSpoilersToggle}
/>
</div>
</div>
<div ref={playerSectionRef} className={styles['section-container']}>
<div className={styles['section-title']}>{ t('SETTINGS_NAV_PLAYER') }</div>

View file

@ -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,

View file

@ -21,6 +21,7 @@ type Settings = {
hardwareDecoding: boolean,
escExitFullscreen: boolean,
interfaceLanguage: string,
hideSpoilers: boolean,
nextVideoNotificationDuration: number,
playInBackground: boolean,
playerType: string | null,