Browse Source

完成用户空间界面UI;完成投稿页面UI;修复已知问题

leiurayer 3 years ago
parent
commit
3afc5e010f
32 changed files with 1973 additions and 436 deletions
  1. 0 201
      LICENSE
  2. 4 3
      src/DownKyi.Core/BiliApi/BilibiliImages.xaml
  3. 14 0
      src/DownKyi.Core/BiliApi/Users/UserSpace.cs
  4. 3 0
      src/DownKyi/App.xaml.cs
  5. 352 0
      src/DownKyi/CustomControl/CustomPager.xaml
  6. 15 0
      src/DownKyi/CustomControl/CustomPager.xaml.cs
  7. 460 0
      src/DownKyi/CustomControl/CustomPagerViewModel.cs
  8. 85 0
      src/DownKyi/CustomControl/MyDelegateCommand.cs
  9. 20 0
      src/DownKyi/DownKyi.csproj
  10. 10 4
      src/DownKyi/Languages/Default.xaml
  11. BIN
      src/DownKyi/Resources/play.png
  12. BIN
      src/DownKyi/Resources/time.png
  13. 21 1
      src/DownKyi/Themes/Styles/StyleListBox.xaml
  14. 71 0
      src/DownKyi/ViewModels/PageViewModels/PublicationMedia.cs
  15. 50 1
      src/DownKyi/ViewModels/UserSpace/ViewArchiveViewModel.cs
  16. 1 1
      src/DownKyi/ViewModels/UserSpace/ViewChannelViewModel.cs
  17. 2 0
      src/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs
  18. 2 2
      src/DownKyi/ViewModels/ViewLoginViewModel.cs
  19. 7 0
      src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs
  20. 478 0
      src/DownKyi/ViewModels/ViewPublicationViewModel.cs
  21. 2 0
      src/DownKyi/ViewModels/ViewSettingsViewModel.cs
  22. 2 0
      src/DownKyi/ViewModels/ViewToolboxViewModel.cs
  23. 1 0
      src/DownKyi/ViewModels/ViewUserSpaceViewModel.cs
  24. 2 0
      src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs
  25. 10 0
      src/DownKyi/Views/SplashWindow.xaml
  26. 3 12
      src/DownKyi/Views/SplashWindow.xaml.cs
  27. 13 2
      src/DownKyi/Views/UserSpace/ViewArchive.xaml
  28. 2 2
      src/DownKyi/Views/UserSpace/ViewChannel.xaml
  29. 312 0
      src/DownKyi/Views/ViewPublication.xaml
  30. 28 0
      src/DownKyi/Views/ViewPublication.xaml.cs
  31. 3 6
      src/DownKyi/Views/ViewUserSpace.xaml
  32. 0 201
      src/LICENSE

+ 0 - 201
LICENSE

@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2021 FlySelfLog
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.

+ 4 - 3
src/DownKyi.Core/BiliApi/BilibiliImages.xaml

@@ -1,6 +1,7 @@
-<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-                    xmlns:local="clr-namespace:DownKyi.ui.images">
+<ResourceDictionary
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:DownKyi.ui.images">
     <DrawingImage x:Key="bilibiliDrawingImage">
         <DrawingImage.Drawing>
             <DrawingGroup ClipGeometry="M0,0 V1024 H2250 V0 H0 Z">

+ 14 - 0
src/DownKyi.Core/BiliApi/Users/UserSpace.cs

@@ -230,6 +230,20 @@ namespace DownKyi.Core.BiliApi.Users
 
         #endregion
 
+        #region 合集和列表
+
+        // TODO
+        // https://api.bilibili.com/x/polymer/space/seasons_series_list?mid=27899754&page_num=1&page_size=18
+        // page_size最大值为20
+
+        // https://api.bilibili.com/x/polymer/space/seasons_archives_list?mid=23947287&season_id=665&sort_reverse=false&page_num=1&page_size=30
+
+        // https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=desc&pn=1&ps=30
+        // https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=asc&pn=1&ps=30
+        // https://api.bilibili.com/x/series/series?series_id=1253087
+
+        #endregion
+
         #region 课程
 
         /// <summary>

+ 3 - 0
src/DownKyi/App.xaml.cs

@@ -164,6 +164,7 @@ namespace DownKyi
             containerRegistry.RegisterForNavigation<ViewUserSpace>(ViewUserSpaceViewModel.Tag);
             containerRegistry.RegisterForNavigation<ViewMySpace>(ViewMySpaceViewModel.Tag);
             containerRegistry.RegisterForNavigation<ViewPublicFavorites>(ViewPublicFavoritesViewModel.Tag);
+            containerRegistry.RegisterForNavigation<ViewPublication>(ViewPublicationViewModel.Tag);
 
             // downloadManager pages
             containerRegistry.RegisterForNavigation<ViewDownloading>(ViewDownloadingViewModel.Tag);
@@ -197,6 +198,8 @@ namespace DownKyi
         /// <param name="callback"></param>
         public static void PropertyChangeAsync(Action callback)
         {
+            if (Current == null) { return; }
+
             Current.Dispatcher.Invoke(callback);
         }
 

+ 352 - 0
src/DownKyi/CustomControl/CustomPager.xaml

@@ -0,0 +1,352 @@
+<UserControl
+    x:Class="DownKyi.CustomControl.CustomPager"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+    xmlns:local="clr-namespace:DownKyi.CustomControl"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d">
+    <UserControl.Resources>
+        <DrawingImage x:Key="ToLeftDrawingImage">
+            <DrawingImage.Drawing>
+                <DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
+                    <DrawingGroup.Transform>
+                        <TranslateTransform X="0" Y="1.7763568394002505E-15" />
+                    </DrawingGroup.Transform>
+                    <GeometryDrawing Brush="#FF666666" Geometry="F1 M1024,1024z M0,0z M345.49,512L778.27,80.69Q791.47,66.02 791.47,46.95 791.47,27.88 777.54,13.94 763.6,-1.77635683940025E-15 744.53,-1.77635683940025E-15 725.46,-1.77635683940025E-15 710.79,14.67L247.2,478.26Q232.53,492.93 232.53,512 232.53,531.07 247.2,545.74L710.79,1009.33Q725.46,1024 744.53,1024 763.6,1024 777.54,1010.07 791.47,996.13 791.47,977.06 791.47,957.98 778.27,943.31L345.49,512z" />
+                </DrawingGroup>
+            </DrawingImage.Drawing>
+        </DrawingImage>
+        <DrawingImage x:Key="ToLeftHoverDrawingImage">
+            <DrawingImage.Drawing>
+                <DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
+                    <DrawingGroup.Transform>
+                        <TranslateTransform X="0" Y="1.7763568394002505E-15" />
+                    </DrawingGroup.Transform>
+                    <GeometryDrawing Brush="#FF00A1D6" Geometry="F1 M1024,1024z M0,0z M345.49,512L778.27,80.69Q791.47,66.02 791.47,46.95 791.47,27.88 777.54,13.94 763.6,-1.77635683940025E-15 744.53,-1.77635683940025E-15 725.46,-1.77635683940025E-15 710.79,14.67L247.2,478.26Q232.53,492.93 232.53,512 232.53,531.07 247.2,545.74L710.79,1009.33Q725.46,1024 744.53,1024 763.6,1024 777.54,1010.07 791.47,996.13 791.47,977.06 791.47,957.98 778.27,943.31L345.49,512z" />
+                </DrawingGroup>
+            </DrawingImage.Drawing>
+        </DrawingImage>
+
+        <DrawingImage x:Key="ToRightDrawingImage">
+            <DrawingImage.Drawing>
+                <DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
+                    <DrawingGroup.Transform>
+                        <TranslateTransform X="0" Y="1.7763568394002505E-15" />
+                    </DrawingGroup.Transform>
+                    <GeometryDrawing Brush="#FF666666" Geometry="F1 M1024,1024z M0,0z M678.51,512L245.73,80.69Q232.53,66.02 232.53,46.95 232.53,27.88 246.46,13.94 260.4,-1.77635683940025E-15 279.47,-1.77635683940025E-15 298.54,-1.77635683940025E-15 313.21,14.67L776.8,478.26Q791.47,492.93 791.47,512 791.47,531.07 776.8,545.74L313.21,1009.33Q298.54,1024 279.47,1024 260.4,1024 246.46,1010.07 232.53,996.13 232.53,977.06 232.53,957.98 245.73,943.31L678.51,512z" />
+                </DrawingGroup>
+            </DrawingImage.Drawing>
+        </DrawingImage>
+        <DrawingImage x:Key="ToRightHoverDrawingImage">
+            <DrawingImage.Drawing>
+                <DrawingGroup ClipGeometry="M0,0 V1024 H1024 V0 H0 Z">
+                    <DrawingGroup.Transform>
+                        <TranslateTransform X="0" Y="1.7763568394002505E-15" />
+                    </DrawingGroup.Transform>
+                    <GeometryDrawing Brush="#FF00A1D6" Geometry="F1 M1024,1024z M0,0z M678.51,512L245.73,80.69Q232.53,66.02 232.53,46.95 232.53,27.88 246.46,13.94 260.4,-1.77635683940025E-15 279.47,-1.77635683940025E-15 298.54,-1.77635683940025E-15 313.21,14.67L776.8,478.26Q791.47,492.93 791.47,512 791.47,531.07 776.8,545.74L313.21,1009.33Q298.54,1024 279.47,1024 260.4,1024 246.46,1010.07 232.53,996.13 232.53,977.06 232.53,957.98 245.73,943.31L678.51,512z" />
+                </DrawingGroup>
+            </DrawingImage.Drawing>
+        </DrawingImage>
+
+        <Style x:Key="pagerBorder" TargetType="{x:Type Border}">
+            <Setter Property="Background" Value="#FFFFFFFF" />
+            <Setter Property="BorderBrush" Value="#FFD7DDE4" />
+            <Setter Property="BorderThickness" Value="1" />
+            <Setter Property="CornerRadius" Value="4" />
+            <Setter Property="Width" Value="30" />
+            <Setter Property="Height" Value="30" />
+            <Setter Property="Cursor" Value="Hand" />
+            <Style.Triggers>
+                <Trigger Property="IsMouseOver" Value="true">
+                    <Setter Property="BorderBrush" Value="#FF00A1D6" />
+                </Trigger>
+                <Trigger Property="IsMouseOver" Value="false">
+                    <Setter Property="BorderBrush" Value="#FFD7DDE4" />
+                </Trigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style x:Key="pagerButton" TargetType="{x:Type Button}">
+            <Setter Property="Background" Value="#FFFFFFFF" />
+            <Setter Property="BorderBrush" Value="#FFD7DDE4" />
+            <Setter Property="BorderThickness" Value="1" />
+            <Setter Property="Width" Value="30" />
+            <Setter Property="Height" Value="30" />
+            <Setter Property="Cursor" Value="Hand" />
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="{x:Type Button}">
+                        <Border
+                            Background="{TemplateBinding Background}"
+                            BorderBrush="{TemplateBinding BorderBrush}"
+                            BorderThickness="{TemplateBinding BorderThickness}"
+                            CornerRadius="4">
+                            <ContentPresenter
+                                Name="content"
+                                HorizontalAlignment="Center"
+                                VerticalAlignment="Center" />
+                        </Border>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+            <Style.Triggers>
+                <Trigger Property="IsMouseOver" Value="true">
+                    <Setter Property="BorderBrush" Value="#FF00A1D6" />
+                </Trigger>
+                <Trigger Property="IsMouseOver" Value="false">
+                    <Setter Property="BorderBrush" Value="#FFD7DDE4" />
+                </Trigger>
+            </Style.Triggers>
+        </Style>
+    </UserControl.Resources>
+
+    <StackPanel Orientation="Horizontal" Visibility="{Binding Visibility}">
+        <Button
+            x:Name="namePrevious"
+            Margin="0,0,8,0"
+            Command="{Binding PreviousCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding PreviousVisibility}">
+            <Image
+                Width="16"
+                Height="16"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center">
+                <Image.Style>
+                    <Style TargetType="Image">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePrevious}" Value="True">
+                                <Setter Property="Source" Value="{StaticResource ToLeftHoverDrawingImage}" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePrevious}" Value="False">
+                                <Setter Property="Source" Value="{StaticResource ToLeftDrawingImage}" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Image.Style>
+            </Image>
+        </Button>
+
+        <Button
+            x:Name="nameFirst"
+            Margin="0,0,4,0"
+            Command="{Binding FirstCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding FirstVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding First, Mode=TwoWay}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameFirst}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameFirst}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Border
+            Width="20"
+            Height="40"
+            Margin="0,0,4,0"
+            Visibility="{Binding LeftJumpVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                Foreground="#FF666666"
+                Text="&#x2022;&#x2022;&#x2022;" />
+        </Border>
+
+        <Button
+            x:Name="namePreviousSecond"
+            Margin="0,0,4,0"
+            Command="{Binding PreviousSecondCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding PreviousSecondVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding PreviousSecond, Mode=TwoWay}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePreviousSecond}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePreviousSecond}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Button
+            x:Name="namePreviousFirst"
+            Margin="0,0,4,0"
+            Command="{Binding PreviousFirstCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding PreviousFirstVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding PreviousFirst, Mode=TwoWay}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePreviousFirst}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=namePreviousFirst}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Border
+            Margin="0,0,4,0"
+            Background="#FF00A1D6"
+            BorderThickness="0"
+            Style="{StaticResource pagerBorder}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Foreground="#FFFFFFFF"
+                Text="{Binding Current, Mode=TwoWay}" />
+        </Border>
+
+        <Button
+            x:Name="nameNextFirst"
+            Margin="0,0,4,0"
+            Command="{Binding NextFirstCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding NextFirstVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding NextFirst, Mode=TwoWay}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNextFirst}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNextFirst}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Button
+            x:Name="nameNextSecond"
+            Margin="0,0,4,0"
+            Command="{Binding NextSecondCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding NextSecondVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding NextSecond, Mode=TwoWay}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNextSecond}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNextSecond}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Border
+            Width="20"
+            Height="40"
+            Margin="0,0,4,0"
+            Visibility="{Binding RightJumpVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                Foreground="#FF666666"
+                Text="&#x2022;&#x2022;&#x2022;" />
+        </Border>
+
+        <Button
+            x:Name="nameLast"
+            Margin="0,0,4,0"
+            Command="{Binding LastCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding LastVisibility}">
+            <TextBlock
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="14"
+                Text="{Binding Count}">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameLast}" Value="True">
+                                <Setter Property="Foreground" Value="#FF00A1D6" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameLast}" Value="False">
+                                <Setter Property="Foreground" Value="#FF657180" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </Button>
+
+        <Button
+            x:Name="nameNext"
+            Margin="4,0,4,0"
+            Command="{Binding NextCommand}"
+            Style="{StaticResource pagerButton}"
+            Visibility="{Binding NextVisibility}">
+            <Image
+                Width="16"
+                Height="16"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center">
+                <Image.Style>
+                    <Style TargetType="Image">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNext}" Value="True">
+                                <Setter Property="Source" Value="{StaticResource ToRightHoverDrawingImage}" />
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Path=IsMouseOver, ElementName=nameNext}" Value="False">
+                                <Setter Property="Source" Value="{StaticResource ToRightDrawingImage}" />
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Image.Style>
+            </Image>
+        </Button>
+
+    </StackPanel>
+</UserControl>

+ 15 - 0
src/DownKyi/CustomControl/CustomPager.xaml.cs

@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace DownKyi.CustomControl
+{
+    /// <summary>
+    /// CustomPager.xaml 的交互逻辑
+    /// </summary>
+    public partial class CustomPager : UserControl
+    {
+        public CustomPager()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 460 - 0
src/DownKyi/CustomControl/CustomPagerViewModel.cs

@@ -0,0 +1,460 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace DownKyi.CustomControl
+{
+    public class CustomPagerViewModel : INotifyPropertyChanged
+    {
+        public CustomPagerViewModel(int current, int count)
+        {
+            Current = current;
+            Count = count;
+
+            SetView();
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        // Current修改的回调
+        public delegate bool CurrentChangedHandler(int old, int current);
+        public event CurrentChangedHandler CurrentChanged;
+        protected virtual bool OnCurrentChanged(int old, int current)
+        {
+            if (CurrentChanged == null)
+            {
+                return false;
+            }
+            else
+            {
+                return CurrentChanged.Invoke(old, current);
+            }
+        }
+
+        // Count修改的回调
+        public delegate void CountChangedHandler(int count);
+        public event CountChangedHandler CountChanged;
+        protected virtual void OnCountChanged(int count)
+        {
+            CountChanged?.Invoke(count);
+        }
+
+        #region 绑定属性
+
+        private Visibility visibility;
+        public Visibility Visibility
+        {
+            get { return visibility; }
+            set
+            {
+                visibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Visibility"));
+            }
+        }
+
+        private int count;
+        public int Count
+        {
+            get
+            {
+                return count;
+            }
+            set
+            {
+                if (value < Current || value < 1)
+                {
+                    //throw new Exception("数值不在允许的范围内。");
+                    System.Console.WriteLine(value.ToString());
+                }
+                else
+                {
+                    count = value;
+
+                    if (count == 1) { Visibility = Visibility.Hidden; }
+                    else { Visibility = Visibility.Visible; }
+
+                    OnCountChanged(count);
+
+                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+                }
+            }
+        }
+
+        private int current;
+        public int Current
+        {
+            get
+            {
+                if (current < 1) { current = 1; }
+                return current;
+            }
+            set
+            {
+                if (Count > 0 && (value > Count || value < 1))
+                {
+                    //throw new Exception("数值不在允许的范围内。");
+                }
+                else
+                {
+                    bool isSuccess = OnCurrentChanged(current, value);
+                    if (isSuccess)
+                    {
+                        current = value;
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Current"));
+                    }
+                }
+            }
+        }
+
+        private int first;
+        public int First
+        {
+            get { return first; }
+            set
+            {
+                first = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("First"));
+            }
+        }
+
+        private int previousSecond;
+        public int PreviousSecond
+        {
+            get { return previousSecond; }
+            set
+            {
+                previousSecond = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousSecond"));
+            }
+        }
+
+        private int previousFirst;
+        public int PreviousFirst
+        {
+            get { return previousFirst; }
+            set
+            {
+                previousFirst = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousFirst"));
+            }
+        }
+
+        private int nextFirst;
+        public int NextFirst
+        {
+            get { return nextFirst; }
+            set
+            {
+                nextFirst = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextFirst"));
+            }
+        }
+
+        private int nextSecond;
+        public int NextSecond
+        {
+            get { return nextSecond; }
+            set
+            {
+                nextSecond = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextSecond"));
+            }
+        }
+
+        // 控制Current左边的控件
+        private Visibility previousVisibility;
+        public Visibility PreviousVisibility
+        {
+            get { return previousVisibility; }
+            set
+            {
+                previousVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousVisibility"));
+            }
+        }
+
+        private Visibility firstVisibility;
+        public Visibility FirstVisibility
+        {
+            get { return firstVisibility; }
+            set
+            {
+                firstVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FirstVisibility"));
+            }
+        }
+
+        private Visibility leftJumpVisibility;
+        public Visibility LeftJumpVisibility
+        {
+            get { return leftJumpVisibility; }
+            set
+            {
+                leftJumpVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LeftJumpVisibility"));
+            }
+        }
+
+        private Visibility previousSecondVisibility;
+        public Visibility PreviousSecondVisibility
+        {
+            get { return previousSecondVisibility; }
+            set
+            {
+                previousSecondVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousSecondVisibility"));
+            }
+        }
+
+        private Visibility previousFirstVisibility;
+        public Visibility PreviousFirstVisibility
+        {
+            get { return previousFirstVisibility; }
+            set
+            {
+                previousFirstVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PreviousFirstVisibility"));
+            }
+        }
+
+        // 控制Current右边的控件
+        private Visibility nextFirstVisibility;
+        public Visibility NextFirstVisibility
+        {
+            get { return nextFirstVisibility; }
+            set
+            {
+                nextFirstVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextFirstVisibility"));
+            }
+        }
+
+        private Visibility nextSecondVisibility;
+        public Visibility NextSecondVisibility
+        {
+            get { return nextSecondVisibility; }
+            set
+            {
+                nextSecondVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextSecondVisibility"));
+            }
+        }
+
+        private Visibility rightJumpVisibility;
+        public Visibility RightJumpVisibility
+        {
+            get { return rightJumpVisibility; }
+            set
+            {
+                rightJumpVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("RightJumpVisibility"));
+            }
+        }
+
+        private Visibility lastVisibility;
+        public Visibility LastVisibility
+        {
+            get { return lastVisibility; }
+            set
+            {
+                lastVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastVisibility"));
+            }
+        }
+
+        private Visibility nextVisibility;
+        public Visibility NextVisibility
+        {
+            get { return nextVisibility; }
+            set
+            {
+                nextVisibility = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("NextVisibility"));
+            }
+        }
+
+        #endregion
+
+
+        private MyDelegateCommand previousCommand;
+        public MyDelegateCommand PreviousCommand => previousCommand ?? (previousCommand = new MyDelegateCommand(PreviousExecuted));
+
+        public void PreviousExecuted(object obj)
+        {
+            Current -= 1;
+
+            SetView();
+        }
+
+        private MyDelegateCommand firstCommand;
+        public MyDelegateCommand FirstCommand => firstCommand ?? (firstCommand = new MyDelegateCommand(FirstExecuted));
+
+        public void FirstExecuted(object obj)
+        {
+            Current = 1;
+
+            SetView();
+        }
+
+        private MyDelegateCommand previousSecondCommand;
+        public MyDelegateCommand PreviousSecondCommand => previousSecondCommand ?? (previousSecondCommand = new MyDelegateCommand(PreviousSecondExecuted));
+
+        public void PreviousSecondExecuted(object obj)
+        {
+            Current -= 2;
+
+            SetView();
+        }
+
+        private MyDelegateCommand previousFirstCommand;
+        public MyDelegateCommand PreviousFirstCommand => previousFirstCommand ?? (previousFirstCommand = new MyDelegateCommand(PreviousFirstExecuted));
+
+        public void PreviousFirstExecuted(object obj)
+        {
+            Current -= 1;
+
+            SetView();
+        }
+
+        private MyDelegateCommand nextFirstCommand;
+        public MyDelegateCommand NextFirstCommand => nextFirstCommand ?? (nextFirstCommand = new MyDelegateCommand(NextFirstExecuted));
+
+        public void NextFirstExecuted(object obj)
+        {
+            Current += 1;
+
+            SetView();
+        }
+
+        private MyDelegateCommand nextSecondCommand;
+        public MyDelegateCommand NextSecondCommand => nextSecondCommand ?? (nextSecondCommand = new MyDelegateCommand(NextSecondExecuted));
+
+        public void NextSecondExecuted(object obj)
+        {
+            Current += 2;
+
+            SetView();
+        }
+
+        private MyDelegateCommand lastCommand;
+        public MyDelegateCommand LastCommand => lastCommand ?? (lastCommand = new MyDelegateCommand(LastExecuted));
+
+        public void LastExecuted(object obj)
+        {
+            Current = Count;
+
+            SetView();
+        }
+
+        private MyDelegateCommand nextCommand;
+        public MyDelegateCommand NextCommand => nextCommand ?? (nextCommand = new MyDelegateCommand(NextExecuted));
+
+        public void NextExecuted(object obj)
+        {
+            Current += 1;
+
+            SetView();
+        }
+
+        /// <summary>
+        /// 控制显示,暴力实现,以后重构
+        /// </summary>
+        private void SetView()
+        {
+            First = 1;
+            PreviousSecond = Current - 2;
+            PreviousFirst = Current - 1;
+            NextFirst = Current + 1;
+            NextSecond = Current + 2;
+
+            // 控制Current左边的控件
+            if (Current == 1)
+            {
+                PreviousVisibility = Visibility.Collapsed;
+                FirstVisibility = Visibility.Collapsed;
+                LeftJumpVisibility = Visibility.Collapsed;
+                PreviousSecondVisibility = Visibility.Collapsed;
+                PreviousFirstVisibility = Visibility.Collapsed;
+            }
+            else if (Current == 2)
+            {
+                PreviousVisibility = Visibility.Visible;
+                FirstVisibility = Visibility.Collapsed;
+                LeftJumpVisibility = Visibility.Collapsed;
+                PreviousSecondVisibility = Visibility.Collapsed;
+                PreviousFirstVisibility = Visibility.Visible;
+            }
+            else if (Current == 3)
+            {
+                PreviousVisibility = Visibility.Visible;
+                FirstVisibility = Visibility.Collapsed;
+                LeftJumpVisibility = Visibility.Collapsed;
+                PreviousSecondVisibility = Visibility.Visible;
+                PreviousFirstVisibility = Visibility.Visible;
+            }
+            else if (Current == 4)
+            {
+                PreviousVisibility = Visibility.Visible;
+                FirstVisibility = Visibility.Visible;
+                LeftJumpVisibility = Visibility.Collapsed;
+                PreviousSecondVisibility = Visibility.Visible;
+                PreviousFirstVisibility = Visibility.Visible;
+            }
+            else
+            {
+                PreviousVisibility = Visibility.Visible;
+                FirstVisibility = Visibility.Visible;
+                LeftJumpVisibility = Visibility.Visible;
+                PreviousSecondVisibility = Visibility.Visible;
+                PreviousFirstVisibility = Visibility.Visible;
+            }
+
+            // 控制Current右边的控件
+            if (Current == Count)
+            {
+                NextFirstVisibility = Visibility.Collapsed;
+                NextSecondVisibility = Visibility.Collapsed;
+                RightJumpVisibility = Visibility.Collapsed;
+                LastVisibility = Visibility.Collapsed;
+                NextVisibility = Visibility.Collapsed;
+            }
+            else if (Current == Count - 1)
+            {
+                NextFirstVisibility = Visibility.Visible;
+                NextSecondVisibility = Visibility.Collapsed;
+                RightJumpVisibility = Visibility.Collapsed;
+                LastVisibility = Visibility.Collapsed;
+                NextVisibility = Visibility.Visible;
+            }
+            else if (Current == Count - 2)
+            {
+                NextFirstVisibility = Visibility.Visible;
+                NextSecondVisibility = Visibility.Visible;
+                RightJumpVisibility = Visibility.Collapsed;
+                LastVisibility = Visibility.Collapsed;
+                NextVisibility = Visibility.Visible;
+            }
+            else if (Current == Count - 3)
+            {
+                NextFirstVisibility = Visibility.Visible;
+                NextSecondVisibility = Visibility.Visible;
+                RightJumpVisibility = Visibility.Collapsed;
+                LastVisibility = Visibility.Visible;
+                NextVisibility = Visibility.Visible;
+            }
+            else
+            {
+                NextFirstVisibility = Visibility.Visible;
+                NextSecondVisibility = Visibility.Visible;
+                RightJumpVisibility = Visibility.Visible;
+                LastVisibility = Visibility.Visible;
+                NextVisibility = Visibility.Visible;
+            }
+        }
+
+
+
+
+
+    }
+}

+ 85 - 0
src/DownKyi/CustomControl/MyDelegateCommand.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Windows.Input;
+
+namespace DownKyi.CustomControl
+{
+    /// <summary>
+    /// 绑定命令的工具类
+    /// </summary>
+    public class MyDelegateCommand : ICommand
+    {
+
+        /// <summary>
+        /// 检查命令是否可以执行的事件,在UI事件发生导致控件状态或数据发生变化时触发
+        /// </summary>
+        public event EventHandler CanExecuteChanged
+        {
+            add
+            {
+                if (_canExecute != null)
+                {
+                    CommandManager.RequerySuggested += value;
+                }
+            }
+            remove
+            {
+                if (_canExecute != null)
+                {
+                    CommandManager.RequerySuggested -= value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 判断命令是否可以执行的方法
+        /// </summary>
+        private Func<object, bool> _canExecute;
+
+        /// <summary>
+        /// 命令需要执行的方法
+        /// </summary>
+        private Action<object> _execute;
+
+        /// <summary>
+        /// 创建一个命令
+        /// </summary>
+        /// <param name="execute">命令要执行的方法</param>
+        public MyDelegateCommand(Action<object> execute) : this(execute, null)
+        {
+        }
+
+        /// <summary>
+        /// 创建一个命令
+        /// </summary>
+        /// <param name="execute">命令要执行的方法</param>
+        /// <param name="canExecute">判断命令是否能够执行的方法</param>
+        public MyDelegateCommand(Action<object> execute, Func<object, bool> canExecute)
+        {
+            _execute = execute;
+            _canExecute = canExecute;
+        }
+
+        /// <summary>
+        /// 判断命令是否可以执行
+        /// </summary>
+        /// <param name="parameter">命令传入的参数</param>
+        /// <returns>是否可以执行</returns>
+        public bool CanExecute(object parameter)
+        {
+            if (_canExecute == null) return true;
+            return _canExecute(parameter);
+        }
+
+        /// <summary>
+        /// 执行命令
+        /// </summary>
+        /// <param name="parameter"></param>
+        public void Execute(object parameter)
+        {
+            if (_execute != null && CanExecute(parameter))
+            {
+                _execute(parameter);
+            }
+        }
+    }
+}

+ 20 - 0
src/DownKyi/DownKyi.csproj

@@ -80,6 +80,11 @@
       <SubType>Designer</SubType>
     </ApplicationDefinition>
     <Compile Include="Converter\CountConverter.cs" />
+    <Compile Include="CustomControl\CustomPager.xaml.cs">
+      <DependentUpon>CustomPager.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="CustomControl\CustomPagerViewModel.cs" />
+    <Compile Include="CustomControl\MyDelegateCommand.cs" />
     <Compile Include="CustomControl\GifImage.cs" />
     <Compile Include="Events\MessageEvent.cs" />
     <Compile Include="Events\NavigationEvent.cs" />
@@ -97,6 +102,7 @@
     <Compile Include="Services\SearchService.cs" />
     <Compile Include="ViewModels\PageViewModels\Favorites.cs" />
     <Compile Include="ViewModels\PageViewModels\FavoritesMedia.cs" />
+    <Compile Include="ViewModels\PageViewModels\PublicationMedia.cs" />
     <Compile Include="ViewModels\PageViewModels\SpaceItem.cs" />
     <Compile Include="ViewModels\Settings\DisplayFileNamePart.cs" />
     <Compile Include="Models\ParseScopeDisplay.cs" />
@@ -145,6 +151,7 @@
     <Compile Include="ViewModels\Settings\ViewNetworkViewModel.cs" />
     <Compile Include="ViewModels\Dialogs\ViewParsingSelectorViewModel.cs" />
     <Compile Include="ViewModels\ViewMySpaceViewModel.cs" />
+    <Compile Include="ViewModels\ViewPublicationViewModel.cs" />
     <Compile Include="ViewModels\ViewPublicFavoritesViewModel.cs" />
     <Compile Include="ViewModels\ViewSettingsViewModel.cs" />
     <Compile Include="ViewModels\ViewToolboxViewModel.cs" />
@@ -208,6 +215,9 @@
     <Compile Include="Views\ViewMySpace.xaml.cs">
       <DependentUpon>ViewMySpace.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\ViewPublication.xaml.cs">
+      <DependentUpon>ViewPublication.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\ViewPublicFavorites.xaml.cs">
       <DependentUpon>ViewPublicFavorites.xaml</DependentUpon>
     </Compile>
@@ -223,6 +233,10 @@
     <Compile Include="Views\ViewVideoDetail.xaml.cs">
       <DependentUpon>ViewVideoDetail.xaml</DependentUpon>
     </Compile>
+    <Page Include="CustomControl\CustomPager.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="Languages\en_US.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -379,6 +393,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\ViewPublication.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="Views\ViewPublicFavorites.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -497,6 +515,8 @@
     <Resource Include="Resources\level\lv9.png" />
     <Resource Include="Resources\video-placeholder.png" />
     <Resource Include="Resources\channel.png" />
+    <Resource Include="Resources\play.png" />
+    <Resource Include="Resources\time.png" />
     <Content Include="打不开DownKyi请点我.txt">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>

+ 10 - 4
src/DownKyi/Languages/Default.xaml

@@ -63,6 +63,16 @@
     <system:String x:Key="AllPublicationZones">全部</system:String>
     <system:String x:Key="Publication">投稿视频</system:String>
     <system:String x:Key="Channel">频道</system:String>
+    <system:String x:Key="UserSpaceWait">请稍等,马上就好~</system:String>
+
+    <!--  PublicFavorites  -->
+    <system:String x:Key="PublicFavorites">收藏夹</system:String>
+    <system:String x:Key="FavoritesMediaCount">个内容</system:String>
+
+    <!--  Publication  -->
+    <system:String x:Key="DownloadSelectedPublication">下载选中项</system:String>
+    <system:String x:Key="DownloadAllPublication">下载全部</system:String>
+    <system:String x:Key="PublicationWait">请稍等,马上就好~</system:String>
 
     <!--  VideoDetail  -->
     <system:String x:Key="CopyCover">复制封面图片</system:String>
@@ -252,10 +262,6 @@
     <system:String x:Key="ExtractAudio">提取音频</system:String>
     <system:String x:Key="ExtractVideo">提取视频</system:String>
 
-    <!--  PublicFavorites  -->
-    <system:String x:Key="PublicFavorites">收藏夹</system:String>
-    <system:String x:Key="FavoritesMediaCount">个内容</system:String>
-
     <!--  Dialog  -->
     <system:String x:Key="Cancel">取消</system:String>
     <system:String x:Key="SelectDirectory">请选择文件夹</system:String>

BIN
src/DownKyi/Resources/play.png


BIN
src/DownKyi/Resources/time.png


+ 21 - 1
src/DownKyi/Themes/Styles/StyleListBox.xaml

@@ -6,7 +6,13 @@
             <Setter.Value>
                 <ControlTemplate TargetType="{x:Type ListBoxItem}">
                     <Grid x:Name="panel" Height="60">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="2*" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
                         <StackPanel
+                            Grid.Column="0"
                             Margin="20,0,0,0"
                             HorizontalAlignment="Left"
                             VerticalAlignment="Center"
@@ -34,12 +40,26 @@
                                     Fill="{Binding Image.Fill}"
                                     Stretch="UniformToFill" />
                             </ContentControl>
+
                             <TextBlock
                                 x:Name="text"
+                                HorizontalAlignment="Left"
                                 VerticalAlignment="Center"
                                 FontSize="12"
-                                Text="{Binding Title}" />
+                                Foreground="{DynamicResource BrushTextDark}"
+                                Text="{Binding Title}"
+                                TextTrimming="CharacterEllipsis" />
                         </StackPanel>
+
+                        <TextBlock
+                            x:Name="sub"
+                            Grid.Column="1"
+                            Margin="10,0,20,0"
+                            HorizontalAlignment="Right"
+                            VerticalAlignment="Center"
+                            FontSize="12"
+                            Foreground="{DynamicResource BrushTextGrey2}"
+                            Text="{Binding SubTitle}" />
                     </Grid>
 
                     <ControlTemplate.Triggers>

+ 71 - 0
src/DownKyi/ViewModels/PageViewModels/PublicationMedia.cs

@@ -0,0 +1,71 @@
+using Prism.Commands;
+using Prism.Mvvm;
+using System.Windows.Media.Imaging;
+
+namespace DownKyi.ViewModels.PageViewModels
+{
+    public class PublicationMedia : BindableBase
+    {
+        public long Avid { get; set; }
+        public string Bvid { get; set; }
+
+        private bool isSelected;
+        public bool IsSelected
+        {
+            get => isSelected;
+            set => SetProperty(ref isSelected, value);
+        }
+
+        private BitmapImage cover;
+        public BitmapImage Cover
+        {
+            get => cover;
+            set => SetProperty(ref cover, value);
+        }
+
+        private string title;
+        public string Title
+        {
+            get => title;
+            set => SetProperty(ref title, value);
+        }
+
+        private string duration;
+        public string Duration
+        {
+            get => duration;
+            set => SetProperty(ref duration, value);
+        }
+
+        private string playNumber;
+        public string PlayNumber
+        {
+            get => playNumber;
+            set => SetProperty(ref playNumber, value);
+        }
+
+        private string createTime;
+        public string CreateTime
+        {
+            get => createTime;
+            set => SetProperty(ref createTime, value);
+        }
+
+        // 视频标题点击事件
+        private DelegateCommand<object> titleCommand;
+        public DelegateCommand<object> TitleCommand => titleCommand ?? (titleCommand = new DelegateCommand<object>(ExecuteTitleCommand));
+
+        /// <summary>
+        /// 视频标题点击事件
+        /// </summary>
+        /// <param name="parameter"></param>
+        private void ExecuteTitleCommand(object parameter)
+        {
+            if (!(parameter is string tag)) { return; }
+
+            string url = "https://www.bilibili.com/video/" + tag;
+            System.Diagnostics.Process.Start(url);
+        }
+
+    }
+}

+ 50 - 1
src/DownKyi/ViewModels/UserSpace/ViewArchiveViewModel.cs

@@ -1,10 +1,13 @@
 using DownKyi.Core.BiliApi.Users.Models;
 using DownKyi.Core.BiliApi.Zone;
+using DownKyi.Events;
 using DownKyi.Utils;
+using Prism.Commands;
 using Prism.Events;
 using Prism.Regions;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Linq;
 using System.Windows;
 using System.Windows.Media;
 
@@ -12,7 +15,9 @@ namespace DownKyi.ViewModels.UserSpace
 {
     public class ViewArchiveViewModel : BaseViewModel
     {
-        public const string Tag = "ArchiveView";
+        public const string Tag = "PageUserSpaceArchiveView";
+
+        private long mid = -1;
 
         #region 页面属性申明
 
@@ -23,6 +28,13 @@ namespace DownKyi.ViewModels.UserSpace
             set => SetProperty(ref publicationZones, value);
         }
 
+        private int selectedItem;
+        public int SelectedItem
+        {
+            get => selectedItem;
+            set => SetProperty(ref selectedItem, value);
+        }
+
         #endregion
 
         public ViewArchiveViewModel(IEventAggregator eventAggregator) : base(eventAggregator)
@@ -35,6 +47,38 @@ namespace DownKyi.ViewModels.UserSpace
         }
 
         #region 命令申明
+
+        // 视频选择事件
+        private DelegateCommand<object> publicationZonesCommand;
+        public DelegateCommand<object> PublicationZonesCommand => publicationZonesCommand ?? (publicationZonesCommand = new DelegateCommand<object>(ExecutePublicationZonesCommand));
+
+        /// <summary>
+        /// 视频选择事件
+        /// </summary>
+        /// <param name="parameter"></param>
+        private void ExecutePublicationZonesCommand(object parameter)
+        {
+            if (!(parameter is PublicationZone zone)) { return; }
+
+            Dictionary<string, object> data = new Dictionary<string, object>
+            {
+                { "mid", mid },
+                { "tid", zone.Tid },
+                { "list", PublicationZones.ToList() }
+            };
+
+            // 进入视频页面
+            NavigationParam param = new NavigationParam
+            {
+                ViewName = ViewPublicationViewModel.Tag,
+                ParentViewName = ViewUserSpaceViewModel.Tag,
+                Parameter = data
+            };
+            eventAggregator.GetEvent<NavigationEvent>().Publish(param);
+
+            SelectedItem = -1;
+        }
+
         #endregion
 
         public override void OnNavigatedFrom(NavigationContext navigationContext)
@@ -42,6 +86,7 @@ namespace DownKyi.ViewModels.UserSpace
             base.OnNavigatedFrom(navigationContext);
 
             PublicationZones.Clear();
+            SelectedItem = -1;
         }
 
         /// <summary>
@@ -53,6 +98,7 @@ namespace DownKyi.ViewModels.UserSpace
             base.OnNavigatedTo(navigationContext);
 
             PublicationZones.Clear();
+            SelectedItem = -1;
 
             // 根据传入参数不同执行不同任务
             var parameter = navigationContext.Parameters.GetValue<List<SpacePublicationListTypeVideoZone>>("object");
@@ -61,6 +107,9 @@ namespace DownKyi.ViewModels.UserSpace
                 return;
             }
 
+            // 传入mid
+            mid = navigationContext.Parameters.GetValue<long>("mid");
+
             int VideoCount = 0;
             foreach (var zone in parameter)
             {

+ 1 - 1
src/DownKyi/ViewModels/UserSpace/ViewChannelViewModel.cs

@@ -12,7 +12,7 @@ namespace DownKyi.ViewModels.UserSpace
 {
     public class ViewChannelViewModel : BaseViewModel
     {
-        public const string Tag = "Channel";
+        public const string Tag = "PageUserSpaceChannel";
 
         #region 页面属性申明
 

+ 2 - 0
src/DownKyi/ViewModels/ViewDownloadManagerViewModel.cs

@@ -119,6 +119,8 @@ namespace DownKyi.ViewModels
             //// 进入设置页面时显示的设置项
             SelectTabId = 0;
             regionManager.RequestNavigate("DownloadManagerContentRegion", ViewDownloadingViewModel.Tag, new NavigationParameters());
+
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
         }
 
     }

+ 2 - 2
src/DownKyi/ViewModels/ViewLoginViewModel.cs

@@ -63,7 +63,6 @@ namespace DownKyi.ViewModels
             #endregion
         }
 
-
         #region 命令申明
 
         // 返回
@@ -92,7 +91,6 @@ namespace DownKyi.ViewModels
 
         #endregion
 
-
         #region 业务逻辑
 
         /// <summary>
@@ -224,6 +222,8 @@ namespace DownKyi.ViewModels
         /// </summary>
         private void InitStatus()
         {
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
+
             LoginQRCode = null;
             LoginQRCodeOpacity = 1;
             LoginQRCodeStatus = Visibility.Hidden;

+ 7 - 0
src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs

@@ -212,6 +212,11 @@ namespace DownKyi.ViewModels
                 }
             });
 
+            if (directory == null)
+            {
+                return;
+            }
+
             // 通知用户添加到下载列表的结果
             if (i == 0)
             {
@@ -230,6 +235,8 @@ namespace DownKyi.ViewModels
         {
             LogManager.Debug(Tag, "初始化页面元素");
 
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
+
             ContentVisibility = Visibility.Collapsed;
             NoDataVisibility = Visibility.Collapsed;
 

+ 478 - 0
src/DownKyi/ViewModels/ViewPublicationViewModel.cs

@@ -0,0 +1,478 @@
+using DownKyi.Core.BiliApi.VideoStream;
+using DownKyi.Core.Storage;
+using DownKyi.Core.Utils;
+using DownKyi.CustomControl;
+using DownKyi.Events;
+using DownKyi.Images;
+using DownKyi.Services;
+using DownKyi.Services.Download;
+using DownKyi.Utils;
+using DownKyi.ViewModels.PageViewModels;
+using DownKyi.ViewModels.UserSpace;
+using Prism.Commands;
+using Prism.Events;
+using Prism.Regions;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media.Imaging;
+
+namespace DownKyi.ViewModels
+{
+    public class ViewPublicationViewModel : BaseViewModel
+    {
+        public const string Tag = "PagePublication";
+
+        private readonly IDialogService dialogService;
+
+        private CancellationTokenSource tokenSource;
+
+        private long mid = -1;
+
+        // 每页视频数量,暂时在此写死,以后在设置中增加选项
+        private readonly int VideoNumberInPage = 30;
+
+        #region 页面属性申明
+
+        private GifImage loading;
+        public GifImage Loading
+        {
+            get => loading;
+            set => SetProperty(ref loading, value);
+        }
+
+        private Visibility loadingVisibility;
+        public Visibility LoadingVisibility
+        {
+            get => loadingVisibility;
+            set => SetProperty(ref loadingVisibility, value);
+        }
+
+        private Visibility noDataVisibility;
+        public Visibility NoDataVisibility
+        {
+            get => noDataVisibility;
+            set => SetProperty(ref noDataVisibility, value);
+        }
+
+        private VectorImage arrowBack;
+        public VectorImage ArrowBack
+        {
+            get => arrowBack;
+            set => SetProperty(ref arrowBack, value);
+        }
+
+        private ObservableCollection<TabHeader> tabHeaders;
+        public ObservableCollection<TabHeader> TabHeaders
+        {
+            get => tabHeaders;
+            set => SetProperty(ref tabHeaders, value);
+        }
+
+        private int selectTabId;
+        public int SelectTabId
+        {
+            get => selectTabId;
+            set => SetProperty(ref selectTabId, value);
+        }
+
+        private bool isEnabled = true;
+        public bool IsEnabled
+        {
+            get => isEnabled;
+            set => SetProperty(ref isEnabled, value);
+        }
+
+        private CustomPagerViewModel pager;
+        public CustomPagerViewModel Pager
+        {
+            get => pager;
+            set => SetProperty(ref pager, value);
+        }
+
+        private ObservableCollection<PublicationMedia> medias;
+        public ObservableCollection<PublicationMedia> Medias
+        {
+            get => medias;
+            set => SetProperty(ref medias, value);
+        }
+
+        private bool isSelectAll;
+        public bool IsSelectAll
+        {
+            get => isSelectAll;
+            set => SetProperty(ref isSelectAll, value);
+        }
+
+        #endregion
+
+        public ViewPublicationViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator)
+        {
+            this.dialogService = dialogService;
+
+            #region 属性初始化
+
+            // 初始化loading gif
+            Loading = new GifImage(Properties.Resources.loading);
+            Loading.StartAnimate();
+            LoadingVisibility = Visibility.Collapsed;
+            NoDataVisibility = Visibility.Collapsed;
+
+            ArrowBack = NavigationIcon.Instance().ArrowBack;
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
+
+            TabHeaders = new ObservableCollection<TabHeader>();
+            Medias = new ObservableCollection<PublicationMedia>();
+
+            #endregion
+        }
+
+        #region 命令申明
+
+        // 返回事件
+        private DelegateCommand backSpaceCommand;
+        public DelegateCommand BackSpaceCommand => backSpaceCommand ?? (backSpaceCommand = new DelegateCommand(ExecuteBackSpace));
+
+        /// <summary>
+        /// 返回事件
+        /// </summary>
+        private void ExecuteBackSpace()
+        {
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorText");
+
+            // 结束任务
+            tokenSource.Cancel();
+
+            NavigationParam parameter = new NavigationParam
+            {
+                ViewName = ParentView,
+                ParentViewName = null,
+                Parameter = null
+            };
+            eventAggregator.GetEvent<NavigationEvent>().Publish(parameter);
+        }
+
+        // 左侧tab点击事件
+        private DelegateCommand<object> leftTabHeadersCommand;
+        public DelegateCommand<object> LeftTabHeadersCommand => leftTabHeadersCommand ?? (leftTabHeadersCommand = new DelegateCommand<object>(ExecuteLeftTabHeadersCommand, CanExecuteLeftTabHeadersCommand));
+
+        /// <summary>
+        /// 左侧tab点击事件
+        /// </summary>
+        /// <param name="parameter"></param>
+        private void ExecuteLeftTabHeadersCommand(object parameter)
+        {
+            if (!(parameter is TabHeader tabHeader)) { return; }
+
+            // 页面选择
+            Pager = new CustomPagerViewModel(1, (int)Math.Ceiling(double.Parse(tabHeader.SubTitle) / VideoNumberInPage));
+            Pager.CurrentChanged += OnCurrentChanged_Pager;
+            Pager.CountChanged += OnCountChanged_Pager;
+            Pager.Current = 1;
+        }
+
+        /// <summary>
+        /// 左侧tab点击事件是否允许执行
+        /// </summary>
+        /// <param name="parameter"></param>
+        /// <returns></returns>
+        private bool CanExecuteLeftTabHeadersCommand(object parameter)
+        {
+            return IsEnabled;
+        }
+
+        // 全选按钮点击事件
+        private DelegateCommand<object> selectAllCommand;
+        public DelegateCommand<object> SelectAllCommand => selectAllCommand ?? (selectAllCommand = new DelegateCommand<object>(ExecuteSelectAllCommand));
+
+        /// <summary>
+        /// 全选按钮点击事件
+        /// </summary>
+        /// <param name="parameter"></param>
+        private void ExecuteSelectAllCommand(object parameter)
+        {
+            if (IsSelectAll)
+            {
+                foreach (var item in Medias)
+                {
+                    item.IsSelected = true;
+                }
+            }
+            else
+            {
+                foreach (var item in Medias)
+                {
+                    item.IsSelected = false;
+                }
+            }
+        }
+
+        // 列表选择事件
+        private DelegateCommand<object> mediasCommand;
+        public DelegateCommand<object> MediasCommand => mediasCommand ?? (mediasCommand = new DelegateCommand<object>(ExecuteMediasCommand));
+
+        /// <summary>
+        /// 列表选择事件
+        /// </summary>
+        /// <param name="parameter"></param>
+        private void ExecuteMediasCommand(object parameter)
+        {
+            if (!(parameter is IList selectedMedia)) { return; }
+
+            if (selectedMedia.Count == Medias.Count)
+            {
+                IsSelectAll = true;
+            }
+            else
+            {
+                IsSelectAll = false;
+            }
+        }
+
+        // 添加选中项到下载列表事件
+        private DelegateCommand addToDownloadCommand;
+        public DelegateCommand AddToDownloadCommand => addToDownloadCommand ?? (addToDownloadCommand = new DelegateCommand(ExecuteAddToDownloadCommand));
+
+        /// <summary>
+        /// 添加选中项到下载列表事件
+        /// </summary>
+        private void ExecuteAddToDownloadCommand()
+        {
+            AddToDownload(true);
+        }
+
+        // 添加所有视频到下载列表事件
+        private DelegateCommand addAllToDownloadCommand;
+        public DelegateCommand AddAllToDownloadCommand => addAllToDownloadCommand ?? (addAllToDownloadCommand = new DelegateCommand(ExecuteAddAllToDownloadCommand));
+
+        /// <summary>
+        /// 添加所有视频到下载列表事件
+        /// </summary>
+        private void ExecuteAddAllToDownloadCommand()
+        {
+            AddToDownload(false);
+        }
+
+        #endregion
+
+        private async void AddToDownload(bool isOnlySelected)
+        {
+            // 收藏夹里只有视频
+            AddToDownloadService addToDownloadService = new AddToDownloadService(PlayStreamType.VIDEO);
+
+            // 选择文件夹
+            string directory = addToDownloadService.SetDirectory(dialogService);
+
+            // 视频计数
+            int i = 0;
+            await Task.Run(() =>
+            {
+                // 添加到下载
+                foreach (var media in Medias)
+                {
+                    // 只下载选中项,跳过未选中项
+                    if (isOnlySelected && !media.IsSelected) { continue; }
+
+                    /// 有分P的就下载全部
+
+                    // 开启服务
+                    VideoInfoService videoInfoService = new VideoInfoService(media.Bvid);
+
+                    addToDownloadService.SetVideoInfoService(videoInfoService);
+                    addToDownloadService.GetVideo();
+                    addToDownloadService.ParseVideo(videoInfoService);
+                    // 下载
+                    i += addToDownloadService.AddToDownload(eventAggregator, directory);
+                }
+            });
+
+            if (directory == null)
+            {
+                return;
+            }
+
+            // 通知用户添加到下载列表的结果
+            if (i == 0)
+            {
+                eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipAddDownloadingZero"));
+            }
+            else
+            {
+                eventAggregator.GetEvent<MessageEvent>().Publish($"{DictionaryResource.GetString("TipAddDownloadingFinished1")}{i}{DictionaryResource.GetString("TipAddDownloadingFinished2")}");
+            }
+        }
+
+        private void OnCountChanged_Pager(int count) { }
+
+        private bool OnCurrentChanged_Pager(int old, int current)
+        {
+            if (!IsEnabled)
+            {
+                //Pager.Current = old;
+                return false;
+            }
+
+            Medias.Clear();
+            LoadingVisibility = Visibility.Visible;
+            NoDataVisibility = Visibility.Collapsed;
+
+            UpdatePublication(current);
+
+            return true;
+        }
+
+        private async void UpdatePublication(int current)
+        {
+            // 是否正在获取数据
+            // 在所有的退出分支中都需要设为true
+            IsEnabled = false;
+
+            var tab = TabHeaders[SelectTabId];
+
+            await Task.Run(() =>
+            {
+                CancellationToken cancellationToken = tokenSource.Token;
+
+                var publications = Core.BiliApi.Users.UserSpace.GetPublication(mid, current, VideoNumberInPage, tab.Id);
+                if (publications == null)
+                {
+                    // 没有数据,UI提示
+                    LoadingVisibility = Visibility.Collapsed;
+                    NoDataVisibility = Visibility.Visible;
+                    return;
+                }
+
+                var videos = publications.Vlist;
+                if (videos == null)
+                {
+                    // 没有数据,UI提示
+                    LoadingVisibility = Visibility.Collapsed;
+                    NoDataVisibility = Visibility.Visible;
+                    return;
+                }
+
+                foreach (var video in videos)
+                {
+                    // 查询、保存封面
+                    string coverUrl = video.Pic;
+                    BitmapImage cover;
+                    if (coverUrl == null || coverUrl == "")
+                    {
+                        cover = new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png"));
+                    }
+                    else
+                    {
+                        if (!coverUrl.ToLower().StartsWith("http"))
+                        {
+                            coverUrl = $"https:{video.Pic}";
+                        }
+
+                        StorageCover storageCover = new StorageCover();
+                        cover = storageCover.GetCoverThumbnail(video.Aid, video.Bvid, -1, coverUrl, 200, 125);
+                    }
+
+                    // 播放数
+                    string play = string.Empty;
+                    if (video.Play > 0)
+                    {
+                        play = Format.FormatNumber(video.Play);
+                    }
+                    else
+                    {
+                        play = "--";
+                    }
+
+                    DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区
+                    DateTime dateCTime = startTime.AddSeconds(video.Created);
+                    string ctime = dateCTime.ToString("yyyy-MM-dd");
+
+                    App.PropertyChangeAsync(new Action(() =>
+                    {
+                        PublicationMedia media = new PublicationMedia
+                        {
+                            Avid = video.Aid,
+                            Bvid = video.Bvid,
+                            Cover = cover,
+                            Duration = video.Length,
+                            Title = video.Title,
+                            PlayNumber = play,
+                            CreateTime = ctime
+                        };
+                        medias.Add(media);
+
+                        LoadingVisibility = Visibility.Collapsed;
+                        NoDataVisibility = Visibility.Collapsed;
+                    }));
+
+                    // 判断是否该结束线程,若为true,跳出循环
+                    if (cancellationToken.IsCancellationRequested)
+                    {
+                        break;
+                    }
+                }
+
+            }, (tokenSource = new CancellationTokenSource()).Token);
+            IsEnabled = true;
+        }
+
+        /// <summary>
+        /// 初始化页面数据
+        /// </summary>
+        private void InitView()
+        {
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
+
+            TabHeaders.Clear();
+            SelectTabId = -1;
+        }
+
+        /// <summary>
+        /// 导航到VideoDetail页面时执行
+        /// </summary>
+        /// <param name="navigationContext"></param>
+        public override void OnNavigatedTo(NavigationContext navigationContext)
+        {
+            base.OnNavigatedTo(navigationContext);
+
+            InitView();
+
+            // 根据传入参数不同执行不同任务
+            var parameter = navigationContext.Parameters.GetValue<Dictionary<string, object>>("Parameter");
+            if (parameter == null)
+            {
+                return;
+            }
+
+            mid = (long)parameter["mid"];
+            int tid = (int)parameter["tid"];
+            List<PublicationZone> zones = (List<PublicationZone>)parameter["list"];
+
+            foreach (var item in zones)
+            {
+                TabHeaders.Add(new TabHeader
+                {
+                    Id = item.Tid,
+                    Title = item.Name,
+                    SubTitle = item.Count.ToString()
+                });
+            }
+
+            // 初始选中项
+            var selectTab = TabHeaders.FirstOrDefault(item => item.Id == tid);
+            SelectTabId = TabHeaders.IndexOf(selectTab);
+
+            // 页面选择
+            Pager = new CustomPagerViewModel(1, (int)Math.Ceiling(double.Parse(selectTab.SubTitle) / VideoNumberInPage));
+            Pager.CurrentChanged += OnCurrentChanged_Pager;
+            Pager.CountChanged += OnCountChanged_Pager;
+            Pager.Current = 1;
+        }
+
+    }
+}

+ 2 - 0
src/DownKyi/ViewModels/ViewSettingsViewModel.cs

@@ -131,6 +131,8 @@ namespace DownKyi.ViewModels
             // 进入设置页面时显示的设置项
             SelectTabId = 0;
             regionManager.RequestNavigate("SettingsContentRegion", ViewBasicViewModel.Tag, new NavigationParameters());
+
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
         }
 
     }

+ 2 - 0
src/DownKyi/ViewModels/ViewToolboxViewModel.cs

@@ -122,6 +122,8 @@ namespace DownKyi.ViewModels
             // 进入设置页面时显示的设置项
             SelectTabId = 0;
             regionManager.RequestNavigate("ToolboxContentRegion", ViewBiliHelperViewModel.Tag, new NavigationParameters());
+
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
         }
 
     }

+ 1 - 0
src/DownKyi/ViewModels/ViewUserSpaceViewModel.cs

@@ -216,6 +216,7 @@ namespace DownKyi.ViewModels
             NavigationParameters param = new NavigationParameters()
             {
                { "object", banner.Object },
+               { "mid", mid },
             };
 
             switch (banner.Id)

+ 2 - 0
src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs

@@ -665,6 +665,8 @@ namespace DownKyi.ViewModels
         {
             base.OnNavigatedTo(navigationContext);
 
+            ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark");
+
             DownloadManage = ButtonIcon.Instance().DownloadManage;
             DownloadManage.Height = 24;
             DownloadManage.Width = 24;

+ 10 - 0
src/DownKyi/Views/SplashWindow.xaml

@@ -27,5 +27,15 @@
             FontSize="30"
             Foreground="White"
             Text="Welcome to DownKyi!" />
+
+        <TextBlock
+            x:Name="nameVersion"
+            Grid.Row="0"
+            Margin="10"
+            HorizontalAlignment="Right"
+            VerticalAlignment="Bottom"
+            FontSize="12"
+            Foreground="White" />
+
     </Grid>
 </Window>

+ 3 - 12
src/DownKyi/Views/SplashWindow.xaml.cs

@@ -1,16 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using DownKyi.Models;
 using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
 
 namespace DownKyi.Views
 {
@@ -22,6 +11,8 @@ namespace DownKyi.Views
         public SplashWindow()
         {
             InitializeComponent();
+
+            nameVersion.Text = new AppInfo().VersionName;
         }
     }
 }

+ 13 - 2
src/DownKyi/Views/UserSpace/ViewArchive.xaml

@@ -2,6 +2,7 @@
     x:Class="DownKyi.Views.UserSpace.ViewArchive"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
     xmlns:prism="http://prismlibrary.com/"
     prism:ViewModelLocator.AutoWireViewModel="True">
     <Grid>
@@ -9,7 +10,17 @@
             <RowDefinition Height="*" />
         </Grid.RowDefinitions>
 
-        <ListBox ItemsSource="{Binding PublicationZones}">
+        <ListBox
+            Name="namePublicationZones"
+            Margin="30,0,0,0"
+            ItemsSource="{Binding PublicationZones}"
+            ScrollViewer.HorizontalScrollBarVisibility="Disabled"
+            SelectedIndex="{Binding SelectedItem}">
+            <i:Interaction.Triggers>
+                <i:EventTrigger EventName="SelectionChanged">
+                    <i:InvokeCommandAction Command="{Binding PublicationZonesCommand}" CommandParameter="{Binding ElementName=namePublicationZones, Path=SelectedItem}" />
+                </i:EventTrigger>
+            </i:Interaction.Triggers>
             <ListBox.ItemsPanel>
                 <ItemsPanelTemplate>
                     <WrapPanel IsItemsHost="True" />
@@ -42,7 +53,7 @@
                             <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                 <StackPanel
                                     Grid.Row="0"
-                                    Margin="0,0,30,0"
+                                    Margin="0,20,30,0"
                                     HorizontalAlignment="Left"
                                     Cursor="Hand"
                                     Orientation="Vertical">

+ 2 - 2
src/DownKyi/Views/UserSpace/ViewChannel.xaml

@@ -9,7 +9,7 @@
             <RowDefinition Height="*" />
         </Grid.RowDefinitions>
 
-        <ListBox ItemsSource="{Binding Channels}">
+        <ListBox ItemsSource="{Binding Channels}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
             <ListBox.ItemsPanel>
                 <ItemsPanelTemplate>
                     <WrapPanel IsItemsHost="True" />
@@ -42,7 +42,7 @@
                             <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                 <StackPanel
                                     Grid.Row="0"
-                                    Margin="40,0,0,10"
+                                    Margin="30,0,30,10"
                                     HorizontalAlignment="Left"
                                     Orientation="Vertical">
                                     <Border

+ 312 - 0
src/DownKyi/Views/ViewPublication.xaml

@@ -0,0 +1,312 @@
+<UserControl
+    x:Class="DownKyi.Views.ViewPublication"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:custom="clr-namespace:DownKyi.CustomControl"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+    xmlns:prism="http://prismlibrary.com/"
+    prism:ViewModelLocator.AutoWireViewModel="True">
+    <UserControl.Resources>
+        <Style x:Key="MediaListStyle" TargetType="{x:Type ListBoxItem}">
+            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
+                        <StackPanel
+                            Name="nameItemPanel"
+                            Margin="15,15,15,5"
+                            Cursor="Hand"
+                            Orientation="Vertical">
+                            <Border
+                                Name="nameCover"
+                                Width="190"
+                                Height="119"
+                                HorizontalAlignment="Center"
+                                CornerRadius="5">
+                                <Border.Background>
+                                    <!--  长宽比:1.6  -->
+                                    <ImageBrush ImageSource="{Binding Cover}" />
+                                </Border.Background>
+
+                                <Grid Width="190" Height="119">
+                                    <Image
+                                        Name="nameChecked"
+                                        Width="24"
+                                        Height="24"
+                                        Margin="0,10,10,0"
+                                        HorizontalAlignment="Right"
+                                        VerticalAlignment="Top"
+                                        Source="/DownKyi;component/Resources/checked.png" />
+
+                                    <Border
+                                        HorizontalAlignment="Right"
+                                        VerticalAlignment="Bottom"
+                                        Background="#7F000000"
+                                        CornerRadius="5 0 5 0">
+                                        <TextBlock
+                                            Name="nameDuration"
+                                            Padding="6,0"
+                                            Foreground="White"
+                                            Text="{Binding Duration}" />
+                                    </Border>
+                                </Grid>
+                            </Border>
+
+                            <TextBlock
+                                Name="nameTitle"
+                                Height="35"
+                                MaxWidth="190"
+                                Margin="0,10,0,0"
+                                Foreground="Black"
+                                Tag="{Binding Bvid}"
+                                Text="{Binding Title}"
+                                TextTrimming="CharacterEllipsis"
+                                TextWrapping="Wrap"
+                                ToolTip="{Binding Title}">
+                                <i:Interaction.Triggers>
+                                    <i:EventTrigger EventName="MouseLeftButtonUp">
+                                        <i:InvokeCommandAction Command="{Binding TitleCommand}" CommandParameter="{Binding Bvid}" />
+                                    </i:EventTrigger>
+                                </i:Interaction.Triggers>
+                            </TextBlock>
+
+                            <Grid Margin="0,3,0,0">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition />
+                                    <ColumnDefinition />
+                                </Grid.ColumnDefinitions>
+
+                                <StackPanel Grid.Column="0" Orientation="Horizontal">
+                                    <Image
+                                        Width="11"
+                                        Height="11"
+                                        Source="/DownKyi;component/Resources/play.png" />
+                                    <TextBlock
+                                        Margin="5,0,0,0"
+                                        Foreground="#FF999999"
+                                        Text="{Binding PlayNumber}" />
+                                </StackPanel>
+                                <StackPanel Grid.Column="1" Orientation="Horizontal">
+                                    <Image
+                                        Width="12"
+                                        Height="11"
+                                        Source="/DownKyi;component/Resources/time.png" />
+                                    <TextBlock
+                                        Margin="5,0,0,0"
+                                        Foreground="#FF999999"
+                                        Text="{Binding CreateTime}" />
+                                </StackPanel>
+                            </Grid>
+                        </StackPanel>
+                        <ControlTemplate.Triggers>
+                            <Trigger Property="IsSelected" Value="True">
+                                <Setter TargetName="nameChecked" Property="Visibility" Value="Visible" />
+                            </Trigger>
+                            <Trigger Property="IsSelected" Value="False">
+                                <Setter TargetName="nameChecked" Property="Visibility" Value="Hidden" />
+                            </Trigger>
+                            <Trigger SourceName="nameTitle" Property="IsMouseOver" Value="True">
+                                <Setter TargetName="nameTitle" Property="Foreground" Value="{DynamicResource BrushPrimary}" />
+                            </Trigger>
+                        </ControlTemplate.Triggers>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="50" />
+            <RowDefinition Height="1" />
+            <RowDefinition Height="*" />
+        </Grid.RowDefinitions>
+
+        <Grid Grid.Row="0">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="100" />
+                <ColumnDefinition Width="*" />
+                <ColumnDefinition Width="100" />
+            </Grid.ColumnDefinitions>
+
+            <Button
+                Grid.Column="0"
+                Margin="10,5,0,5"
+                HorizontalAlignment="Left"
+                VerticalAlignment="Center"
+                Command="{Binding BackSpaceCommand}"
+                Style="{StaticResource ImageBtnStyle}">
+                <StackPanel Orientation="Horizontal">
+                    <ContentControl Width="24" Height="24">
+                        <Path
+                            Width="{Binding ArrowBack.Width}"
+                            Height="{Binding ArrowBack.Height}"
+                            Data="{Binding ArrowBack.Data}"
+                            Fill="{Binding ArrowBack.Fill}"
+                            Stretch="None" />
+                    </ContentControl>
+                    <TextBlock
+                        VerticalAlignment="Center"
+                        FontSize="16"
+                        Foreground="{DynamicResource BrushTextDark}"
+                        Text="{DynamicResource Publication}" />
+                </StackPanel>
+            </Button>
+        </Grid>
+
+        <TextBlock Grid.Row="1" Background="{DynamicResource BrushBorder}" />
+
+        <Grid Grid.Row="2">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="200" />
+                <ColumnDefinition />
+            </Grid.ColumnDefinitions>
+
+            <!--  左侧tab header  -->
+            <ListBox
+                Name="nameLeftTabHeaders"
+                Grid.Column="0"
+                BorderThickness="0"
+                IsEnabled="{Binding IsEnabled}"
+                ItemContainerStyle="{StaticResource LeftTabHeaderItemStyle}"
+                ItemsSource="{Binding TabHeaders}"
+                ScrollViewer.HorizontalScrollBarVisibility="Disabled"
+                ScrollViewer.VerticalScrollBarVisibility="Hidden"
+                SelectedIndex="{Binding SelectTabId, Mode=TwoWay}"
+                Style="{StaticResource LeftTabHeaderStyle}">
+                <i:Interaction.Triggers>
+                    <i:EventTrigger EventName="SelectionChanged">
+                        <i:InvokeCommandAction Command="{Binding LeftTabHeadersCommand}" CommandParameter="{Binding ElementName=nameLeftTabHeaders, Path=SelectedItem}" />
+                    </i:EventTrigger>
+                </i:Interaction.Triggers>
+            </ListBox>
+
+            <Grid Name="nameMediaPanel" Grid.Column="1">
+                <Grid.RowDefinitions>
+                    <RowDefinition />
+                    <RowDefinition Height="1" />
+                    <RowDefinition Height="49" />
+                </Grid.RowDefinitions>
+
+                <ListBox
+                    x:Name="nameMedias"
+                    Grid.Row="0"
+                    BorderThickness="0"
+                    ItemContainerStyle="{StaticResource MediaListStyle}"
+                    ItemsSource="{Binding Medias}"
+                    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
+                    SelectionMode="Extended">
+                    <i:Interaction.Triggers>
+                        <i:EventTrigger EventName="SelectionChanged">
+                            <i:InvokeCommandAction Command="{Binding MediasCommand}" CommandParameter="{Binding ElementName=nameMedias, Path=SelectedItems}" />
+                        </i:EventTrigger>
+                    </i:Interaction.Triggers>
+                    <ListBox.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <WrapPanel IsItemsHost="True" />
+                        </ItemsPanelTemplate>
+                    </ListBox.ItemsPanel>
+                    <ListBox.Style>
+                        <Style TargetType="ListBox">
+                            <Setter Property="Template">
+                                <Setter.Value>
+                                    <ControlTemplate TargetType="ListBox">
+                                        <Border
+                                            x:Name="Bd"
+                                            Padding="0"
+                                            Background="{TemplateBinding Background}"
+                                            BorderBrush="{TemplateBinding BorderBrush}"
+                                            BorderThickness="{TemplateBinding BorderThickness}">
+                                            <ScrollViewer Focusable="False">
+                                                <ItemsPresenter />
+                                            </ScrollViewer>
+                                        </Border>
+                                    </ControlTemplate>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </ListBox.Style>
+                </ListBox>
+
+                <!--  加载gif  -->
+                <StackPanel
+                    Grid.Row="0"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    Orientation="Vertical"
+                    Visibility="{Binding LoadingVisibility}">
+                    <ContentControl
+                        Width="40"
+                        Height="40"
+                        Content="{Binding Loading}" />
+                    <TextBlock
+                        Margin="0,10,0,0"
+                        FontSize="14"
+                        Foreground="{DynamicResource BrushTextDark}"
+                        Text="{DynamicResource PublicationWait}" />
+                </StackPanel>
+
+                <!--  没有数据提示  -->
+                <Image
+                    Grid.Row="0"
+                    Width="256"
+                    Height="256"
+                    Source="/DownKyi;component/Resources/no-data.png"
+                    Visibility="{Binding NoDataVisibility}" />
+
+                <TextBlock Grid.Row="1" Background="{DynamicResource BrushBorder}" />
+
+                <Grid Grid.Row="2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="80" />
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="100" />
+                        <ColumnDefinition Width="80" />
+                    </Grid.ColumnDefinitions>
+
+                    <CheckBox
+                        Grid.Column="0"
+                        Margin="10,0,0,0"
+                        HorizontalAlignment="Left"
+                        VerticalAlignment="Center"
+                        Command="{Binding SelectAllCommand}"
+                        CommandParameter="{Binding ElementName=nameMedias, Path=SelectedItem}"
+                        Content="{DynamicResource SelectAll}"
+                        Foreground="{DynamicResource BrushTextDark}"
+                        IsChecked="{Binding IsSelectAll, Mode=TwoWay}"
+                        Style="{StaticResource CheckBoxStyle}" />
+
+                    <custom:CustomPager
+                        Grid.Column="1"
+                        HorizontalAlignment="Center"
+                        VerticalAlignment="Center"
+                        DataContext="{Binding Pager}" />
+
+                    <Button
+                        Grid.Column="2"
+                        Margin="0,0,10,0"
+                        HorizontalAlignment="Right"
+                        VerticalAlignment="Center"
+                        Command="{Binding AddToDownloadCommand}"
+                        Content="{DynamicResource DownloadSelectedPublication}"
+                        FontSize="12"
+                        Foreground="{DynamicResource BrushText}"
+                        Style="{StaticResource BtnStyle}" />
+
+                    <Button
+                        Grid.Column="3"
+                        Margin="0,0,10,0"
+                        HorizontalAlignment="Right"
+                        VerticalAlignment="Center"
+                        Command="{Binding AddAllToDownloadCommand}"
+                        Content="{DynamicResource DownloadAllPublication}"
+                        FontSize="12"
+                        Foreground="{DynamicResource BrushText}"
+                        Style="{StaticResource BtnStyle}" />
+                </Grid>
+            </Grid>
+        </Grid>
+
+    </Grid>
+</UserControl>

+ 28 - 0
src/DownKyi/Views/ViewPublication.xaml.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace DownKyi.Views
+{
+    /// <summary>
+    /// ViewPublication.xaml 的交互逻辑
+    /// </summary>
+    public partial class ViewPublication : UserControl
+    {
+        public ViewPublication()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 3 - 6
src/DownKyi/Views/ViewUserSpace.xaml

@@ -243,7 +243,7 @@
             <!--  内容  -->
             <Grid Grid.Row="1" Visibility="{Binding ContentVisibility}">
                 <Grid.RowDefinitions>
-                    <RowDefinition Height="70" />
+                    <RowDefinition Height="65" />
                     <RowDefinition />
                 </Grid.RowDefinitions>
 
@@ -294,10 +294,7 @@
                 </Grid>
 
                 <!--  内容区content  -->
-                <ContentControl
-                    Grid.Row="1"
-                    Margin="20,20,20,0"
-                    prism:RegionManager.RegionName="UserSpaceContentRegion" />
+                <ContentControl Grid.Row="1" prism:RegionManager.RegionName="UserSpaceContentRegion" />
             </Grid>
 
         </Grid>
@@ -316,7 +313,7 @@
                 Margin="0,10,0,0"
                 FontSize="14"
                 Foreground="{DynamicResource BrushTextDark}"
-                Text="{DynamicResource MySpaceWait}" />
+                Text="{DynamicResource UserSpaceWait}" />
         </StackPanel>
 
         <!--  没有数据提示  -->

+ 0 - 201
src/LICENSE

@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright 2021 FlySelfLog
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.