Skip to content

Commit

Permalink
add episodes section in about anime page
Browse files Browse the repository at this point in the history
  • Loading branch information
insomniachi committed Jun 17, 2024
1 parent 0d55b9c commit a9b2ff9
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 19 deletions.
9 changes: 9 additions & 0 deletions Totoro.Core/Contracts/IEpisodesInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Totoro.Core.Services.Anizip;

namespace Totoro.Core.Contracts;


public interface IEpisodesInfoProvider
{
IAsyncEnumerable<EpisodeInfo> GetEpisodeInfos(long id, string serviceType);
}
2 changes: 2 additions & 0 deletions Totoro.Core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Totoro.Core.Services;
using Totoro.Core.Services.AniList;
using Totoro.Core.Services.Aniskip;
using Totoro.Core.Services.Anizip;
using Totoro.Core.Services.Debrid;
using Totoro.Core.Services.MediaEvents;
using Totoro.Core.Services.MyAnimeList;
Expand Down Expand Up @@ -57,6 +58,7 @@ public static IServiceCollection AddTotoro(this IServiceCollection services)
services.AddTransient<ISimklService, SimklService>();
services.AddTransient<IVideoStreamResolverFactory, VideoStreamResolverFactory>();
services.AddTransient<IAnimeDetectionService, AnimeDetectionService>();
services.AddTransient<IEpisodesInfoProvider, AnizipEpisodeInfoProvider>();

services.AddTransient<IMediaEventListener, MediaSessionStateStorage>();
services.AddTransient<IMediaEventListener, TrackingUpdater>();
Expand Down
31 changes: 31 additions & 0 deletions Totoro.Core/Services/Anizip/AnizipEpisodeInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json.Nodes;
using Flurl;
using Flurl.Http;

namespace Totoro.Core.Services.Anizip;

public class AnizipEpisodeInfoProvider(TimeProvider timeProvider) : IEpisodesInfoProvider
{
private readonly string _baseUrl = @"https://api.ani.zip/mappings";
private readonly TimeProvider _timeProvider;
private readonly DateTimeOffset _today = timeProvider.GetUtcNow();

public async IAsyncEnumerable<EpisodeInfo> GetEpisodeInfos(long id, string serviceType)
{
var response = await _baseUrl.SetQueryParam(serviceType, id).GetStringAsync();
var jObject = (JsonObject)JsonNode.Parse(response);
var episodesObj = jObject["episodes"].AsObject();

foreach (var property in episodesObj)
{
var model = property.Value.Deserialize<EpisodeInfo>();

if(model.AirDateUtc is null || model.AirDateUtc > _today)
{
continue;
}

yield return model;
}
}
}
46 changes: 46 additions & 0 deletions Totoro.Core/Services/Anizip/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

using System.Text.Json.Serialization;

namespace Totoro.Core.Services.Anizip;

public class EpisodeInfo
{
[JsonPropertyName("seasonNumber")]
public int SeasonNumber { get; set; }

[JsonPropertyName("episodeNumber")]
public int EpisodeNumber { get; set; }

[JsonPropertyName("absoluteEpisodeNumber")]
public int AbsoluteEpisodeNumber { get; set; }

[JsonPropertyName("title")]
public Titles Titles { get; set; }

[JsonPropertyName("overview")]
public string Overview { get; set; }

[JsonPropertyName("image")]
public string Image { get; set; }

[JsonPropertyName("airDate")]
public string AirDate { get; set; }

[JsonPropertyName("runtime")]
public int Runtime { get; set; }

[JsonPropertyName("airDateUtc")]
public DateTime? AirDateUtc { get; set; }
}

public class Titles
{
[JsonPropertyName("ja")]
public string Japanese { get; set; }

[JsonPropertyName("en")]
public string English { get; set; }

[JsonPropertyName("x-jat")]
public string Romaji { get; set; }
}
28 changes: 28 additions & 0 deletions Totoro.Core/ViewModels/About/AnimeEpisodesViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Totoro.Core.Services.Anizip;

namespace Totoro.Core.ViewModels.About;

public class AnimeEpisodesViewModel : BaseAboutAnimeViewModel
{
public AnimeEpisodesViewModel(INavigationService navigationService)
{
Watch = ReactiveCommand.Create<EpisodeInfo>(info =>
{
navigationService.NavigateTo<WatchViewModel>(parameter: new Dictionary<string, object>
{
{ WatchViewModelParamters.Anime, Anime },
{ WatchViewModelParamters.EpisodeNumber, info.EpisodeNumber }
});
});
}

[Reactive] public List<EpisodeInfo> Episodes { get; set; }
public ICommand Watch { get; }

public override Task OnNavigatedTo(IReadOnlyDictionary<string, object> parameters)
{
Episodes = (List<EpisodeInfo>)parameters.GetValueOrDefault(nameof(Episodes), new List<EpisodeInfo>());

return base.OnNavigatedTo(parameters);
}
}
70 changes: 51 additions & 19 deletions Totoro.Core/ViewModels/AboutAnimeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using Totoro.Core.ViewModels.About;
using Totoro.Plugins;
using Totoro.Plugins.Anime.Contracts;
using Totoro.Plugins.Contracts;
using Totoro.Plugins.Torrents.Contracts;

namespace Totoro.Core.ViewModels;

Expand All @@ -15,14 +13,11 @@ public class AboutAnimeViewModel : NavigatableViewModel
public ObservableCollection<PivotItemModel> Pages { get; }

public AboutAnimeViewModel(IAnimeServiceContext animeService,
IViewService viewService,
IAnimeSoundsService animeSoundService,
IPluginFactory<ITorrentTracker> torrentCatalogFactory,
ISettings settings,
IMyAnimeListService myAnimeListService,
IAnimeIdService animeIdService,
IDebridServiceContext debridServiceContext,
ISimklService simklService)
ISimklService simklService,
IEpisodesInfoProvider episodesInfoProvider)
{
_sectionsList
.Connect()
Expand All @@ -39,6 +34,11 @@ public AboutAnimeViewModel(IAnimeServiceContext animeService,
ViewModel = typeof(PreviewsViewModel)
});
_sectionsList.Add(new PivotItemModel
{
Header = "Episodes",
ViewModel = typeof(AnimeEpisodesViewModel)
});
_sectionsList.Add(new PivotItemModel
{
Header = "Related",
ViewModel = typeof(AnimeCardListViewModel),
Expand Down Expand Up @@ -68,41 +68,57 @@ public AboutAnimeViewModel(IAnimeServiceContext animeService,
this.WhenAnyValue(x => x.Anime)
.WhereNotNull()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(anime =>
.Subscribe(async anime =>
{
var previewsItem = _sectionsList.Items.ElementAt(0);
var relatedItem = _sectionsList.Items.ElementAt(1);
var recommendedItem = _sectionsList.Items.ElementAt(2);
var ostsItem = _sectionsList.Items.ElementAt(3);
var torrentsItem = _sectionsList.Items.ElementAt(4);
var episodesItem = _sectionsList.Items.ElementAt(1);
var relatedItem = _sectionsList.Items.ElementAt(2);
var recommendedItem = _sectionsList.Items.ElementAt(3);
var ostsItem = _sectionsList.Items.ElementAt(4);
var torrentsItem = _sectionsList.Items.ElementAt(5);
var sounds = animeSoundService.GetThemes(anime.Id);
var episodes = await episodesInfoProvider.GetEpisodeInfos(anime.Id, GetServiceName(settings.DefaultListService)).ToListAsync();

if (episodes.FirstOrDefault() is { EpisodeNumber: > 1 } first)
{
var offset = first.EpisodeNumber - 1;
foreach (var episode in episodes)
{
episode.EpisodeNumber -= offset;
}
}

previewsItem.NavigationParameters = new()
{
[nameof(PreviewsViewModel.Anime)] = anime
};
relatedItem.NavigationParameters = new()
episodesItem.NavigationParameters = new()
{
[nameof(AnimeCardListViewModel.Anime)] = anime.Related.ToList()
[nameof(AnimeEpisodesViewModel.Anime)] = anime,
[nameof(AnimeEpisodesViewModel.Episodes)] = episodes
};
recommendedItem.NavigationParameters = new()
relatedItem.NavigationParameters = new()
{
[nameof(AnimeCardListViewModel.Anime)] = anime.Related.ToList()
};
recommendedItem.NavigationParameters = new()
{
[nameof(AnimeCardListViewModel.Anime)] = anime.Recommended.ToList()
};
ostsItem.NavigationParameters = new()
ostsItem.NavigationParameters = new()
{
[nameof(OriginalSoundTracksViewModel.Sounds)] = sounds
[nameof(OriginalSoundTracksViewModel.Sounds)] = sounds
};
torrentsItem.NavigationParameters = new()
{
[nameof(AnimeEpisodesTorrentViewModel.Anime)] = anime
};

if (anime.Videos is not { Count: > 0})
if (anime.Videos is not { Count: > 0 })
{
previewsItem.Visible = false;
}
if(anime.Related is not { Length : > 0})
if (anime.Related is not { Length: > 0 })
{
relatedItem.Visible = false;
}
Expand All @@ -114,6 +130,10 @@ public AboutAnimeViewModel(IAnimeServiceContext animeService,
{
ostsItem.Visible = false;
}
if (episodes is not { Count: > 0 })
{
episodesItem.Visible = false;
}

SelectedSection = null;
SelectedSection = Sections.FirstOrDefault();
Expand Down Expand Up @@ -192,6 +212,18 @@ public override Task OnNavigatedTo(IReadOnlyDictionary<string, object> parameter
return Task.CompletedTask;
}

private static string GetServiceName(ListServiceType type)
{
return type switch
{
ListServiceType.MyAnimeList => @"mal_id",
ListServiceType.AniList => @"anilist_id",
ListServiceType.AniDb => @"anidb_id",
ListServiceType.Kitsu => @"kitsu_id",
_ => throw new NotSupportedException()
};
}

}

public class PivotItemModel : ReactiveObject
Expand Down
5 changes: 5 additions & 0 deletions Totoro.Core/ViewModels/WatchViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class WatchViewModelParamters
public const string TorrentManager = "TorrentManager";
public const string Anime = "Anime";
public const string EpisodeInfo = "EpisodeInfo";
public const string EpisodeNumber = "EpisodeNumber";
public const string AnimeId = "Id";
public const string SearchResult = "SearchResult";
public const string UseDebrid = "UseDebrid";
Expand Down Expand Up @@ -352,6 +353,10 @@ public override async Task OnNavigatedTo(IReadOnlyDictionary<string, object> par
_isCrunchyroll = _settings.DefaultProviderType == "consumet" && _providerOptions.GetString("Provider", "zoro") == "crunchyroll";
SelectedAudioStream = GetDefaultAudioStream();

if (parameters.ContainsKey(WatchViewModelParamters.EpisodeNumber))
{
_episodeRequest = (int)parameters[WatchViewModelParamters.EpisodeNumber];
}
if (parameters.ContainsKey(WatchViewModelParamters.Anime))
{
var anime = parameters[WatchViewModelParamters.Anime] as AnimeModel;
Expand Down
1 change: 1 addition & 0 deletions Totoro.WinUI/Behaviors/SelectorBarBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ protected override void OnAttached()

this.WhenAnyValue(x => x.SelectedItem)
.Where(_ => AssociatedObject is not null)
.WhereNotNull()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(item =>
{
Expand Down
1 change: 1 addition & 0 deletions Totoro.WinUI/Helpers/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ private static IServiceCollection AddChildPages(this IServiceCollection services
// About Anime
services.AddPageForNavigation<AnimeCardListViewModel, AnimeCardListSection>();
services.AddPageForNavigation<PreviewsViewModel, PreviewsSection>();
services.AddPageForNavigation<AnimeEpisodesViewModel, EpisodesSection>();
services.AddPageForNavigation<OriginalSoundTracksViewModel, OriginalSoundTracksSection>();
services.AddPageForNavigation<AnimeEpisodesTorrentViewModel, EpisodesTorrentsSection>();

Expand Down
4 changes: 4 additions & 0 deletions Totoro.WinUI/Totoro.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Views\AboutSections\EpisodesSection.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\DiscoverSections\MyAnimeListDiscoverSection.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
Expand Down
67 changes: 67 additions & 0 deletions Totoro.WinUI/Views/AboutSections/EpisodesSection.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8" ?>
<local:EpisodesSectionBase
x:Class="Totoro.WinUI.Views.AboutSections.EpisodesSection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cm="using:Totoro.Core.Services.Anizip"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Totoro.WinUI.Views.AboutSections"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:root="using:Totoro.WinUI"
xmlns:uc="using:Totoro.WinUI.UserControls"
xmlns:ctk="using:CommunityToolkit.WinUI.Controls"
mc:Ignorable="d">

<ItemsView
x:Name="EpisodesView"
Margin="{StaticResource LargeTopMargin}"
IsItemInvokedEnabled="True"
ItemsSource="{x:Bind ViewModel.Episodes, Mode=OneWay}"
ItemInvoked="EpisodesView_ItemInvoked"
SelectionMode="None">
<ItemsView.ItemTemplate>
<DataTemplate x:DataType="cm:EpisodeInfo">
<ItemContainer Background="{ThemeResource CardBackgroundFillColorDefault}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>

<ctk:ConstrainedBox AspectRatio="16:9">
<uc:ImageEx Source="{x:Bind Image}"
Stretch="UniformToFill"
Width="360"
PlaceholderSource="/Assets/placeholder.jpg"
PlaceholderStretch="Fill"/>
</ctk:ConstrainedBox>

<StackPanel Grid.Column="1" Padding="10" Spacing="5">
<TextBlock Style="{ThemeResource SubtitleTextBlockStyle}"
Margin="0 0 0 8">
<Run Text="Episode" Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<Run Text="{x:Bind EpisodeNumber}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<Run Text=":" Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<Run Text="{x:Bind Titles.English}"/>
</TextBlock>
<StackPanel Orientation="Horizontal" Spacing="5" Margin="0 0 0 8">
<SymbolIcon Symbol="CalendarDay"/>
<TextBlock Text="{x:Bind AirDate}" Margin="0 0 16 0"/>
<SymbolIcon Symbol="Clock"/>
<TextBlock>
<Run Text="{x:Bind Runtime}"/>
<Run Text="Min"/>
</TextBlock>
</StackPanel>
<TextBlock Text="{x:Bind Overview}" Style="{ThemeResource BodyTextBlockStyle}" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
</ItemContainer>
</DataTemplate>
</ItemsView.ItemTemplate>
<ItemsView.Layout>
<StackLayout Spacing="10"/>
</ItemsView.Layout>
</ItemsView>

</local:EpisodesSectionBase>
Loading

0 comments on commit a9b2ff9

Please sign in to comment.