feat: add navigation to Notification Cell
This commit is contained in:
parent
2d71a48e36
commit
f3394ff382
|
@ -45,6 +45,7 @@
|
|||
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */; };
|
||||
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; };
|
||||
2D24E11D2626D8B100A59D4F /* NotificationStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D24E11C2626D8B100A59D4F /* NotificationStatusTableViewCell.swift */; };
|
||||
2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */; };
|
||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; };
|
||||
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; };
|
||||
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */; };
|
||||
|
@ -433,6 +434,7 @@
|
|||
2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlaybackService.swift; sourceTree = "<group>"; };
|
||||
2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
|
||||
2D24E11C2626D8B100A59D4F /* NotificationStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Gesture.swift"; sourceTree = "<group>"; };
|
||||
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
|
||||
2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1615,6 +1617,7 @@
|
|||
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
|
||||
2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */,
|
||||
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
|
||||
2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */,
|
||||
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
|
||||
2D84350425FF858100EECE90 /* UIScrollView.swift */,
|
||||
DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */,
|
||||
|
@ -2264,6 +2267,7 @@
|
|||
2D61335825C188A000CAE157 /* APIService+Persist+Status.swift in Sources */,
|
||||
2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */,
|
||||
DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */,
|
||||
2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */,
|
||||
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
||||
2D19864F261C372A00F0B013 /* SearchBottomLoader.swift in Sources */,
|
||||
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */,
|
||||
|
|
|
@ -10,7 +10,7 @@ import CoreData
|
|||
|
||||
enum NotificationItem {
|
||||
|
||||
case notification(ObjectID: NSManagedObjectID)
|
||||
case notification(objectID: NSManagedObjectID)
|
||||
|
||||
case bottomLoader
|
||||
}
|
||||
|
|
|
@ -92,7 +92,10 @@ extension NotificationSection {
|
|||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
|
||||
cell.avatatImageView.gesture().sink { [weak cell] _ in
|
||||
cell?.delegate?.userAvatarDidPressed(notification: notification)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
if let actionImage = UIImage(systemName: actionImageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))?.withRenderingMode(.alwaysTemplate) {
|
||||
cell.actionImageView.image = actionImage
|
||||
}
|
||||
|
@ -115,7 +118,10 @@ extension NotificationSection {
|
|||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
|
||||
cell.avatatImageView.gesture().sink { [weak cell] _ in
|
||||
cell?.delegate?.userAvatarDidPressed(notification: notification)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
if let actionImage = UIImage(systemName: actionImageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))?.withRenderingMode(.alwaysTemplate) {
|
||||
cell.actionImageView.image = actionImage
|
||||
}
|
||||
|
@ -399,9 +405,20 @@ extension NotificationSection {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.closed
|
||||
|
||||
if poll.expired {
|
||||
cell.pollCountdownSubscription = nil
|
||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.closed
|
||||
} else if let expiresAt = poll.expiresAt {
|
||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.timeLeft(expiresAt.shortTimeAgoSinceNow)
|
||||
cell.pollCountdownSubscription = timestampUpdatePublisher
|
||||
.sink { _ in
|
||||
cell.statusView.pollCountdownLabel.text = L10n.Common.Controls.Status.Poll.timeLeft(expiresAt.shortTimeAgoSinceNow)
|
||||
}
|
||||
} else {
|
||||
// assertionFailure()
|
||||
cell.pollCountdownSubscription = nil
|
||||
cell.statusView.pollCountdownLabel.text = "-"
|
||||
}
|
||||
|
||||
cell.statusView.pollTableView.allowsSelection = !poll.expired
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// UIView+Gesture.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/14.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct GesturePublisher: Publisher {
|
||||
typealias Output = GestureType
|
||||
typealias Failure = Never
|
||||
private let view: UIView
|
||||
private let gestureType: GestureType
|
||||
init(view: UIView, gestureType: GestureType) {
|
||||
self.view = view
|
||||
self.gestureType = gestureType
|
||||
}
|
||||
|
||||
func receive<S>(subscriber: S) where S: Subscriber,
|
||||
GesturePublisher.Failure == S.Failure, GesturePublisher.Output
|
||||
== S.Input
|
||||
{
|
||||
let subscription = GestureSubscription(
|
||||
subscriber: subscriber,
|
||||
view: view,
|
||||
gestureType: gestureType
|
||||
)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
enum GestureType {
|
||||
case tap(UITapGestureRecognizer = .init())
|
||||
case swipe(UISwipeGestureRecognizer = .init())
|
||||
case longPress(UILongPressGestureRecognizer = .init())
|
||||
case pan(UIPanGestureRecognizer = .init())
|
||||
case pinch(UIPinchGestureRecognizer = .init())
|
||||
case edge(UIScreenEdgePanGestureRecognizer = .init())
|
||||
func get() -> UIGestureRecognizer {
|
||||
switch self {
|
||||
case let .tap(tapGesture):
|
||||
return tapGesture
|
||||
case let .swipe(swipeGesture):
|
||||
return swipeGesture
|
||||
case let .longPress(longPressGesture):
|
||||
return longPressGesture
|
||||
case let .pan(panGesture):
|
||||
return panGesture
|
||||
case let .pinch(pinchGesture):
|
||||
return pinchGesture
|
||||
case let .edge(edgePanGesture):
|
||||
return edgePanGesture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GestureSubscription<S: Subscriber>: Subscription where S.Input == GestureType, S.Failure == Never {
|
||||
private var subscriber: S?
|
||||
private var gestureType: GestureType
|
||||
private var view: UIView
|
||||
init(subscriber: S, view: UIView, gestureType: GestureType) {
|
||||
self.subscriber = subscriber
|
||||
self.view = view
|
||||
self.gestureType = gestureType
|
||||
configureGesture(gestureType)
|
||||
}
|
||||
|
||||
private func configureGesture(_ gestureType: GestureType) {
|
||||
let gesture = gestureType.get()
|
||||
gesture.addTarget(self, action: #selector(handler))
|
||||
view.addGestureRecognizer(gesture)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {}
|
||||
func cancel() {
|
||||
subscriber = nil
|
||||
}
|
||||
|
||||
@objc
|
||||
private func handler() {
|
||||
_ = subscriber?.receive(gestureType)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func gesture(_ gestureType: GestureType = .tap()) -> GesturePublisher {
|
||||
self.isUserInteractionEnabled = true
|
||||
return GesturePublisher(view: self, gestureType: gestureType)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ final class NotificationViewController: UIViewController, NeedsDependency {
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
private(set) lazy var viewModel = NotificationViewModel(context: context, coordinator: coordinator)
|
||||
private(set) lazy var viewModel = NotificationViewModel(context: context)
|
||||
|
||||
let segmentControl: UISegmentedControl = {
|
||||
let control = UISegmentedControl(items: [L10n.Scene.Notification.Title.everything,L10n.Scene.Notification.Title.mentions])
|
||||
|
@ -136,6 +136,24 @@ extension NotificationViewController {
|
|||
// MARK: - UITableViewDelegate
|
||||
extension NotificationViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
switch item {
|
||||
case .notification(let objectID):
|
||||
let notification = context.managedObjectContext.object(with: objectID) as! MastodonNotification
|
||||
if notification.status != nil {
|
||||
// TODO goto status detail vc
|
||||
} else {
|
||||
let viewModel = ProfileViewModel(context: self.context, optionalMastodonUser: notification.account)
|
||||
DispatchQueue.main.async {
|
||||
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -147,9 +165,18 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl
|
|||
}
|
||||
|
||||
extension NotificationViewController: NotificationTableViewCellDelegate {
|
||||
func userAvatarDidPressed(notification: MastodonNotification) {
|
||||
let viewModel = ProfileViewModel(context: self.context, optionalMastodonUser: notification.account)
|
||||
DispatchQueue.main.async {
|
||||
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
|
||||
}
|
||||
}
|
||||
|
||||
func parent() -> UIViewController {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//// MARK: - UIScrollViewDelegate
|
||||
|
|
|
@ -71,7 +71,7 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate {
|
|||
|
||||
var newSnapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
||||
newSnapshot.appendSections([.main])
|
||||
newSnapshot.appendItems(notifications.map({NotificationItem.notification(ObjectID: $0.objectID)}), toSection: .main)
|
||||
newSnapshot.appendItems(notifications.map({NotificationItem.notification(objectID: $0.objectID)}), toSection: .main)
|
||||
if !notifications.isEmpty {
|
||||
newSnapshot.appendItems([.bottomLoader], toSection: .main)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ final class NotificationViewModel: NSObject {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
weak var coordinator: SceneCoordinator!
|
||||
weak var tableView: UITableView?
|
||||
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
||||
|
||||
|
@ -49,8 +48,7 @@ final class NotificationViewModel: NSObject {
|
|||
|
||||
lazy var loadLatestStateMachinePublisher = CurrentValueSubject<LoadLatestState?, Never>(nil)
|
||||
|
||||
init(context: AppContext,coordinator: SceneCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
self.activeMastodonAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
||||
self.fetchedResultsController = {
|
||||
|
|
|
@ -14,7 +14,7 @@ final class NotificationStatusTableViewCell: UITableViewCell {
|
|||
|
||||
static let statusPadding: UIEdgeInsets = UIEdgeInsets(top: 50, left: 73, bottom: 24, right: 24)
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
var pollCountdownSubscription: AnyCancellable?
|
||||
var delegate: NotificationTableViewCellDelegate?
|
||||
|
||||
let avatatImageView: UIImageView = {
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
|
||||
protocol NotificationTableViewCellDelegate: class {
|
||||
var context: AppContext! { get }
|
||||
|
||||
func parent() -> UIViewController
|
||||
|
||||
func userAvatarDidPressed(notification:MastodonNotification)
|
||||
}
|
||||
|
||||
final class NotificationTableViewCell: UITableViewCell {
|
||||
|
|
Loading…
Reference in New Issue