diff --git a/src/common/MainNavBar/MainNavBar.js b/src/common/MainNavBar/MainNavBar.js index 88672b44c..9f2db6a02 100644 --- a/src/common/MainNavBar/MainNavBar.js +++ b/src/common/MainNavBar/MainNavBar.js @@ -17,6 +17,7 @@ const MainNavBar = ({ className }) => { searchBar={true} addonsButton={true} fullscreenButton={true} + notificationsMenu={true} navMenu={true} /> ); diff --git a/src/common/NavBar/NavBar.js b/src/common/NavBar/NavBar.js index 1a75f843e..92663af21 100644 --- a/src/common/NavBar/NavBar.js +++ b/src/common/NavBar/NavBar.js @@ -5,10 +5,11 @@ const NavTabButton = require('./NavTabButton'); const SearchBar = require('./SearchBar'); const AddonsButton = require('./AddonsButton'); const FullscreenButton = require('./FullscreenButton'); +const NotificationsMenu = require('./NotificationsMenu'); const NavMenu = require('./NavMenu'); const styles = require('./styles'); -const NavBar = React.memo(({ className, backButton, tabs, title, searchBar, addonsButton, fullscreenButton, navMenu }) => { +const NavBar = React.memo(({ className, backButton, tabs, title, searchBar, addonsButton, fullscreenButton, notificationsMenu, navMenu }) => { const backButtonOnClick = React.useCallback(() => { window.history.back(); }, []); @@ -62,6 +63,12 @@ const NavBar = React.memo(({ className, backButton, tabs, title, searchBar, addo : null } + { + notificationsMenu ? + + : + null + } { navMenu ? @@ -87,6 +94,7 @@ NavBar.propTypes = { searchBar: PropTypes.bool, addonsButton: PropTypes.bool, fullscreenButton: PropTypes.bool, + notificationsMenu: PropTypes.bool, navMenu: PropTypes.bool }; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/Notification.js b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/Notification.js new file mode 100644 index 000000000..53c7b0e81 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/Notification.js @@ -0,0 +1,98 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const Icon = require('stremio-icons/dom'); +const Button = require('stremio/common/Button'); +const Image = require('stremio/common/Image'); +const PlayIconCircleCentered = require('stremio/common/PlayIconCircleCentered'); +const styles = require('./styles'); + +const ICON_FOR_TYPE = Object.assign(Object.create(null), { + 'movie': 'ic_movies', + 'series': 'ic_series', + 'channel': 'ic_channels', + 'tv': 'ic_tv', + 'other': 'ic_movies' +}); + +const Notification = ({ className, id, type, name, poster, thumbnail, season, episode, released, onClick }) => { + const daysAgo = React.useMemo(() => { + if (released instanceof Date) { + return Math.floor(Math.abs((Date.now() - released) / (24 * 60 * 60 * 1000))); + } + }, [released]); + return ( + + ); +} + +Notification.propTypes = { + className: PropTypes.string, + id: PropTypes.string, + type: PropTypes.string, + name: PropTypes.string, + poster: PropTypes.string, + thumbnail: PropTypes.string, + season: PropTypes.number, + episode: PropTypes.number, + released: PropTypes.instanceOf(Date), + onClick: PropTypes.func +}; + +module.exports = Notification; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/index.js b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/index.js new file mode 100644 index 000000000..bf7c2ebf9 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/index.js @@ -0,0 +1,3 @@ +const Notification = require('./Notification'); + +module.exports = Notification; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/styles.less b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/styles.less new file mode 100644 index 000000000..462bde6a9 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/Notification/styles.less @@ -0,0 +1,135 @@ +:import('~stremio/common/PlayIconCircleCentered/styles.less') { + play-icon-circle-centered-background: background; + play-icon-circle-centered-icon: icon; +} + +.notification-container { + display: flex; + flex-direction: row; + padding: 1rem; + background-color: var(--color-surfacelighter); + + &:hover, &:focus { + background-color: var(--color-surfacelight); + + .poster-container { + .play-icon-layer { + display: block; + } + } + } + + .logo-container, .poster-container { + position: relative; + z-index: 0; + height: 3.5rem; + background-color: var(--color-backgroundlight); + + .logo-image-layer, .poster-image-layer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -3; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + .logo-image, .poster-image { + flex: none; + width: 100%; + height: 100%; + object-position: center; + object-fit: cover; + } + + .placeholder-icon { + flex: none; + width: 80%; + height: 50%; + fill: var(--color-surfacelight20); + } + } + } + + .logo-container { + width: 3.5rem; + border-radius: 50%; + } + + .info-container { + flex: 1; + align-self: stretch; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-content: flex-start; + padding: 0 1rem; + + &:first-child { + align-content: center; + } + + .episode-container { + flex-grow: 0; + flex-shrink: 0; + flex-basis: 100%; + white-space: nowrap; + text-overflow: ellipsis; + } + + .name-container { + flex-grow: 0; + flex-shrink: 0; + flex-basis: 100%; + max-height: 3.6em; + } + + .released-container { + flex-grow: 0; + flex-shrink: 0; + flex-basis: 100%; + margin-top: 0.5rem; + font-size: 0.9rem; + white-space: nowrap; + text-overflow: ellipsis; + color: var(--color-surfacedark); + } + } + + .poster-container { + width: 6rem; + + .play-icon-layer { + position: absolute; + top: 20%; + right: 0; + bottom: 20%; + left: 0; + z-index: -2; + display: none; + overflow: visible; + + .play-icon { + display: block; + width: 100%; + height: 100%; + filter: drop-shadow(0 0 0.5rem var(--color-backgroundlight)); + + .play-icon-circle-centered-background { + fill: var(--color-surfacelighter); + } + + .play-icon-circle-centered-icon { + fill: var(--color-primary); + } + } + } + } + + &:not(:last-child) { + border-bottom: thin solid var(--color-surfacelight); + } +} \ No newline at end of file diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/NotificationPlaceholder.js b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/NotificationPlaceholder.js new file mode 100644 index 000000000..3bbac1538 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/NotificationPlaceholder.js @@ -0,0 +1,25 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const Icon = require('stremio-icons/dom'); +const styles = require('./styles'); + +const NotificationPlaceholder = ({ className }) => { + return ( +
+
+
+
+
+
+
+
+
+ ); +}; + +NotificationPlaceholder.propTypes = { + className: PropTypes.string +} + +module.exports = NotificationPlaceholder; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/index.js b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/index.js new file mode 100644 index 000000000..16386c3a1 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/index.js @@ -0,0 +1,3 @@ +const NotificationPlaceholder = require('./NotificationPlaceholder'); + +module.exports = NotificationPlaceholder; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/styles.less b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/styles.less new file mode 100644 index 000000000..7ebcc670c --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationPlaceholder/styles.less @@ -0,0 +1,39 @@ +.notification-placeholder-container { + display: flex; + flex-direction: row; + align-items: center; + padding: 1rem; + + .logo-image-container { + flex: none; + border-radius: 50%; + + .logo-image { + width: 3.5rem; + height: 3.5rem; + background-color: var(--color-placeholder); + } + } + + .info-container { + flex: 1; + padding: 0 1rem; + + .name-container { + width: 100%; + height: 1.2rem; + background: var(--color-placeholder); + } + + .released-container { + width: 50%; + height: 1.2rem; + margin-top: 0.5rem; + background: var(--color-placeholder); + } + } + + &:not(:last-child) { + border-bottom: thin solid var(--color-surfacelight); + } +} \ No newline at end of file diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationsList.js b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationsList.js new file mode 100644 index 000000000..e54bacb68 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/NotificationsList.js @@ -0,0 +1,67 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const Icon = require('stremio-icons/dom'); +const Notification = require('./Notification'); +const NotificationPlaceholder = require('./NotificationPlaceholder'); +const styles = require('./styles'); + +const NotificationsList = ({ className, metaItems }) => { + return ( +
+ { + metaItems.map(({ req, content }, index) => { + switch (content.type) { + case 'Ready': + return ( + content.content.length > 0 ? + content.content.map((notification) => { + //notifications videos are not available in useCatalogs, but in useNotifications hook + notification.videos = notification.videos || [{ + title: 'Demo title', + thumbnail: 'https://www.stremio.com/website/home-testimonials.jpg', + season: 1, + episode: 1, + released: new Date() + }]; + + return ( + + ); + }) + : +
+ +
No new notifications
+
+ ); + case 'Loading': + return ( + + ); + } + }) + } +
+ ); +} + +NotificationsList.propTypes = { + className: PropTypes.string, + metaItems: PropTypes.arrayOf(PropTypes.object) +}; + +module.exports = NotificationsList; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/index.js b/src/common/NavBar/NotificationsMenu/NotificationsList/index.js new file mode 100644 index 000000000..730a78fdc --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/index.js @@ -0,0 +1,3 @@ +const NotificationsList = require('./NotificationsList'); + +module.exports = NotificationsList; diff --git a/src/common/NavBar/NotificationsMenu/NotificationsList/styles.less b/src/common/NavBar/NotificationsMenu/NotificationsList/styles.less new file mode 100644 index 000000000..6d66ab02d --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsList/styles.less @@ -0,0 +1,37 @@ +.notifications-list-scroll-container { + flex: 1; + align-self: stretch; + overflow-y: auto; + background-color: var(--color-surfacelighter); + + .notification { + width: var(--item-size); + } + + .no-notifications-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: var(--item-size); + height: 100%; + padding: 1rem; + + .icon { + width: 6rem; + height: 6rem; + margin-bottom: 2rem; + fill: var(--color-surfacedark); + } + + .label { + font-size: 1.2rem; + text-align: center; + color: var(--color-surfacedark); + } + } + + &:global(::-webkit-scrollbar-track) { + background-color: var(--color-surfacelighter); + } +} \ No newline at end of file diff --git a/src/common/NavBar/NotificationsMenu/NotificationsMenu.js b/src/common/NavBar/NotificationsMenu/NotificationsMenu.js new file mode 100644 index 000000000..14ea95b43 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/NotificationsMenu.js @@ -0,0 +1,48 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const Icon = require('stremio-icons/dom'); +const Button = require('stremio/common/Button'); +const Popup = require('stremio/common/Popup'); +const NotificationsList = require('./NotificationsList'); +const useNotifications = require('./useNotifications'); +const useCatalogs = require('stremio/routes/Board/useCatalogs'); +const useBinaryState = require('stremio/common/useBinaryState'); +const styles = require('./styles'); + +const NotificationsMenu = ({ className, onClearButtonClicked }) => { + const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false); + //TODO use useNotifications hook instead of useCatalogs + const metaItems = useCatalogs(); + + return ( + ( + + )} + renderMenu={() => ( +
+
+
Notifications
+ +
+ +
+ )} + /> + ); +}; + +NotificationsMenu.propTypes = { + className: PropTypes.string, + onClearButtonClicked: PropTypes.func +}; + +module.exports = NotificationsMenu; diff --git a/src/common/NavBar/NotificationsMenu/index.js b/src/common/NavBar/NotificationsMenu/index.js new file mode 100644 index 000000000..a18e804c8 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/index.js @@ -0,0 +1,3 @@ +const NotificationsMenu = require('./NotificationsMenu'); + +module.exports = NotificationsMenu; diff --git a/src/common/NavBar/NotificationsMenu/styles.less b/src/common/NavBar/NotificationsMenu/styles.less new file mode 100644 index 000000000..e1179e5a7 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/styles.less @@ -0,0 +1,68 @@ +.notifications-menu-label-container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + &:hover { + background-color: var(--color-secondary); + } + + &:global(.active) { + background-color: var(--color-background); + } + + .icon { + flex: none; + width: 50%; + height: 50%; + fill: var(--color-surfacelighter); + } + + .notifications-menu-container { + display: flex; + flex-direction: column; + + .notifications-bar { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.5rem; + border-bottom: thin solid var(--color-surfacelight); + + background-color: var(--color-surfacelighter); + + .notifications-label { + padding-left: 0.5rem; + color: var(--color-surfacedark); + } + + .button-container { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + + &:hover, &:focus { + background-color: var(--color-surfacelight); + } + + .icon { + width: 1.2rem; + fill: var(--color-surfacedark); + } + } + } + + .notifications-list { + --item-size: 28rem; + flex-grow: 0; + flex-shrink: 0; + flex-basis: auto; + height: 30rem; + align-self: stretch; + } + } +} \ No newline at end of file diff --git a/src/common/NavBar/NotificationsMenu/useNotifications.js b/src/common/NavBar/NotificationsMenu/useNotifications.js new file mode 100644 index 000000000..83156dd18 --- /dev/null +++ b/src/common/NavBar/NotificationsMenu/useNotifications.js @@ -0,0 +1,26 @@ +const React = require('react'); +const { useServices } = require('stremio/services'); + +const useNotifications = () => { + const [notifications, setNotifications] = React.useState([]); + const { core } = useServices(); + React.useEffect(() => { + const onNewState = () => { + const state = core.getState(); + setNotifications(state.notifications.groups); + }; + core.on('NewModel', onNewState); + core.dispatch({ + action: 'Load', + args: { + load: 'Notifications' + } + }); + return () => { + core.off('NewModel', onNewState); + }; + }, []); + return notifications; +}; + +module.exports = useNotifications; diff --git a/src/common/NavBar/styles.less b/src/common/NavBar/styles.less index 5cb75559e..3c9ff80d2 100644 --- a/src/common/NavBar/styles.less +++ b/src/common/NavBar/styles.less @@ -44,7 +44,7 @@ } } - .addons-button, .fullscreen-button, .nav-menu { + .addons-button, .fullscreen-button, .notifications-menu, .nav-menu { flex: none; width: var(--nav-bar-size); height: var(--nav-bar-size); diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 4884722dd..d7018c9d3 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -56,6 +56,7 @@ const Settings = () => { backButton={true} addonsButton={true} fullscreenButton={true} + notificationsMenu={true} navMenu={true} />
diff --git a/storybook/stories/Notification/MultipleVideosNotification.js b/storybook/stories/Notification/MultipleVideosNotification.js new file mode 100644 index 000000000..4521f0070 --- /dev/null +++ b/storybook/stories/Notification/MultipleVideosNotification.js @@ -0,0 +1,18 @@ +const React = require('react'); +const { storiesOf } = require('@storybook/react'); +const { action } = require('@storybook/addon-actions'); +const Notification = require('stremio/common/NavBar/NotificationsMenu/NotificationsList/Notification'); +const styles = require('./styles'); + +storiesOf('Notification', module).add('MultipleVideos', () => ( + +)); diff --git a/storybook/stories/Notification/SingleVideoNotification.js b/storybook/stories/Notification/SingleVideoNotification.js new file mode 100644 index 000000000..0258ec5d0 --- /dev/null +++ b/storybook/stories/Notification/SingleVideoNotification.js @@ -0,0 +1,20 @@ +const React = require('react'); +const { storiesOf } = require('@storybook/react'); +const { action } = require('@storybook/addon-actions'); +const Notification = require('stremio/common/NavBar/NotificationsMenu/NotificationsList/Notification'); +const styles = require('./styles'); + +storiesOf('Notification', module).add('SingleVideo', () => ( + +)); diff --git a/storybook/stories/Notification/index.js b/storybook/stories/Notification/index.js new file mode 100644 index 000000000..179433820 --- /dev/null +++ b/storybook/stories/Notification/index.js @@ -0,0 +1,2 @@ +require('./SingleVideoNotification'); +require('./MultipleVideosNotification'); diff --git a/storybook/stories/Notification/styles.less b/storybook/stories/Notification/styles.less new file mode 100644 index 000000000..b2af526a0 --- /dev/null +++ b/storybook/stories/Notification/styles.less @@ -0,0 +1,3 @@ +.single-video-notification-container, .multiple-videos-notification-container { + width: 28rem; +} \ No newline at end of file diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 47a0282dd..97f1030cf 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -2,3 +2,4 @@ require('./Addon'); require('./MetaItem'); require('./ColorPicker'); require('./SharePrompt'); +require('./Notification');