Collection section type

This commit is contained in:
Justin Mazzocchi 2021-01-22 22:15:52 -08:00
parent c4da421846
commit 182bc5ce18
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
22 changed files with 141 additions and 102 deletions

View file

@ -425,7 +425,7 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func process(results: Results) -> AnyPublisher<[[CollectionItem]], Error> { func process(results: Results) -> AnyPublisher<[CollectionSection], Error> {
databaseWriter.writePublisher { db -> ([StatusInfo], [Status.Id]) in databaseWriter.writePublisher { db -> ([StatusInfo], [Status.Id]) in
for account in results.accounts { for account in results.accounts {
try account.save(db) try account.save(db)
@ -442,22 +442,23 @@ public extension ContentDatabase {
return (statusInfos, ids) return (statusInfos, ids)
} }
.map { statusInfos, ids -> [[CollectionItem]] in .map { statusInfos, ids -> [CollectionSection] in
[ [
results.accounts.map(CollectionItem.account), .init(items: results.accounts.map(CollectionItem.account), titleLocalizedStringKey: "search.accounts"),
statusInfos .init(items: statusInfos
.sorted { ids.firstIndex(of: $0.record.id) ?? 0 < ids.firstIndex(of: $1.record.id) ?? 0 } .sorted { ids.firstIndex(of: $0.record.id) ?? 0 < ids.firstIndex(of: $1.record.id) ?? 0 }
.map { .map {
.status(.init(info: $0), .status(.init(info: $0),
.init(showContentToggled: $0.showContentToggled, .init(showContentToggled: $0.showContentToggled,
showAttachmentsToggled: $0.showAttachmentsToggled)) showAttachmentsToggled: $0.showAttachmentsToggled))
} },
titleLocalizedStringKey: "search.statuses")
] ]
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func timelinePublisher(_ timeline: Timeline) -> AnyPublisher<[[CollectionItem]], Error> { func timelinePublisher(_ timeline: Timeline) -> AnyPublisher<[CollectionSection], Error> {
ValueObservation.tracking( ValueObservation.tracking(
TimelineItemsInfo.request(TimelineRecord.filter(TimelineRecord.Columns.id == timeline.id)).fetchOne) TimelineItemsInfo.request(TimelineRecord.filter(TimelineRecord.Columns.id == timeline.id)).fetchOne)
.removeDuplicates() .removeDuplicates()
@ -482,7 +483,7 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func contextPublisher(id: Status.Id) -> AnyPublisher<[[CollectionItem]], Error> { func contextPublisher(id: Status.Id) -> AnyPublisher<[CollectionSection], Error> {
ValueObservation.tracking( ValueObservation.tracking(
ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == id)).fetchOne) ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == id)).fetchOne)
.removeDuplicates() .removeDuplicates()
@ -526,13 +527,13 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func notificationsPublisher() -> AnyPublisher<[[CollectionItem]], Error> { func notificationsPublisher() -> AnyPublisher<[CollectionSection], Error> {
ValueObservation.tracking( ValueObservation.tracking(
NotificationInfo.request( NotificationInfo.request(
NotificationRecord.order(NotificationRecord.Columns.id.desc)).fetchAll) NotificationRecord.order(NotificationRecord.Columns.id.desc)).fetchAll)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseWriter) .publisher(in: databaseWriter)
.map { [$0.map { .map { [.init(items: $0.map {
let configuration: CollectionItem.StatusConfiguration? let configuration: CollectionItem.StatusConfiguration?
if $0.record.type == .mention, let statusInfo = $0.statusInfo { if $0.record.type == .mention, let statusInfo = $0.statusInfo {
@ -544,7 +545,7 @@ public extension ContentDatabase {
} }
return .notification(MastodonNotification(info: $0), configuration) return .notification(MastodonNotification(info: $0), configuration)
}] } })] }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }

View file

@ -21,7 +21,7 @@ extension ContextItemsInfo {
addingIncludes(request).asRequest(of: self) addingIncludes(request).asRequest(of: self)
} }
func items(filters: [Filter]) -> [[CollectionItem]] { func items(filters: [Filter]) -> [CollectionSection] {
let regularExpression = filters.regularExpression(context: .thread) let regularExpression = filters.regularExpression(context: .thread)
return [ancestors, [parent], descendants].map { section in return [ancestors, [parent], descendants].map { section in
@ -52,5 +52,6 @@ extension ContextItemsInfo {
hasReplyFollowing: hasReplyFollowing)) hasReplyFollowing: hasReplyFollowing))
} }
} }
.map { CollectionSection(items: $0) }
} }
} }

View file

@ -28,7 +28,7 @@ extension TimelineItemsInfo {
addingIncludes(request).asRequest(of: self) addingIncludes(request).asRequest(of: self)
} }
func items(filters: [Filter]) -> [[CollectionItem]] { func items(filters: [Filter]) -> [CollectionSection] {
let timeline = Timeline(record: timelineRecord)! let timeline = Timeline(record: timelineRecord)!
let filterRegularExpression = filters.regularExpression(context: timeline.filterContext) let filterRegularExpression = filters.regularExpression(context: timeline.filterContext)
var timelineItems = statusInfos.filtered(regularExpression: filterRegularExpression) var timelineItems = statusInfos.filtered(regularExpression: filterRegularExpression)
@ -55,17 +55,17 @@ extension TimelineItemsInfo {
} }
if let pinnedStatusInfos = pinnedStatusesInfo?.pinnedStatusInfos { if let pinnedStatusInfos = pinnedStatusesInfo?.pinnedStatusInfos {
return [pinnedStatusInfos.filtered(regularExpression: filterRegularExpression) return [.init(items: pinnedStatusInfos.filtered(regularExpression: filterRegularExpression)
.map { .map {
CollectionItem.status( CollectionItem.status(
.init(info: $0), .init(info: $0),
.init(showContentToggled: $0.showContentToggled, .init(showContentToggled: $0.showContentToggled,
showAttachmentsToggled: $0.showAttachmentsToggled, showAttachmentsToggled: $0.showAttachmentsToggled,
isPinned: true)) isPinned: true))
}, }),
timelineItems] .init(items: timelineItems)]
} else { } else {
return [timelineItems] return [.init(items: timelineItems)]
} }
} }
} }

View file

@ -0,0 +1,13 @@
// Copyright © 2021 Metabolist. All rights reserved.
import Foundation
public struct CollectionSection: Hashable {
public let items: [CollectionItem]
public let titleLocalizedStringKey: String?
public init(items: [CollectionItem], titleLocalizedStringKey: String? = nil) {
self.items = items
self.titleLocalizedStringKey = titleLocalizedStringKey
}
}

View file

@ -3,7 +3,7 @@
import UIKit import UIKit
import ViewModels import ViewModels
final class TableViewDataSource: UITableViewDiffableDataSource<Int, CollectionItem> { final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection.Identifier, CollectionItem> {
private let updateQueue = private let updateQueue =
DispatchQueue(label: "com.metabolist.metatext.collection-data-source.update-queue") DispatchQueue(label: "com.metabolist.metatext.collection-data-source.update-queue")
@ -36,13 +36,25 @@ final class TableViewDataSource: UITableViewDiffableDataSource<Int, CollectionIt
} }
} }
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<Int, CollectionItem>, override func apply(_ snapshot: NSDiffableDataSourceSnapshot<CollectionSection.Identifier, CollectionItem>,
animatingDifferences: Bool = true, animatingDifferences: Bool = true,
completion: (() -> Void)? = nil) { completion: (() -> Void)? = nil) {
updateQueue.async { updateQueue.async {
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion) super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
} }
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let currentSnapshot = snapshot()
let section = currentSnapshot.sectionIdentifiers[section]
if currentSnapshot.numberOfItems(inSection: section) > 0,
let localizedStringKey = section.titleLocalizedStringKey {
return NSLocalizedString(localizedStringKey, comment: "")
}
return nil
}
} }
extension TableViewDataSource { extension TableViewDataSource {

View file

@ -1,25 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import UIKit
extension Array where Element: Sequence, Element.Element: Hashable {
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Element.Element> {
var snapshot = NSDiffableDataSourceSnapshot<Int, Element.Element>()
let sections = [Int](0..<count)
snapshot.appendSections(sections)
for section in sections {
snapshot.appendItems(self[section].map { $0 }, toSection: section)
}
return snapshot
}
}
extension Array where Element: Hashable {
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Element> {
[self].snapshot()
}
}

View file

@ -0,0 +1,27 @@
// Copyright © 2021 Metabolist. All rights reserved.
import UIKit
import ViewModels
extension CollectionSection {
struct Identifier: Hashable {
let index: Int
let titleLocalizedStringKey: String?
}
}
extension Array where Element == CollectionSection {
func snapshot() -> NSDiffableDataSourceSnapshot<CollectionSection.Identifier, CollectionItem> {
var snapshot = NSDiffableDataSourceSnapshot<CollectionSection.Identifier, CollectionItem>()
for (index, section) in enumerated() {
let identifier = CollectionSection.Identifier(
index: index,
titleLocalizedStringKey: section.titleLocalizedStringKey)
snapshot.appendSections([identifier])
snapshot.appendItems(section.items, toSection: identifier)
}
return snapshot
}
}

View file

@ -174,6 +174,9 @@
"report.target-%@" = "Reporting %@"; "report.target-%@" = "Reporting %@";
"report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?"; "report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?";
"report.forward-%@" = "Forward report to %@"; "report.forward-%@" = "Forward report to %@";
"search.accounts" = "People";
"search.statuses" = "Posts";
"search.tags" = "Hashtags";
"share-extension-error.no-account-found" = "No account found"; "share-extension-error.no-account-found" = "No account found";
"status.bookmark" = "Bookmark"; "status.bookmark" = "Bookmark";
"status.content-warning-abbreviation" = "CW"; "status.content-warning-abbreviation" = "CW";

View file

@ -18,7 +18,6 @@
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; }; D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
D015B13F25A812EC006D88A8 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; }; D015B13F25A812EC006D88A8 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C8E253686F9003EF1EB /* PlayerView.swift */; };
D015B14425A812F6006D88A8 /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; }; D015B14425A812F6006D88A8 /* PlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE1C9725368A9D003EF1EB /* PlayerCache.swift */; };
D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EF22325182B1F00650C6B /* AccountHeaderView.swift */; }; D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EF22325182B1F00650C6B /* AccountHeaderView.swift */; };
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; }; D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; }; D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
@ -36,7 +35,6 @@
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA16254CA823009094DF /* StatusBodyView.swift */; }; D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036AA16254CA823009094DF /* StatusBodyView.swift */; };
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; }; D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */; }; D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */; };
D036EBBD259FE2A100EC1CFC /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C6FAB252024BD003D0300 /* Array+Extensions.swift */; };
D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7AD3825870B13005F5E2D /* UIVIewController+Extensions.swift */; }; D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7AD3825870B13005F5E2D /* UIVIewController+Extensions.swift */; };
D036EBC7259FE2B700EC1CFC /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; }; D036EBC7259FE2B700EC1CFC /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B1B29253818F3008F964B /* MediaPreferencesView.swift */; }; D03B1B2A253818F3008F964B /* MediaPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B1B29253818F3008F964B /* MediaPreferencesView.swift */; };
@ -131,6 +129,7 @@
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; }; D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; }; D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; }; D0CE9F88258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */; };
D0D2AC3925BBEC0F003D5DF2 /* CollectionSection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */; };
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; }; D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; }; D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; }; D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
@ -204,7 +203,6 @@
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAvatarsView.swift; sourceTree = "<group>"; }; D007023D25562A2800F38136 /* ConversationAvatarsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAvatarsView.swift; sourceTree = "<group>"; };
D0070251255921B100F38136 /* AccountFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFieldView.swift; sourceTree = "<group>"; }; D0070251255921B100F38136 /* AccountFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFieldView.swift; sourceTree = "<group>"; };
D00CB2EC2533ACC00080096B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; }; D00CB2EC2533ACC00080096B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; }; D01EF22325182B1F00650C6B /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = "<group>"; };
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; }; D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; }; D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
@ -304,6 +302,7 @@
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; }; D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; }; D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploadView.swift; sourceTree = "<group>"; }; D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploadView.swift; sourceTree = "<group>"; };
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionSection+Extensions.swift"; sourceTree = "<group>"; };
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; }; D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; }; D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; }; D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
@ -585,9 +584,9 @@
D0C7D46824F76169001EBDBB /* Extensions */ = { D0C7D46824F76169001EBDBB /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */,
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */, D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */,
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */, D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
D0D2AC3825BBEC0F003D5DF2 /* CollectionSection+Extensions.swift */,
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */, D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
D035F88625B8016000DC75ED /* NavigationViewModel+Extensions.swift */, D035F88625B8016000DC75ED /* NavigationViewModel+Extensions.swift */,
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */, D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
@ -596,13 +595,13 @@
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */, D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */,
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */, D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
D07EC81025B232C2006DF726 /* SystemEmoji+Extensions.swift */, D07EC81025B232C2006DF726 /* SystemEmoji+Extensions.swift */,
D035F8B225B9616000DC75ED /* Timeline+Extensions.swift */,
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */, D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */,
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */, D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
D05936F325AA66A600754FDF /* UIView+Extensions.swift */, D05936F325AA66A600754FDF /* UIView+Extensions.swift */,
D0E7AD3825870B13005F5E2D /* UIVIewController+Extensions.swift */, D0E7AD3825870B13005F5E2D /* UIVIewController+Extensions.swift */,
D0030981250C6C8500EACB32 /* URL+Extensions.swift */, D0030981250C6C8500EACB32 /* URL+Extensions.swift */,
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */, D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
D035F8B225B9616000DC75ED /* Timeline+Extensions.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -883,7 +882,6 @@
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */, D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */,
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */, D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */, D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */,
D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */,
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */, D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */, D08B8D72254246E200B1EBEF /* PollView.swift in Sources */,
D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */, D035F8A925B9155900DC75ED /* NewStatusButtonView.swift in Sources */,
@ -929,6 +927,7 @@
D088406D25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */, D088406D25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
D07EC7FD25B16994006DF726 /* EmojiCategoryHeaderView.swift in Sources */, D07EC7FD25B16994006DF726 /* EmojiCategoryHeaderView.swift in Sources */,
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */, D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
D0D2AC3925BBEC0F003D5DF2 /* CollectionSection+Extensions.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -968,7 +967,6 @@
D05936EA25AA3F3D00754FDF /* EditAttachmentView.swift in Sources */, D05936EA25AA3F3D00754FDF /* EditAttachmentView.swift in Sources */,
D07EC7D025B13921006DF726 /* PickerEmoji+Extensions.swift in Sources */, D07EC7D025B13921006DF726 /* PickerEmoji+Extensions.swift in Sources */,
D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */, D0FCC106259C4E62000B67DF /* NewStatusViewController.swift in Sources */,
D036EBBD259FE2A100EC1CFC /* Array+Extensions.swift in Sources */,
D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */, D036EBB3259FE28800EC1CFC /* UIColor+Extensions.swift in Sources */,
D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */, D088406E25AFBBE200BB749B /* EmojiPickerViewController.swift in Sources */,
D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */, D036EBB8259FE29800EC1CFC /* Status+Extensions.swift in Sources */,

View file

@ -0,0 +1,5 @@
// Copyright © 2021 Metabolist. All rights reserved.
import DB
public typealias CollectionSection = DB.CollectionSection

View file

@ -7,7 +7,7 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct AccountListService { public struct AccountListService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let navigationService: NavigationService public let navigationService: NavigationService
public let canRefresh = false public let canRefresh = false
@ -27,7 +27,7 @@ public struct AccountListService {
self.mastodonAPIClient = mastodonAPIClient self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase self.contentDatabase = contentDatabase
self.titleComponents = titleComponents self.titleComponents = titleComponents
sections = accountList.map { [$0.map(CollectionItem.account)] }.eraseToAnyPublisher() sections = accountList.map { [.init(items: $0.map(CollectionItem.account))] }.eraseToAnyPublisher()
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher() nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
} }

View file

@ -4,7 +4,7 @@ import Combine
import Mastodon import Mastodon
public protocol CollectionService { public protocol CollectionService {
var sections: AnyPublisher<[[CollectionItem]], Error> { get } var sections: AnyPublisher<[CollectionSection], Error> { get }
var nextPageMaxId: AnyPublisher<String, Never> { get } var nextPageMaxId: AnyPublisher<String, Never> { get }
var preferLastPresentIdOverNextPageMaxId: Bool { get } var preferLastPresentIdOverNextPageMaxId: Bool { get }
var canRefresh: Bool { get } var canRefresh: Bool { get }

View file

@ -7,7 +7,7 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct ContextService { public struct ContextService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let navigationService: NavigationService public let navigationService: NavigationService
private let id: Status.Id private let id: Status.Id

View file

@ -7,7 +7,7 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct ConversationsService { public struct ConversationsService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let navigationService: NavigationService public let navigationService: NavigationService
@ -19,7 +19,7 @@ public struct ConversationsService {
self.mastodonAPIClient = mastodonAPIClient self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase self.contentDatabase = contentDatabase
sections = contentDatabase.conversationsPublisher() sections = contentDatabase.conversationsPublisher()
.map { [$0.map(CollectionItem.conversation)] } .map { [.init(items: $0.map(CollectionItem.conversation))] }
.eraseToAnyPublisher() .eraseToAnyPublisher()
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher() nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)

View file

@ -7,7 +7,7 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct NotificationsService { public struct NotificationsService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let navigationService: NavigationService public let navigationService: NavigationService
@ -24,7 +24,7 @@ public struct NotificationsService {
self.nextPageMaxIdSubject = nextPageMaxIdSubject self.nextPageMaxIdSubject = nextPageMaxIdSubject
sections = contentDatabase.notificationsPublisher() sections = contentDatabase.notificationsPublisher()
.handleEvents(receiveOutput: { .handleEvents(receiveOutput: {
guard case let .notification(notification, _) = $0.last?.last, guard case let .notification(notification, _) = $0.last?.items.last,
notification.id < nextPageMaxIdSubject.value notification.id < nextPageMaxIdSubject.value
else { return } else { return }

View file

@ -7,14 +7,14 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct SearchService { public struct SearchService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let navigationService: NavigationService public let navigationService: NavigationService
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>() private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
private let sectionsSubject = PassthroughSubject<[[CollectionItem]], Error>() private let sectionsSubject = PassthroughSubject<[CollectionSection], Error>()
init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { init(mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.mastodonAPIClient = mastodonAPIClient self.mastodonAPIClient = mastodonAPIClient

View file

@ -7,7 +7,7 @@ import Mastodon
import MastodonAPI import MastodonAPI
public struct TimelineService { public struct TimelineService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[CollectionSection], Error>
public let navigationService: NavigationService public let navigationService: NavigationService
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let preferLastPresentIdOverNextPageMaxId = true public let preferLastPresentIdOverNextPageMaxId = true

View file

@ -169,9 +169,8 @@ private extension NewStatusViewController {
} }
func set(compositionViewModels: [CompositionViewModel]) { func set(compositionViewModels: [CompositionViewModel]) {
let diff = compositionViewModels.map(\.id).snapshot().itemIdentifiers.difference( let diff = compositionViewModels.map(\.id)
from: stackView.arrangedSubviews.compactMap { ($0 as? CompositionView)?.id } .difference(from: stackView.arrangedSubviews.compactMap { ($0 as? CompositionView)?.id })
.snapshot().itemIdentifiers)
for insertion in diff.insertions { for insertion in diff.insertions {
guard case let .insert(index, id, _) = insertion, guard case let .insert(index, id, _) = insertion,

View file

@ -302,7 +302,7 @@ private extension TableViewController {
positionMaintenanceOffset = 0 positionMaintenanceOffset = 0
} }
self.dataSource.apply(update.items.snapshot(), animatingDifferences: false) { [weak self] in self.dataSource.apply(update.sections.snapshot(), animatingDifferences: false) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
if let itemId = update.maintainScrollPositionItemId, if let itemId = update.maintainScrollPositionItemId,

View file

@ -10,7 +10,7 @@ public class CollectionItemsViewModel: ObservableObject {
public private(set) var nextPageMaxId: String? public private(set) var nextPageMaxId: String?
@Published private var lastUpdate = CollectionUpdate( @Published private var lastUpdate = CollectionUpdate(
items: [], sections: [],
maintainScrollPositionItemId: nil, maintainScrollPositionItemId: nil,
shouldAdjustContentInset: false) shouldAdjustContentInset: false)
private let collectionService: CollectionService private let collectionService: CollectionService
@ -34,7 +34,7 @@ public class CollectionItemsViewModel: ObservableObject {
? .expand : .hidden) ? .expand : .hidden)
collectionService.sections collectionService.sections
.handleEvents(receiveOutput: { [weak self] in self?.process(items: $0) }) .handleEvents(receiveOutput: { [weak self] in self?.process(sections: $0) })
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.sink { _ in } .sink { _ in }
@ -119,7 +119,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
} }
public func select(indexPath: IndexPath) { public func select(indexPath: IndexPath) {
let item = lastUpdate.items[indexPath.section][indexPath.item] let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
switch item { switch item {
case let .status(status, _): case let .status(status, _):
@ -161,14 +161,14 @@ extension CollectionItemsViewModel: CollectionViewModel {
topVisibleIndexPath = indexPath topVisibleIndexPath = indexPath
if !shouldRestorePositionOfLocalLastReadId, if !shouldRestorePositionOfLocalLastReadId,
lastUpdate.items.count > indexPath.section, lastUpdate.sections.count > indexPath.section,
lastUpdate.items[indexPath.section].count > indexPath.item { lastUpdate.sections[indexPath.section].items.count > indexPath.item {
lastReadId.send(lastUpdate.items[indexPath.section][indexPath.item].itemId) lastReadId.send(lastUpdate.sections[indexPath.section].items[indexPath.item].itemId)
} }
} }
public func canSelect(indexPath: IndexPath) -> Bool { public func canSelect(indexPath: IndexPath) -> Bool {
switch lastUpdate.items[indexPath.section][indexPath.item] { switch lastUpdate.sections[indexPath.section].items[indexPath.item] {
case let .status(_, configuration): case let .status(_, configuration):
return !configuration.isContextParent return !configuration.isContextParent
case .loadMore: case .loadMore:
@ -180,7 +180,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
// swiftlint:disable:next function_body_length cyclomatic_complexity // swiftlint:disable:next function_body_length cyclomatic_complexity
public func viewModel(indexPath: IndexPath) -> CollectionItemViewModel { public func viewModel(indexPath: IndexPath) -> CollectionItemViewModel {
let item = lastUpdate.items[indexPath.section][indexPath.item] let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
let cachedViewModel = viewModelCache[item]?.viewModel let cachedViewModel = viewModelCache[item]?.viewModel
switch item { switch item {
@ -260,7 +260,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
} }
public func toggleExpandAll() { public func toggleExpandAll() {
let statusIds = Set(lastUpdate.items.reduce([], +).compactMap { item -> Status.Id? in let statusIds = Set(lastUpdate.sections.map(\.items).reduce([], +).compactMap { item -> Status.Id? in
guard case let .status(status, _) = item else { return nil } guard case let .status(status, _) = item else { return nil }
return status.id return status.id
@ -289,7 +289,7 @@ private extension CollectionItemsViewModel {
private static let lastReadIdDebounceInterval: TimeInterval = 0.5 private static let lastReadIdDebounceInterval: TimeInterval = 0.5
var lastUpdateWasContextParentOnly: Bool { var lastUpdateWasContextParentOnly: Bool {
collectionService is ContextService && lastUpdate.items.map(\.count) == [0, 1, 0] collectionService is ContextService && lastUpdate.sections.map(\.items).map(\.count) == [0, 1, 0]
} }
func cache(viewModel: CollectionItemViewModel, forItem item: CollectionItem) { func cache(viewModel: CollectionItemViewModel, forItem item: CollectionItem) {
@ -303,14 +303,14 @@ private extension CollectionItemsViewModel {
.sink { [weak self] in self?.eventsSubject.send($0) }) .sink { [weak self] in self?.eventsSubject.send($0) })
} }
func process(items: [[CollectionItem]]) { func process(sections: [CollectionSection]) {
let flatItems = items.reduce([], +) let items = sections.map(\.items).reduce([], +)
let itemsSet = Set(flatItems) let itemsSet = Set(items)
self.lastUpdate = .init( self.lastUpdate = .init(
items: items, sections: sections,
maintainScrollPositionItemId: idForScrollPositionMaintenance(newItems: items), maintainScrollPositionItemId: idForScrollPositionMaintenance(newSections: sections),
shouldAdjustContentInset: lastUpdateWasContextParentOnly && flatItems.count > 1) shouldAdjustContentInset: lastUpdateWasContextParentOnly && items.count > 1)
viewModelCache = viewModelCache.filter { itemsSet.contains($0.key) } viewModelCache = viewModelCache.filter { itemsSet.contains($0.key) }
} }
@ -320,35 +320,35 @@ private extension CollectionItemsViewModel {
guard let markerTimeline = collectionService.markerTimeline, guard let markerTimeline = collectionService.markerTimeline,
identification.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition, identification.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition,
let lastItemId = lastUpdate.items.last?.last?.itemId let lastItemId = lastUpdate.sections.last?.items.last?.itemId
else { return maxId } else { return maxId }
return min(maxId, lastItemId) return min(maxId, lastItemId)
} }
func idForScrollPositionMaintenance(newItems: [[CollectionItem]]) -> CollectionItem.Id? { func idForScrollPositionMaintenance(newSections: [CollectionSection]) -> CollectionItem.Id? {
let flatItems = lastUpdate.items.reduce([], +) let items = lastUpdate.sections.map(\.items).reduce([], +)
let flatNewItems = newItems.reduce([], +) let newItems = newSections.map(\.items).reduce([], +)
if shouldRestorePositionOfLocalLastReadId, if shouldRestorePositionOfLocalLastReadId,
let markerTimeline = collectionService.markerTimeline, let markerTimeline = collectionService.markerTimeline,
let localLastReadId = identification.service.getLocalLastReadId(markerTimeline), let localLastReadId = identification.service.getLocalLastReadId(markerTimeline),
flatNewItems.contains(where: { $0.itemId == localLastReadId }) { newItems.contains(where: { $0.itemId == localLastReadId }) {
shouldRestorePositionOfLocalLastReadId = false shouldRestorePositionOfLocalLastReadId = false
return localLastReadId return localLastReadId
} }
if collectionService is ContextService, if collectionService is ContextService,
lastUpdate.items.isEmpty || lastUpdate.items.map(\.count) == [0, 1, 0], lastUpdate.sections.isEmpty || lastUpdate.sections.map(\.items.count) == [0, 1, 0],
let contextParent = flatNewItems.first(where: { let contextParent = newItems.first(where: {
guard case let .status(_, configuration) = $0 else { return false } guard case let .status(_, configuration) = $0 else { return false }
return configuration.isContextParent // Maintain scroll position of parent after initial load of context return configuration.isContextParent // Maintain scroll position of parent after initial load of context
}) { }) {
return contextParent.itemId return contextParent.itemId
} else if collectionService is TimelineService { } else if collectionService is TimelineService {
let difference = flatNewItems.difference(from: flatItems) let difference = newItems.difference(from: items)
if let lastSelectedLoadMore = lastSelectedLoadMore { if let lastSelectedLoadMore = lastSelectedLoadMore {
for removal in difference.removals { for removal in difference.removals {
@ -357,7 +357,7 @@ private extension CollectionItemsViewModel {
loadMore == lastSelectedLoadMore, loadMore == lastSelectedLoadMore,
let direction = (viewModelCache[item]?.viewModel as? LoadMoreViewModel)?.direction, let direction = (viewModelCache[item]?.viewModel as? LoadMoreViewModel)?.direction,
direction == .up, direction == .up,
let statusAfterLoadMore = flatItems.first(where: { let statusAfterLoadMore = items.first(where: {
guard case let .status(status, _) = $0 else { return false } guard case let .status(status, _) = $0 else { return false }
return status.id == loadMore.beforeStatusId return status.id == loadMore.beforeStatusId
@ -367,13 +367,13 @@ private extension CollectionItemsViewModel {
} }
} }
if lastUpdate.items.count > topVisibleIndexPath.section, if lastUpdate.sections.count > topVisibleIndexPath.section,
lastUpdate.items[topVisibleIndexPath.section].count > topVisibleIndexPath.item { lastUpdate.sections[topVisibleIndexPath.section].items.count > topVisibleIndexPath.item {
let topVisibleItem = lastUpdate.items[topVisibleIndexPath.section][topVisibleIndexPath.item] let topVisibleItem = lastUpdate.sections[topVisibleIndexPath.section].items[topVisibleIndexPath.item]
if newItems.count > topVisibleIndexPath.section, if newSections.count > topVisibleIndexPath.section,
let newIndex = newItems[topVisibleIndexPath.section] let newIndex = newSections[topVisibleIndexPath.section]
.firstIndex(where: { $0.itemId == topVisibleItem.itemId }), .items.firstIndex(where: { $0.itemId == topVisibleItem.itemId }),
newIndex > topVisibleIndexPath.item { newIndex > topVisibleIndexPath.item {
return topVisibleItem.itemId return topVisibleItem.itemId
} }

View file

@ -0,0 +1,5 @@
// Copyright © 2021 Metabolist. All rights reserved.
import ServiceLayer
public typealias CollectionSection = ServiceLayer.CollectionSection

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
public struct CollectionUpdate: Hashable { public struct CollectionUpdate: Hashable {
public let items: [[CollectionItem]] public let sections: [CollectionSection]
public let maintainScrollPositionItemId: String? public let maintainScrollPositionItemId: String?
public let shouldAdjustContentInset: Bool public let shouldAdjustContentInset: Bool
} }