Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/player-local-subtitles

This commit is contained in:
Tim 2025-02-07 17:32:48 +01:00
commit 02d3c421bc
42 changed files with 486 additions and 237 deletions

12
package-lock.json generated
View file

@ -1,18 +1,18 @@
{
"name": "stremio",
"version": "5.0.0-beta.16",
"version": "5.0.0-beta.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stremio",
"version": "5.0.0-beta.16",
"version": "5.0.0-beta.17",
"license": "gpl-2.0",
"dependencies": {
"@babel/runtime": "7.26.0",
"@sentry/browser": "8.42.0",
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.48.4",
"@stremio/stremio-core-web": "0.48.5",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-video": "0.0.53",
"a-color-picker": "1.2.1",
@ -3371,9 +3371,9 @@
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.48.4",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.4.tgz",
"integrity": "sha512-848OLm0dtP75aAlYhUB0KoOqwosJIj+ubB8/abuaAzH/N3dtxs40vu2AezmMpGjwR4V60rlOUkUZeWFvrUOjrw==",
"version": "0.48.5",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.5.tgz",
"integrity": "sha512-oDTNBrv8zZi1VGbeV+1Bm6CliI2rF23ERdJpz+gv8EnbFjRIo78WIsoS0yO0EOg8HHXYsFytPq5+c0+YlxmBlA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "7.24.1"

View file

@ -1,7 +1,7 @@
{
"name": "stremio",
"displayName": "Stremio",
"version": "5.0.0-beta.16",
"version": "5.0.0-beta.17",
"author": "Smart Code OOD",
"private": true,
"license": "gpl-2.0",
@ -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.48.4",
"@stremio/stremio-core-web": "0.48.5",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-video": "0.0.53",
"a-color-picker": "1.2.1",

View file

@ -10,6 +10,7 @@ const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, CONS
const ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
const { default: UpdaterBanner } = require('./UpdaterBanner');
const ErrorDialog = require('./ErrorDialog');
const withProtectedRoutes = require('./withProtectedRoutes');
const routerViewsConfig = require('./routerViewsConfig');
@ -169,6 +170,7 @@ const App = () => {
<ServicesToaster />
<DeepLinkHandler />
<SearchParamsHandler />
<UpdaterBanner className={styles['updater-banner-container']} />
<RouterWithProtectedRoutes
className={styles['router']}
viewsConfig={routerViewsConfig}

View file

@ -0,0 +1,46 @@
.updater-banner {
height: 4rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 0 1rem;
font-size: 1rem;
font-weight: bold;
color: var(--primary-foreground-color);
background-color: var(--primary-accent-color);
.button {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 2.5rem;
padding: 0 1rem;
border-radius: var(--border-radius);
color: var(--primary-background-color);
background-color: var(--primary-foreground-color);
transition: all 0.1s ease-out;
&:hover {
color: var(--primary-foreground-color);
background-color: transparent;
box-shadow: inset 0 0 0 0.15rem var(--primary-foreground-color);
}
}
.close {
position: absolute;
right: 0;
height: 4rem;
width: 4rem;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.icon {
height: 2rem;
}
}
}

View file

@ -0,0 +1,50 @@
import React, { useEffect } from 'react';
import Icon from '@stremio/stremio-icons/react';
import { useTranslation } from 'react-i18next';
import { useServices } from 'stremio/services';
import { useBinaryState, useShell } from 'stremio/common';
import { Button, Transition } from 'stremio/components';
import styles from './UpdaterBanner.less';
type Props = {
className: string,
};
const UpdaterBanner = ({ className }: Props) => {
const { t } = useTranslation();
const { shell } = useServices();
const shellTransport = useShell();
const [visible, show, hide] = useBinaryState(false);
const onInstallClick = () => {
shellTransport.send('autoupdater-notif-clicked');
};
useEffect(() => {
shell.transport && shell.transport.on('autoupdater-show-notif', show);
return () => {
shell.transport && shell.transport.off('autoupdater-show-notif', show);
};
}, []);
return (
<div className={className}>
<Transition when={visible} name={'slide-up'}>
<div className={styles['updater-banner']}>
<div className={styles['label']}>
{ t('UPDATER_TITLE') }
</div>
<Button className={styles['button']} onClick={onInstallClick}>
{ t('UPDATER_INSTALL_BUTTON') }
</Button>
<Button className={styles['close']} onClick={hide}>
<Icon className={styles['icon']} name={'close'} />
</Button>
</div>
</Transition>
</div>
);
};
export default UpdaterBanner;

View file

@ -0,0 +1,2 @@
import UpdaterBanner from './UpdaterBanner';
export default UpdaterBanner;

View file

@ -23,8 +23,8 @@
// HTML sizes
@html-width: ~"calc(max(var(--small-viewport-width), var(--dynamic-viewport-width)))";
@html-height: ~"calc(max(var(--small-viewport-height), var(--dynamic-viewport-height)))";
@html-standalone-width: ~"calc(max(100%, var(--large-viewport-width)))";
@html-standalone-height: ~"calc(max(100%, var(--large-viewport-height)))";
@html-standalone-width: ~"calc(max(100%, var(--small-viewport-width)))";
@html-standalone-height: ~"calc(max(100%, var(--small-viewport-height)))";
// Safe area insets
@safe-area-inset-top: env(safe-area-inset-top, 0rem);
@ -64,7 +64,6 @@
--modal-background-color: rgba(15, 13, 32, 1);
--outer-glow: 0px 0px 15px rgba(123, 91, 245, 0.37);
--border-radius: 0.75rem;
--calculated-bottom-safe-inset: @calculated-bottom-safe-inset;
--top-overlay-size: @top-overlay-size;
--bottom-overlay-size: @bottom-overlay-size;
--overlap-size: @overlap-size;
@ -99,6 +98,10 @@
@supports (height: 100lvh) and (height: 100svh) {
--viewport-height-diff: calc(100lvh - 100svh);
}
@media (display-mode: standalone) {
--safe-area-inset-bottom: @calculated-bottom-safe-inset;
}
}
* {
@ -174,7 +177,7 @@ html {
position: absolute;
top: calc(1.2 * var(--horizontal-nav-bar-size) + var(--safe-area-inset-top));
right: var(--safe-area-inset-right);
bottom: calc(1.2 * var(--horizontal-nav-bar-size) + var(--calculated-bottom-safe-inset, 0rem));
bottom: calc(1.2 * var(--horizontal-nav-bar-size) + var(--safe-area-inset-bottom, 0rem));
left: auto;
z-index: 1;
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
@ -217,6 +220,14 @@ html {
}
}
.updater-banner-container {
z-index: 1;
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.router {
width: 100%;
height: 100%;

View file

@ -1,6 +1,6 @@
import React, { createContext, useContext } from 'react';
import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS';
import useShell from './useShell';
import useShell from 'stremio/common/useShell';
import { name, isMobile } from './device';
interface PlatformContext {

View file

@ -69,6 +69,19 @@
transform: translateX(100%);
}
.slide-up-enter {
transform: translateY(100%);
}
.slide-up-active {
transform: translateY(0%);
transition: transform 0.3s cubic-bezier(0.32, 0, 0.67, 0);
}
.slide-up-exit {
transform: translateY(100%);
}
@keyframes fade-in-no-motion {
0% {
opacity: 0;

View file

@ -20,6 +20,7 @@ const useModelState = require('./useModelState');
const useNotifications = require('./useNotifications');
const useOnScrollToBottom = require('./useOnScrollToBottom');
const useProfile = require('./useProfile');
const { default: useShell } = require('./useShell');
const useStreamingServer = require('./useStreamingServer');
const useTorrent = require('./useTorrent');
const useTranslate = require('./useTranslate');
@ -50,6 +51,7 @@ module.exports = {
useNotifications,
useOnScrollToBottom,
useProfile,
useShell,
useStreamingServer,
useTorrent,
useTranslate,

View file

@ -21,5 +21,5 @@
@small-phone-portrait: ~"screen and (max-width: @{small-phone-landscape-size}) and (max-height: @{small-phone-portrait-size}) and (orientation: portrait)";
@phone-landscape: ~"screen and (max-width: @{phone-portrait-size}) and (max-height: @{phone-landscape-size}) and (orientation: landscape)";
@phone-portrait: ~"screen and (max-width: @{phone-portrait-size}) and (max-height: @{phone-portrait-size}) and (orientation: portrait)";
@phone-portrait: ~"screen and (max-width: @{phone-landscape-size}) and (max-height: @{phone-portrait-size}) and (orientation: portrait)";

View file

@ -101,7 +101,7 @@
@media only screen and (orientation: landscape) {
.bottom-sheet {
.container {
max-width: 90%;
max-width: calc(90% - var(--safe-area-inset-left) - var(--safe-area-inset-right));
}
}
}

View file

@ -5,7 +5,8 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const AColorPicker = require('a-color-picker');
const { useTranslation } = require('react-i18next');
const { Button, ModalDialog } = require('stremio/components');
const { Button } = require('stremio/components');
const ModalDialog = require('stremio/components/ModalDialog');
const useBinaryState = require('stremio/common/useBinaryState');
const ColorPicker = require('./ColorPicker');
const styles = require('./styles');

View file

@ -6,9 +6,9 @@
position: relative;
z-index: 0;
overflow: clip;
margin-left: env(safe-area-inset-left, 0px);
margin-right: env(safe-area-inset-right, 0px);
width: calc(100% - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
margin-left: var(--safe-area-inset-left);
margin-right: var(--safe-area-inset-right);
width: calc(100% - var(--safe-area-inset-left) - var(--safe-area-inset-right));
height: 100%;
.horizontal-nav-bar {
@ -22,14 +22,14 @@
.vertical-nav-bar {
position: absolute;
top: var(--horizontal-nav-bar-size);
bottom: var(--calculated-bottom-safe-inset);
bottom: 0;
left: 0;
z-index: 1;
}
.nav-content-container {
position: absolute;
padding-top: calc(var(--horizontal-nav-bar-size) + env(safe-area-inset-top, 0px));
padding-top: calc(var(--horizontal-nav-bar-size) + var(--safe-area-inset-top));
top: 0;
right: 0;
bottom: 0;

View file

@ -22,7 +22,7 @@
}
.addons-container {
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
background-color: transparent;
.addons-content {

View file

@ -4,12 +4,12 @@ const React = require('react');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next');
const { useStreamingServer, useNotifications, withCoreSuspender, getVisibleChildrenRange } = require('stremio/common');
const { useStreamingServer, useNotifications, withCoreSuspender, getVisibleChildrenRange, useProfile } = require('stremio/common');
const { ContinueWatchingItem, EventModal, MainNavBars, MetaItem, MetaRow } = require('stremio/components');
const StreamingServerWarning = require('./StreamingServerWarning');
const useBoard = require('./useBoard');
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
const styles = require('./styles');
const { default: StreamingServerWarning } = require('./StreamingServerWarning');
const THRESHOLD = 5;
@ -19,8 +19,15 @@ const Board = () => {
const continueWatchingPreview = useContinueWatchingPreview();
const [board, loadBoardRows] = useBoard();
const notifications = useNotifications();
const profile = useProfile();
const boardCatalogsOffset = continueWatchingPreview.items.length > 0 ? 1 : 0;
const scrollContainerRef = React.useRef();
const streamingServerWarningDismissed = React.useMemo(() => {
return streamingServer.settings !== null && streamingServer.settings.type === 'Ready' || (
!isNaN(profile.settings.streamingServerWarningDismissed.getTime()) &&
profile.settings.streamingServerWarningDismissed.getTime() > Date.now()
);
}, [profile.settings, streamingServer.settings]);
const onVisibleRangeChange = React.useCallback(() => {
const range = getVisibleChildrenRange(scrollContainerRef.current);
if (range === null) {
@ -95,7 +102,7 @@ const Board = () => {
</div>
</MainNavBars>
{
streamingServer.settings !== null && streamingServer.settings.type === 'Err' ?
!streamingServerWarningDismissed ?
<StreamingServerWarning className={styles['board-warning-container']} />
:
null

View file

@ -1,73 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { useServices } = require('stremio/services');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const { Button } = require('stremio/components');
const useProfile = require('stremio/common/useProfile');
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
const styles = require('./styles');
const StreamingServerWarning = ({ className }) => {
const { t } = useTranslation();
const { core } = useServices();
const profile = useProfile();
const onLaterClick = React.useCallback(() => {
const streamingServerWarningDismissed = new Date();
streamingServerWarningDismissed.setMonth(streamingServerWarningDismissed.getMonth() + 1);
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
streamingServerWarningDismissed
}
}
});
}, [profile.settings]);
const onDismissClick = React.useCallback(() => {
const streamingServerWarningDismissed = new Date();
streamingServerWarningDismissed.setFullYear(streamingServerWarningDismissed.getFullYear() + 50);
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
streamingServerWarningDismissed
}
}
});
}, [profile.settings]);
if (!isNaN(profile.settings.streamingServerWarningDismissed.getTime()) &&
profile.settings.streamingServerWarningDismissed.getTime() > Date.now()) {
return null;
}
return (
<div className={classnames(className, styles['warning-container'])}>
<div className={styles['warning-statement']}>{ t('SETTINGS_SERVER_UNAVAILABLE') }</div>
<a href="https://www.stremio.com/download-service" target="_blank" rel="noreferrer">
<Button className={styles['warning-button']} title={t('SERVICE_INSTALL')} tabIndex={-1}>
<div className={styles['warning-label']}>{ t('SERVICE_INSTALL') }</div>
</Button>
</a>
<Button className={styles['warning-button']} title={t('WARNING_STREAMING_SERVER_LATER')} onClick={onLaterClick} tabIndex={-1}>
<div className={styles['warning-label']}>{ t('WARNING_STREAMING_SERVER_LATER') }</div>
</Button>
<Button className={styles['warning-button']} title={t('DONT_SHOW_AGAIN')} onClick={onDismissClick} tabIndex={-1}>
<div className={styles['warning-label']}>{ t('DONT_SHOW_AGAIN') }</div>
</Button>
</div>
);
};
StreamingServerWarning.propTypes = {
className: PropTypes.string
};
module.exports = withCoreSuspender(StreamingServerWarning);

View file

@ -0,0 +1,61 @@
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
.warning-container {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
background-color: @color-accent5-dark3;
border-radius: 0.5rem;
box-shadow: 0rem 0.25rem 1rem rgba(0, 0, 0, 0.48), 0rem 0.5rem 3rem rgba(0, 0, 0, 0.64);
.warning-statement {
flex: 1;
font-size: 1.2rem;
max-height: 2.4em;
color: @color-surface-light5-90;
}
.actions {
display: flex;
gap: 1rem;
.action {
flex: none;
padding: 0.5rem 1rem;
color: @color-surface-light5-90;
background-color: rgba(0, 0, 0, 0.24);
border-radius: var(--border-radius);
&:first-child {
margin-left: 0;
}
.label {
font-size: 1.2rem;
color: @color-surface-light5-90;
}
&:hover {
.label {
text-decoration: underline;
}
}
}
}
}
@media only screen and (max-width: @minimum) {
.warning-container {
flex-direction: column;
text-align: center;
padding: 1rem 0.5rem;
.actions {
justify-content: space-around;
}
}
}

View file

@ -0,0 +1,101 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import { useServices } from 'stremio/services';
import { Button } from 'stremio/components';
import useProfile from 'stremio/common/useProfile';
import { withCoreSuspender } from 'stremio/common/CoreSuspender';
import styles from './StreamingServerWarning.less';
type Props = {
className?: string;
};
const StreamingServerWarning = ({ className }: Props) => {
const { t } = useTranslation();
const { core } = useServices();
const profile = useProfile();
const createDismissalDate = (months: number, years = 0): Date => {
const dismissalDate = new Date();
if (months) {
dismissalDate.setMonth(dismissalDate.getMonth() + months);
}
if (years) {
dismissalDate.setFullYear(dismissalDate.getFullYear() + years);
}
return dismissalDate;
};
const updateSettings = useCallback((streamingServerWarningDismissed: Date) => {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
streamingServerWarningDismissed
}
}
});
}, [profile.settings]);
const onLater = useCallback(() => {
updateSettings(createDismissalDate(1));
}, [updateSettings]);
const onDismiss = useCallback(() => {
updateSettings(createDismissalDate(0, 50));
}, [updateSettings]);
return (
<div className={classnames(className, styles['warning-container'])}>
<div className={styles['warning-statement']}>
{t('SETTINGS_SERVER_UNAVAILABLE')}
</div>
<div className={styles['actions']}>
<a
href='https://www.stremio.com/download-service'
target='_blank'
rel='noreferrer'
>
<Button
className={styles['action']}
title={t('SERVICE_INSTALL')}
tabIndex={-1}
>
<div className={styles['label']}>
{t('SERVICE_INSTALL')}
</div>
</Button>
</a>
<Button
className={styles['action']}
title={t('WARNING_STREAMING_SERVER_LATER')}
onClick={onLater}
tabIndex={-1}
>
<div className={styles['label']}>
{t('WARNING_STREAMING_SERVER_LATER')}
</div>
</Button>
<Button
className={styles['action']}
title={t('DONT_SHOW_AGAIN')}
onClick={onDismiss}
tabIndex={-1}
>
<div className={styles['label']}>
{t('DONT_SHOW_AGAIN')}
</div>
</Button>
</div>
</div>
);
};
export default withCoreSuspender(StreamingServerWarning);

View file

@ -1,5 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const StreamingServerWarning = require('./StreamingServerWarning');
module.exports = StreamingServerWarning;

View file

@ -0,0 +1,5 @@
// Copyright (C) 2017-2024 Smart code 203358507
import StreamingServerWarning from './StreamingServerWarning';
export default StreamingServerWarning;

View file

@ -1,60 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
.warning-container {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
background-color: @color-accent5-dark3;
.warning-statement {
flex: 1;
margin-right: 1rem;
font-size: 1.2rem;
max-height: 2.4em;
color: @color-surface-light5-90;
}
.warning-button {
flex: none;
margin-left: 1rem;
color: @color-surface-light5-90;
&:first-child {
margin-left: 0;
}
&:hover {
.warning-label {
text-decoration: underline;
}
}
.warning-label {
font-size: 1.2rem;
max-height: 1.2em;
color: @color-surface-light5-90;
}
}
.warning-button:hover {
text-decoration: underline;
}
}
@media only screen and (max-width: 500px) {
.warning-container {
display: block;
height: auto !important;
text-align: center;
.warning-statement {
margin-bottom: 0.5rem;
margin-right: 0;
}
.warning-button {
display: inline-block;
}
}
}

View file

@ -13,7 +13,7 @@
.board-container {
width: 100%;
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
display: flex;
flex-direction: column;
@ -36,9 +36,10 @@
}
.board-warning-container {
flex: none;
align-self: stretch;
margin-bottom: var(--calculated-bottom-safe-inset, 0rem);
position: absolute;
bottom: calc(var(--safe-area-inset-bottom) + 0.5rem);
left: calc(var(--safe-area-inset-left) + 0.5rem);
right: calc(var(--safe-area-inset-right) + 0.5rem);
}
}
@ -216,12 +217,16 @@
}
.board-warning-container {
position: absolute;
left: 0;
right: 0;
bottom: calc(var(--vertical-nav-bar-size) + var(--calculated-bottom-safe-inset, 0rem));
height: 4rem;
margin-bottom: 0rem;
bottom: calc(var(--vertical-nav-bar-size) + 0.5rem);
height: 7rem;
}
}
}
@media @phone-landscape {
.board-container {
.board-warning-container {
left: calc(var(--safe-area-inset-left) + var(--vertical-nav-bar-size) + 0.5rem);
}
}
}

View file

@ -3,8 +3,7 @@
@import (reference) '~stremio/common/screen-sizes.less';
.calendar {
width: 100%;
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
background-color: transparent;
.content {

View file

@ -17,7 +17,7 @@
}
.discover-container {
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
background-color: transparent;
.discover-content {

View file

@ -13,7 +13,7 @@
}
.library-container {
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
background-color: transparent;
.library-content {

View file

@ -181,12 +181,17 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
const renderMenu = React.useMemo(() => function renderMenu() {
return (
<div className={styles['context-menu-content']} onPointerDown={popupMenuOnPointerDown} onContextMenu={popupMenuOnContextMenu} onClick={popupMenuOnClick} onKeyDown={popupMenuOnKeyDown}>
<div className={styles['context-menu-title']}>
{description}
</div>
<Button className={styles['context-menu-option-container']} title={t('CTX_PLAY')}>
<Icon className={styles['menu-icon']} name={'play'} />
<div className={styles['context-menu-option-label']}>{t('CTX_PLAY')}</div>
</Button>
{
streamLink &&
<Button className={styles['context-menu-option-container']} title={t('CTX_COPY_STREAM_LINK')} onClick={copyStreamLink}>
<Icon className={styles['menu-icon']} name={'link'} />
<div className={styles['context-menu-option-label']}>{t('CTX_COPY_STREAM_LINK')}</div>
</Button>
}

View file

@ -111,12 +111,29 @@
background-color: var(--secondary-accent-color);
}
.menu-icon {
flex: none;
width: 1.7rem;
height: 1.7rem;
margin-right: 1rem;
color: var(--color-placeholder);
}
.context-menu-container {
max-width: calc(90% - 1.5rem);
z-index: 2;
.context-menu-content {
--spatial-navigation-contain: contain;
.context-menu-title {
font-size: 0.9rem;
padding: 1rem 1.5rem;
font-weight: 100;
border-bottom: 1px solid var(--color-placeholder);
color: var(--primary-foreground-color);
white-space: break-spaces;
}
.context-menu-option-container {
display: flex;
@ -131,8 +148,9 @@
.context-menu-option-label {
font-size: 1rem;
font-weight: 500;
color:var(--primary-foreground-color);
font-weight: 300;
color: var(--primary-foreground-color);
text-transform: capitalize;
}
}
}

View file

@ -9,7 +9,7 @@ const { Button, Image, Multiselect } = require('stremio/components');
const { useServices } = require('stremio/services');
const Stream = require('./Stream');
const styles = require('./styles');
const { usePlatform } = require('stremio/common');
const { usePlatform, useProfile } = require('stremio/common');
const ALL_ADDONS_KEY = 'ALL';
@ -17,17 +17,21 @@ const StreamsList = ({ className, video, ...props }) => {
const { t } = useTranslation();
const { core } = useServices();
const platform = usePlatform();
const profile = useProfile();
const streamsContainerRef = React.useRef(null);
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
const onAddonSelected = React.useCallback((event) => {
streamsContainerRef.current.scrollTo({ top: 0, left: 0, behavior: platform.name === 'ios' ? 'smooth' : 'instant' });
setSelectedAddon(event.value);
}, [platform]);
const showInstallAddonsButton = React.useMemo(() => {
return !profile || profile.auth === null || profile.auth?.user?.isNewUser === true;
}, [profile]);
const backButtonOnClick = React.useCallback(() => {
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
window.location.replace(video.deepLinks.metaDetailsVideos + (
typeof video.season === 'number' ?
`?${new URLSearchParams({'season': video.season})}`
`?${new URLSearchParams({ 'season': video.season })}`
:
null
));
@ -126,6 +130,15 @@ const StreamsList = ({ className, video, ...props }) => {
<div className={styles['message-container']}>
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
<div className={styles['label']}>{t('NO_STREAM')}</div>
{
showInstallAddonsButton ?
<Button className={styles['install-button-container']} title={t('ADDON_CATALOGUE_MORE')} href={'#/addons'}>
<Icon className={styles['icon']} name={'addons'} />
<div className={styles['label']}>{t('ADDON_CATALOGUE_MORE')}</div>
</Button>
:
null
}
</div>
:
filteredStreams.length === 0 ?
@ -161,13 +174,18 @@ const StreamsList = ({ className, video, ...props }) => {
onClick={stream.onClick}
/>
))}
{
showInstallAddonsButton ?
<Button className={styles['install-button-container']} title={t('ADDON_CATALOGUE_MORE')} href={'#/addons'}>
<Icon className={styles['icon']} name={'addons'} />
<div className={styles['label']}>{t('ADDON_CATALOGUE_MORE')}</div>
</Button>
:
null
}
</div>
</React.Fragment>
}
<Button className={styles['install-button-container']} title={t('ADDON_CATALOGUE_MORE')} href={'#/addons'}>
<Icon className={styles['icon']} name={'addons'} />
<div className={styles['label']}>{ t('ADDON_CATALOGUE_MORE') }</div>
</Button>
</div>
);
};

View file

@ -19,7 +19,7 @@
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem 1rem 0;
padding: 1rem;
overflow-y: auto;
.image {
@ -144,11 +144,12 @@
flex-direction: row;
align-items: center;
justify-content: center;
margin: 1rem;
padding: 1.5rem 1rem;
border-radius: var(--border-radius);
background-color: var(--secondary-accent-color);
border-radius: 3rem;
height: 4rem;
padding: 0 2rem;
margin: 1rem auto;
max-width: 50%;
border-radius: 2rem;
&:hover {
outline: var(--focus-outline-size) solid var(--secondary-accent-color);
@ -165,7 +166,7 @@
.label {
flex: 0 1 auto;
font-size: 1.5rem;
font-size: 1rem;
font-weight: 700;
max-height: 3.6em;
text-align: center;

View file

@ -52,7 +52,7 @@
flex-direction: row;
margin-top: calc(var(--top-overlay-size) * -1);
padding-top: var(--top-overlay-size);
padding-bottom: var(--calculated-bottom-safe-inset, 0rem);
padding-bottom: var(--safe-area-inset-bottom, 0rem);
.vertical-nav-bar {
flex: none;
}

View file

@ -138,6 +138,7 @@ const ControlBar = ({
<VolumeSlider
className={styles['volume-slider']}
volume={volume}
muted={muted}
onVolumeChangeRequested={onVolumeChangeRequested}
/>
<div className={styles['spacing']} />

View file

@ -8,7 +8,7 @@ const { useRouteFocused } = require('stremio-router');
const { Slider } = require('stremio/components');
const styles = require('./styles');
const VolumeSlider = ({ className, volume, onVolumeChangeRequested }) => {
const VolumeSlider = ({ className, volume, onVolumeChangeRequested, muted }) => {
const disabled = volume === null || isNaN(volume);
const routeFocused = useRouteFocused();
const [slidingVolume, setSlidingVolume] = React.useState(null);
@ -45,7 +45,9 @@ const VolumeSlider = ({ className, volume, onVolumeChangeRequested }) => {
className={classnames(className, styles['volume-slider'], { 'active': slidingVolume !== null })}
value={
!disabled ?
slidingVolume !== null ? slidingVolume : volume
!muted ?
slidingVolume !== null ? slidingVolume : volume
: 0
:
100
}
@ -61,7 +63,8 @@ const VolumeSlider = ({ className, volume, onVolumeChangeRequested }) => {
VolumeSlider.propTypes = {
className: PropTypes.string,
volume: PropTypes.number,
onVolumeChangeRequested: PropTypes.func
onVolumeChangeRequested: PropTypes.func,
muted: PropTypes.bool,
};
module.exports = VolumeSlider;

View file

@ -76,14 +76,6 @@ const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideo
:
null
}
{
nextVideo !== null && typeof nextVideo.overview === 'string' ?
<div className={styles['description']}>
{ nextVideo.overview }
</div>
:
null
}
</div>
<div className={styles['buttons-container']}>
<Button className={classnames(styles['button-container'], styles['dismiss'])} onClick={onDismissButtonClick}>

View file

@ -1,11 +1,13 @@
// Copyright (C) 2017-2023 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
.next-video-popup-container {
display: flex;
flex-direction: row;
width: 50rem;
width: 38rem;
min-height: 13rem;
animation: slide-fade-in 0.5s ease-in;
@keyframes slide-fade-in {
@ -21,7 +23,7 @@
}
.poster-container {
flex: 1 1 45%;
flex: 1 1 25%;
display: flex;
justify-content: center;
align-items: center;
@ -50,14 +52,16 @@
.details-container {
flex: auto;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem 2rem;
.name {
flex: none;
align-self: stretch;
max-height: 2.4em;
font-weight: 700;
margin-bottom: 1.5rem;
color: var(--primary-accent-color);
.label {
@ -70,36 +74,30 @@
align-self: stretch;
max-height: 2.4em;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--primary-foreground-color);
}
.description {
color: var(--primary-foreground-color);
opacity: 0.5;
max-width: 80%;
padding: 0.5rem 0;
}
}
.buttons-container {
display: flex;
flex-direction: row;
padding: 0 1rem 2rem;
justify-content: space-between;
gap: 1rem;
padding: 0 1rem 1.5rem;
.spacing {
flex: 0 0 50%;
}
.button-container {
flex: 0 0 45%;
flex: 0 1 50%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 1rem;
height: 3.5rem;
padding: 0 0.5rem;
margin-left: 1rem;
padding: 0 1rem;
border-radius: 1.75rem;
&.play-button {
@ -123,7 +121,6 @@
flex: none;
width: 1.4rem;
height: 1.4rem;
margin-right: 1rem;
color: var(--primary-foreground-color);
}
@ -145,4 +142,26 @@
}
}
}
}
@media @phone-portrait {
.next-video-popup-container {
.info-container {
.buttons-container {
gap: 0.5rem;
.button-container {
margin-left: 0rem;
.icon {
margin-right: 0rem;
}
.label {
display: none;
}
}
}
}
}
}

View file

@ -84,7 +84,7 @@
}
}
@media (orientation: portrait) and (max-width: @xsmall) {
@media @phone-portrait {
.side-drawer {
max-width: 100dvw;
@ -94,12 +94,12 @@
}
}
@media (orientation: landscape) and (max-width: @xsmall) {
@media @phone-landscape {
.side-drawer {
max-width: 50dvw;
.info {
max-height: 30dvh;
flex: 1;
}
}
}

View file

@ -85,6 +85,7 @@ html:not(.active-slider-within) {
}
&.side-drawer-button-layer {
position: fixed;
top: 50%;
right: -4rem;
left: initial;

View file

@ -187,6 +187,12 @@ const Settings = () => {
:
null
}
{
typeof shell?.transport?.props?.shellVersion === 'string' ?
<div className={styles['version-info-label']} title={shell.transport.props.shellVersion}>Shell Version: {shell.transport.props.shellVersion}</div>
:
null
}
</div>
<div ref={sectionsContainerRef} className={styles['sections-container']} onScroll={sectionsContainerOnScroll}>
<div ref={generalSectionRef} className={styles['section-container']}>
@ -717,6 +723,23 @@ const Settings = () => {
:
null
}
{
typeof shell?.transport?.props?.shellVersion === 'string' ?
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>
Shell Version
</div>
</div>
<div className={classnames(styles['option-input-container'], styles['info-container'])}>
<div className={styles['label']}>
{ shell.transport.props.shellVersion }
</div>
</div>
</div>
:
null
}
</div>
</div>
</div>

View file

@ -13,7 +13,7 @@
}
.settings-container {
height: 100%;
height: calc(100% - var(--safe-area-inset-bottom));
width: 100%;
background-color: transparent;

View file

@ -9,6 +9,7 @@ type Auth = {
created_at: number,
expires_in: number,
},
isNewUser: boolean,
},
};

View file

@ -163,7 +163,7 @@ module.exports = (env, argv) => ({
exclude: /node_modules/,
type: 'asset/resource',
generator: {
filename: `${COMMIT_HASH}/images/[name][ext][query]`
filename: 'images/[name][ext][query]'
}
},
{
@ -231,9 +231,9 @@ module.exports = (env, argv) => ({
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'favicons', to: `${COMMIT_HASH}/favicons` },
{ from: 'images', to: `${COMMIT_HASH}/images` },
{ from: 'screenshots/*.webp', to: `${COMMIT_HASH}` },
{ from: 'favicons', to: 'favicons' },
{ from: 'images', to: 'images' },
{ from: 'screenshots/*.webp', to: './' },
]
}),
new MiniCssExtractPlugin({
@ -243,8 +243,8 @@ module.exports = (env, argv) => ({
template: './src/index.html',
inject: false,
scriptLoading: 'blocking',
faviconsPath: `${COMMIT_HASH}/favicons`,
imagesPath: `${COMMIT_HASH}/images`,
faviconsPath: 'favicons',
imagesPath: 'images',
}),
new WebpackPwaManifest({
name: 'Stremio Web',
@ -261,33 +261,33 @@ module.exports = (env, argv) => ({
icons: [
{
src: 'images/icon.png',
destination: `${COMMIT_HASH}/images`,
destination: 'icons',
sizes: [196, 512],
purpose: 'any',
ios: true,
purpose: 'any'
},
{
src: 'images/maskable_icon.png',
destination: `${COMMIT_HASH}/images`,
destination: 'maskable_icons',
sizes: [196, 512],
purpose: 'maskable',
ios: true
},
{
src: 'favicons/favicon.ico',
destination: `${COMMIT_HASH}/favicons`,
destination: 'favicons',
sizes: [256],
}
],
screenshots : [
{
src: `${COMMIT_HASH}/screenshots/board_wide.webp`,
src: 'screenshots/board_wide.webp',
sizes: '1440x900',
type: 'image/webp',
form_factor: 'wide',
label: 'Homescreen of Stremio'
},
{
src: `${COMMIT_HASH}/screenshots/board_narrow.webp`,
src: 'screenshots/board_narrow.webp',
sizes: '414x896',
type: 'image/webp',
form_factor: 'narrow',