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={() => (
+
+ )}
+ />
+ );
+};
+
+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');