Browse Source

Added news notification

Krzysztof Krysiński 2 years ago
parent
commit
af5ca59352

+ 3 - 1
src/PixiEditor.Extensions/Common/UserPreferences/PreferencesConstants.cs

@@ -7,5 +7,7 @@ public static class PreferencesConstants
 
 
     public const string MaxOpenedRecently = "MaxOpenedRecently";
     public const string MaxOpenedRecently = "MaxOpenedRecently";
     public const int MaxOpenedRecentlyDefault = 8;
     public const int MaxOpenedRecentlyDefault = 8;
-    public const string HideNewsPanel = "HideNewsPanel";
+    public const string DisableNewsPanel = "DisableNewsPanel";
+    public const string LastCheckedNewsIds = "LastCheckedNewsIds";
+    public const string NewsPanelCollapsed = "NewsPanelCollapsed";
 }
 }

BIN
src/PixiEditor/Images/Chevron-right.png


+ 19 - 5
src/PixiEditor/Models/Services/NewsFeed/News.cs

@@ -1,4 +1,7 @@
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
 using PixiEditor.Extensions.Helpers;
 using PixiEditor.Extensions.Helpers;
 
 
@@ -20,12 +23,23 @@ internal enum NewsType
 
 
 internal record News
 internal record News
 {
 {
-    public string Title { get; set; }
-    public NewsType NewsType { get; set; } = NewsType.Misc;
-    public string Url { get; set; }
-    public DateTime Date { get; set; }
-    public string CoverImageUrl { get; set; }
+    public string Title { get; init; } = string.Empty;
+    public NewsType NewsType { get; init; } = NewsType.Misc;
+    public string Url { get; init; }
+    public DateTime Date { get; init; }
+    public string CoverImageUrl { get; init; } = string.Empty;
 
 
     [JsonIgnore]
     [JsonIgnore]
     public string ResolvedIconUrl => $"/Images/News/{NewsType.GetDescription()}";
     public string ResolvedIconUrl => $"/Images/News/{NewsType.GetDescription()}";
+
+    [JsonIgnore]
+    public bool IsNew { get; set; } = false;
+
+    public int GetIdentifyingNumber()
+    {
+        MD5 md5Hasher = MD5.Create();
+        string data = Title + Url + Date.ToString(CultureInfo.InvariantCulture) + CoverImageUrl;
+        var hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(data));
+        return BitConverter.ToInt32(hashed, 0);
+    }
 }
 }

+ 31 - 3
src/PixiEditor/Models/Services/NewsFeed/NewsProvider.cs

@@ -1,6 +1,7 @@
 using System.Net;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
+using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Platform;
 using PixiEditor.Platform;
 using PixiEditor.UpdateModule;
 using PixiEditor.UpdateModule;
 
 
@@ -8,17 +9,28 @@ namespace PixiEditor.Models.Services.NewsFeed;
 
 
 internal class NewsProvider
 internal class NewsProvider
 {
 {
+    private const int MaxNewsCount = 20;
     private const string FeedUrl = "https://raw.githubusercontent.com/PixiEditor/news-feed/main/";
     private const string FeedUrl = "https://raw.githubusercontent.com/PixiEditor/news-feed/main/";
+
+    private List<int> _lastCheckedIds = new List<int>();
+
+    public NewsProvider()
+    {
+        _lastCheckedIds = IPreferences.Current.GetPreference(PreferencesConstants.LastCheckedNewsIds, new List<int>());
+    }
+
     public async Task<List<News>?> FetchNewsAsync()
     public async Task<List<News>?> FetchNewsAsync()
     {
     {
         List<News> allNews = new List<News>();
         List<News> allNews = new List<News>();
         await FetchFrom(allNews, "shared.json");
         await FetchFrom(allNews, "shared.json");
         await FetchFrom(allNews, $"{IPlatform.Current.Id}.json");
         await FetchFrom(allNews, $"{IPlatform.Current.Id}.json");
 
 
-        return allNews.OrderByDescending(x => x.Date).Take(20).ToList();
+        var sorted = allNews.OrderByDescending(x => x.Date).Take(MaxNewsCount).ToList();
+        MarkNewOnes(sorted);
+        return sorted;
     }
     }
 
 
-    private static async Task FetchFrom(List<News> output, string fileName)
+    private async Task FetchFrom(List<News> output, string fileName)
     {
     {
         using HttpClient client = new HttpClient();
         using HttpClient client = new HttpClient();
         client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
         client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
@@ -27,9 +39,25 @@ internal class NewsProvider
         {
         {
             string content = await response.Content.ReadAsStringAsync();
             string content = await response.Content.ReadAsStringAsync();
             var list = JsonConvert.DeserializeObject<List<News>>(content);
             var list = JsonConvert.DeserializeObject<List<News>>(content);
+            output.AddRange(list);
+        }
+    }
+
+    private void MarkNewOnes(List<News> list)
+    {
+        foreach (var news in list)
+        {
+            if (news.GetIdentifyingNumber() is var num && !_lastCheckedIds.Contains(num))
             {
             {
-                output.AddRange(list);
+                news.IsNew = true;
+                _lastCheckedIds.Add(num);
+                if (_lastCheckedIds.Count > MaxNewsCount)
+                {
+                    _lastCheckedIds.RemoveAt(0);
+                }
             }
             }
         }
         }
+
+        IPreferences.Current.UpdatePreference(PreferencesConstants.LastCheckedNewsIds, _lastCheckedIds);
     }
     }
 }
 }

+ 2 - 0
src/PixiEditor/PixiEditor.csproj

@@ -453,6 +453,8 @@
 		<Resource Include="Images\News\NewVersion.png" />
 		<Resource Include="Images\News\NewVersion.png" />
 		<None Remove="Images\News\OfficialAnnouncement.png" />
 		<None Remove="Images\News\OfficialAnnouncement.png" />
 		<Resource Include="Images\News\OfficialAnnouncement.png" />
 		<Resource Include="Images\News\OfficialAnnouncement.png" />
+		<None Remove="Images\Chevron-right.png" />
+		<Resource Include="Images\Chevron-right.png" />
 	</ItemGroup>
 	</ItemGroup>
 	<ItemGroup>
 	<ItemGroup>
 		<None Include="..\LICENSE">
 		<None Include="..\LICENSE">

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/FileSettings.cs

@@ -47,7 +47,7 @@ internal class FileSettings : SettingsGroup
         set => RaiseAndUpdatePreference(ref maxOpenedRecently, value);
         set => RaiseAndUpdatePreference(ref maxOpenedRecently, value);
     }
     }
 
 
-    private bool hideNewsPanel = GetPreference(PreferencesConstants.HideNewsPanel, false);
+    private bool hideNewsPanel = GetPreference(PreferencesConstants.DisableNewsPanel, false);
 
 
     public bool HideNewsPanel
     public bool HideNewsPanel
     {
     {

+ 37 - 3
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -16,7 +16,7 @@
         xmlns:newsFeed="clr-namespace:PixiEditor.Views.UserControls.NewsFeed"
         xmlns:newsFeed="clr-namespace:PixiEditor.Views.UserControls.NewsFeed"
         xmlns:gif="http://wpfanimatedgif.codeplex.com"
         xmlns:gif="http://wpfanimatedgif.codeplex.com"
         mc:Ignorable="d" ShowInTaskbar="False"
         mc:Ignorable="d" ShowInTaskbar="False"
-        Title="Hello there!" Height="662" Width="832" MinHeight="500" MinWidth="500"
+        Title="Hello there!" Height="662" Width="982" MinHeight="500" MinWidth="500"
         d:DataContext="{d:DesignInstance local:HelloTherePopup}"
         d:DataContext="{d:DesignInstance local:HelloTherePopup}"
         WindowStyle="None" WindowStartupLocation="CenterScreen" Loaded="HelloTherePopup_OnLoaded"
         WindowStyle="None" WindowStartupLocation="CenterScreen" Loaded="HelloTherePopup_OnLoaded"
         FlowDirection="{helpers:Localization FlowDirection}">
         FlowDirection="{helpers:Localization FlowDirection}">
@@ -63,7 +63,40 @@
                     <RowDefinition Height="Auto"/>
                     <RowDefinition Height="Auto"/>
                 </Grid.RowDefinitions>
                 </Grid.RowDefinitions>
 
 
-                <StackPanel HorizontalAlignment="Center">
+                <Grid Grid.RowSpan="3" HorizontalAlignment="Right" VerticalAlignment="Center">
+                    <CheckBox Visibility="{Binding NewsDisabled, Converter={converters:InverseBoolToVisibilityConverter}}"
+                              Width="40" Height="40" IsChecked="{Binding NewsPanelCollapsed}">
+                    <CheckBox.Template>
+                        <ControlTemplate TargetType="{x:Type CheckBox}">
+                            <StackPanel Orientation="Horizontal" Focusable="False">
+                                <StackPanel.Background>
+                                    <VisualBrush>
+                                        <VisualBrush.Visual>
+                                            <Ellipse Fill="{StaticResource MainColor}" Width="20" Height="20"/>
+                                        </VisualBrush.Visual>
+                                    </VisualBrush>
+                                </StackPanel.Background>
+                                <Image Focusable="False" Cursor="Hand" x:Name="checkboxImage" Source="/Images/Chevron-right.png">
+                                    <Image.RenderTransform>
+                                        <RotateTransform Angle="180" CenterX="19" CenterY="20"/>
+                                    </Image.RenderTransform>
+                                </Image>
+                                <ContentPresenter Focusable="False"/>
+                            </StackPanel>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsChecked" Value="True">
+                                    <Setter TargetName="checkboxImage" Property="RenderTransform">
+                                        <Setter.Value>
+                                            <RotateTransform Angle="0" CenterX="1" CenterY="0"/>
+                                        </Setter.Value>
+                                    </Setter>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </CheckBox.Template>
+                </CheckBox>
+                </Grid>
+                <StackPanel Grid.Row="0" HorizontalAlignment="Center">
                     <StackPanel Orientation="Horizontal">
                     <StackPanel Orientation="Horizontal">
                         <Image Source="../../Images/PixiEditorLogo.png" Height="40" VerticalAlignment="Center"/>
                         <Image Source="../../Images/PixiEditorLogo.png" Height="40" VerticalAlignment="Center"/>
                         <TextBlock FontSize="40" FontWeight="SemiBold" VerticalAlignment="Center" Margin="10,0,0,0">PixiEditor</TextBlock>
                         <TextBlock FontSize="40" FontWeight="SemiBold" VerticalAlignment="Center" Margin="10,0,0,0">PixiEditor</TextBlock>
@@ -282,7 +315,8 @@
             </Grid>
             </Grid>
         </ScrollViewer>
         </ScrollViewer>
 
 
-        <ScrollViewer Grid.Row="1" Grid.Column="1">
+        <ScrollViewer Grid.Row="1" Grid.Column="1"
+                      Visibility="{Binding NewsPanelCollapsed, Converter={converters:InverseBoolToVisibilityConverter}}">
             <Border Padding="5" BorderThickness="3 0 0 0" BorderBrush="{StaticResource MainColor}">
             <Border Padding="5" BorderThickness="3 0 0 0" BorderBrush="{StaticResource MainColor}">
                 <Grid>
                 <Grid>
                     <Image gif:ImageBehavior.AnimatedSource="/Images/Processing.gif" HorizontalAlignment="Center" VerticalAlignment="Center"
                     <Image gif:ImageBehavior.AnimatedSource="/Images/Processing.gif" HorizontalAlignment="Center" VerticalAlignment="Center"

+ 44 - 5
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -28,6 +28,15 @@ internal partial class HelloTherePopup : Window
     public static readonly DependencyProperty IsFetchingNewsProperty = DependencyProperty.Register(
     public static readonly DependencyProperty IsFetchingNewsProperty = DependencyProperty.Register(
         nameof(IsFetchingNews), typeof(bool), typeof(HelloTherePopup), new PropertyMetadata(default(bool)));
         nameof(IsFetchingNews), typeof(bool), typeof(HelloTherePopup), new PropertyMetadata(default(bool)));
 
 
+    public static readonly DependencyProperty NewsPanelCollapsedProperty = DependencyProperty.Register(
+        nameof(NewsPanelCollapsed), typeof(bool), typeof(HelloTherePopup),
+        new PropertyMetadata(false, NewsPanelCollapsedChangedCallback));
+
+    public bool NewsPanelCollapsed
+    {
+        get { return (bool)GetValue(NewsPanelCollapsedProperty); }
+        set { SetValue(NewsPanelCollapsedProperty, value); }
+    }
     public bool IsFetchingNews
     public bool IsFetchingNews
     {
     {
         get { return (bool)GetValue(IsFetchingNewsProperty); }
         get { return (bool)GetValue(IsFetchingNewsProperty); }
@@ -64,6 +73,8 @@ internal partial class HelloTherePopup : Window
 
 
     private NewsProvider NewsProvider { get; set; }
     private NewsProvider NewsProvider { get; set; }
 
 
+    public bool NewsDisabled => _newsDisabled;
+
     public bool ShowDonateButton => // Steam doesn't allow external donations :(
     public bool ShowDonateButton => // Steam doesn't allow external donations :(
 #if STEAM
 #if STEAM
         false;
         false;
@@ -71,7 +82,7 @@ internal partial class HelloTherePopup : Window
         true;
         true;
 #endif
 #endif
 
 
-    private bool _newsHidden = false;
+    private bool _newsDisabled = false;
 
 
     public HelloTherePopup(FileViewModel fileViewModel)
     public HelloTherePopup(FileViewModel fileViewModel)
     {
     {
@@ -87,7 +98,7 @@ internal partial class HelloTherePopup : Window
         RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
         RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
         RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
         RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
 
 
-        _newsHidden = IPreferences.Current.GetPreference<bool>(PreferencesConstants.HideNewsPanel);
+        _newsDisabled = IPreferences.Current.GetPreference<bool>(PreferencesConstants.DisableNewsPanel);
 
 
         NewsProvider = new NewsProvider();
         NewsProvider = new NewsProvider();
 
 
@@ -95,9 +106,12 @@ internal partial class HelloTherePopup : Window
 
 
         InitializeComponent();
         InitializeComponent();
 
 
-        int newsWidth = 200;
 
 
-        if (_newsHidden)
+        int newsWidth = 300;
+
+        NewsPanelCollapsed = IPreferences.Current.GetPreference<bool>(PreferencesConstants.NewsPanelCollapsed);
+
+        if (_newsDisabled || NewsPanelCollapsed)
         {
         {
             newsColumn.Width = new GridLength(0);
             newsColumn.Width = new GridLength(0);
             newsWidth = 0;
             newsWidth = 0;
@@ -120,6 +134,27 @@ internal partial class HelloTherePopup : Window
         }
         }
     }
     }
 
 
+    private static void NewsPanelCollapsedChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        HelloTherePopup helloTherePopup = (HelloTherePopup)d;
+
+        if(helloTherePopup._newsDisabled || e.NewValue is not bool newValue)
+            return;
+
+        if (newValue)
+        {
+            helloTherePopup.Width -= 300;
+            helloTherePopup.newsColumn.Width = new GridLength(0);
+        }
+        else
+        {
+            helloTherePopup.Width += 300;
+            helloTherePopup.newsColumn.Width = new GridLength(300);
+        }
+
+        IPreferences.Current.UpdatePreference(PreferencesConstants.NewsPanelCollapsed, newValue);
+    }
+
     private void RecentlyOpened_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     private void RecentlyOpened_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     {
     {
         RecentlyOpenedEmpty = FileViewModel.RecentlyOpened.Count == 0;
         RecentlyOpenedEmpty = FileViewModel.RecentlyOpened.Count == 0;
@@ -166,7 +201,7 @@ internal partial class HelloTherePopup : Window
 
 
     private async void HelloTherePopup_OnLoaded(object sender, RoutedEventArgs e)
     private async void HelloTherePopup_OnLoaded(object sender, RoutedEventArgs e)
     {
     {
-        if(_newsHidden) return;
+        if(_newsDisabled) return;
 
 
         try
         try
         {
         {
@@ -177,6 +212,10 @@ internal partial class HelloTherePopup : Window
                 IsFetchingNews = false;
                 IsFetchingNews = false;
                 News.Clear();
                 News.Clear();
                 News.AddRange(news);
                 News.AddRange(news);
+                if (NewsPanelCollapsed && News.Any(x => x.IsNew))
+                {
+                    NewsPanelCollapsed = false;
+                }
             }
             }
             else
             else
             {
             {

+ 9 - 5
src/PixiEditor/Views/UserControls/NewsFeed/NewsItem.xaml

@@ -38,11 +38,15 @@
                     </Hyperlink>
                     </Hyperlink>
                 </Label>
                 </Label>
             </StackPanel>
             </StackPanel>
-            <!--<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Foreground="White" FontSize="12"
-                       Text="{Binding ElementName=newsItem, Path=News.ShortDescription}"
-                       MaxHeight="50" TextTrimming="CharacterEllipsis"/>-->
-            <TextBlock Margin="8 2.5" HorizontalAlignment="Right" FontSize="12" Foreground="LightGray"
-                       Text="{Binding ElementName=newsItem, Path=News.Date, StringFormat=d}"/>
+            <Grid>
+                <Border Visibility="{Binding ElementName=newsItem, Path=News.IsNew, Converter={converters:BoolToVisibilityConverter}}"
+                        Margin="0 2.5" Background="{StaticResource AccentRed}" HorizontalAlignment="Left"
+                        Padding="5 2.5" CornerRadius="0 5 0 0">
+                    <TextBlock Text="New!" Foreground="White" FontSize="12" FontStyle="Italic"/>
+                </Border>
+                <TextBlock Margin="8 5" HorizontalAlignment="Right" FontSize="12" Foreground="LightGray"
+                           Text="{Binding ElementName=newsItem, Path=News.Date, StringFormat=d}"/>
+            </Grid>
         </StackPanel>
         </StackPanel>
     </Border>
     </Border>
 </UserControl>
 </UserControl>