Pixeval/.github/CONTRIBUTING.md
2024-12-27 02:36:27 +08:00

157 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pixeval 贡献指南
## LoginPage
用户登录的界面,应该以尽量简单可靠的方式完成登录。
如果可以找到pixiv登录的api是最好的方法可以直接不使用WebView来登录但可能没有这种方法毕竟官方的app也是使用网页登录。
如果可以使用外部浏览器来登录也很好但这样缺点是难以操作代理设置返回token的时候也需要注册应用链接打开十分麻烦。
原来使用Playwright当自动化登录的方式但Playwright包太大了WebView包不包含在应用包内故使用js代码直接操作。
pixiv的页面是使用react写的设置自动化费了好大劲最终参考[这个方法](https://www.jianshu.com/p/78f5a4baf88c)实现了填写表单。
## 控件命名方式
### XXXItem
如`IllustrationItem`等,表示可以在列表视图中展示的一个卡片元素,可以展示一个画师,一幅插画等。
一般由`AdvancedItemsView`进行承载,并支持加载更多。
### XXXView
如`IllustrationView`等,是包含了一个`AdvancedItemsView`来展示`XXXItem`的控件,方便控件的复用。
也有一些`AdvancedItemsView`控件,由于目前没有复杂的复用需求,所以被直接包含在某些页面里了。
### XXXContainer
如`IllustrationContainer`等,是包含了一个`XXXView`的控件,并且加上了上部的工具栏,可以简单地往里面添加`ComboBox`等控件。
由于逻辑过于简单所以没有对应的XAML文件。
### XXXViewerPage
如`IllustrationViewerPage`等(`ImageViewerPage`除外),表示用一个单独窗口来展示详细信息的页面,可以展示一个画师,一幅插画等。
由于单独使用一个窗口,所以要继承自`SupportCustomTitleBarDragRegionPage`来支持拖拽区域定义,
同时搭配一个`XXXViewerPageHelper`的静态类,用来方便地从任何代码上下文呼出包含`XXXViewerPage`的窗口。
### EntryXXX
一般表示被Illustration、Illustrator、Novel共用的模型如`IllustrationView`、`IllustratorView`都包含`EntryView`。
本来打算是让`IllustrationView`、`IllustratorView`等继承`EntryView`但由于都有XAML会导致`InitializeComponent`重定义,所以最终没有这样做。
### WorkXXX
Work表示作品所以这个一般表示被Illustration、Novel共用的模型由于它的ViewModel既可以接受Illustration也可以接受Novel
所以从理论上来说这个取代了相应的`IllustrationXXX`和`IllustratorXXX`。例如`WorkView`就取代了`IllustrationView`和`IllustratorView`。
### AdvancedItemsView
继承自`ItemsView`,除了可以方便地指定`ItemsViewLayoutType`外,还支持滚动到底部时增量加载新的数据(需要`ItemsSource`实现`ISupportIncrementalLoading`)。
和`ItemsView`一样,它要求`ItemTemplate`中的控件被`ItemContainer`包裹。
## 模型类设计
### 下载模型
以`XXXDownloadTask`格式命名的都是下载模型,每种模型分为`XXXDownloadTask`、`IntrinsicXXXDownloadTask`、`LazyInitializedXXXDownloadTask`三种,
分别是普通的下载,已经完成的下载,和已经完成并且要懒加载的下载。其中后两种直接继承于第一种,第三种主要用在重新打开应用时显示。
### 增量加载器
与此相关的有`XXXDataProvider`和`XXXIncrementalSource`。
一般来说它们分别继承于`IDataProvider<T, TViewModel>`和`FetchEngineIncrementalSource<T, TModel>`
(如`IllustrationViewDataProvider`和`IllustrationFetchEngineIncrementalSource`)但并非必要。
如果不是从网络上获取的数据源(如本地数据源),则不需要继承那么多方法,可以直接自己实现一个类似的即可
(如`DownloadListEntryDataProvider`和`DownloadListEntryIncrementalSource`)。
总的来说,`XXXIncrementalSource`是从数据源(如`IFetchEngine<T>`等)获取数据,并对外封装为`IEnumerable<T>`的形式,
`XXXDataProvider`是对`XXXIncrementalSource`的封装,管理它的新建、刷新、`Dispose`等功能,所以不是必要的。
## 工具类设计思想
### WindowsFactory
一个统一的窗口构建类,窗口默认包含一个`Frame`,用来承载页面。
由于`Window`类并不是继承自`UIElement`在XAML等各处使用都很不方便故通过封装隐藏所有操作。
除了第一个窗口,其他都是第一个窗口的子窗口。
### EnhancedPage/EnhancedWindowPage/SupportCustomTitleBarDragRegionPage
`EnhancedPage`通过封装将`OnNavigatedTo`、`OnNavigatingFrom`封装为更简单的`OnPageActivated`、`OnPageDeactivated`。
并且记录了同一个页面的导航次数、自动清理页面缓存。
`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`,也没有`SelectedItems`。
`AdvancedCollectionView`由于编写者疏忽在许多地方都有一些BUG尤其是插入新元素的逻辑被发现的BUG在本类中都悉数修复了。
## 总体思想
### 少用WinRT API
例如`IRandomAccessStream`和`Stream`项目中更倾向于使用原生的后者。除了因为WinRT需要COM可能会降低效率也是因为在类型封送的时候不稳定
可能导致`Position`为负数的情况。
WinRT自带的Bitmap解码器功能较差经常出现正常图片无法解码的功能。本项目使用`ImageSharp`代替其解码、编码的职能。
剪切板api必须传入原生的`IRandomAccessStream`才能正常展示(`Stream.AsRandomAccessStream()`是不行的但这个api别无选择。
### 重视内存管理
由于WinUI的内存泄露问题十分严重在一切可以释放内存的地方都应该注意释放。例如项目中使用`SoftwareBitmap`和`SharedRef`就是为了尽快释放内存。
`RecyclableMemoryStream`可以提高`MemoryStream`的利用效率,在`IoHelper`中声明了`RecyclableMemoryStreamManager`
如果需要大量使用`MemoryStream`,应该调用`IoHelper`。
### 通过继承、组合减少代码量
如`IllustrationView`、`IllustratorView`都包含`EntryView`,这是为了不要将相似的逻辑写两遍,否则在重构某部分时也许会漏掉另一部分。
提取不同类的共同部分并不总是为了抽象。
### XAML中少用复杂的Margin、Padding
布局如果可以就尽量使用`Grid`实现,`StackPanel`有时也可以使用,但它的堆叠方向长度是无穷的,导致难以适配父控件大小。
尽量使用父控件`Spacing`参数实现控件的间隔,指定`Padding`时也使用比较统一、简单的值(如资源中的`CardControlPadding`
因为阅读XAML时复杂的值不方便人理解想象在修改界面时也更难维护。
### 注意控件回收Recycle带来的BUG
被`DataTemplate`包裹的控件都可能会被回收。回收时XAML属性会被重新赋值但不会重新触发从构造函数到`Loaded`中的内容。
如果XAML中的绑定没有使用`OneWay`,或者把某些数据加载逻辑写在`Loaded`中或之前,都有可能导致数据对不上的问题。
### 捕获网络异常
项目中最常出现的异常就是网络的异常,而且是不可避免的。如果要让网络的异常不影响到应用的正常运行,则需要抑制这些异常。
项目中引入了`FileLogger`,在抑制所有异常的同时记录,方便崩溃分析。
为了不需要重构更多代码(也为了不需要在每次网络请求时都考虑失败情况),在获得数据失败后会返回默认数据,给界面渲染。
相关api`FactoryAttribute`,根据属性默认值自动生成默认赋值方法`CreateDefault`,在失败后会调用它并返回。
### 使用新的控件
在WASDK1.4中引入了新的控件`ScrollView`、`ItemsView`等,这些控件完全重写,使用更简洁的实现,获得比原来更多的功能。
而且在设计上也更符合直觉,黑箱操作更少。不过缺点是不够稳定,如`TagsEntry`中为了避免渲染BUG
只好使用旧的`ItemsControl`+`ItemsPanel`代替新的`ItemsRepeater`+`Layout`
### 使用统一的命名空间,不必与文件夹结构同步
为了使`Controls`文件夹内文件更有层次,项目使用文件夹包裹这些控件,但可能会被提示应该与文件夹同步命名空间。
这时应该无视,因为在使用这些控件的时候可以用更加统一的命名空间(如`Pixeval.Controls`)来指定,无需写冗长的命名空间声明。
*CommunityToolkit*中也是这样做的。