Skip to content

Commit d89ed52

Browse files
Nikita KorobeinikovAleksandr SmirnovLastSprintAnton EysnerVadim4016
authored
Develop to master (#121)
* add proof of concept * add base table adapter and start examples * try to fix build * try to fix build * comment current solution * fix gravity table state manager conforming to TableStateManager * add alternative abstract cell generator and conforming to table cell generator * init plugin concept * remove unused code * add examples of table plugins and events * remove generic from table data source * add successfull gravity build example * add comment about not finished working * extract displayable flow support to plugin * init table builder and rddm namespace * remove adapter * add proxy plugin for scroll event * add description of refactoring concept * add changing of delegate * update weak provider in datasource * update weak stateManager in delegate * add really base state manager * debug on example project * update docs * update docs with image * update folder structure * move table plugins to separate files * IDPT-611 added tableScrollablePlugin * IDPT-611 configured tableEvents * IDPT-611 added plugin with support for determining the direction of the scroll * IDPT-611 added tableHeaderVisiblePlugin * IDPT-611 fixes after code review * added prefetching for tableView * IDPT-614 added example * renamed plugin and changed requestId type to Hashable * added example without prefetching and moved layout into storyboard * updated Readme * Added example * fix crash in foldablePlugin * added example * removed TableFoldableItem * update refactor plugins * fix empty plugins init * update Add property view to DataDisplayManager protocol * add Deprecation warnings * add builder for manual manager * fix unit tests * fixes after code review * added example for movable cell * fixes movable delegate and dataSource * Update ReactiveDataDisplayManagerTests/GravityFoldingHeaderGenerator.swift Co-authored-by: Vadim Tikhonov <vadimtikhonov.28@gmail.com> * ignore DS_Store * fix build * add pr template * Create CollectionListViewController and grouped the files into folders * Configure CollectionListViewController * Add main collection controller * Add list header and cell * Add dafault case * Add method for opening screen * Move Appearance to perivate extension * Update methods signature * Add comment and fix spaces * Fix links * Remove lazy * Add base implementation BaseCollectionManager * Add collection builder * Add move event * added and configured sectionTitleTableDataSource * added examples * small fix * Add simple example collection view without prefetching * Remove estimatedHeight from BaseCollectionDelegate * Make custom flow layout * Add cell size constant * Add Marks * update entities scheme * Make abstract delegate and dataSource * extend documentation * fix typo * add description * add description of animator * fixes after code review * Add feature plugins (#90) * added feature plugins * changed example * fixes build * split TableMovable into two protocols * removed array with featurePlugin * Add swipe actions support (#93) * added feature plugins * changed example * fixes build * added plugin * added example * changed file structure * split TableMovable into two protocols * removed array with featurePlugin * Added SwipeableTableGenerator * Add sizable cell support (#91) * Add SizableCollectionCellGenerator support * Add sizable example * Add FlowCollectionDelegate Co-authored-by: Vadim Tikhonov <tikhonov-vadim@surfstudio.co> * Add support prefetching to collection (#95) * fixes marks * added collection prefetching plugins * added example for collection with prefetching data * changed file structure in examples * fixed image size and configured prefetcher * Add examples for item scroll manager (#94) * added plugins and fixes code style * added example for image carousel * removed clearing image cache and moved scrollProvider * Add foldable cell support (#92) * Add base frame for Foldable collection example * Add ManualCollectionManager and FoldablePlugin * Configure example * Comfigure cascade update * Remove ManualCollectionManager * Change image size Co-authored-by: Vadim Tikhonov <tikhonov-vadim@surfstudio.co> * Add new name space for stackView (#97) * maked manager and builder for stackView * changed example for stackView * changed forceRefill with completion * added deprecated for BaseStackDataDisplayManager * Add titles for items in collection (#98) * added plugin * added example * updated layout * Refactoring fixes and finalize concept (#96) * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add swipeActionsPlugin to delegate protocol * remove duplicated tableAnimator setter * update CollectionType? to CollectionType! * fix retain cycle * update doc * update open BaseGenerators * add rddm namespace for cells * Update Source/REFACTORING/Protocols/Wrapper/DataDisplayWrapper.swift Co-authored-by: Anton Eysner <74650281+eysnerSurf@users.noreply.github.com> * fix typo * update Move forceRefill with completion into extension * add animator for collection * fix merge Co-authored-by: Anton Eysner <74650281+eysnerSurf@users.noreply.github.com> * Feature/idpt 660/documentation (#101) * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add swipeActionsPlugin to delegate protocol * remove duplicated tableAnimator setter * update CollectionType? to CollectionType! * fix retain cycle * update doc * update open BaseGenerators * add rddm namespace for cells * update readme structure * move roadmap to separate folder * add About intro * add currently supported features * add stub for migration guide * add footer to readme * fill changelog * remove old usage description from main readme * add Usage description * add example readme * Update README.md * update usage example * update Translating entities overview * fix link to entities scheme * continue translation * add example of extending base generator to foldable item * add atomic architecture explanation * update base generator configuration * update generator docs * add description of plugins and feature plugins * add examples of manual and gravity scenarious * add contributing guide * fix list format * add cellregisterType param to static init * remove old readme * update build status badge * update test os * fix ci run tests command * Replace custom generators to base generator (#99) * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add animator * update reference to datasource * update reference to delegate * move collection delegates and protocols into separate files * add UIKit imports * add gitattributes to merge pbxproj conflicts automatically * update delegate and dataSource protocols * update docs * add swipeActionsPlugin to delegate protocol * remove duplicated tableAnimator setter * update CollectionType? to CollectionType! * fix retain cycle * update doc * update open BaseGenerators * add rddm namespace for cells * update readme structure * move roadmap to separate folder * add About intro * add currently supported features * add stub for migration guide * add footer to readme * fill changelog * remove old usage description from main readme * add Usage description * add example readme * Update README.md * update usage example * update Translating entities overview * fix link to entities scheme * continue translation * add example of extending base generator to foldable item * add atomic architecture explanation * update base generator configuration * update generator docs * add description of plugins and feature plugins * add examples of manual and gravity scenarious * add contributing guide * fix list format * add cellregisterType param to static init * remove old readme * update build status badge * update test os * fix ci run tests command * replaced generators for table cell * replaced generators for collection cells * Add example for table with all plugins (#100) * add example for adapter with all plugins * fixes build * updated table cell generators in examples * updated collection cell generators in example * fix redeclaration * fixes duplicate reference to Collection Delegate Co-authored-by: Nikita Korobeinikov <korobeinikov@surfstudio.ru> * add triggers on push (#106) * Rename generator protocols (#111) * renamed protocols * fixes example * Feature/idpt 661/migration guide (#112) * update Manager init internal * add little migration guide * update Short static constructors for plugins * update Short static constructors for plugins * update doc * update Short static constructors for feature plugins * fix typo in doc * add Useful hints into migration guide * update protocol names in docs and examples * update migration guide * remove RDDM prefix * Feature/idpt 684/pagination plugin (#113) * add plugins to refreshing and pagination including view configuration * add input and outputs for refreshable plugin * add inputs and outputs for paginatable plugin * add setup for plugins * add refreshable plugin for collection * add example of refreshable controller * refactor paginatable progress view * fix example of paginatable plugin * add refreshable plugin to example of all plugins * update ROADMAP * add logo (#115) * update Paginatable plugin example (#116) * update Paginatable plugin example * add comments * add Update progress for Paginatable input * Feature/idpt 665/clean project structure (#120) * update podspec * remove temporary doc in sources * convert to swift 5 * add makefiles and project specs * add missed imports * fix example building * add initialisers for plugins * fix folders structure * remove gitattributes Co-authored-by: Aleksandr Smirnov <smirnov@surfstudio.ru> Co-authored-by: Alexander Kravchenkov <a.a.kravchenkov@gmail.com> Co-authored-by: Anton Eysner <eysner@surfstudio.ru> Co-authored-by: Vadim Tikhonov <vadimtikhonov.28@gmail.com> Co-authored-by: Vadim Tikhonov <tikhonov-vadim@surfstudio.co> Co-authored-by: eysnerSurf <74650281+eysnerSurf@users.noreply.github.com>
1 parent 58e9fc7 commit d89ed52

File tree

224 files changed

+11432
-1356
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

224 files changed

+11432
-1356
lines changed

.DS_Store

-8 KB
Binary file not shown.

.github/workflows/Build.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
name: Build
22

3-
on: [pull_request]
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- develop
8+
pull_request:
49

510
jobs:
611
build:
@@ -10,8 +15,6 @@ jobs:
1015
- name: Build
1116
run: xcodebuild -target ReactiveDataDisplayManager
1217
- name: Run tests
13-
run: xcodebuild test -scheme ReactiveDataDisplayManager -configuration "Debug" -sdk iphonesimulator -enableCodeCoverage YES -destination 'platform=iOS Simulator,name=iPhone 8,OS=13.2' | xcpretty -c
18+
run: xcodebuild test -scheme ReactiveDataDisplayManager -configuration "Debug" -sdk iphonesimulator -enableCodeCoverage YES -destination 'platform=iOS Simulator,name=iPhone 8' | xcpretty -c
1419
- name: Upload Coverage
1520
run: bash <(curl -s https://codecov.io/bash) -J 'ReactiveDataDisplayManager'
16-
17-

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ fastlane/screenshots
6767
fastlane/test_output
6868
Example/Pods
6969
Podfile.lock
70+
.DS_Store
71+
.bundle/

.swift-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0
1+
5.0

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## 7.0.0 Refactoring
2+
### Added
3+
- `Animator` protocol to isolate deprecated `begin/end updates`
4+
- `PluginAction` protocol to handle simple actions with collection events
5+
- `FeaturePlugin` protocol to extend collection manager with features quickly
6+
- `CollectionBuilder` struct to build `CollectionManager`
7+
- `TableBuilder` struct to build `TableManager`
8+
- `rddm` namespace to quick access to manager builders
9+
10+
### Updated
11+
12+
- required `var view: CollectionType!` of `DataDisplayManager` protocol instead of required constructor
13+
- abstract `Delegate` and `DataSource` is properties of `DataDisplayManager`
14+
15+
### Deprecated
16+
17+
- `BaseTableDataDisplayManager` class replaced with `ManualTableManager`
18+
- `PaginableBaseTableDataDisplayManager` class will be removed at **7.1.**. Use `TableLastCellIsVisiblePlugin` instead.
19+
- `ExtendableBaseTableDataDisplayManager` class will be removed at **7.1**. Part of `BaseTableManager` now.
20+
- `GravityTableDataDisplayManager` class replaced with `GravityTableManager`
21+
- `FoldingTableDataDisplayManager` class will be removed at **7.1.**. Use `TableFoldablePlugin` instead.
22+
- `GravityFoldingTableDataDisplayManager` class will be removed at **7.1.**. Use composition of `GravityTableManager` and `TableFoldablePlugin`
23+
- `SizableCollectionDataDisplayManager` class will be removed at **7.1.**. Part of `BaseCollectionManager`
24+
- `BaseCollectionDataDisplayManager` replaced with `BaseCollectionManager`
25+
- `SelectableItem`, `DisplayableFlow`, `MovableGenerator` and other item protocols will be removed at **7.1.**. Replaced with `{ability}ableItem` for example `SelectableItem`

Documentation/ContributingGuide.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Contributing guide
2+
3+
Thank you for your help! Before you start, let's take a look at some agreements.
4+
5+
## Versioning
6+
7+
Version format is `x.y.z` where
8+
- x is major version number. Bumped only in major updates (implementaion changes, adding new functionality)
9+
- y is minor version number. Bumped only in minor updates (interface changes)
10+
- z is minor version number. Bumped in case of bug fixes and e.t.c.
11+
12+
## Branch name
13+
14+
We are developing with minimal Git-flow.
15+
Base your branch from `develop` and select name with one from pattern
16+
17+
- bugfix/{issue_number}/{description} - for fixing small issues
18+
- feature/{issue_number}/{description} - for adding new functionality
19+
- hotfix/{description} - for fixing very small issues without discovered issues (like fixing unit tests, fixing typo etc)
20+
21+
## Pull request rules
22+
23+
Make sure that your code:
24+
25+
- [ ] Does not contain errors
26+
- [ ] New functionality is covered by tests and new functionality passes old tests
27+
- [ ] Create example that demonstrate new functionality if it is possible
28+
29+
## Accepting the changes
30+
31+
After your pull request passes the review code, the project maintainers will merge the changes
32+
into the branch to which the pull request was sent.
33+
34+
## Issues
35+
36+
Feel free to report any issues and bugs.
37+
38+
1. To report about the problem, create an issue on GithHub
39+
2. In the issue add the description of the problem
40+
3. Do not forget to mention your development environment, Xcode version, iOS version, frameworks required for
41+
illustration of the problem
42+
4. It is necessary to attach the code part that causes an issue or to make a small demo project
43+
that shows the issue
44+
5. Attach stack trace so it helps us to deal with the issue
45+
6. If the issue is related to layout or animation, screen recording is required

Documentation/Entities.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Entities overview
2+
3+
Basically `DataDisplayManager` contains such set of entities.
4+
5+
[![AbstractDataDisplayManager](https://i.ibb.co/rH9Kkp6/Abstract-Data-Display-Manager.png)](https://ibb.co/TtyDc08)
6+
7+
To simplify explanation of entities relationships we can mark each entity with atomic architecture terms.
8+
9+
[![Atomic Architecture](https://i.ibb.co/3cMwnjZ/2021-02-20-18-52-59.png)](https://ibb.co/L1JM3c7)
10+
11+
Greater component is more complex, and can directly or indirectly operate with smaller components.
12+
13+
**RECOMMENDATION** Interact, customize or extend atoms and moleculas when it possible and not greater entities.
14+
15+
## DataDisplayManager
16+
17+
`DataDisplayManager` protocol it is main *template* with client interface which operating with `generators`.
18+
19+
Concrete implementation with injected plugins is *page* in atomic terms.
20+
21+
### Example
22+
23+
In base implementation you can only add `generators` and reload collection.
24+
25+
But if your collection can change size dynamically, you need to choose between `Manual` and `Gravity` manager.
26+
27+
Assume you have such screen with two cells and horizontal scrolling content
28+
```
29+
Screen:
30+
Cell A:
31+
- item 1
32+
- item 2
33+
...
34+
Cell B:
35+
- item 1
36+
- item 2
37+
...
38+
```
39+
40+
Each cell content is loading from separate endpoint. It means that sometimes cell A can be loaded first, and sometimes cell B. But cells order should always be the same.
41+
42+
Should we always wait loading of both sections before layout?
43+
44+
No
45+
46+
With `manual` approach
47+
48+
```swift
49+
50+
private lazy var ddm = tableView.rddm.manualBuilder.build()
51+
52+
private var generatorA: TableCellGenerator?
53+
private var generatorB: TableCellGenerator?
54+
55+
func onContentALoaded(items: [ItemA]) {
56+
57+
let generatorA = GeneratorA(items: items)
58+
59+
if let generatorB = self.generatorB {
60+
ddm.insert(before: generatorB, new: [generatorA])
61+
} else {
62+
ddm.addCellGenerator(generatorA)
63+
ddm.forceRefill()
64+
}
65+
66+
self.generatorA = generatorA
67+
}
68+
69+
func onContentBLoaded(items: [ItemB]) {
70+
71+
let generatorB = GeneratorB(items: items)
72+
73+
if let generatorA = self.generatorA {
74+
ddm.insert(after: generatorA, new: [generatorB])
75+
} else {
76+
ddm.addCellGenerator(generatorB)
77+
ddm.forceRefill()
78+
}
79+
80+
self.generatorB = generatorB
81+
}
82+
83+
```
84+
85+
With `gravity` approach
86+
87+
```swift
88+
89+
private lazy var ddm = tableView.rddm.gravityBuilder.build()
90+
91+
func onContentALoaded(items: [ItemA]) {
92+
let generatorA = GeneratorA(items: items)
93+
ddm.addCellGenerator(generatorA)
94+
ddm.forceRefill()
95+
}
96+
97+
func onContentBLoaded(items: [ItemB]) {
98+
let generatorB = GeneratorB(items: items)
99+
ddm.addCellGenerator(generatorB)
100+
ddm.forceRefill()
101+
}
102+
103+
```
104+
105+
Secret in generators. Each `GravityGenerator` has property heaviness. This allow us to forget about sort order on fill stage.
106+
107+
In this example you can see differences between managing approaches and choose one for your needs.
108+
109+
`Manual` approach will be more useful in simple collections or when you updating cells based on user actions, like tap on plus button inside specific cell..
110+
111+
## Generator
112+
113+
- *Atom*
114+
- Can build cells
115+
- Can configure cells
116+
- Can store closures or current cell state
117+
- Stored inside `DataDisplayManager` and can cached to directly update concrete cell
118+
119+
`BaseCellGenerator` is your choice if you need display cell and process selection event.
120+
121+
This is generic generator with only one requirement to cell - conforming to `ConfigurableItem` protocol.
122+
123+
Recommended way to create generator `YourCellType.rddm.baseGenerator(with: model)` where model is `ConfigurableItem.Model` instance.
124+
125+
**NOTE** If you want store closures, current cell state or extend generator with some protocol, preferable extending `BaseCellGenerator` and not create your own.
126+
127+
### Example
128+
129+
Extending `BaseCellGenerator` to `FoldableItem`
130+
131+
```swift
132+
final class FoldableCellGenerator: BaseCellGenerator<FoldableTableViewCell>, FoldableItem {
133+
134+
// MARK: - FoldableItem
135+
136+
var didFoldEvent = BaseEvent<Bool>()
137+
var isExpanded = false
138+
var childGenerators: [TableCellGenerator] = []
139+
140+
// MARK: - Configuration
141+
142+
override func configure(cell: FoldableTableViewCell, with model: FoldableTableViewCell.Model) {
143+
super.configure(cell: cell, with: model)
144+
145+
didFoldEvent.addListner { isExpanded in
146+
cell.update(expanded: isExpanded)
147+
}
148+
}
149+
150+
}
151+
```
152+
153+
### Errors
154+
155+
- `Could not load NIB in bundle ... with name 'YourCellName'`
156+
This error appears because you are using class based cells (not UINib), but inside RDDM cells registering via UINib.
157+
158+
**Your should override default registering in generator**
159+
```swift
160+
extension YourCellGenerator: TableCellGenerator {
161+
// ...
162+
163+
func registerCell(in tableView: UITableView) {
164+
tableView.register(identifier, forCellReuseIdentifier: String(describing: identifier))
165+
}
166+
167+
// ...
168+
}
169+
```
170+
171+
or simply fix initialising of your BaseGenerator using ` BaseCellGenerator.init(with: Cell.Model, registerType: .class)`
172+
173+
## Delegate
174+
175+
- *Organism*
176+
- based on `UITableViewDelegate` or `UICollectionViewDelegate` (depends on collection type)
177+
- Proxy collection events to plugins
178+
179+
### How to change
180+
181+
We hope, that bult-in delegate will cover 99% of your needs.
182+
183+
Btw you always can replace delegate with your own implementation.
184+
185+
**DO NOT FORGET** inherit BaseDelegate or add calls to plugins to not loose bult-in features.
186+
187+
`tableView.rddm.baseBuilder.set(delegate: YourCustomDelegate()).build()`
188+
189+
## DataSource
190+
191+
- *Organism*
192+
- Based on `UITableViewDataSource` or `UICollectionViewDataSource` (depends on collection type)
193+
- Implement `UITableViewDataSourcePrefetching` or `UICollectionViewDataSourcePrefetching` (depends on collection type)
194+
- Proxy collection events to plugins
195+
196+
### How to change
197+
198+
We hope, that bult-in datasource will cover 99% of your needs.
199+
200+
Btw you always can replace datasource with your own implementation.
201+
202+
**DO NOT FORGET** inherit BaseDataSource or add calls to plugins to not loose bult-in features.
203+
204+
`tableView.rddm.baseBuilder.set(dataSource: YourCustomDataSource()).build()`
205+
206+
## PluginAction
207+
208+
- *Molecula*
209+
- is represent reaction on collection event or events.
210+
- Have access to manager. It means, that you have access to generators and can update collection from plugin.
211+
- Injected in `PluginCollection` - simple list wrapper. It means, that on **each** event we can have **many** reactions (plugin-actions).
212+
- can not return values to delegate or datasource
213+
214+
You can look at full list of proxy events in enums: `TableEvent`, `PrefetchEvent`, `CollectionEvent`, `ScrollEvent`.
215+
216+
### How to add
217+
218+
Simply add plugin in stage of building
219+
220+
`tableView.rddm.baseBuilder.add(plugin: YourCustomPlugin()).build()`
221+
222+
And conform generator to concrete `PluginAction.GeneratorType`
223+
224+
### Example
225+
226+
Handling rows selection.
227+
228+
```swift
229+
public class TableSelectablePlugin: BaseTablePlugin<TableEvent> {
230+
231+
typealias GeneratorType = SelectableItem
232+
233+
public override init() {}
234+
235+
public override func process(event: TableEvent, with manager: BaseTableManager?) {
236+
237+
switch event {
238+
case .didSelect(let indexPath):
239+
guard let selectable = manager?.generators[indexPath.section][indexPath.row] as? GeneratorType else {
240+
return
241+
}
242+
selectable.didSelectEvent.invoke(with: ())
243+
244+
if selectable.isNeedDeselect {
245+
manager?.view?.deselectRow(at: indexPath, animated: true)
246+
}
247+
default:
248+
break
249+
}
250+
}
251+
252+
}
253+
```
254+
255+
More examples in bult-in plugins and example project.
256+
257+
## FeaturePlugin
258+
259+
- *Molecula*
260+
- is implement part of delegate, datasource or both
261+
- injected as **one** optional instance to avoid conflicts
262+
- can return values to delegate or datasource
263+
264+
Basically this entity is adding fixed part of functionality like moving or dragging of cells.
265+
266+
### How to add
267+
268+
Simply set plugin in stage of building
269+
270+
`tableView.rddm.baseBuilder.add(featurePlugin: YourCustomPlugin()).build()`
271+
272+
And conform generator to concrete `FeaturePlugin.GeneratorType`
273+
274+
### Examples
275+
276+
Look at `TableMovablePlugin`, `TableSectionTitleDisplayablePlugin` or `TableSwipeActionsConfigurationPlugin` and analogs for `UICollectionView`.
277+
278+
## Animator
279+
280+
Little atom created for approaches to animate collection changes.
281+
282+
`TableUpdatesAnimator` uses **beginUpdates/endUpdates** approach which will be deprecated soon.
283+
284+
`TableBatchUpdatesAnimator` uses **performBatchUpdates** approach which available since iOS 11.
285+
286+
Animator is selected based on iOS version, so most likely you never need to change this entity, but we've save such ability for you.
287+
288+
### How to change
289+
290+
Implement protocol `Animator` and set your implementation in builder
291+
292+
`tableView.rddm.baseBuilder.set(animator: YourCustomAnimator()).build()`

0 commit comments

Comments
 (0)