Skip to content

Latest commit

 

History

History
156 lines (97 loc) · 9.65 KB

CONTRIBUTING.md

File metadata and controls

156 lines (97 loc) · 9.65 KB

Pixeval 贡献指南

LoginPage

用户登录的界面,应该以尽量简单可靠的方式完成登录。

如果可以找到pixiv登录的api是最好的方法,可以直接不使用WebView来登录,但可能没有这种方法,毕竟官方的app也是使用网页登录。

如果可以使用外部浏览器来登录也很好,但这样缺点是难以操作代理设置,返回token的时候也需要注册应用链接打开,十分麻烦。

原来使用Playwright当自动化登录的方式,但Playwright包太大了(WebView包不包含在应用包内)故使用js代码直接操作。 pixiv的页面是使用react写的,设置自动化费了好大劲,最终参考这个方法实现了填写表单。

控件命名方式

XXXItem

IllustrationItem等,表示可以在列表视图中展示的一个卡片元素,可以展示一个画师,一幅插画等。 一般由AdvancedItemsView进行承载,并支持加载更多。

XXXView

IllustrationView等,是包含了一个AdvancedItemsView来展示XXXItem的控件,方便控件的复用。 也有一些AdvancedItemsView控件,由于目前没有复杂的复用需求,所以被直接包含在某些页面里了。

XXXContainer

IllustrationContainer等,是包含了一个XXXView的控件,并且加上了上部的工具栏,可以简单地往里面添加ComboBox等控件。 由于逻辑过于简单,所以没有对应的XAML文件。

XXXViewerPage

IllustrationViewerPage等(ImageViewerPage除外),表示用一个单独窗口来展示详细信息的页面,可以展示一个画师,一幅插画等。 由于单独使用一个窗口,所以要继承自SupportCustomTitleBarDragRegionPage来支持拖拽区域定义, 同时搭配一个XXXViewerPageHelper的静态类,用来方便地从任何代码上下文呼出包含XXXViewerPage的窗口。

EntryXXX

一般表示被Illustration、Illustrator、Novel共用的模型,如IllustrationViewIllustratorView都包含EntryView。 本来打算是让IllustrationViewIllustratorView等继承EntryView,但由于都有XAML,会导致InitializeComponent重定义,所以最终没有这样做。

WorkXXX

Work表示作品,所以这个一般表示被Illustration、Novel共用的模型,由于它的ViewModel既可以接受Illustration,也可以接受Novel, 所以从理论上来说这个取代了相应的IllustrationXXXIllustratorXXX。例如WorkView就取代了IllustrationViewIllustratorView

AdvancedItemsView

继承自ItemsView,除了可以方便地指定ItemsViewLayoutType外,还支持滚动到底部时增量加载新的数据(需要ItemsSource实现ISupportIncrementalLoading)。 和ItemsView一样,它要求ItemTemplate中的控件被ItemContainer包裹。

模型类设计

下载模型

XXXDownloadTask格式命名的都是下载模型,每种模型分为XXXDownloadTaskIntrinsicXXXDownloadTaskLazyInitializedXXXDownloadTask三种, 分别是普通的下载,已经完成的下载,和已经完成并且要懒加载的下载。其中后两种直接继承于第一种,第三种主要用在重新打开应用时显示。

增量加载器

与此相关的有XXXDataProviderXXXIncrementalSource。 一般来说它们分别继承于IDataProvider<T, TViewModel>FetchEngineIncrementalSource<T, TModel>, (如IllustrationViewDataProviderIllustrationFetchEngineIncrementalSource)但并非必要。 如果不是从网络上获取的数据源(如本地数据源),则不需要继承那么多方法,可以直接自己实现一个类似的即可 (如DownloadListEntryDataProviderDownloadListEntryIncrementalSource)。

总的来说,XXXIncrementalSource是从数据源(如IFetchEngine<T>等)获取数据,并对外封装为IEnumerable<T>的形式, XXXDataProvider是对XXXIncrementalSource的封装,管理它的新建、刷新、Dispose等功能,所以不是必要的。

工具类设计思想

WindowsFactory

一个统一的窗口构建类,窗口默认包含一个Frame,用来承载页面。 由于Window类并不是继承自UIElement,在XAML等各处使用都很不方便,故通过封装隐藏所有操作。 除了第一个窗口,其他都是第一个窗口的子窗口。

EnhancedPage/EnhancedWindowPage/SupportCustomTitleBarDragRegionPage

EnhancedPage通过封装将OnNavigatedToOnNavigatingFrom封装为更简单的OnPageActivatedOnPageDeactivated。 并且记录了同一个页面的导航次数、自动清理页面缓存。

EnhancedWindowPage继承自EnhancedPage,唯一的区别是在导航参数中隐式传递了所在的Window,可以方便使用一些需要HWnd的api。 这个参数在EnhancedWindowPage之间传递时是透明的。 如果调用导航的地方无法获取到现在所在的Window(比如说在层次很深的控件内),此时就应该使用EnhancedPage

SupportCustomTitleBarDragRegionPage除了拥有EnhancedWindowPage的功能外,还添加一些方法用来计算、指定所在窗口的拖拽区域, 这对于自定义标题栏来说十分重要。一般这个页面会作为窗口内的底层页面。

SharedRef

SharedRef<T>类似于C++的shared_ptr<T>,但由于C#语言限制,并不能做的十分完美。大致思想就是通过引用计数,来同步不同对象内对同一个对象引用的释放。 为了防止同一个对象错误地多次释放,释放时需要提供本对象的哈希值。

AdvancedObservableCollection

类似于AdvancedCollectionView的泛型版本,但更好用,专门为ItemsView/AdvancedItemsView设计。 本质上是ObservableCollection<T>的底子上加了ISupportIncrementalLoading和筛选、排序功能。

AdvancedCollectionView实现了ICollectionView,所以支持和ListView/GridView中的SelectedItems同步, 但ItemsView/AdvancedItemsView并不支持ICollectionView,所以本类没有实现ICollectionView,也没有SelectedItemsAdvancedCollectionView由于编写者疏忽,在许多地方都有一些BUG(尤其是插入新元素的逻辑),被发现的BUG在本类中都悉数修复了。

总体思想

少用WinRT API

例如IRandomAccessStreamStream,项目中更倾向于使用原生的后者。除了因为WinRT需要COM可能会降低效率,也是因为在类型封送的时候不稳定, 可能导致Position为负数的情况。

WinRT自带的Bitmap解码器功能较差,经常出现正常图片无法解码的功能。本项目使用ImageSharp代替其解码、编码的职能。

剪切板api必须传入原生的IRandomAccessStream才能正常展示(Stream.AsRandomAccessStream()是不行的),但这个api别无选择。

重视内存管理

由于WinUI的内存泄露问题十分严重,在一切可以释放内存的地方都应该注意释放。例如项目中使用SoftwareBitmapSharedRef就是为了尽快释放内存。

RecyclableMemoryStream可以提高MemoryStream的利用效率,在IoHelper中声明了RecyclableMemoryStreamManager, 如果需要大量使用MemoryStream,应该调用IoHelper

通过继承、组合减少代码量

IllustrationViewIllustratorView都包含EntryView,这是为了不要将相似的逻辑写两遍,否则在重构某部分时也许会漏掉另一部分。 提取不同类的共同部分并不总是为了抽象。

XAML中少用复杂的Margin、Padding

布局如果可以就尽量使用Grid实现,StackPanel有时也可以使用,但它的堆叠方向长度是无穷的,导致难以适配父控件大小。 尽量使用父控件Spacing参数实现控件的间隔,指定Padding时也使用比较统一、简单的值(如资源中的CardControlPadding), 因为阅读XAML时复杂的值不方便人理解想象,在修改界面时也更难维护。

注意控件回收(Recycle)带来的BUG

DataTemplate包裹的控件都可能会被回收。回收时XAML属性会被重新赋值,但不会重新触发从构造函数到Loaded中的内容。 如果XAML中的绑定没有使用OneWay,或者把某些数据加载逻辑写在Loaded中或之前,都有可能导致数据对不上的问题。

捕获网络异常

项目中最常出现的异常就是网络的异常,而且是不可避免的。如果要让网络的异常不影响到应用的正常运行,则需要抑制这些异常。 项目中引入了FileLogger,在抑制所有异常的同时记录,方便崩溃分析。 为了不需要重构更多代码(也为了不需要在每次网络请求时都考虑失败情况),在获得数据失败后会返回默认数据,给界面渲染。

相关api:FactoryAttribute,根据属性默认值自动生成默认赋值方法CreateDefault,在失败后会调用它并返回。

使用新的控件

在WASDK1.4中引入了新的控件ScrollViewItemsView等,这些控件完全重写,使用更简洁的实现,获得比原来更多的功能。 而且在设计上也更符合直觉,黑箱操作更少。不过缺点是不够稳定,如TagsEntry中为了避免渲染BUG, 只好使用旧的ItemsControl+ItemsPanel代替新的ItemsRepeater+Layout

使用统一的命名空间,不必与文件夹结构同步

为了使Controls文件夹内文件更有层次,项目使用文件夹包裹这些控件,但可能会被提示应该与文件夹同步命名空间。 这时应该无视,因为在使用这些控件的时候可以用更加统一的命名空间(如Pixeval.Controls)来指定,无需写冗长的命名空间声明。 CommunityToolkit中也是这样做的。