Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Notifications and Continue watching #494

Merged
merged 20 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 52 additions & 33 deletions src/models/continue_watching_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,27 @@ use crate::{
},
};

#[derive(Clone, Serialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Item {
#[serde(flatten)]
pub library_item: LibraryItem,
/// a count of the total notifications we have for this item
pub notifications: usize,
}

/// The continue watching section in the app
#[derive(Default, Clone, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ContinueWatchingPreview {
pub library_items: Vec<LibraryItem>,
pub items: Vec<Item>,
}

impl ContinueWatchingPreview {
pub fn new(library: &LibraryBucket, notifications: &NotificationsBucket) -> (Self, Effects) {
let mut library_items = vec![];
let effects = library_items_update(&mut library_items, library, notifications);
(Self { library_items }, effects.unchanged())
let mut items = vec![];
let effects = library_items_update(&mut items, library, notifications);
(Self { items }, effects.unchanged())
}
}

Expand All @@ -37,70 +46,80 @@ impl<E: Env + 'static> UpdateWithCtx<E> for ContinueWatchingPreview {
Msg::Internal(Internal::LibraryChanged(true))
// notifications have been updated
| Msg::Internal(Internal::NotificationsChanged) => {
library_items_update(&mut self.library_items, &ctx.library, &ctx.notifications)
library_items_update(&mut self.items, &ctx.library, &ctx.notifications)
}
_ => Effects::none().unchanged(),
}
}
}

fn library_items_update(
library_items: &mut Vec<LibraryItem>,
cw_items: &mut Vec<Item>,
library: &LibraryBucket,
notifications: &NotificationsBucket,
) -> Effects {
let next_library_items = library
let next_cw_items = library
.items
.values()
.filter(|library_item| {
.filter_map(|library_item| {
let library_notification = notifications
.items
.get(&library_item.id)
.filter(|meta_notifs| !meta_notifs.is_empty());

// either the library item is in CW
library_item.is_in_continue_watching()
if library_item.is_in_continue_watching()
// or there's a new notification for it
|| notifications
.items
.get(&library_item.id)
.filter(|meta_notifs| !meta_notifs.is_empty())
.is_some()
|| library_notification.is_some()
{
Some((
library_item,
library_notification
.map(|notifs| notifs.len())
.unwrap_or_default(),
))
} else {
None
}
})
// either take the oldest video released date or the modification date of the LibraryItem
.sorted_by(|a, b| {
.sorted_by(|(item_a, _), (item_b, _)| {
let a_time = notifications
.items
.get(&a.id)
.get(&item_a.id)
.and_then(|notifs| {
notifs
.values()
// take the released date of the video if there is one, or skip this notification
.filter_map(|notification| notification.video.released)
.sorted_by(|a_released, b_released| {
// order by the oldest video released!
b_released.cmp(a_released).reverse()
})
// take the video released date of the notification
.map(|notification| notification.video_released)
// order by the newest video released!
.sorted_by(|a_released, b_released| b_released.cmp(a_released))
.next()
})
.unwrap_or(a.mtime);
.unwrap_or(item_a.mtime);

let b_time = notifications
.items
.get(&b.id)
.get(&item_b.id)
.and_then(|notifs| {
notifs
.values()
// take the released date of the video if there is one, or skip this notification
.filter_map(|notification| notification.video.released)
.sorted_by(|a_released, b_released| {
// order by the oldest video released!
b_released.cmp(a_released).reverse()
})
// take the video released date of the notification
.map(|notification| notification.video_released)
// order by the newest video released!
.sorted_by(|a_released, b_released| b_released.cmp(a_released))
.next()
})
.unwrap_or(b.mtime);
.unwrap_or(item_b.mtime);

b_time.cmp(&a_time)
})
.take(CATALOG_PREVIEW_SIZE)
.cloned()
.map(|(library_item, notifications)| Item {
library_item: library_item.clone(),
notifications,
})
.collect::<Vec<_>>();

eq_update(library_items, next_library_items)
eq_update(cw_items, next_cw_items)
}
11 changes: 7 additions & 4 deletions src/models/ctx/update_library.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
use std::{collections::HashMap, marker::PhantomData};

use futures::{
future::{self, Either},
FutureExt, TryFutureExt,
};

use crate::constants::{
LIBRARY_COLLECTION_NAME, LIBRARY_RECENT_COUNT, LIBRARY_RECENT_STORAGE_KEY, LIBRARY_STORAGE_KEY,
};
Expand All @@ -10,10 +17,6 @@ use crate::types::api::{
};
use crate::types::library::{LibraryBucket, LibraryBucketRef, LibraryItem};
use crate::types::profile::{AuthKey, Profile};
use futures::future::Either;
use futures::{future, FutureExt, TryFutureExt};
use std::collections::HashMap;
use std::marker::PhantomData;

pub fn update_library<E: Env + 'static>(
library: &mut LibraryBucket,
Expand Down
59 changes: 34 additions & 25 deletions src/models/ctx/update_notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ pub fn update_notifications<E: Env + 'static>(
.join(notification_items_effects)
.unchanged()
}
Msg::Action(Action::Ctx(ActionCtx::DismissNotificationItem(id))) => {
remove_notification_item(notifications, id)
}
Msg::Action(Action::Ctx(ActionCtx::DismissNotificationItem(id))) => Effects::msg(
Msg::Internal(Internal::DismissNotificationItem(id.to_owned())),
)
.unchanged(),
Msg::Action(Action::Ctx(ActionCtx::Logout)) | Msg::Internal(Internal::Logout) => {
let notification_catalogs_effects = eq_update(notification_catalogs, vec![]);
let next_notifications = NotificationsBucket::new::<E>(profile.uid(), vec![]);
Expand Down Expand Up @@ -135,7 +136,7 @@ pub fn update_notifications<E: Env + 'static>(
.join(notifications_effects)
}
Msg::Internal(Internal::DismissNotificationItem(id)) => {
remove_notification_item(notifications, id)
dismiss_notification_item(notifications, id)
}
Msg::Internal(Internal::NotificationsChanged) => {
Effects::one(push_notifications_to_storage::<E>(notifications)).unchanged()
Expand Down Expand Up @@ -200,35 +201,43 @@ fn update_notification_items<E: Env + 'static>(
// meta items videos
meta_item
.videos_iter()
.filter(|video| {
match (&library_item.state.last_watched, &video.released) {
.filter_map(|video| {
match (&library_item.state.last_watched, video.released) {
(Some(last_watched), Some(video_released)) => {
last_watched < video_released &&
if last_watched < &video_released &&
// exclude future videos (i.e. that will air in the future)
video_released <= &E::now()
video_released <= E::now()
{
Some((&library_item.id, &video.id, video_released))
} else {
None
}
}
_ => false,
_ => None,
}
})
// We need to manually fold, otherwise the last seen element with a given key
// will be present in the final HashMap instead of the first occurrence.
.fold(&mut meta_notifs, |meta_notifs, video| {
let notif_entry = meta_notifs.entry(video.id.clone());
.fold(
&mut meta_notifs,
|meta_notifs, (meta_id, video_id, video_released)| {
let notif_entry = meta_notifs.entry(video_id.to_owned());

// for now just skip same videos that already exist
// leave the first one found in the Vec.
if let Entry::Vacant(new) = notif_entry {
let notification = NotificationItem {
meta_id: meta_id.to_owned(),
video_id: video.id.to_owned(),
video: video.to_owned(),
};
// for now just skip same videos that already exist
// leave the first one found in the Vec.
if let Entry::Vacant(new) = notif_entry {
let notification = NotificationItem {
meta_id: meta_id.to_owned(),
video_id: video_id.to_owned(),
video_released,
};

new.insert(notification);
}
new.insert(notification);
}

meta_notifs
});
meta_notifs
},
);

// if not videos were added and the hashmap is empty, just remove the MetaItem record all together
if meta_notifs.is_empty() {
Expand Down Expand Up @@ -257,9 +266,9 @@ fn push_notifications_to_storage<E: Env + 'static>(notifications: &Notifications
.into()
}

fn remove_notification_item(notifications: &mut NotificationsBucket, id: &String) -> Effects {
fn dismiss_notification_item(notifications: &mut NotificationsBucket, id: &str) -> Effects {
match notifications.items.remove(id) {
Some(_) => Effects::msg(Msg::Internal(Internal::NotificationsChanged)).unchanged(),
Some(_) => Effects::msg(Msg::Internal(Internal::NotificationsChanged)),
_ => Effects::none().unchanged(),
}
}
32 changes: 24 additions & 8 deletions src/models/library_by_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::models::library_with_filters::{LibraryFilter, Sort};
use crate::runtime::msg::{Action, ActionLibraryByType, ActionLoad, Internal, Msg};
use crate::runtime::{Effects, Env, UpdateWithCtx};
use crate::types::library::{LibraryBucket, LibraryItem};
use crate::types::notifications::NotificationsBucket;
use derivative::Derivative;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -65,8 +66,12 @@ impl<E: Env + 'static, F: LibraryFilter> UpdateWithCtx<E> for LibraryByType<F> {
Msg::Action(Action::Load(ActionLoad::LibraryByType(selected))) => {
let selected_effects = eq_update(&mut self.selected, Some(selected.to_owned()));
let selectable_effects = selectable_update(&mut self.selectable, &self.selected);
let catalogs_effects =
catalogs_update::<F>(&mut self.catalogs, &self.selected, &ctx.library);
let catalogs_effects = catalogs_update::<F>(
&mut self.catalogs,
&self.selected,
&ctx.library,
&ctx.notifications,
);
selected_effects
.join(selectable_effects)
.join(catalogs_effects)
Expand All @@ -90,7 +95,13 @@ impl<E: Env + 'static, F: LibraryFilter> UpdateWithCtx<E> for LibraryByType<F> {
.map(|library_item| &library_item.r#type)
.expect("first page of library catalog is empty");
let skip = catalog.iter().fold(0, |result, page| result + page.len());
let page = next_page::<F>(r#type, skip, &self.selected, &ctx.library);
let page = next_page::<F>(
r#type,
skip,
&self.selected,
&ctx.library,
&ctx.notifications,
);
catalog.push(page);
Effects::none()
}
Expand All @@ -99,9 +110,12 @@ impl<E: Env + 'static, F: LibraryFilter> UpdateWithCtx<E> for LibraryByType<F> {
_ => Effects::none().unchanged(),
}
}
Msg::Internal(Internal::LibraryChanged(_)) => {
catalogs_update::<F>(&mut self.catalogs, &self.selected, &ctx.library)
}
Msg::Internal(Internal::LibraryChanged(_)) => catalogs_update::<F>(
&mut self.catalogs,
&self.selected,
&ctx.library,
&ctx.notifications,
),
_ => Effects::none().unchanged(),
}
}
Expand All @@ -127,6 +141,7 @@ fn catalogs_update<F: LibraryFilter>(
catalogs: &mut Vec<Catalog>,
selected: &Option<Selected>,
library: &LibraryBucket,
notifications: &NotificationsBucket,
) -> Effects {
let catalogs_size = catalogs.iter().fold(HashMap::new(), |mut result, catalog| {
let r#type = catalog
Expand All @@ -142,7 +157,7 @@ fn catalogs_update<F: LibraryFilter>(
Some(selected) => library
.items
.values()
.filter(|library_item| F::predicate(library_item))
.filter(|library_item| F::predicate(library_item, notifications))
.fold(HashMap::new(), |mut result, library_item| {
result
.entry(&library_item.r#type)
Expand Down Expand Up @@ -187,12 +202,13 @@ fn next_page<F: LibraryFilter>(
skip: usize,
selected: &Option<Selected>,
library: &LibraryBucket,
notifications: &NotificationsBucket,
) -> CatalogPage {
match selected {
Some(selected) => library
.items
.values()
.filter(|library_item| F::predicate(library_item))
.filter(|library_item| F::predicate(library_item, notifications))
.filter(|library_item| library_item.r#type == *r#type)
.sorted_by(|a, b| match &selected.sort {
Sort::LastWatched => b.state.last_watched.cmp(&a.state.last_watched),
Expand Down
Loading