Merge branch 'release/0.6.1'

This commit is contained in:
CMK 2021-06-15 19:33:43 +08:00
commit bd13fa7e43
20 changed files with 126 additions and 91 deletions

View File

@ -101,6 +101,10 @@ extension MastodonNotification {
])
}
}
public static func predicate(validTypesRaws types: [String]) -> NSPredicate {
return NSPredicate(format: "%K IN %@", #keyPath(MastodonNotification.typeRaw), types)
}
}

View File

@ -169,7 +169,7 @@
5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */; };
5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */; };
5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */; };
5DF1054125F886D400D6C0D4 /* ViedeoPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */; };
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054025F886D400D6C0D4 /* VideoPlaybackService.swift */; };
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */; };
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */; };
5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1057825F88A1D00D6C0D4 /* PlayerContainerView.swift */; };
@ -732,7 +732,7 @@
5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Account.swift"; sourceTree = "<group>"; };
5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Tag.swift"; sourceTree = "<group>"; };
5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+History.swift"; sourceTree = "<group>"; };
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViedeoPlaybackService.swift; sourceTree = "<group>"; };
5DF1054025F886D400D6C0D4 /* VideoPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlaybackService.swift; sourceTree = "<group>"; };
5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = "<group>"; };
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayer.swift; sourceTree = "<group>"; };
5DF1057825F88A1D00D6C0D4 /* PlayerContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerContainerView.swift; sourceTree = "<group>"; };
@ -1343,7 +1343,7 @@
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */,
2DA6054625F716A2006356F9 /* PlaybackState.swift */,
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */,
5DF1054025F886D400D6C0D4 /* VideoPlaybackService.swift */,
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */,
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */,
2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */,
@ -2947,7 +2947,7 @@
DBBF1DC7265251D400E5B703 /* AutoCompleteViewModel+State.swift in Sources */,
DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */,
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
5DF1054125F886D400D6C0D4 /* ViedeoPlaybackService.swift in Sources */,
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
@ -3614,7 +3614,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -3622,7 +3622,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.6.0;
MARKETING_VERSION = 0.6.1;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3641,7 +3641,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -3649,7 +3649,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.6.0;
MARKETING_VERSION = 0.6.1;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3904,7 +3904,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -3912,7 +3912,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.6.0;
MARKETING_VERSION = 0.6.1;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3927,7 +3927,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -3935,7 +3935,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.6.0;
MARKETING_VERSION = 0.6.1;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@ -30,14 +30,16 @@ extension NotificationSection {
guard let dependency = dependency else { return nil }
switch notificationItem {
case .notification(let objectID, let attribute):
let notification = managedObjectContext.object(with: objectID) as! MastodonNotification
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw) else {
// filter out invalid type using predicate
assertionFailure()
return nil
return UITableViewCell()
}
let timeText = notification.createAt.slowedTimeAgoSinceNow
let createAt = notification.createAt
let timeText = createAt.slowedTimeAgoSinceNow
let actionText = type.actionText
let actionImageName = type.actionImageName
let color = type.color
@ -57,23 +59,24 @@ extension NotificationSection {
requestUserID: requestUserID,
statusItemAttribute: attribute
)
cell.actionImageBackground.backgroundColor = color
cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName
cell.actionLabel.text = actionText + " · " + timeText
timestampUpdatePublisher
.sink { _ in
let timeText = notification.createAt.slowedTimeAgoSinceNow
.sink { [weak cell] _ in
guard let cell = cell else { return }
let timeText = createAt.slowedTimeAgoSinceNow
cell.actionLabel.text = actionText + " · " + timeText
}
.store(in: &cell.disposeBag)
cell.actionImageBackground.backgroundColor = color
cell.actionLabel.text = actionText + " · " + timeText
cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName
if let url = notification.account.avatarImageURL() {
cell.avatatImageView.af.setImage(
cell.avatarImageView.af.setImage(
withURL: url,
placeholderImage: UIImage.placeholder(color: .systemFill),
imageTransition: .crossDissolve(0.2)
)
}
cell.avatatImageView.gesture().sink { [weak cell] _ in
cell.avatarImageView.gesture().sink { [weak cell] _ in
cell?.delegate?.userAvatarDidPressed(notification: notification)
}
.store(in: &cell.disposeBag)
@ -86,8 +89,9 @@ extension NotificationSection {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
cell.delegate = delegate
timestampUpdatePublisher
.sink { _ in
let timeText = notification.createAt.slowedTimeAgoSinceNow
.sink { [weak cell] _ in
guard let cell = cell else { return }
let timeText = createAt.slowedTimeAgoSinceNow
cell.actionLabel.text = actionText + " · " + timeText
}
.store(in: &cell.disposeBag)

View File

@ -8,11 +8,11 @@
import Foundation
import MastodonSDK
protocol EmojiContinaer {
protocol EmojiContainer {
var emojisData: Data? { get }
}
extension EmojiContinaer {
extension EmojiContainer {
static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? {
return try? JSONEncoder().encode(emojis)

View File

@ -8,11 +8,11 @@
import Foundation
import MastodonSDK
protocol FieldContinaer {
protocol FieldContainer {
var fieldsData: Data? { get }
}
extension FieldContinaer {
extension FieldContainer {
static func encode(fields: [Mastodon.Entity.Field]) -> Data? {
return try? JSONEncoder().encode(fields)

View File

@ -101,5 +101,5 @@ extension MastodonUser {
}
}
extension MastodonUser: EmojiContinaer { }
extension MastodonUser: FieldContinaer { }
extension MastodonUser: EmojiContainer { }
extension MastodonUser: FieldContainer { }

View File

@ -88,4 +88,4 @@ extension Status {
}
}
extension Status: EmojiContinaer { }
extension Status: EmojiContainer { }

View File

@ -17,7 +17,6 @@ enum MastodonStatusContent {
static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult {
let document: String = {
var content = content
content = content.replacingOccurrences(of: "<br/>", with: "\n")
for (shortcode, url) in emojiDict {
let emojiNode = "<span class=\"emoji\" href=\"\(url.absoluteString)\">\(shortcode)</span>"
let pattern = ":\(shortcode):"
@ -189,6 +188,14 @@ extension MastodonStatusContent {
static func parse(document: String) throws -> MastodonStatusContent.Node {
let html = try HTML(html: document, encoding: .utf8)
// add `\r\n` explicit due to Kanna text missing it after convert to text
// ref: https://github.com/tid-kijyun/Kanna/issues/150
let brNodes = html.css("br").makeIterator()
while let brNode = brNodes.next() {
brNode.addNextSibling(try! HTML(html: "<span>\r\n</span>", encoding: .utf8).body!)
}
let body = html.body ?? nil
let text = body?.text ?? ""
let level = 0

View File

@ -368,6 +368,7 @@ extension ComposeViewController {
guard let self = self else { return }
let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
self.composeToolbarView.activeVisibilityType.value = type
}
.store(in: &disposeBag)
@ -676,7 +677,7 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
updateAttributedString attributedString: NSAttributedString,
completion: @escaping (NSAttributedString?) -> Void
) {
// FIXME: needs O(1) update completion to fix profermance issue
// FIXME: needs O(1) update completion to fix performance issue
DispatchQueue.global().async {
let string = attributedString.string
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update: %s", ((#file as NSString).lastPathComponent), #line, #function, string)
@ -1291,7 +1292,8 @@ extension ComposeViewController {
case togglePoll
case toggleContentWarning
case selectVisibilityPublic
case selectVisibilityUnlisted
// TODO: remove selectVisibilityUnlisted from codebase
// case selectVisibilityUnlisted
case selectVisibilityPrivate
case selectVisibilityDirect
@ -1305,7 +1307,7 @@ extension ComposeViewController {
case .togglePoll: return L10n.Scene.Compose.Keyboard.togglePoll
case .toggleContentWarning: return L10n.Scene.Compose.Keyboard.toggleContentWarning
case .selectVisibilityPublic: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.public)
case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
// case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
case .selectVisibilityPrivate: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.private)
case .selectVisibilityDirect: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.direct)
}
@ -1322,9 +1324,9 @@ extension ComposeViewController {
case .togglePoll: return "p" // + shift + command
case .toggleContentWarning: return "c" // + shift + command
case .selectVisibilityPublic: return "1" // + command
case .selectVisibilityUnlisted: return "2" // + command
case .selectVisibilityPrivate: return "3" // + command
case .selectVisibilityDirect: return "4" // + command
// case .selectVisibilityUnlisted: return "2" // + command
case .selectVisibilityPrivate: return "2" // + command
case .selectVisibilityDirect: return "3" // + command
}
}
@ -1338,7 +1340,7 @@ extension ComposeViewController {
case .togglePoll: return [.shift, .command]
case .toggleContentWarning: return [.shift, .command]
case .selectVisibilityPublic: return [.command]
case .selectVisibilityUnlisted: return [.command]
// case .selectVisibilityUnlisted: return [.command]
case .selectVisibilityPrivate: return [.command]
case .selectVisibilityDirect: return [.command]
}
@ -1390,8 +1392,8 @@ extension ComposeViewController {
composeToolbarView.contentWarningButton.sendActions(for: .touchUpInside)
case .selectVisibilityPublic:
viewModel.selectedStatusVisibility.value = .public
case .selectVisibilityUnlisted:
viewModel.selectedStatusVisibility.value = .unlisted
// case .selectVisibilityUnlisted:
// viewModel.selectedStatusVisibility.value = .unlisted
case .selectVisibilityPrivate:
viewModel.selectedStatusVisibility.value = .private
case .selectVisibilityDirect:

View File

@ -92,7 +92,8 @@ final class ComposeViewModel {
self.activeAuthentication = CurrentValueSubject(context.authenticationService.activeMastodonAuthentication.value)
self.activeAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
// end init
if case let .reply(repliedToStatusObjectID) = composeKind {
switch composeKind {
case .reply(let repliedToStatusObjectID):
context.managedObjectContext.performAndWait {
guard let status = context.managedObjectContext.object(with: repliedToStatusObjectID) as? Status else { return }
let composeAuthor: MastodonUser? = {
@ -124,14 +125,13 @@ final class ComposeViewModel {
self.preInsertedContent = preInsertedContent
self.composeStatusAttribute.composeContent.value = preInsertedContent
}
} else if case let .hashtag(text) = composeKind {
let initialComposeContent = "#" + text
case .hashtag(let hashtag):
let initialComposeContent = "#" + hashtag
UITextChecker.learnWord(initialComposeContent)
let preInsertedContent = initialComposeContent + " "
self.preInsertedContent = preInsertedContent
self.composeStatusAttribute.composeContent.value = preInsertedContent
} else if case let .mention(mastodonUserObjectID) = composeKind {
case .mention(let mastodonUserObjectID):
context.managedObjectContext.performAndWait {
let mastodonUser = context.managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser
let initialComposeContent = "@" + mastodonUser.acct
@ -140,7 +140,7 @@ final class ComposeViewModel {
self.preInsertedContent = preInsertedContent
self.composeStatusAttribute.composeContent.value = preInsertedContent
}
} else {
case .post:
self.preInsertedContent = nil
}
@ -263,7 +263,7 @@ final class ComposeViewModel {
}
// if preInsertedContent plus a space is equal to the content, simply dismiss the modal
if let preInsertedContent = self?.preInsertedContent {
return content == (preInsertedContent + " ")
return content == preInsertedContent
}
return false
}
@ -374,11 +374,6 @@ final class ComposeViewModel {
self.isPollToolbarButtonEnabled.value = !shouldPollDisable
})
.store(in: &disposeBag)
if let preInsertedContent = preInsertedContent {
// add a space after the injected text
composeStatusAttribute.composeContent.send(preInsertedContent + " ")
}
}
deinit {

View File

@ -7,6 +7,7 @@
import os.log
import UIKit
import Combine
import MastodonSDK
protocol ComposeToolbarViewDelegate: AnyObject {
@ -19,6 +20,8 @@ protocol ComposeToolbarViewDelegate: AnyObject {
final class ComposeToolbarView: UIView {
var disposeBag = Set<AnyCancellable>()
static let toolbarButtonSize: CGSize = CGSize(width: 44, height: 44)
static let toolbarHeight: CGFloat = 44
@ -76,6 +79,8 @@ final class ComposeToolbarView: UIView {
return label
}()
let activeVisibilityType = CurrentValueSubject<VisibilitySelectionType, Never>(.public)
override init(frame: CGRect) {
super.init(frame: frame)
_init()
@ -142,6 +147,15 @@ extension ComposeToolbarView {
visibilityButton.showsMenuAsPrimaryAction = true
updateToolbarButtonUserInterfaceStyle()
// update menu when selected visibility type changed
activeVisibilityType
.receive(on: RunLoop.main)
.sink { [weak self] type in
guard let self = self else { return }
self.visibilityButton.menu = self.createVisibilityContextMenu(interfaceStyle: self.traitCollection.userInterfaceStyle)
}
.store(in: &disposeBag)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
@ -161,14 +175,15 @@ extension ComposeToolbarView {
enum VisibilitySelectionType: String, CaseIterable {
case `public`
case unlisted
// TODO: remove unlisted option from codebase
// case unlisted
case `private`
case direct
var title: String {
switch self {
case .public: return L10n.Scene.Compose.Visibility.public
case .unlisted: return L10n.Scene.Compose.Visibility.unlisted
// case .unlisted: return L10n.Scene.Compose.Visibility.unlisted
case .private: return L10n.Scene.Compose.Visibility.private
case .direct: return L10n.Scene.Compose.Visibility.direct
}
@ -181,7 +196,7 @@ extension ComposeToolbarView {
case .light: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
default: return UIImage(systemName: "person.3.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
}
case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))!
// case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))!
case .private: return UIImage(systemName: "person.crop.circle.badge.plus", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))!
case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .regular))!
}
@ -190,7 +205,7 @@ extension ComposeToolbarView {
func imageNameForTimeline() -> String {
switch self {
case .public: return "person.3"
case .unlisted: return "eye.slash"
// case .unlisted: return "eye.slash"
case .private: return "person.crop.circle.badge.plus"
case .direct: return "at"
}
@ -199,7 +214,7 @@ extension ComposeToolbarView {
var visibility: Mastodon.Entity.Status.Visibility {
switch self {
case .public: return .public
case .unlisted: return .unlisted
// case .unlisted: return .unlisted
case .private: return .private
case .direct: return .direct
}
@ -268,7 +283,8 @@ extension ComposeToolbarView {
private func createVisibilityContextMenu(interfaceStyle: UIUserInterfaceStyle) -> UIMenu {
let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in
UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
let state: UIMenuElement.State = activeVisibilityType.value == type ? .on : .off
return UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { [weak self] action in
guard let self = self else { return }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue)
self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type)

View File

@ -9,6 +9,7 @@ import CoreData
import CoreDataStack
import os.log
import UIKit
import MastodonSDK
extension NotificationViewModel {
func setupDiffableDataSource(
@ -16,7 +17,7 @@ extension NotificationViewModel {
delegate: NotificationTableViewCellDelegate,
dependency: NeedsDependency
) {
let timestampUpdatePublisher = Timer.publish(every: 30.0, on: .main, in: .common)
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.share()
.eraseToAnyPublisher()
@ -44,7 +45,14 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate {
guard let diffableDataSource = self.diffableDataSource else { return }
let predicate = fetchedResultsController.fetchRequest.predicate
let predicate: NSPredicate = {
let notificationTypePredicate = MastodonNotification.predicate(
validTypesRaws: Mastodon.Entity.Notification.NotificationType.knownCases.map { $0.rawValue }
)
return fetchedResultsController.fetchRequest.predicate.flatMap {
NSCompoundPredicate(andPredicateWithSubpredicates: [$0, notificationTypePredicate])
} ?? notificationTypePredicate
}()
let parentManagedObjectContext = fetchedResultsController.managedObjectContext
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.parent = parentManagedObjectContext
@ -73,19 +81,6 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate {
newSnapshot.appendSections([.main])
let items: [NotificationItem] = notifications.map { notification in
let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute()
// let attribute: Item.StatusAttribute = {
// if let attribute = oldSnapshotAttributeDict[notification.objectID] {
// return attribute
// } else if let status = notification.status {
// let attribute = Item.StatusAttribute()
// let isSensitive = status.sensitive || !(status.spoilerText ?? "").isEmpty
// attribute.isRevealing.value = !isSensitive
// return attribute
// } else {
// return Item.StatusAttribute()
// }
// }()
return NotificationItem.notification(objectID: notification.objectID, attribute: attribute)
}
newSnapshot.appendItems(items, toSection: .main)

View File

@ -17,7 +17,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
var pollCountdownSubscription: AnyCancellable?
var delegate: NotificationTableViewCellDelegate?
let avatatImageView: UIImageView = {
let avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 4
imageView.layer.cornerCurve = .continuous
@ -86,7 +86,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
override func prepareForReuse() {
super.prepareForReuse()
avatatImageView.af.cancelImageRequest()
avatarImageView.af.cancelImageRequest()
statusView.updateContentWarningDisplay(isHidden: true, animated: false)
statusView.pollTableView.dataSource = nil
statusView.playerContainerView.reset()
@ -142,13 +142,13 @@ extension NotificationStatusTableViewCell {
avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1)
])
avatarContainer.addSubview(avatatImageView)
avatatImageView.translatesAutoresizingMaskIntoConstraints = false
avatarContainer.addSubview(avatarImageView)
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatatImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatatImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatatImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
avatatImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor)
avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor)
])
avatarContainer.addSubview(actionImageBackground)

View File

@ -13,7 +13,7 @@ extension APIService {
func uploadMedia(
domain: String,
query: Mastodon.API.Media.UploadMeidaQuery,
query: Mastodon.API.Media.UploadMediaQuery,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization

View File

@ -27,7 +27,7 @@ extension APIService {
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> {
let authorization = authorizationBox.userAuthorization
let requestMastodonUserID = authorizationBox.userID
let query = Mastodon.API.Account.AccountStatuseseQuery(
let query = Mastodon.API.Account.AccountStatusesQuery(
maxID: maxID,
sinceID: sinceID,
excludeReplies: excludeReplies,

View File

@ -56,7 +56,7 @@ extension MastodonAttachmentService.UploadState {
guard let file = service.file.value else { return }
let description = service.description.value
let query = Mastodon.API.Media.UploadMeidaQuery(
let query = Mastodon.API.Media.UploadMediaQuery(
file: file,
thumbnail: nil,
description: description,

View File

@ -71,14 +71,14 @@ extension Mastodon.API.Account {
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: `AccountStatuseseQuery` with query parameters
/// - query: `AccountStatusesQuery` with query parameters
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `Token` nested in the response
public static func statuses(
session: URLSession,
domain: String,
accountID: Mastodon.Entity.Account.ID,
query: AccountStatuseseQuery,
query: AccountStatusesQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> {
let request = Mastodon.API.get(
@ -94,7 +94,7 @@ extension Mastodon.API.Account {
.eraseToAnyPublisher()
}
public struct AccountStatuseseQuery: GetQuery {
public struct AccountStatusesQuery: GetQuery {
public let maxID: Mastodon.Entity.Status.ID?
public let sinceID: Mastodon.Entity.Status.ID?
public let excludeReplies: Bool? // undocumented

View File

@ -33,7 +33,7 @@ extension Mastodon.API.Media {
public static func uploadMedia(
session: URLSession,
domain: String,
query: UploadMeidaQuery,
query: UploadMediaQuery,
authorization: Mastodon.API.OAuth.Authorization?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error> {
var request = Mastodon.API.post(
@ -56,7 +56,7 @@ extension Mastodon.API.Media {
.eraseToAnyPublisher()
}
public struct UploadMeidaQuery: PostQuery, PutQuery {
public struct UploadMediaQuery: PostQuery, PutQuery {
public let file: Mastodon.Query.MediaAttachment?
public let thumbnail: Mastodon.Query.MediaAttachment?
public let description: String?

View File

@ -49,6 +49,18 @@ extension Mastodon.Entity.Notification {
case _other(String)
public static var knownCases: [NotificationType] {
return [
.follow,
.followRequest,
.mention,
.reblog,
.favourite,
.poll,
.status
]
}
public init?(rawValue: String) {
switch rawValue {
case "follow": self = .follow