chore: code format

This commit is contained in:
sunxiaojian 2021-04-16 13:45:54 +08:00
parent ecf622b866
commit ca7eb7bb12
18 changed files with 130 additions and 164 deletions

View File

@ -70,8 +70,9 @@
<attribute name="domain" attributeType="String"/> <attribute name="domain" attributeType="String"/>
<attribute name="id" attributeType="String"/> <attribute name="id" attributeType="String"/>
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/> <attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="type" attributeType="String"/> <attribute name="typeRaw" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/> <attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="userID" attributeType="String"/>
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/> <relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/> <relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
<uniquenessConstraints> <uniquenessConstraints>
@ -223,7 +224,7 @@
<element name="History" positionX="0" positionY="0" width="128" height="119"/> <element name="History" positionX="0" positionY="0" width="128" height="119"/>
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/> <element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/> <element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
<element name="MastodonNotification" positionX="9" positionY="162" width="128" height="149"/> <element name="MastodonNotification" positionX="9" positionY="162" width="128" height="164"/>
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/> <element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
<element name="Mention" positionX="0" positionY="0" width="128" height="134"/> <element name="Mention" positionX="0" positionY="0" width="128" height="134"/>
<element name="Poll" positionX="0" positionY="0" width="128" height="194"/> <element name="Poll" positionX="0" positionY="0" width="128" height="194"/>

View File

@ -12,13 +12,14 @@ public final class MastodonNotification: NSManagedObject {
public typealias ID = UUID public typealias ID = UUID
@NSManaged public private(set) var identifier: ID @NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var id: String @NSManaged public private(set) var id: String
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var createAt: Date @NSManaged public private(set) var createAt: Date
@NSManaged public private(set) var updatedAt: Date @NSManaged public private(set) var updatedAt: Date
@NSManaged public private(set) var type: String @NSManaged public private(set) var typeRaw: String
@NSManaged public private(set) var account: MastodonUser @NSManaged public private(set) var account: MastodonUser
@NSManaged public private(set) var status: Status? @NSManaged public private(set) var status: Status?
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var userID: String
} }
extension MastodonNotification { extension MastodonNotification {
@ -26,12 +27,6 @@ extension MastodonNotification {
super.awakeFromInsert() super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: #keyPath(MastodonNotification.identifier)) setPrimitiveValue(UUID(), forKey: #keyPath(MastodonNotification.identifier))
} }
public override func willSave() {
super.willSave()
setPrimitiveValue(Date(), forKey: #keyPath(MastodonNotification.updatedAt))
}
} }
public extension MastodonNotification { public extension MastodonNotification {
@ -39,16 +34,19 @@ public extension MastodonNotification {
static func insert( static func insert(
into context: NSManagedObjectContext, into context: NSManagedObjectContext,
domain: String, domain: String,
userID: String,
networkDate: Date,
property: Property property: Property
) -> MastodonNotification { ) -> MastodonNotification {
let notification: MastodonNotification = context.insertObject() let notification: MastodonNotification = context.insertObject()
notification.id = property.id notification.id = property.id
notification.createAt = property.createdAt notification.createAt = property.createdAt
notification.updatedAt = property.createdAt notification.updatedAt = networkDate
notification.type = property.type notification.typeRaw = property.typeRaw
notification.account = property.account notification.account = property.account
notification.status = property.status notification.status = property.status
notification.domain = domain notification.domain = domain
notification.userID = userID
return notification return notification
} }
} }
@ -56,19 +54,20 @@ public extension MastodonNotification {
public extension MastodonNotification { public extension MastodonNotification {
struct Property { struct Property {
public init(id: String, public init(id: String,
type: String, typeRaw: String,
account: MastodonUser, account: MastodonUser,
status: Status?, status: Status?,
createdAt: Date) { createdAt: Date
) {
self.id = id self.id = id
self.type = type self.typeRaw = typeRaw
self.account = account self.account = account
self.status = status self.status = status
self.createdAt = createdAt self.createdAt = createdAt
} }
public let id: String public let id: String
public let type: String public let typeRaw: String
public let account: MastodonUser public let account: MastodonUser
public let status: Status? public let status: Status?
public let createdAt: Date public let createdAt: Date
@ -76,19 +75,31 @@ public extension MastodonNotification {
} }
extension MastodonNotification { extension MastodonNotification {
public static func predicate(domain: String) -> NSPredicate { static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.domain), domain) return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.domain), domain)
} }
static func predicate(type: String) -> NSPredicate { static func predicate(userID: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.type), type) return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.userID), userID)
} }
public static func predicate(domain: String, type: String) -> NSPredicate { static func predicate(typeRaw: String) -> NSPredicate {
return NSCompoundPredicate(andPredicateWithSubpredicates: [ return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.typeRaw), typeRaw)
MastodonNotification.predicate(domain: domain), }
MastodonNotification.predicate(type: type)
]) public static func predicate(domain: String, userID: String, typeRaw: String? = nil) -> NSPredicate {
if let typeRaw = typeRaw {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
MastodonNotification.predicate(domain: domain),
MastodonNotification.predicate(typeRaw: typeRaw),
MastodonNotification.predicate(userID: userID),
])
} else {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
MastodonNotification.predicate(domain: domain),
MastodonNotification.predicate(userID: userID)
])
}
} }
} }

View File

@ -37,7 +37,6 @@
2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; }; 2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; };
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; }; 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; }; 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; };
2D19864F261C372A00F0B013 /* CommonBottomLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D19864E261C372A00F0B013 /* CommonBottomLoader.swift */; };
2D198655261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198654261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift */; }; 2D198655261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198654261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift */; };
2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; }; 2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; };
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7F25F5F45E00143C56 /* UIImage.swift */; }; 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7F25F5F45E00143C56 /* UIImage.swift */; };
@ -427,7 +426,6 @@
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; }; 2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = "<group>"; }; 2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = "<group>"; };
2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = "<group>"; }; 2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = "<group>"; };
2D19864E261C372A00F0B013 /* CommonBottomLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonBottomLoader.swift; sourceTree = "<group>"; };
2D198654261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewModel+LoadOldestState.swift"; sourceTree = "<group>"; }; 2D198654261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewModel+LoadOldestState.swift"; sourceTree = "<group>"; };
2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = "<group>"; }; 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = "<group>"; };
2D206B7F25F5F45E00143C56 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; }; 2D206B7F25F5F45E00143C56 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
@ -1154,7 +1152,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2DFAD5362617010500F9EE7C /* SearchingTableViewCell.swift */, 2DFAD5362617010500F9EE7C /* SearchingTableViewCell.swift */,
2D19864E261C372A00F0B013 /* CommonBottomLoader.swift */,
); );
path = TableViewCell; path = TableViewCell;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2272,7 +2269,6 @@
DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */, DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */,
2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */, 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */,
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
2D19864F261C372A00F0B013 /* CommonBottomLoader.swift in Sources */,
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */, DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */,
0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */, 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */,
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */, DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,

View File

@ -17,10 +17,10 @@ enum NotificationItem {
extension NotificationItem: Equatable { extension NotificationItem: Equatable {
static func == (lhs: NotificationItem, rhs: NotificationItem) -> Bool { static func == (lhs: NotificationItem, rhs: NotificationItem) -> Bool {
switch (lhs, rhs) { switch (lhs, rhs) {
case (.bottomLoader, .bottomLoader):
return true
case (.notification(let idLeft), .notification(let idRight)): case (.notification(let idLeft), .notification(let idRight)):
return idLeft == idRight return idLeft == idRight
case (.bottomLoader, .bottomLoader):
return true
default: default:
return false return false
} }

View File

@ -33,7 +33,7 @@ extension NotificationSection {
case .notification(let objectID): case .notification(let objectID):
let notification = managedObjectContext.object(with: objectID) as! MastodonNotification let notification = managedObjectContext.object(with: objectID) as! MastodonNotification
let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.type) let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw)
let timeText = notification.createAt.shortTimeAgoSinceNow let timeText = notification.createAt.shortTimeAgoSinceNow
@ -128,7 +128,7 @@ extension NotificationSection {
return cell return cell
} }
case .bottomLoader: case .bottomLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CommonBottomLoader.self)) as! CommonBottomLoader let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
cell.startAnimating() cell.startAnimating()
return cell return cell
} }

View File

@ -44,7 +44,7 @@ extension SearchResultSection {
cell.config(with: user) cell.config(with: user)
return cell return cell
case .bottomLoader: case .bottomLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CommonBottomLoader.self)) as! CommonBottomLoader let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
cell.startAnimating() cell.startAnimating()
return cell return cell
} }

View File

@ -174,8 +174,9 @@ extension UIView {
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return } guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false
constrain([ constrain([
widthAnchor.constraint(equalToConstant: toSize.width), widthAnchor.constraint(equalToConstant: toSize.width).priority(.required - 1),
heightAnchor.constraint(equalToConstant: toSize.height)]) heightAnchor.constraint(equalToConstant: toSize.height).priority(.required - 1)
])
} }
func pin(top: CGFloat?,left: CGFloat?,bottom: CGFloat?, right: CGFloat?) { func pin(top: CGFloat?,left: CGFloat?,bottom: CGFloat?, right: CGFloat?) {

View File

@ -33,7 +33,7 @@ final class NotificationViewController: UIViewController, NeedsDependency {
tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self)) tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self)) tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self))
tableView.register(CommonBottomLoader.self, forCellReuseIdentifier: String(describing: CommonBottomLoader.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
tableView.tableFooterView = UIView() tableView.tableFooterView = UIView()
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
return tableView return tableView
@ -111,15 +111,15 @@ extension NotificationViewController {
extension NotificationViewController { extension NotificationViewController {
@objc private func segmentedControlValueChanged(_ sender: UISegmentedControl) { @objc private func segmentedControlValueChanged(_ sender: UISegmentedControl) {
os_log("%{public}s[%{public}ld], %{public}s: select at index: %ld", (#file as NSString).lastPathComponent, #line, #function, sender.selectedSegmentIndex) os_log("%{public}s[%{public}ld], %{public}s: select at index: %ld", (#file as NSString).lastPathComponent, #line, #function, sender.selectedSegmentIndex)
guard let domain = viewModel.activeMastodonAuthenticationBox.value?.domain else { guard let domain = viewModel.activeMastodonAuthenticationBox.value?.domain, let userID = viewModel.activeMastodonAuthenticationBox.value?.userID else {
return return
} }
if sender.selectedSegmentIndex == 0 { if sender.selectedSegmentIndex == 0 {
viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain) viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID)
} else { } else {
viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, type: Mastodon.Entity.Notification.NotificationType.mention.rawValue) viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain,userID: userID, typeRaw: Mastodon.Entity.Notification.NotificationType.mention.rawValue)
} }
viewModel.selectedIndex.value = sender.selectedSegmentIndex viewModel.selectedIndex.value = NotificationViewModel.NotificationSegment.init(rawValue: sender.selectedSegmentIndex)!
} }
@objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
@ -196,7 +196,7 @@ extension NotificationViewController {
} }
extension NotificationViewController: LoadMoreConfigurableTableViewContainer { extension NotificationViewController: LoadMoreConfigurableTableViewContainer {
typealias BottomLoaderTableViewCell = CommonBottomLoader typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
typealias LoadingState = NotificationViewModel.LoadOldestState.Loading typealias LoadingState = NotificationViewModel.LoadOldestState.Loading
var loadMoreConfigurableTableView: UITableView { tableView } var loadMoreConfigurableTableView: UITableView { tableView }
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine } var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine }

View File

@ -53,12 +53,14 @@ extension NotificationViewModel.LoadLatestState {
sinceID: nil, sinceID: nil,
minID: nil, minID: nil,
limit: nil, limit: nil,
excludeTypes: Mastodon.API.Notifications.allExcludeTypes(), excludeTypes: [.followRequest],
accountID: nil) accountID: nil
)
viewModel.context.apiService.allNotifications( viewModel.context.apiService.allNotifications(
domain: activeMastodonAuthenticationBox.domain, domain: activeMastodonAuthenticationBox.domain,
query: query, query: query,
mastodonAuthenticationBox: activeMastodonAuthenticationBox) mastodonAuthenticationBox: activeMastodonAuthenticationBox
)
.sink { completion in .sink { completion in
switch completion { switch completion {
case .failure(let error): case .failure(let error):

View File

@ -50,7 +50,7 @@ extension NotificationViewModel.LoadOldestState {
} }
let notifications: [MastodonNotification]? = { let notifications: [MastodonNotification]? = {
let request = MastodonNotification.sortedFetchRequest let request = MastodonNotification.sortedFetchRequest
request.predicate = MastodonNotification.predicate(domain: activeMastodonAuthenticationBox.domain) request.predicate = MastodonNotification.predicate(domain: activeMastodonAuthenticationBox.domain, userID: activeMastodonAuthenticationBox.userID)
request.returnsObjectsAsFaults = false request.returnsObjectsAsFaults = false
do { do {
return try self.viewModel?.context.managedObjectContext.fetch(request) return try self.viewModel?.context.managedObjectContext.fetch(request)
@ -71,12 +71,13 @@ extension NotificationViewModel.LoadOldestState {
sinceID: nil, sinceID: nil,
minID: nil, minID: nil,
limit: nil, limit: nil,
excludeTypes: Mastodon.API.Notifications.allExcludeTypes(), excludeTypes: [.followRequest],
accountID: nil) accountID: nil)
viewModel.context.apiService.allNotifications( viewModel.context.apiService.allNotifications(
domain: activeMastodonAuthenticationBox.domain, domain: activeMastodonAuthenticationBox.domain,
query: query, query: query,
mastodonAuthenticationBox: activeMastodonAuthenticationBox) mastodonAuthenticationBox: activeMastodonAuthenticationBox
)
.sink { completion in .sink { completion in
switch completion { switch completion {
case .failure(let error): case .failure(let error):
@ -89,16 +90,17 @@ extension NotificationViewModel.LoadOldestState {
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)
} receiveValue: { [weak viewModel] response in } receiveValue: { [weak viewModel] response in
guard let viewModel = viewModel else { return } guard let viewModel = viewModel else { return }
if viewModel.selectedIndex.value == 1 { switch viewModel.selectedIndex.value {
viewModel.noMoreNotification.value = response.value.isEmpty case .EveryThing:
let list = response.value.filter { $0.type == Mastodon.Entity.Notification.NotificationType.mention } if response.value.isEmpty {
if list.isEmpty {
stateMachine.enter(NoMore.self) stateMachine.enter(NoMore.self)
} else { } else {
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)
} }
} else { case .Mentions:
if response.value.isEmpty { viewModel.noMoreNotification.value = response.value.isEmpty
let list = response.value.filter { $0.type == Mastodon.Entity.Notification.NotificationType.mention }
if list.isEmpty {
stateMachine.enter(NoMore.self) stateMachine.enter(NoMore.self)
} else { } else {
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)

View File

@ -22,7 +22,7 @@ final class NotificationViewModel: NSObject {
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate? weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
let viewDidLoad = PassthroughSubject<Void, Never>() let viewDidLoad = PassthroughSubject<Void, Never>()
let selectedIndex = CurrentValueSubject<Int, Never>(0) let selectedIndex = CurrentValueSubject<NotificationSegment, Never>(.EveryThing)
let noMoreNotification = CurrentValueSubject<Bool, Never>(false) let noMoreNotification = CurrentValueSubject<Bool, Never>(false)
let activeMastodonAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never> let activeMastodonAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
@ -88,8 +88,8 @@ final class NotificationViewModel: NSObject {
.sink(receiveValue: { [weak self] box in .sink(receiveValue: { [weak self] box in
guard let self = self else { return } guard let self = self else { return }
self.activeMastodonAuthenticationBox.value = box self.activeMastodonAuthenticationBox.value = box
if let domain = box?.domain { if let domain = box?.domain, let userID = box?.userID {
self.notificationPredicate.value = MastodonNotification.predicate(domain: domain) self.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID)
} }
}) })
.store(in: &disposeBag) .store(in: &disposeBag)
@ -115,9 +115,16 @@ final class NotificationViewModel: NSObject {
viewDidLoad viewDidLoad
.sink { [weak self] in .sink { [weak self] in
guard let domain = self?.activeMastodonAuthenticationBox.value?.domain else { return } guard let domain = self?.activeMastodonAuthenticationBox.value?.domain, let userID = self?.activeMastodonAuthenticationBox.value?.userID else { return }
self?.notificationPredicate.value = MastodonNotification.predicate(domain: domain) self?.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID)
} }
.store(in: &disposeBag) .store(in: &disposeBag)
} }
} }
extension NotificationViewModel {
enum NotificationSegment: Int {
case EveryThing
case Mentions
}
}

View File

@ -103,7 +103,6 @@ final class NotificationStatusTableViewCell: UITableViewCell {
extension NotificationStatusTableViewCell { extension NotificationStatusTableViewCell {
func configure() { func configure() {
selectionStyle = .none
let container = UIView() let container = UIView()
container.backgroundColor = .clear container.backgroundColor = .clear
@ -117,11 +116,11 @@ extension NotificationStatusTableViewCell {
container.addSubview(avatatImageView) container.addSubview(avatatImageView)
avatatImageView.pin(toSize: CGSize(width: 35, height: 35)) avatatImageView.pin(toSize: CGSize(width: 35, height: 35))
avatatImageView.pin(top: 12, left: 12, bottom: nil, right: nil) avatatImageView.pin(top: 12, left: 0, bottom: nil, right: nil)
container.addSubview(actionImageBackground) container.addSubview(actionImageBackground)
actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth)) actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth))
actionImageBackground.pin(top: 33, left: 33, bottom: nil, right: nil) actionImageBackground.pin(top: 33, left: 21, bottom: nil, right: nil)
actionImageBackground.addSubview(actionImageView) actionImageBackground.addSubview(actionImageView)
actionImageView.constrainToCenter() actionImageView.constrainToCenter()
@ -130,22 +129,21 @@ extension NotificationStatusTableViewCell {
nameLabel.constrain([ nameLabel.constrain([
nameLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 12), nameLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 12),
nameLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 61) nameLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 61)
]) ])
container.addSubview(actionLabel) container.addSubview(actionLabel)
actionLabel.constrain([ actionLabel.constrain([
actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4), actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4),
actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor), actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor),
container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4).priority(.defaultLow) container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4)
]) ])
statusView.contentWarningBlurContentImageView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color statusView.contentWarningBlurContentImageView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color
statusView.isUserInteractionEnabled = false statusView.isUserInteractionEnabled = false
// remove item don't display // remove item don't display
statusView.actionToolbarContainer.removeFromSuperview() statusView.actionToolbarContainer.isHidden = true
statusView.avatarView.removeFromSuperview() statusView.avatarView.isHidden = true
statusView.usernameLabel.removeFromSuperview() statusView.usernameLabel.isHidden = true
container.addSubview(statusBorder) container.addSubview(statusBorder)
statusBorder.pin(top: 40, left: 63, bottom: 14, right: 14) statusBorder.pin(top: 40, left: 63, bottom: 14, right: 14)

View File

@ -85,7 +85,6 @@ final class NotificationTableViewCell: UITableViewCell {
extension NotificationTableViewCell { extension NotificationTableViewCell {
func configure() { func configure() {
selectionStyle = .none
let container = UIView() let container = UIView()
container.backgroundColor = .clear container.backgroundColor = .clear
@ -99,7 +98,7 @@ extension NotificationTableViewCell {
container.addSubview(avatatImageView) container.addSubview(avatatImageView)
avatatImageView.pin(toSize: CGSize(width: 35, height: 35)) avatatImageView.pin(toSize: CGSize(width: 35, height: 35))
avatatImageView.pin(top: 12, left: 12, bottom: nil, right: nil) avatatImageView.pin(top: 12, left: 0, bottom: nil, right: nil)
container.addSubview(actionImageBackground) container.addSubview(actionImageBackground)
actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth)) actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth))
@ -119,7 +118,7 @@ extension NotificationTableViewCell {
actionLabel.constrain([ actionLabel.constrain([
actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4), actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4),
actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor), actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor),
container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4).priority(.defaultLow) container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4)
]) ])
} }

View File

@ -17,7 +17,7 @@ extension SearchViewController {
func setupSearchingTableView() { func setupSearchingTableView() {
searchingTableView.delegate = self searchingTableView.delegate = self
searchingTableView.register(SearchingTableViewCell.self, forCellReuseIdentifier: String(describing: SearchingTableViewCell.self)) searchingTableView.register(SearchingTableViewCell.self, forCellReuseIdentifier: String(describing: SearchingTableViewCell.self))
searchingTableView.register(CommonBottomLoader.self, forCellReuseIdentifier: String(describing: CommonBottomLoader.self)) searchingTableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
view.addSubview(searchingTableView) view.addSubview(searchingTableView)
searchingTableView.constrain([ searchingTableView.constrain([
searchingTableView.frameLayoutGuide.topAnchor.constraint(equalTo: searchBar.bottomAnchor), searchingTableView.frameLayoutGuide.topAnchor.constraint(equalTo: searchBar.bottomAnchor),

View File

@ -227,7 +227,7 @@ extension SearchViewController: UISearchBarDelegate {
} }
extension SearchViewController: LoadMoreConfigurableTableViewContainer { extension SearchViewController: LoadMoreConfigurableTableViewContainer {
typealias BottomLoaderTableViewCell = CommonBottomLoader typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
typealias LoadingState = SearchViewModel.LoadOldestState.Loading typealias LoadingState = SearchViewModel.LoadOldestState.Loading
var loadMoreConfigurableTableView: UITableView { searchingTableView } var loadMoreConfigurableTableView: UITableView { searchingTableView }
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine } var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.loadoldestStateMachine }

View File

@ -1,47 +0,0 @@
//
// CommonBottomLoader.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/6.
//
import Foundation
import UIKit
final class CommonBottomLoader: UITableViewCell {
let activityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
activityIndicatorView.tintColor = Asset.Colors.Label.primary.color
activityIndicatorView.hidesWhenStopped = true
return activityIndicatorView
}()
override func prepareForReuse() {
super.prepareForReuse()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
func startAnimating() {
activityIndicatorView.startAnimating()
}
func stopAnimating() {
activityIndicatorView.stopAnimating()
}
func _init() {
selectionStyle = .none
backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
contentView.addSubview(activityIndicatorView)
activityIndicatorView.constrainToCenter()
}
}

View File

@ -16,48 +16,52 @@ extension APIService {
func allNotifications( func allNotifications(
domain: String, domain: String,
query: Mastodon.API.Notifications.Query, query: Mastodon.API.Notifications.Query,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
{ ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization let authorization = mastodonAuthenticationBox.userAuthorization
let userID = mastodonAuthenticationBox.userID
return Mastodon.API.Notifications.getNotifications( return Mastodon.API.Notifications.getNotifications(
session: session, session: session,
domain: domain, domain: domain,
query: query, query: query,
authorization: authorization) authorization: authorization
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> in )
let log = OSLog.api .flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> in
return self.backgroundManagedObjectContext.performChanges { let log = OSLog.api
response.value.forEach { notification in return self.backgroundManagedObjectContext.performChanges {
let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.backgroundManagedObjectContext, for: nil, in: domain, entity: notification.account, userCache: nil, networkDate: Date(), log: log) response.value.forEach { notification in
var status: Status? let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.backgroundManagedObjectContext, for: nil, in: domain, entity: notification.account, userCache: nil, networkDate: Date(), log: log)
if let statusEntity = notification.status { var status: Status?
let (statusInCoreData, _, _) = APIService.CoreData.createOrMergeStatus( if let statusEntity = notification.status {
into: self.backgroundManagedObjectContext, let (statusInCoreData, _, _) = APIService.CoreData.createOrMergeStatus(
for: nil, into: self.backgroundManagedObjectContext,
domain: domain, for: nil,
entity: statusEntity, domain: domain,
statusCache: nil, entity: statusEntity,
userCache: nil, statusCache: nil,
networkDate: Date(), userCache: nil,
log: log) networkDate: Date(),
status = statusInCoreData log: log
} )
// use constrain to avoid repeated save status = statusInCoreData
let notification = MastodonNotification.insert(into: self.backgroundManagedObjectContext, domain: domain, property: MastodonNotification.Property(id: notification.id, type: notification.type.rawValue, account: mastodonUser, status: status, createdAt: notification.createdAt))
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: fetch mastodon user [%s](%s)", (#file as NSString).lastPathComponent, #line, #function, notification.type, notification.account.username)
} }
// use constrain to avoid repeated save
let property = MastodonNotification.Property(id: notification.id, typeRaw: notification.type.rawValue, account: mastodonUser, status: status, createdAt: notification.createdAt)
let notification = MastodonNotification.insert(into: self.backgroundManagedObjectContext, domain: domain, userID: userID, networkDate: response.networkDate, property: property)
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: fetch mastodon user [%s](%s)", (#file as NSString).lastPathComponent, #line, #function, notification.typeRaw, notification.account.username)
} }
.setFailureType(to: Error.self) }
.tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Notification]> in .setFailureType(to: Error.self)
switch result { .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Notification]> in
case .success: switch result {
return response case .success:
case .failure(let error): return response
throw error case .failure(let error):
} throw error
} }
.eraseToAnyPublisher()
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
}
.eraseToAnyPublisher()
} }
} }

View File

@ -8,7 +8,7 @@
import Combine import Combine
import Foundation import Foundation
public extension Mastodon.API.Notifications { extension Mastodon.API.Notifications {
internal static func notificationsEndpointURL(domain: String) -> URL { internal static func notificationsEndpointURL(domain: String) -> URL {
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("notifications") Mastodon.API.endpointURL(domain: domain).appendingPathComponent("notifications")
} }
@ -31,7 +31,7 @@ public extension Mastodon.API.Notifications {
/// - query: `NotificationsQuery` with query parameters /// - query: `NotificationsQuery` with query parameters
/// - authorization: User token /// - authorization: User token
/// - Returns: `AnyPublisher` contains `Token` nested in the response /// - Returns: `AnyPublisher` contains `Token` nested in the response
static func getNotifications( public static func getNotifications(
session: URLSession, session: URLSession,
domain: String, domain: String,
query: Mastodon.API.Notifications.Query, query: Mastodon.API.Notifications.Query,
@ -64,7 +64,7 @@ public extension Mastodon.API.Notifications {
/// - notificationID: ID of the notification. /// - notificationID: ID of the notification.
/// - authorization: User token /// - authorization: User token
/// - Returns: `AnyPublisher` contains `Token` nested in the response /// - Returns: `AnyPublisher` contains `Token` nested in the response
static func getNotification( public static func getNotification(
session: URLSession, session: URLSession,
domain: String, domain: String,
notificationID: String, notificationID: String,
@ -82,18 +82,10 @@ public extension Mastodon.API.Notifications {
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
static func allExcludeTypes() -> [Mastodon.Entity.Notification.NotificationType] {
[.followRequest]
}
static func mentionsExcludeTypes() -> [Mastodon.Entity.Notification.NotificationType] {
[.follow, .followRequest, .favourite, .reblog, .poll]
}
} }
public extension Mastodon.API.Notifications { extension Mastodon.API.Notifications {
struct Query: Codable, PagedQueryType, GetQuery { public struct Query: PagedQueryType, GetQuery {
public let maxID: Mastodon.Entity.Status.ID? public let maxID: Mastodon.Entity.Status.ID?
public let sinceID: Mastodon.Entity.Status.ID? public let sinceID: Mastodon.Entity.Status.ID?
public let minID: Mastodon.Entity.Status.ID? public let minID: Mastodon.Entity.Status.ID?