Add and show filter-banner in "everything"-notifications (if there are any) (IOS-241)
This commit is contained in:
parent
f297cbabc5
commit
4843348034
@ -158,6 +158,7 @@
|
||||
D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */; };
|
||||
D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A892A4468DC00C0FB73 /* AboutViewController.swift */; };
|
||||
D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; };
|
||||
D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; };
|
||||
D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; };
|
||||
D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; };
|
||||
D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; };
|
||||
@ -791,6 +792,7 @@
|
||||
D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
D8318A892A4468DC00C0FB73 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = "<group>"; tabWidth = 4; };
|
||||
D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = "<group>"; };
|
||||
D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = "<group>"; };
|
||||
@ -1512,6 +1514,7 @@
|
||||
DB63F76E279A7D1100455B82 /* NotificationTableViewCell.swift */,
|
||||
DB63F774279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift */,
|
||||
D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */,
|
||||
D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
@ -3780,6 +3783,7 @@
|
||||
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
|
||||
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
|
||||
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,
|
||||
D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */,
|
||||
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
||||
D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */,
|
||||
D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */,
|
||||
|
@ -17,32 +17,30 @@ extension DataSourceFacade {
|
||||
item: DataSourceItem
|
||||
) async {
|
||||
switch item {
|
||||
case .account(account: let account, relationship: _):
|
||||
let now = Date()
|
||||
let userID = provider.authContext.mastodonAuthenticationBox.userID
|
||||
let searchEntry = Persistence.SearchHistory.Item(
|
||||
updatedAt: now,
|
||||
userID: userID,
|
||||
account: account,
|
||||
hashtag: nil
|
||||
)
|
||||
case .account(account: let account, relationship: _):
|
||||
let now = Date()
|
||||
let userID = provider.authContext.mastodonAuthenticationBox.userID
|
||||
let searchEntry = Persistence.SearchHistory.Item(
|
||||
updatedAt: now,
|
||||
userID: userID,
|
||||
account: account,
|
||||
hashtag: nil
|
||||
)
|
||||
|
||||
try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
|
||||
case .hashtag(let tag):
|
||||
try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
|
||||
case .hashtag(let tag):
|
||||
|
||||
let now = Date()
|
||||
let userID = provider.authContext.mastodonAuthenticationBox.userID
|
||||
let searchEntry = Persistence.SearchHistory.Item(
|
||||
updatedAt: now,
|
||||
userID: userID,
|
||||
account: nil,
|
||||
hashtag: tag
|
||||
)
|
||||
let now = Date()
|
||||
let userID = provider.authContext.mastodonAuthenticationBox.userID
|
||||
let searchEntry = Persistence.SearchHistory.Item(
|
||||
updatedAt: now,
|
||||
userID: userID,
|
||||
account: nil,
|
||||
hashtag: tag
|
||||
)
|
||||
|
||||
try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
|
||||
case .status:
|
||||
break
|
||||
case .notification:
|
||||
try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
|
||||
case .status, .notification, .notificationBanner(_):
|
||||
break
|
||||
|
||||
}
|
||||
|
@ -514,10 +514,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
||||
)
|
||||
case .account(let account, _):
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
case .hashtag(_):
|
||||
assertionFailure("TODO")
|
||||
case .notification, .hashtag(_), .notificationBanner(_):
|
||||
print("TODO")
|
||||
}
|
||||
} // end Task
|
||||
}
|
||||
|
@ -618,9 +618,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
||||
provider: self,
|
||||
account: account
|
||||
)
|
||||
case .notification:
|
||||
assertionFailure("TODO")
|
||||
case .hashtag(_):
|
||||
case .notification, .hashtag(_), .notificationBanner(_):
|
||||
assertionFailure("TODO")
|
||||
}
|
||||
}
|
||||
|
@ -22,42 +22,45 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case .account(let account, relationship: _):
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
|
||||
case .status(let status):
|
||||
case .account(let account, relationship: _):
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
|
||||
case .status(let status):
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .hashtag(let tag):
|
||||
await DataSourceFacade.coordinateToHashtagScene(
|
||||
provider: self,
|
||||
tag: tag
|
||||
)
|
||||
case .notification(let notification):
|
||||
let _status: MastodonStatus? = notification.status
|
||||
if let status = _status {
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
case .hashtag(let tag):
|
||||
await DataSourceFacade.coordinateToHashtagScene(
|
||||
provider: self,
|
||||
tag: tag
|
||||
} else if let accountWarning = notification.entity.accountWarning {
|
||||
let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id)
|
||||
_ = coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: self,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
case .notification(let notification):
|
||||
let _status: MastodonStatus? = notification.status
|
||||
if let status = _status {
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
} else if let accountWarning = notification.entity.accountWarning {
|
||||
let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id)
|
||||
_ = coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: self,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
|
||||
} else {
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
account: notification.entity.account
|
||||
)
|
||||
} // end Task
|
||||
} // end func
|
||||
} else {
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
account: notification.entity.account
|
||||
)
|
||||
}
|
||||
case .notificationBanner(let policy):
|
||||
//TODO: Coordinate to pending notification-screen
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ enum DataSourceItem: Hashable {
|
||||
case status(record: MastodonStatus)
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case notification(record: MastodonNotification)
|
||||
case notificationBanner(policy: Mastodon.Entity.NotificationPolicy)
|
||||
case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
class NotificationFilteringBannerTableViewCell: UITableViewCell {
|
||||
static let reuseIdentifier = "NotificationFilteringBannerTableViewCell"
|
||||
|
||||
//TODO: Add separator
|
||||
|
||||
func configure(with policy: Mastodon.Entity.NotificationPolicy) {
|
||||
var configuration = defaultContentConfiguration()
|
||||
|
||||
//TODO: Add localization
|
||||
configuration.text = "Filtered notifications"
|
||||
configuration.secondaryText = "\(policy.summary.pendingRequestsCount) people you may know"
|
||||
configuration.image = UIImage(systemName: "archivebox")
|
||||
|
||||
self.contentConfiguration = configuration
|
||||
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
enum NotificationItem: Hashable {
|
||||
case filteredNotifications(policy: Mastodon.Entity.NotificationPolicy)
|
||||
case feed(record: MastodonFeed)
|
||||
case feedLoader(record: MastodonFeed)
|
||||
case bottomLoader
|
||||
|
@ -39,6 +39,7 @@ extension NotificationSection {
|
||||
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
|
||||
tableView.register(AccountWarningNotificationCell.self, forCellReuseIdentifier: AccountWarningNotificationCell.reuseIdentifier)
|
||||
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||
tableView.register(NotificationFilteringBannerTableViewCell.self, forCellReuseIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier)
|
||||
|
||||
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
@ -67,6 +68,12 @@ extension NotificationSection {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
cell.activityIndicatorView.startAnimating()
|
||||
return cell
|
||||
|
||||
case .filteredNotifications(let policy):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier, for: indexPath) as! NotificationFilteringBannerTableViewCell
|
||||
cell.configure(with: policy)
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ extension NotificationTimelineViewController: DataSourceProvider {
|
||||
}
|
||||
}()
|
||||
return item
|
||||
default:
|
||||
case .filteredNotifications(let policy):
|
||||
return DataSourceItem.notificationBanner(policy: policy)
|
||||
case .bottomLoader, .feedLoader(_):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -13,16 +13,16 @@ import MastodonLocalization
|
||||
|
||||
final class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var context: AppContext!
|
||||
weak var coordinator: SceneCoordinator!
|
||||
|
||||
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
var viewModel: NotificationTimelineViewModel!
|
||||
|
||||
let viewModel: NotificationTimelineViewModel
|
||||
|
||||
private(set) lazy var refreshControl: RefreshControl = {
|
||||
let refreshControl = RefreshControl()
|
||||
refreshControl.addTarget(self, action: #selector(NotificationTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged)
|
||||
@ -38,6 +38,16 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc
|
||||
}()
|
||||
|
||||
let cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||
|
||||
init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator) {
|
||||
self.viewModel = viewModel
|
||||
self.context = context
|
||||
self.coordinator = coordinator
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
}
|
||||
|
||||
extension NotificationTimelineViewController {
|
||||
|
@ -33,7 +33,7 @@ extension NotificationTimelineViewModel {
|
||||
dataController.$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
guard let self else { return }
|
||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||
|
||||
Task {
|
||||
@ -44,6 +44,9 @@ extension NotificationTimelineViewModel {
|
||||
}
|
||||
var snapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
||||
snapshot.appendSections([.main])
|
||||
if self.scope == .everything, let notificationPolicy = self.notificationPolicy, notificationPolicy.summary.pendingRequestsCount > 0 {
|
||||
snapshot.appendItems([.filteredNotifications(policy: notificationPolicy)])
|
||||
}
|
||||
snapshot.appendItems(newItems.removingDuplicates(), toSection: .main)
|
||||
return snapshot
|
||||
}()
|
||||
|
@ -20,6 +20,7 @@ final class NotificationTimelineViewModel {
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let scope: Scope
|
||||
let notificationPolicy: Mastodon.Entity.NotificationPolicy?
|
||||
let dataController: FeedDataController
|
||||
@Published var isLoadingLatest = false
|
||||
@Published var lastAutomaticFetchTimestamp: Date?
|
||||
@ -46,12 +47,14 @@ final class NotificationTimelineViewModel {
|
||||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
scope: Scope
|
||||
scope: Scope,
|
||||
notificationPolicy: Mastodon.Entity.NotificationPolicy?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.scope = scope
|
||||
self.dataController = FeedDataController(context: context, authContext: authContext)
|
||||
self.notificationPolicy = notificationPolicy
|
||||
|
||||
switch scope {
|
||||
case .everything:
|
||||
|
@ -146,17 +146,20 @@ extension NotificationViewController {
|
||||
pageSegmentedControl.selectedSegmentIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func createViewController(for scope: NotificationTimelineViewModel.Scope) -> UIViewController {
|
||||
guard let authContext = viewModel?.authContext else { return UITableViewController() }
|
||||
let viewController = NotificationTimelineViewController()
|
||||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
viewController.viewModel = NotificationTimelineViewModel(
|
||||
guard let viewModel else { return UITableViewController() }
|
||||
|
||||
let viewController = NotificationTimelineViewController(
|
||||
viewModel: NotificationTimelineViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
scope: scope, notificationPolicy: viewModel.notificationPolicy
|
||||
),
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
scope: scope
|
||||
coordinator: coordinator
|
||||
)
|
||||
|
||||
return viewController
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ extension SearchResultViewController {
|
||||
provider: self,
|
||||
tag: tag
|
||||
)
|
||||
case .notification:
|
||||
case .notification, .notificationBanner(_):
|
||||
assertionFailure()
|
||||
} // end switch
|
||||
|
||||
|
@ -3,12 +3,12 @@
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.Entity {
|
||||
public struct NotificationPolicy: Codable {
|
||||
let filterNotFollowing: Bool
|
||||
let filterNotFollowers: Bool
|
||||
let filterNewAccounts: Bool
|
||||
let filterPrivateMentions: Bool
|
||||
let summary: Summary
|
||||
public struct NotificationPolicy: Codable, Hashable {
|
||||
public let filterNotFollowing: Bool
|
||||
public let filterNotFollowers: Bool
|
||||
public let filterNewAccounts: Bool
|
||||
public let filterPrivateMentions: Bool
|
||||
public let summary: Summary
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case filterNotFollowing = "filter_not_following"
|
||||
@ -18,9 +18,9 @@ extension Mastodon.Entity {
|
||||
case summary
|
||||
}
|
||||
|
||||
public struct Summary: Codable {
|
||||
let pendingRequestsCount: Int
|
||||
let pendingNotificationsCount: Int
|
||||
public struct Summary: Codable, Hashable {
|
||||
public let pendingRequestsCount: Int
|
||||
public let pendingNotificationsCount: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case pendingRequestsCount = "pending_requests_count"
|
||||
|
Loading…
x
Reference in New Issue
Block a user