mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-28 19:11:30 +00:00
Collection section type
This commit is contained in:
parent
c4da421846
commit
182bc5ce18
22 changed files with 141 additions and 102 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
DB/Sources/DB/Entities/CollectionSection.swift
Normal file
13
DB/Sources/DB/Entities/CollectionSection.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
27
Extensions/CollectionSection+Extensions.swift
Normal file
27
Extensions/CollectionSection+Extensions.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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 */,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import DB
|
||||||
|
|
||||||
|
public typealias CollectionSection = DB.CollectionSection
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public typealias CollectionSection = ServiceLayer.CollectionSection
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue