Merge pull request #931 from Stremio/feat/user-item-ratings

MetaPreview: Implement user item ratings
This commit is contained in:
Timothy Z. 2025-06-26 14:48:19 +03:00 committed by GitHub
commit 306dd09f24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 187 additions and 14 deletions

18
package-lock.json generated
View file

@ -12,8 +12,8 @@
"@babel/runtime": "7.26.0",
"@sentry/browser": "8.42.0",
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.49.3",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-core-web": "0.49.4",
"@stremio/stremio-icons": "5.7.1",
"@stremio/stremio-video": "0.0.60",
"a-color-picker": "1.2.1",
"bowser": "2.11.0",
@ -3374,10 +3374,9 @@
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.49.3",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.3.tgz",
"integrity": "sha512-Ql/08LbwU99IUL6fOLy+v1Iv75boHXpunEPScKgXJALdq/OV5tZLG/IycN0O+5+50Nc/NHrI6HslnMNLTWA8JQ==",
"license": "MIT",
"version": "0.49.4",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.49.4.tgz",
"integrity": "sha512-K9LJGKXs8juV3pZOHH6thWTwOShAhjFt9bLL6K1VlORAe6AiieZ2uRp9wdOwFmPX+UgzWLIOd0r2aFXJ4OsJCw==",
"dependencies": {
"@babel/runtime": "7.24.1"
}
@ -3401,9 +3400,10 @@
"license": "MIT"
},
"node_modules/@stremio/stremio-icons": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.1.tgz",
"integrity": "sha512-7g4JP7tPRT1UDZxbuH/Urq7fc6te3joy8qyx/NGWIW7wO169TTISO7ZWdejzESvUVgZ/7i6rzkRmXZ3wefWcBg==",
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.7.1.tgz",
"integrity": "sha512-Z96p36LLX3G+ewMnFKmNZVsO/AtcHA33WQ3wGOYFubxiYADPRAkcLVU5rHIfiGSC9IUaUVhxQWTPVB9ScY4Q5Q==",
"license": "MIT",
"workspaces": [
"react",
"react-native",

View file

@ -17,8 +17,8 @@
"@babel/runtime": "7.26.0",
"@sentry/browser": "8.42.0",
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.49.3",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-core-web": "0.49.4",
"@stremio/stremio-icons": "5.7.1",
"@stremio/stremio-video": "0.0.60",
"a-color-picker": "1.2.1",
"bowser": "2.11.0",

View file

@ -17,6 +17,7 @@ const ActionButton = require('./ActionButton');
const MetaLinks = require('./MetaLinks');
const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder');
const styles = require('./styles');
const { Ratings } = require('./Ratings');
const ALLOWED_LINK_REDIRECTS = [
routesRegexp.search.regexp,
@ -24,7 +25,7 @@ const ALLOWED_LINK_REDIRECTS = [
routesRegexp.metadetails.regexp
];
const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary }, ref) => {
const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary, ratingInfo }, ref) => {
const { t } = useTranslation();
const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false);
const linksGroups = React.useMemo(() => {
@ -232,6 +233,15 @@ const MetaPreview = React.forwardRef(({ className, compact, name, logo, backgrou
:
null
}
{
!compact && ratingInfo !== null ?
<Ratings
ratingInfo={ratingInfo}
className={styles['ratings']}
/>
:
null
}
{
linksGroups.has(CONSTANTS.SHARE_LINK_CATEGORY) && !compact ?
<React.Fragment>
@ -287,7 +297,8 @@ MetaPreview.propTypes = {
})),
trailerStreams: PropTypes.array,
inLibrary: PropTypes.bool,
toggleInLibrary: PropTypes.func
toggleInLibrary: PropTypes.func,
ratingInfo: PropTypes.object,
};
module.exports = MetaPreview;

View file

@ -0,0 +1,62 @@
// Copyright (C) 2017-2025 Smart code 203358507
@import (reference) '~stremio/common/screen-sizes.less';
@height: 4rem;
@width: 4rem;
@height-mobile: 3rem;
@width-mobile: 3rem;
.ratings-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
background-color: var(--overlay-color);
border-radius: 2rem;
height: @height;
width: fit-content;
.icon-container {
display: flex;
justify-content: center;
align-items: center;
height: @height;
width: @width;
padding: 0 1rem;
cursor: pointer;
.icon {
width: calc(@width / 2);
height: calc(@height / 2);
color: var(--primary-foreground-color);
opacity: 0.7;
transition: 0.3s all ease-in-out;
&:hover {
opacity: 1;
}
}
&.disabled {
pointer-events: none;
}
}
}
@media @phone-landscape {
.ratings-container {
height: @height-mobile;
.icon-container {
height: @height-mobile;
width: @width-mobile;
.icon {
width: 1.75rem;
height: 1.75rem;
}
}
}
}

View file

@ -0,0 +1,31 @@
// Copyright (C) 2017-2025 Smart code 203358507
import React, { useMemo } from 'react';
import useRating from './useRating';
import styles from './Ratings.less';
import Icon from '@stremio/stremio-icons/react';
import classNames from 'classnames';
type Props = {
metaId?: string;
ratingInfo?: Loadable<RatingInfo>;
className?: string;
};
const Ratings = ({ ratingInfo, className }: Props) => {
const { onLiked, onLoved, liked, loved } = useRating(ratingInfo);
const disabled = useMemo(() => ratingInfo?.type !== 'Ready', [ratingInfo]);
return (
<div className={classNames(styles['ratings-container'], className)}>
<div className={classNames(styles['icon-container'], { [styles['disabled']]: disabled })} onClick={onLiked}>
<Icon name={liked ? 'thumbs-up' : 'thumbs-up-outline'} className={styles['icon']} />
</div>
<div className={classNames(styles['icon-container'], { [styles['disabled']]: disabled })} onClick={onLoved}>
<Icon name={loved ? 'heart' : 'heart-outline'} className={styles['icon']} />
</div>
</div>
);
};
export default Ratings;

View file

@ -0,0 +1,5 @@
// Copyright (C) 2017-2025 Smart code 203358507
import Ratings from './Ratings';
export { Ratings };

View file

@ -0,0 +1,48 @@
// Copyright (C) 2017-2025 Smart code 203358507
import { useMemo, useCallback } from 'react';
import { useServices } from 'stremio/services';
const useRating = (ratingInfo?: Loadable<RatingInfo>) => {
const { core } = useServices();
const setRating = useCallback((status: Rating) => {
core.transport.dispatch({
action: 'MetaDetails',
args: {
action: 'Rate',
args: status,
},
});
}, []);
const status = useMemo(() => {
const content = ratingInfo?.type === 'Ready' ? ratingInfo.content as RatingInfo : null;
return content?.status;
}, [ratingInfo]);
const liked = useMemo(() => {
return status === 'liked';
}, [status]);
const loved = useMemo(() => {
return status === 'loved';
}, [status]);
const onLiked = useCallback(() => {
setRating(status === 'liked' ? null : 'liked');
}, [status]);
const onLoved = useCallback(() => {
setRating(status === 'loved' ? null : 'loved');
}, [status]);
return {
onLiked,
onLoved,
liked,
loved,
};
};
export default useRating;

View file

@ -159,7 +159,6 @@
display: flex;
flex-direction: row;
align-items: flex-end;
max-height: 15rem;
flex-wrap: wrap;
padding-top: 3.5rem;
overflow: visible;
@ -209,6 +208,11 @@
}
}
}
.ratings {
margin-bottom: 1rem;
margin-right: 1rem;
}
}
.share-prompt {

View file

@ -193,6 +193,8 @@ const Discover = ({ urlParams, queryParams }) => {
trailerStreams={selectedMetaItem.trailerStreams}
inLibrary={selectedMetaItem.inLibrary}
toggleInLibrary={selectedMetaItem.inLibrary ? removeFromLibrary : addToLibrary}
metaId={selectedMetaItem.id}
like={selectedMetaItem.like}
/>
:
discover.catalog !== null && discover.catalog.content.type === 'Loading' ?

View file

@ -168,6 +168,8 @@ const MetaDetails = ({ urlParams, queryParams }) => {
trailerStreams={metaDetails.metaItem.content.content.trailerStreams}
inLibrary={metaDetails.metaItem.content.content.inLibrary}
toggleInLibrary={metaDetails.metaItem.content.content.inLibrary ? removeFromLibrary : addToLibrary}
metaId={metaDetails.metaItem.content.content.id}
ratingInfo={metaDetails.ratingInfo}
/>
</React.Fragment>
}

View file

@ -24,4 +24,5 @@ type MetaDetails = {
content: Loadable<Stream[]>
}[],
title: string | null,
ratingInfo: Loadable<RatingInfo> | null,
};

View file

@ -68,3 +68,10 @@ type AudioTrack = {
lang: string,
origin: string,
};
type Rating = 'liked' | 'loved' | null;
type RatingInfo = {
metaId: string,
status: Rating,
};