fix: resolve requested changes

This commit is contained in:
jk234ert 2021-04-07 13:57:03 +08:00
parent 28cfe96171
commit a61e662f38
9 changed files with 119 additions and 19 deletions

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
0F1E2D0B2615C39400C38565 /* HashtagTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1E2D0A2615C39400C38565 /* HashtagTimelineTitleView.swift */; };
0F1E2FE9261D7FD000C38565 /* StatusWithGapFetchResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1E2FE8261D7FD000C38565 /* StatusWithGapFetchResultController.swift */; };
0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */; };
0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */; };
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */; };
@ -356,6 +357,7 @@
/* Begin PBXFileReference section */
0F1E2D0A2615C39400C38565 /* HashtagTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineTitleView.swift; sourceTree = "<group>"; };
0F1E2FE8261D7FD000C38565 /* StatusWithGapFetchResultController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusWithGapFetchResultController.swift; sourceTree = "<group>"; };
0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = "<group>"; };
0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewModel.swift; sourceTree = "<group>"; };
0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
@ -1659,6 +1661,7 @@
isa = PBXGroup;
children = (
DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */,
0F1E2FE8261D7FD000C38565 /* StatusWithGapFetchResultController.swift */,
);
path = FetchedResultsController;
sourceTree = "<group>";
@ -2137,6 +2140,7 @@
DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */,
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */,
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
0F1E2FE9261D7FD000C38565 /* StatusWithGapFetchResultController.swift in Sources */,
DBCC3B7B261443AD0045B23D /* ViewController.swift in Sources */,
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */,
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,

View File

@ -0,0 +1,85 @@
//
// StatusWithGapFetchResultController.swift
// Mastodon
//
// Created by BradGao on 2021/4/7.
//
import os.log
import UIKit
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
class StatusWithGapFetchResultController: NSObject {
var disposeBag = Set<AnyCancellable>()
let fetchedResultsController: NSFetchedResultsController<Status>
// input
let domain = CurrentValueSubject<String?, Never>(nil)
let statusIDs = CurrentValueSubject<[Mastodon.Entity.Status.ID], Never>([])
var needLoadMiddleIndex: Int? = nil
// output
let objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate) {
self.domain.value = domain ?? ""
self.fetchedResultsController = {
let fetchRequest = Status.sortedFetchRequest
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.fetchBatchSize = 20
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil
)
return controller
}()
super.init()
fetchedResultsController.delegate = self
Publishers.CombineLatest(
self.domain.removeDuplicates().eraseToAnyPublisher(),
self.statusIDs.removeDuplicates().eraseToAnyPublisher()
)
.receive(on: DispatchQueue.main)
.sink { [weak self] domain, ids in
guard let self = self else { return }
self.fetchedResultsController.fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
Status.predicate(domain: domain ?? "", ids: ids),
additionalTweetPredicate
])
do {
try self.fetchedResultsController.performFetch()
} catch {
assertionFailure(error.localizedDescription)
}
}
.store(in: &disposeBag)
}
}
// MARK: - NSFetchedResultsControllerDelegate
extension StatusWithGapFetchResultController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let indexes = statusIDs.value
let objects = fetchedResultsController.fetchedObjects ?? []
let items: [NSManagedObjectID] = objects
.compactMap { object in
indexes.firstIndex(of: object.id).map { index in (index, object) }
}
.sorted { $0.0 < $1.0 }
.map { $0.1.objectID }
self.objectIDs.value = items
}
}

View File

@ -56,7 +56,8 @@ final class ComposeViewModel {
let isPollToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true)
let characterCount = CurrentValueSubject<Int, Never>(0)
var injectedContent: String? = nil
// In some specific scenes(hashtag scene e.g.), we need to display the compose scene with pre-inserted text(insert '#mastodon ' in #mastodon hashtag scene, e.g.), the pre-inserted text should be treated as mannually inputed by users.
var preInsertedContent: String? = nil
// custom emojis
var customEmojiViewModelSubscription: AnyCancellable?
@ -74,11 +75,11 @@ final class ComposeViewModel {
init(
context: AppContext,
composeKind: ComposeStatusSection.ComposeKind,
injectedContent: String? = nil
preInsertedContent: String? = nil
) {
self.context = context
self.composeKind = composeKind
self.injectedContent = injectedContent
self.preInsertedContent = preInsertedContent
switch composeKind {
case .post: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newPost)
case .reply: self.title = CurrentValueSubject(L10n.Scene.Compose.Title.newReply)
@ -204,9 +205,9 @@ final class ComposeViewModel {
if content.isEmpty {
return true
}
// if injectedContent plus a space is equal to the content, simply dismiss the modal
if let injectedContent = self?.injectedContent {
return content == (injectedContent + " ")
// if preInsertedContent plus a space is equal to the content, simply dismiss the modal
if let preInsertedContent = self?.preInsertedContent {
return content == (preInsertedContent + " ")
}
return false
}
@ -316,9 +317,9 @@ final class ComposeViewModel {
})
.store(in: &disposeBag)
if let injectedContent = injectedContent {
if let preInsertedContent = preInsertedContent {
// add a space after the injected text
composeStatusAttribute.composeContent.send(injectedContent + " ")
composeStatusAttribute.composeContent.send(preInsertedContent + " ")
}
}

View File

@ -32,11 +32,11 @@ extension HashtagTimelineViewController: StatusProvider {
}
switch item {
case .homeTimelineIndex(let objectID, _):
case .status(let objectID, _):
let managedObjectContext = self.viewModel.context.managedObjectContext
managedObjectContext.perform {
let timelineIndex = managedObjectContext.object(with: objectID) as? HomeTimelineIndex
promise(.success(timelineIndex?.status))
let status = managedObjectContext.object(with: objectID) as? Status
promise(.success(status))
}
default:
promise(.success(nil))

View File

@ -165,7 +165,7 @@ extension HashtagTimelineViewController {
@objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let composeViewModel = ComposeViewModel(context: context, composeKind: .post, injectedContent: "#\(viewModel.hashTag)")
let composeViewModel = ComposeViewModel(context: context, composeKind: .post, preInsertedContent: "#\(viewModel.hashTag)")
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
}

View File

@ -55,14 +55,17 @@ extension HashtagTimelineViewModel: NSFetchedResultsControllerDelegate {
let oldSnapshot = diffableDataSource.snapshot()
let snapshot = snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:]
for item in oldSnapshot.itemIdentifiers {
guard case let .status(objectID, attribute) = item else { continue }
oldSnapshotAttributeDict[objectID] = attribute
}
let statusItemList: [Item] = snapshot.itemIdentifiers.map {
let status = managedObjectContext.object(with: $0) as! Status
let isStatusTextSensitive: Bool = {
guard let spoilerText = status.spoilerText, !spoilerText.isEmpty else { return false }
return true
}()
return Item.status(objectID: $0, attribute: Item.StatusAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: status.sensitive))
let attribute = oldSnapshotAttributeDict[$0] ?? Item.StatusAttribute()
return Item.status(objectID: $0, attribute: attribute)
}
var newSnapshot = NSDiffableDataSourceSnapshot<StatusSection, Item>()

View File

@ -222,6 +222,6 @@ extension WelcomeViewController: OnboardingViewControllerAppearance { }
extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// make underneath view controller alive to fix layout issue due to view life cycle
return .overFullScreen
return .fullScreen
}
}

View File

@ -258,5 +258,12 @@ extension UserTimelineViewModel.State {
return false
}
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
viewModel.statusFetchedResultsController.objectIDs.value = viewModel.statusFetchedResultsController.objectIDs.value
}
}
}

View File

@ -121,7 +121,7 @@ extension Mastodon.API.Favorites {
case destroy
}
public struct ListQuery: GetQuery,PagedQueryType {
public struct ListQuery: GetQuery, PagedQueryType {
public var limit: Int?
public var minID: String?