diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 4bd06add..18a99202 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -273,7 +273,6 @@ DB49A63D25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A63C25FF609300B98345 /* PlayerContainerView+MediaTypeIndicotorView.swift */; }; DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; }; DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; }; - DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; }; DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; }; DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; }; DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; }; @@ -296,6 +295,7 @@ DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F526391D580018D199 /* MediaPreviewableViewController.swift */; }; DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; }; DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; }; + DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; }; DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; @@ -921,6 +921,7 @@ DB6180F526391D580018D199 /* MediaPreviewableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewableViewController.swift; sourceTree = ""; }; DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = ""; }; DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = ""; }; + DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = ""; }; DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+Diffable.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; @@ -1144,7 +1145,6 @@ DBB525082611EAC0002F1F29 /* Tabman in Frameworks */, 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */, - DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */, DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */, DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */, @@ -2369,6 +2369,7 @@ isa = PBXGroup; children = ( DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */, + DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */, 2D607AD726242FC500B70763 /* NotificationViewModel.swift */, 2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */, 2D084B9226259545003AA3AF /* NotificationViewModel+LoadLatestState.swift */, @@ -2674,7 +2675,6 @@ 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */, 2D42FF6025C8177C004A627A /* ActiveLabel */, DB0140BC25C40D7500F9F3CF /* CommonOSLog */, - DB5086B725CC0D6400C2C187 /* Kingfisher */, 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, DB9A487D2603456B008B817C /* UITextView+Placeholder */, @@ -2868,7 +2868,6 @@ 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */, 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */, DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */, - DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */, 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */, @@ -3177,6 +3176,7 @@ 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */, DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */, + DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */, DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */, DBAC648F267DC84D007FE9FD /* TableNodeDiffableDataSource.swift in Sources */, 2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */, @@ -3843,7 +3843,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -3851,7 +3851,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3870,7 +3870,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -3878,7 +3878,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4198,7 +4198,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4206,7 +4206,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4312,7 +4312,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4320,7 +4320,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4431,7 +4431,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4439,7 +4439,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4545,7 +4545,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4553,7 +4553,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4599,7 +4599,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4607,7 +4607,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4622,7 +4622,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 25; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4630,7 +4630,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.8.0; + MARKETING_VERSION = 0.8.2; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4778,7 +4778,7 @@ repositoryURL = "https://github.com/TwidereProject/MetaTextView.git"; requirement = { kind = exactVersion; - version = 1.2.3; + version = 1.2.4; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { @@ -4797,14 +4797,6 @@ minimumVersion = 4.1.0; }; }; - DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 6.1.0; - }; - }; DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; @@ -4934,11 +4926,6 @@ package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; - DB5086B725CC0D6400C2C187 /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; DB68050F2637D0F800430867 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 7b70d1af..f1135b12 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 27 + 20 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -37,7 +37,7 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 26 + 21 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8414b940..817624a3 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -114,8 +114,8 @@ "repositoryURL": "https://github.com/TwidereProject/MetaTextView.git", "state": { "branch": null, - "revision": "5b86b386464be8a6da5383aa714c458c07da6c01", - "version": "1.2.3" + "revision": "28e53130d16f12e0eeb479d83b77a0a718ef2088", + "version": "1.2.4" } }, { diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index a12a72d9..fbe0ced5 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -279,7 +279,10 @@ private extension SceneCoordinator { scheme == "http" || scheme == "https" else { return nil } - viewController = SFSafariViewController(url: url) + let _viewController = SFSafariViewController(url: url) + _viewController.preferredControlTintColor = Asset.Colors.brandBlue.color + viewController = _viewController + case .alertController(let alertController): if let popoverPresentationController = alertController.popoverPresentationController { assert( diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 984e8a54..820f9d4b 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -11,6 +11,7 @@ import CoreDataStack import Foundation import MastodonSDK import UIKit +import Nuke enum NotificationSection: Equatable, Hashable { case main @@ -30,7 +31,9 @@ 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 notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { + return UITableViewCell() + } guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw) else { // filter out invalid type using predicate assertionFailure() @@ -60,7 +63,7 @@ extension NotificationSection { statusItemAttribute: attribute ) cell.actionImageBackground.backgroundColor = color - cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName + cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict) cell.actionLabel.text = actionText + " · " + timeText timestampUpdatePublisher .sink { [weak cell] _ in @@ -70,10 +73,13 @@ extension NotificationSection { } .store(in: &cell.disposeBag) if let url = notification.account.avatarImageURL() { - cell.avatarImageView.af.setImage( - withURL: url, - placeholderImage: UIImage.placeholder(color: .systemFill), - imageTransition: .crossDissolve(0.2) + cell.avatarImageViewTask = Nuke.loadImage( + with: url, + options: ImageLoadingOptions( + placeholder: UIImage.placeholder(color: .systemFill), + transition: .fadeIn(duration: 0.2) + ), + into: cell.avatarImageView ) } cell.avatarImageView.gesture().sink { [weak cell] _ in @@ -109,15 +115,18 @@ extension NotificationSection { .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 + cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict) if let url = notification.account.avatarImageURL() { - cell.avatatImageView.af.setImage( - withURL: url, - placeholderImage: UIImage.placeholder(color: .systemFill), - imageTransition: .crossDissolve(0.2) + cell.avatarImageViewTask = Nuke.loadImage( + with: url, + options: ImageLoadingOptions( + placeholder: UIImage.placeholder(color: .systemFill), + transition: .fadeIn(duration: 0.2) + ), + into: cell.avatarImageView ) } - cell.avatatImageView.gesture().sink { [weak cell] _ in + cell.avatarImageView.gesture().sink { [weak cell] _ in cell?.delegate?.userAvatarDidPressed(notification: notification) } .store(in: &cell.disposeBag) diff --git a/Mastodon/Diffiable/Section/PollSection.swift b/Mastodon/Diffiable/Section/PollSection.swift index 3c31d9b9..581620c0 100644 --- a/Mastodon/Diffiable/Section/PollSection.swift +++ b/Mastodon/Diffiable/Section/PollSection.swift @@ -67,13 +67,13 @@ extension PollSection { cell.pollOptionView.checkmarkBackgroundView.isHidden = true cell.pollOptionView.checkmarkImageView.isHidden = true case .off: - cell.pollOptionView.checkmarkBackgroundView.backgroundColor = .systemBackground - cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.systemGray3.cgColor + cell.pollOptionView.checkmarkBackgroundView.backgroundColor = Asset.Colors.Background.tertiarySystemBackground.color + cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = Asset.Colors.Background.Cell.highlight.color.withAlphaComponent(0.3).cgColor cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 1 cell.pollOptionView.checkmarkBackgroundView.isHidden = false cell.pollOptionView.checkmarkImageView.isHidden = true case .on: - cell.pollOptionView.checkmarkBackgroundView.backgroundColor = .systemBackground + cell.pollOptionView.checkmarkBackgroundView.backgroundColor = Asset.Colors.Background.tertiarySystemBackground.color cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.clear.cgColor cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 0 cell.pollOptionView.checkmarkBackgroundView.isHidden = false diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index ce9f07ae..35dd535c 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -99,6 +99,21 @@ extension StatusSection { ) cell.delegate = statusTableViewCellDelegate cell.isAccessibilityElement = true + // FIXME: + cell.accessibilityLabel = { + var accessibilityViews: [UIView?] = [] + if !cell.statusView.headerContainerView.isHidden { + accessibilityViews.append(cell.statusView.headerInfoLabel) + } + accessibilityViews.append(contentsOf: [ + cell.statusView.nameLabel, + cell.statusView.dateLabel, + cell.statusView.contentMetaText.textView, + ]) + return accessibilityViews + .compactMap { $0?.accessibilityLabel } + .joined(separator: " ") + }() return cell case .status(let objectID, let attribute), .root(let objectID, let attribute), @@ -244,6 +259,7 @@ extension StatusSection { // set timestamp let createdAt = (status.reblog ?? status).createdAt cell.statusView.dateLabel.text = createdAt.slowedTimeAgoSinceNow + cell.statusView.dateLabel.accessibilityValue = createdAt.timeAgoSinceNow AppContext.shared.timestampUpdatePublisher .receive(on: RunLoop.main) // will be paused when scrolling (on purpose) .sink { [weak cell] _ in @@ -503,6 +519,7 @@ extension StatusSection { cell.statusView.headerInfoLabel.configure(contentParseResult: parseResult) } .store(in: &cell.disposeBag) + cell.statusView.headerInfoLabel.accessibilityLabel = headerText cell.statusView.headerInfoLabel.isAccessibilityElement = true } else if status.inReplyToID != nil { cell.statusView.headerContainerView.isHidden = false @@ -522,7 +539,8 @@ extension StatusSection { cell.statusView.headerInfoLabel.configure(contentParseResult: parseResult) } .store(in: &cell.disposeBag) - cell.statusView.headerInfoLabel.isAccessibilityElement = true + cell.statusView.headerInfoLabel.accessibilityLabel = headerText + cell.statusView.headerInfoLabel.isAccessibilityElement = status.replyTo != nil } else { cell.statusView.headerContainerView.isHidden = true cell.statusView.headerInfoLabel.isAccessibilityElement = false @@ -536,7 +554,14 @@ extension StatusSection { // name let author = (status.reblog ?? status).author let nameContent = author.displayNameWithFallback - cell.statusView.nameLabel.configure(content: nameContent, emojiDict: author.emojiDict) + MastodonStatusContent.parseResult(content: nameContent, emojiDict: author.emojiDict) + .receive(on: DispatchQueue.main) + .sink { [weak cell] parseResult in + guard let cell = cell else { return } + cell.statusView.nameLabel.configure(contentParseResult: parseResult) + } + .store(in: &cell.disposeBag) + cell.statusView.nameLabel.accessibilityLabel = nameContent // username cell.statusView.usernameLabel.text = "@" + author.acct // avatar @@ -560,17 +585,22 @@ extension StatusSection { ) { // set content do { + let status = status.reblog ?? status let content = MastodonContent( - content: (status.reblog ?? status).content, - emojis: (status.reblog ?? status).emojiMeta + content: status.content, + emojis: status.emojiMeta ) let metaContent = try MastodonMetaContent.convert(document: content) cell.statusView.contentMetaText.configure(content: metaContent) + cell.statusView.contentMetaText.textView.accessibilityLabel = metaContent.trimmed } catch { cell.statusView.contentMetaText.textView.text = " " + cell.statusView.contentMetaText.textView.accessibilityLabel = "" assertionFailure() } + cell.statusView.contentMetaText.textView.accessibilityTraits = [.staticText] + cell.statusView.contentMetaText.textView.accessibilityElementsHidden = false cell.statusView.contentMetaText.textView.accessibilityLanguage = (status.reblog ?? status).language // set visibility @@ -633,46 +663,19 @@ extension StatusSection { let isSingleMosaicLayout = mosaics.count == 1 - // set link preview -// cell.statusView.linkPreview.isHidden = true -// -// var _firstURL: URL? = { -// for entity in cell.statusView.activeTextLabel.activeEntities { -// guard case let .url(_, _, url, _) = entity.type else { continue } -// return URL(string: url) -// } -// return nil -// }() -// -// if let url = _firstURL { -// Future { promise in -// LPMetadataProvider().startFetchingMetadata(for: url) { meta, error in -// if let error = error { -// promise(.failure(error)) -// } else { -// promise(.success(meta)) -// } -// } -// } -// .receive(on: RunLoop.main) -// .sink { _ in -// // do nothing -// } receiveValue: { [weak cell] meta in -// guard let meta = meta else { return } -// guard let cell = cell else { return } -// cell.statusView.linkPreview.metadata = meta -// cell.statusView.linkPreview.isHidden = false -// } -// .store(in: &cell.disposeBag) -// } - // set image let imageSize = CGSize( width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale, height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale ) + let url: URL? = { + if UIDevice.current.userInterfaceIdiom == .phone { + return meta.previewURL ?? meta.url + } + return meta.url + }() let request = ImageRequest( - url: meta.url, + url: url, processors: [ ImageProcessors.Resize( size: imageSize, diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index b4f89e71..ebb82655 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -57,6 +57,15 @@ extension ActiveLabel { } +extension ActiveLabel { + func configure(text: String) { + attributedText = nil + activeEntities.removeAll() + self.text = text + accessibilityLabel = text + } +} + extension ActiveLabel { /// status content diff --git a/Mastodon/Extension/UIView.swift b/Mastodon/Extension/UIView.swift index 05940f7b..50dd08f1 100644 --- a/Mastodon/Extension/UIView.swift +++ b/Mastodon/Extension/UIView.swift @@ -7,12 +7,23 @@ import UIKit -// MARK: - Convinience view creation method +// MARK: - Convenience view creation method extension UIView { + + static let separatorColor: UIColor = { + UIColor(dynamicProvider: { collection in + switch collection.userInterfaceStyle { + case .dark: + return Asset.Colors.Background.Cell.separator.color + default: + return .separator + } + }) + }() static var separatorLine: UIView { let line = UIView() - line.backgroundColor = .separator + line.backgroundColor = UIView.separatorColor return line } diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 4740c938..da2eee46 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -32,6 +32,10 @@ internal enum Asset { } internal enum Colors { internal enum Background { + internal enum Cell { + internal static let highlight = ColorAsset(name: "Colors/Background/Cell/highlight") + internal static let separator = ColorAsset(name: "Colors/Background/Cell/separator") + } internal enum Poll { internal static let disabled = ColorAsset(name: "Colors/Background/Poll/disabled") } diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift index 1e52f150..d19463a8 100755 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ b/Mastodon/Helper/MastodonStatusContent.swift @@ -15,7 +15,7 @@ enum MastodonStatusContent { typealias EmojiShortcode = String typealias EmojiDict = [EmojiShortcode: URL] - static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive) + static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive, attributes: .concurrent) static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { return Future { promise in diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 26dcf5cc..cbb93eab 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -16,6 +16,17 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleURLName + org.joinmastodon.app + CFBundleURLSchemes + + mastodon + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift index a9e1898a..5d6e81d5 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDataSourcePrefetching.swift @@ -14,27 +14,27 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { // prefetch reply status guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } let domain = activeMastodonAuthenticationBox.domain - - var statusObjectIDs: [NSManagedObjectID] = [] - for item in items(indexPaths: indexPaths) { - switch item { - case .homeTimelineIndex(let objectID, _): - let homeTimelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex - statusObjectIDs.append(homeTimelineIndex.status.objectID) - case .status(let objectID, _): - statusObjectIDs.append(objectID) - default: - continue - } - } - - let backgroundManagedObjectContext = context.backgroundManagedObjectContext - backgroundManagedObjectContext.perform { [weak self] in + let items = self.items(indexPaths: indexPaths) + + let managedObjectContext = context.managedObjectContext + managedObjectContext.perform { [weak self] in guard let self = self else { return } - for objectID in statusObjectIDs { - let status = backgroundManagedObjectContext.object(with: objectID) as! Status - - // fetch in-reply info if needs + + var statuses: [Status] = [] + for item in items { + switch item { + case .homeTimelineIndex(let objectID, _): + guard let homeTimelineIndex = try? managedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else { continue } + statuses.append(homeTimelineIndex.status) + case .status(let objectID, _): + guard let status = try? managedObjectContext.existingObject(with: objectID) as? Status else { continue } + statuses.append(status) + default: + continue + } + } + + for status in statuses { if let replyToID = status.inReplyToID, status.replyTo == nil { self.context.statusPrefetchingService.prefetchReplyTo( domain: domain, @@ -44,12 +44,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { authorizationBox: activeMastodonAuthenticationBox ) } - -// self.context.statusContentCacheService.prefetch( -// content: (status.reblog ?? status).content, -// emojiDict: (status.reblog ?? status).emojiDict -// ) - } - } - } + } // end for in + } // end context.perform + } // end func } diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift index e6711bb1..eff4ad12 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift @@ -523,32 +523,6 @@ extension StatusProviderFacade { extension StatusProviderFacade { - static func responseToStatusContentWarningRevealAction(dependency: NotificationViewController, cell: UITableViewCell) { - let status = Future { promise in - guard let diffableDataSource = dependency.viewModel.diffableDataSource, - let indexPath = dependency.tableView.indexPath(for: cell), - let item = diffableDataSource.itemIdentifier(for: indexPath) else { - promise(.success(nil)) - return - } - - switch item { - case .notification(let objectID, _): - dependency.viewModel.fetchedResultsController.managedObjectContext.perform { - let notification = dependency.viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! MastodonNotification - promise(.success(notification.status)) - } - default: - promise(.success(nil)) - } - } - - _responseToStatusContentWarningRevealAction( - dependency: dependency, - status: status - ) - } - static func responseToStatusContentWarningRevealAction(provider: StatusProvider, cell: UITableViewCell) { _responseToStatusContentWarningRevealAction( dependency: provider, diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/highlight.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/highlight.colorset/Contents.json new file mode 100644 index 00000000..da756e55 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/highlight.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "214", + "green" : "209", + "red" : "209" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6E", + "green" : "0x57", + "red" : "0x4F" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/separator.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/separator.colorset/Contents.json new file mode 100644 index 00000000..8ae75b24 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/Cell/separator.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.839", + "green" : "0.820", + "red" : "0.820" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.431", + "green" : "0.341", + "red" : "0.310" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json index acd80352..69752587 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "67", - "green" : "53", - "red" : "49" + "blue" : "0x43", + "green" : "0x35", + "red" : "0x31" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json index 01c3e3ff..f09411b7 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "55", - "green" : "44", - "red" : "40" + "blue" : "0x37", + "green" : "0x2C", + "red" : "0x28" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json index dd6cbfd9..147cca83 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "55", - "green" : "44", - "red" : "40" + "blue" : "0x37", + "green" : "0x2C", + "red" : "0x28" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json index 9fa2b261..b769998d 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x3C", - "green" : "0x3A", - "red" : "0x3A" + "blue" : "0.216", + "green" : "0.173", + "red" : "0.157" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json index 5da572b1..06932c34 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x3C", - "green" : "0x3A", - "red" : "0x3A" + "blue" : "0.263", + "green" : "0.208", + "red" : "0.192" } }, "idiom" : "universal" diff --git a/Mastodon/Scene/Compose/AutoComplete/Cell/AutoCompleteTableViewCell.swift b/Mastodon/Scene/Compose/AutoComplete/Cell/AutoCompleteTableViewCell.swift index f629177d..f150dd75 100644 --- a/Mastodon/Scene/Compose/AutoComplete/Cell/AutoCompleteTableViewCell.swift +++ b/Mastodon/Scene/Compose/AutoComplete/Cell/AutoCompleteTableViewCell.swift @@ -12,7 +12,7 @@ final class AutoCompleteTableViewCell: UITableViewCell { static let avatarImageSize = CGSize(width: 42, height: 42) static let avatarImageCornerRadius: CGFloat = 4 static let avatarToLabelSpacing: CGFloat = 12 - + let containerStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal @@ -47,12 +47,6 @@ final class AutoCompleteTableViewCell: UITableViewCell { let separatorLine = UIView.separatorLine - override func prepareForReuse() { - super.prepareForReuse() - avatarImageView.af.cancelImageRequest() - avatarImageView.kf.cancelDownloadTask() - } - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) _init() @@ -80,6 +74,11 @@ extension AutoCompleteTableViewCell { private func _init() { backgroundColor = .clear + selectedBackgroundView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.Cell.highlight.color + return view + }() let topPaddingView = UIView() let bottomPaddingView = UIView() diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index d3b83f98..2d0c7f4c 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -9,12 +9,11 @@ import os.log import UIKit import Combine import PhotosUI -import Kingfisher import MastodonSDK -import TwitterTextEditor import MetaTextView import MastodonMeta import Meta +import Nuke final class ComposeViewController: UIViewController, NeedsDependency { @@ -788,147 +787,6 @@ extension ComposeViewController: UITextViewDelegate { } -// MARK: - TextEditorViewTextAttributesDelegate -extension ComposeViewController: TextEditorViewTextAttributesDelegate { - - func textEditorView( - _ textEditorView: TextEditorView, - updateAttributedString attributedString: NSAttributedString, - completion: @escaping (NSAttributedString?) -> Void - ) { - // 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) - - let stringRange = NSRange(location: 0, length: string.length) - let highlightMatches = string.matches(pattern: MastodonRegex.highlightPattern) - let emojiMatches = string.matches(pattern: MastodonRegex.emojiPattern) - // only accept http/https scheme - let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") - - DispatchQueue.main.async { [weak self] in - guard let self = self else { - completion(nil) - return - } - let customEmojiViewModel = self.viewModel.customEmojiViewModel.value - for view in self.suffixedAttachmentViews { - view.removeFromSuperview() - } - self.suffixedAttachmentViews.removeAll() - - // set normal appearance - let attributedString = NSMutableAttributedString(attributedString: attributedString) - attributedString.removeAttribute(.suffixedAttachment, range: stringRange) - attributedString.removeAttribute(.underlineStyle, range: stringRange) - attributedString.addAttribute(.foregroundColor, value: Asset.Colors.Label.primary.color, range: stringRange) - attributedString.addAttribute(.font, value: UIFont.preferredFont(forTextStyle: .body), range: stringRange) - - // hashtag - for match in highlightMatches { - // set highlight - var attributes = [NSAttributedString.Key: Any]() - attributes[.foregroundColor] = Asset.Colors.brandBlue.color - - // See `traitCollectionDidChange(_:)` - // set accessibility - if #available(iOS 13.0, *) { - switch self.traitCollection.accessibilityContrast { - case .high: - attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue - default: - break - } - } - attributedString.addAttributes(attributes, range: match.range) - } - - // emoji - if let customEmojiViewModel = customEmojiViewModel, !customEmojiViewModel.emojiDict.value.isEmpty { - for match in emojiMatches { - guard let name = string.substring(with: match, at: 2) else { continue } - guard let emoji = customEmojiViewModel.emoji(shortcode: name) else { continue } - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name) - - // set emoji token invisible (without upper bounce space) - var attributes = [NSAttributedString.Key: Any]() - attributes[.font] = UIFont.systemFont(ofSize: 0.01) - attributedString.addAttributes(attributes, range: match.range) - - // append emoji attachment - let imageViewSize = CGSize(width: 20, height: 20) - let imageView = UIImageView(frame: CGRect(origin: .zero, size: imageViewSize)) - textEditorView.textContentView.addSubview(imageView) - self.suffixedAttachmentViews.append(imageView) - let processor = DownsamplingImageProcessor(size: imageViewSize) - imageView.kf.setImage( - with: URL(string: emoji.url), - placeholder: UIImage.placeholder(size: imageViewSize, color: .systemFill), - options: [ - .processor(processor), - .scaleFactor(textEditorView.traitCollection.displayScale), - ], completionHandler: nil - ) - let layoutInTextContainer = { [weak textEditorView] (view: UIView, frame: CGRect) in - // `textEditorView` retains `textStorage`, which retains this block as a part of attributes. - guard let textEditorView = textEditorView else { - return - } - let insets = textEditorView.textContentInsets - view.frame = frame.offsetBy(dx: insets.left, dy: insets.top) - } - let attachment = TextAttributes.SuffixedAttachment( - size: imageViewSize, - attachment: .view(view: imageView, layoutInTextContainer: layoutInTextContainer) - ) - let index = match.range.upperBound - 1 - attributedString.addAttribute( - .suffixedAttachment, - value: attachment, - range: NSRange(location: index, length: 1) - ) - } - } - - // url - for match in urlMatches { - guard let name = string.substring(with: match, at: 0) else { continue } - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name) - - // set highlight - var attributes = [NSAttributedString.Key: Any]() - attributes[.foregroundColor] = Asset.Colors.brandBlue.color - - // See `traitCollectionDidChange(_:)` - // set accessibility - if #available(iOS 13.0, *) { - switch self.traitCollection.accessibilityContrast { - case .high: - attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue - default: - break - } - } - attributedString.addAttributes(attributes, range: match.range) - } - - if string.count > ComposeViewModel.composeContentLimit { - var attributes = [NSAttributedString.Key: Any]() - attributes[.foregroundColor] = Asset.Colors.danger.color - let boundStart = string.index(string.startIndex, offsetBy: ComposeViewModel.composeContentLimit) - let boundEnd = string.endIndex - let range = boundStart.., NeedsDependency, MediaPreviewableViewController { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } @@ -107,7 +103,6 @@ extension AsyncHomeTimelineViewController { #if DEBUG // long press to trigger debug menu settingBarButtonItem.menu = debugMenu - PerformanceMonitor.shared().delegate = self #else settingBarButtonItem.target = self settingBarButtonItem.action = #selector(AsyncHomeTimelineViewController.settingBarButtonItemPressed(_:)) @@ -548,13 +543,6 @@ extension AsyncHomeTimelineViewController: StatusTableViewControllerNavigateable } } -#if DEBUG -extension AsyncHomeTimelineViewController: PerformanceMonitorDelegate { - func performanceMonitor(didReport performanceReport: PerformanceReport) { - // print(performanceReport) - } -} -#endif // MARK: - ASTableDelegate extension AsyncHomeTimelineViewController: ASTableDelegate { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index fdbbfba9..d22c52be 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -77,7 +77,12 @@ final class HomeTimelineViewModel: NSObject { let fetchRequest = HomeTimelineIndex.sortedFetchRequest fetchRequest.fetchBatchSize = 20 fetchRequest.returnsObjectsAsFaults = false - fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(HomeTimelineIndex.status)] + fetchRequest.relationshipKeyPathsForPrefetching = [ + #keyPath(HomeTimelineIndex.status), + #keyPath(HomeTimelineIndex.status.author), + #keyPath(HomeTimelineIndex.status.reblog), + #keyPath(HomeTimelineIndex.status.reblog.author), + ] let controller = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: context.managedObjectContext, diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift index c9cbf744..a0afe1cc 100644 --- a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift +++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift @@ -21,7 +21,7 @@ final class MediaPreviewViewController: UIViewController, NeedsDependency { var viewModel: MediaPreviewViewModel! let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) - let pagingViewConttroller = MediaPreviewPagingViewController() + let pagingViewController = MediaPreviewPagingViewController() let closeButtonBackground: UIVisualEffectView = { let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)) @@ -57,16 +57,16 @@ extension MediaPreviewViewController { visualEffectView.frame = view.bounds view.addSubview(visualEffectView) - pagingViewConttroller.view.translatesAutoresizingMaskIntoConstraints = false - addChild(pagingViewConttroller) - visualEffectView.contentView.addSubview(pagingViewConttroller.view) + pagingViewController.view.translatesAutoresizingMaskIntoConstraints = false + addChild(pagingViewController) + visualEffectView.contentView.addSubview(pagingViewController.view) NSLayoutConstraint.activate([ - visualEffectView.topAnchor.constraint(equalTo: pagingViewConttroller.view.topAnchor), - visualEffectView.bottomAnchor.constraint(equalTo: pagingViewConttroller.view.bottomAnchor), - visualEffectView.leadingAnchor.constraint(equalTo: pagingViewConttroller.view.leadingAnchor), - visualEffectView.trailingAnchor.constraint(equalTo: pagingViewConttroller.view.trailingAnchor), + visualEffectView.topAnchor.constraint(equalTo: pagingViewController.view.topAnchor), + visualEffectView.bottomAnchor.constraint(equalTo: pagingViewController.view.bottomAnchor), + visualEffectView.leadingAnchor.constraint(equalTo: pagingViewController.view.leadingAnchor), + visualEffectView.trailingAnchor.constraint(equalTo: pagingViewController.view.trailingAnchor), ]) - pagingViewConttroller.didMove(toParent: self) + pagingViewController.didMove(toParent: self) closeButtonBackground.translatesAutoresizingMaskIntoConstraints = false view.addSubview(closeButtonBackground) @@ -90,9 +90,9 @@ extension MediaPreviewViewController { viewModel.mediaPreviewImageViewControllerDelegate = self - pagingViewConttroller.interPageSpacing = 10 - pagingViewConttroller.delegate = self - pagingViewConttroller.dataSource = viewModel + pagingViewController.interPageSpacing = 10 + pagingViewController.delegate = self + pagingViewController.dataSource = viewModel closeButton.addTarget(self, action: #selector(MediaPreviewViewController.closeButtonPressed(_:)), for: .touchUpInside) @@ -128,8 +128,8 @@ extension MediaPreviewViewController { // MARK: - MediaPreviewingViewController extension MediaPreviewViewController: MediaPreviewingViewController { - func isInteractiveDismissable() -> Bool { - if let mediaPreviewImageViewController = pagingViewConttroller.currentViewController as? MediaPreviewImageViewController { + func isInteractiveDismissible() -> Bool { + if let mediaPreviewImageViewController = pagingViewController.currentViewController as? MediaPreviewImageViewController { let previewImageView = mediaPreviewImageViewController.previewImageView // TODO: allow zooming pan dismiss guard previewImageView.zoomScale == previewImageView.minimumZoomScale else { @@ -138,12 +138,12 @@ extension MediaPreviewViewController: MediaPreviewingViewController { let safeAreaInsets = previewImageView.safeAreaInsets let statusBarFrameHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0 - let dismissable = previewImageView.contentOffset.y <= -(safeAreaInsets.top - statusBarFrameHeight) - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: dismissable %s", ((#file as NSString).lastPathComponent), #line, #function, dismissable ? "true" : "false") - return dismissable + let dismissible = previewImageView.contentOffset.y <= -(safeAreaInsets.top - statusBarFrameHeight) + 3 // add 3pt tolerance + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: dismissible %s", ((#file as NSString).lastPathComponent), #line, #function, dismissible ? "true" : "false") + return dismissible } - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: dismissable false", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: dismissible false", ((#file as NSString).lastPathComponent), #line, #function) return false } @@ -303,11 +303,11 @@ extension MediaPreviewViewController { } @objc private func showNextKeyCommandHandler(_ sender: UIKeyCommand) { - pagingViewConttroller.scrollToPage(.next, animated: true, completion: nil) + pagingViewController.scrollToPage(.next, animated: true, completion: nil) } @objc private func showPreviousKeyCommandHandler(_ sender: UIKeyCommand) { - pagingViewConttroller.scrollToPage(.previous, animated: true, completion: nil) + pagingViewController.scrollToPage(.previous, animated: true, completion: nil) } } diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift index aa11e494..05f2ce70 100644 --- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift +++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageView.swift @@ -8,11 +8,12 @@ import os.log import func AVFoundation.AVMakeRect import UIKit +import FLAnimatedImage final class MediaPreviewImageView: UIScrollView { - let imageView: UIImageView = { - let imageView = UIImageView() + let imageView: FLAnimatedImageView = { + let imageView = FLAnimatedImageView() imageView.contentMode = .scaleAspectFit imageView.clipsToBounds = true imageView.isUserInteractionEnabled = true @@ -120,7 +121,9 @@ extension MediaPreviewImageView { } }() imageView.frame = CGRect(origin: .zero, size: imageViewSize) - imageView.image = image + if imageView.image == nil { + imageView.image = image + } contentSize = imageViewSize contentInset = imageContentInset diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift index e1f2736f..1bc8fdb2 100644 --- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift +++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift @@ -8,6 +8,7 @@ import os.log import UIKit import Combine +import Nuke protocol MediaPreviewImageViewControllerDelegate: AnyObject { func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer) @@ -68,7 +69,26 @@ extension MediaPreviewImageViewController { let previewImageViewContextMenuInteraction = UIContextMenuInteraction(delegate: self) previewImageView.addInteraction(previewImageViewContextMenuInteraction) - + +// switch viewModel.item { +// case .local(let meta): +// self.previewImageView.imageView.image = meta.image +// self.previewImageView.setup(image: meta.image, container: self.previewImageView, forceUpdate: true) +// self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText +// case .status(let meta): +// Nuke.loadImage( +// with: meta.url, +// into: self.previewImageView.imageView +// ) { result in +// switch result { +// case .failure(let error): +// break +// case .success(let response): +// self.previewImageView.setup(image: response.image, container: self.previewImageView, forceUpdate: true) +// self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText +// } +// } +// } viewModel.image .receive(on: RunLoop.main) // use RunLoop prevent set image during zooming (TODO: handle transitioning state) .sink { [weak self] image in diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift index c9afac8c..a6163a8c 100644 --- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift +++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewModel.swift @@ -8,9 +8,11 @@ import os.log import UIKit import Combine -import AlamofireImage +import Nuke class MediaPreviewImageViewModel { + + var disposeBag = Set() // input let item: ImagePreviewItem @@ -25,16 +27,20 @@ class MediaPreviewImageViewModel { self.altText = meta.altText let url = meta.url - ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in - guard let self = self else { return } - switch response.result { - case .failure(let error): - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription) - case .success(let image): - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription) - self.image.value = image + + ImagePipeline.shared.imagePublisher(with: url) + .sink { completion in + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription) + case .finished: + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription) + } + } receiveValue: { [weak self] response in + guard let self = self else { return } + self.image.value = response.image } - }) + .store(in: &disposeBag) } init(meta: LocalImagePreviewMeta) { diff --git a/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift b/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift new file mode 100644 index 00000000..486d1883 --- /dev/null +++ b/Mastodon/Scene/Notification/NotificationViewController+StatusProvider.swift @@ -0,0 +1,65 @@ +// +// NotificationViewController+StatusProvider.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-1. +// + +import UIKit +import Combine +import CoreData +import CoreDataStack + +extension NotificationViewController: StatusProvider { + func status() -> Future { + return Future { promise in + promise(.success(nil)) + } + } + + func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { + return Future { promise in + guard let cell = cell, + let diffableDataSource = self.viewModel.diffableDataSource, + let indexPath = self.tableView.indexPath(for: cell), + let item = diffableDataSource.itemIdentifier(for: indexPath) else { + promise(.success(nil)) + return + } + + switch item { + case .notification(let objectID, _): + self.viewModel.fetchedResultsController.managedObjectContext.perform { + let notification = self.viewModel.fetchedResultsController.managedObjectContext.object(with: objectID) as! MastodonNotification + promise(.success(notification.status)) + } + default: + promise(.success(nil)) + } + } + } + + func status(for cell: UICollectionViewCell) -> Future { + return Future { promise in + promise(.success(nil)) + } + } + + var managedObjectContext: NSManagedObjectContext { + viewModel.fetchedResultsController.managedObjectContext + } + + var tableViewDiffableDataSource: UITableViewDiffableDataSource? { + return nil + } + + func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item? { + return nil + } + + func items(indexPaths: [IndexPath]) -> [Item] { + return [] + } + + +} diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 6004c48d..200ea2f2 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -12,6 +12,8 @@ import GameplayKit import MastodonSDK import OSLog import UIKit +import Meta +import MetaTextView final class NotificationViewController: UIViewController, NeedsDependency { weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } @@ -141,10 +143,9 @@ extension NotificationViewController { tableView.deselectRow(with: transitionCoordinator, animated: animated) - // fetch latest if has unread push notification - if context.notificationService.hasUnreadPushNotification.value { - viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self) - } + // fetch latest notification when will appear + viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self) + // needs trigger manually after onboarding dismiss setNeedsStatusBarAppearanceUpdate() @@ -191,33 +192,34 @@ extension NotificationViewController { extension NotificationViewController: StatusTableViewControllerAspect { } // MARK: - TableViewCellHeightCacheableContainer -extension NotificationViewController: TableViewCellHeightCacheableContainer { - var cellFrameCache: NSCache { - viewModel.cellFrameCache - } - - func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - let key = item.hashValue - let frame = cell.frame - viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key)) - } +//extension NotificationViewController: TableViewCellHeightCacheableContainer { +// var cellFrameCache: NSCache { +// viewModel.cellFrameCache +// } +// +// func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { +// guard let diffableDataSource = viewModel.diffableDataSource else { return } +// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } +// let key = item.hashValue +// let frame = cell.frame +// viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key)) +// } +// +// func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { +// guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension } +// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension } +// guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else { +// if case .bottomLoader = item { +// return TimelineLoaderTableViewCell.cellHeight +// } else { +// return UITableView.automaticDimension +// } +// } +// +// return ceil(frame.height) +// } +//} - func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension } - guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else { - if case .bottomLoader = item { - return TimelineLoaderTableViewCell.cellHeight - } else { - return UITableView.automaticDimension - } - } - - return ceil(frame.height) - } -} // MARK: - UITableViewDelegate extension NotificationViewController: UITableViewDelegate { @@ -271,6 +273,7 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl // MARK: - NotificationTableViewCellDelegate extension NotificationViewController: NotificationTableViewCellDelegate { + func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) { viewModel.acceptFollowRequest(notification: notification) } @@ -286,20 +289,31 @@ extension NotificationViewController: NotificationTableViewCellDelegate { } } + func userNameLabelDidPressed(notification: MastodonNotification) { + let viewModel = CachedProfileViewModel(context: context, mastodonUser: notification.account) + DispatchQueue.main.async { + self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) + } + } + func parent() -> UIViewController { self } func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) { - StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) } func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { - StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) } func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { - StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) + } + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { + StatusProviderFacade.responseToStatusMetaTextAction(provider: self, cell: cell, metaText: metaText, didSelectMeta: meta) } } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 3fb93d89..83fa5009 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -11,6 +11,8 @@ import UIKit import ActiveLabel import MetaTextView import Meta +import FLAnimatedImage +import Nuke final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { static let actionImageBorderWidth: CGFloat = 2 @@ -18,9 +20,10 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { var disposeBag = Set() var pollCountdownSubscription: AnyCancellable? var delegate: NotificationTableViewCellDelegate? - + + var avatarImageViewTask: ImageTask? let avatarImageView: UIImageView = { - let imageView = UIImageView() + let imageView = FLAnimatedImageView() imageView.layer.cornerRadius = 4 imageView.layer.cornerCurve = .continuous imageView.clipsToBounds = true @@ -57,8 +60,8 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { return label }() - let nameLabel: UILabel = { - let label = UILabel() + let nameLabel: ActiveLabel = { + let label = ActiveLabel(style: .statusName) label.textColor = Asset.Colors.brandBlue.color label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20) label.lineBreakMode = .byTruncatingTail @@ -88,12 +91,12 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { override func prepareForReuse() { super.prepareForReuse() - avatarImageView.af.cancelImageRequest() + avatarImageViewTask?.cancel() + avatarImageViewTask = nil statusView.updateContentWarningDisplay(isHidden: true, animated: false) statusView.pollTableView.dataSource = nil statusView.playerContainerView.reset() statusView.playerContainerView.isHidden = true - disposeBag.removeAll() } @@ -112,7 +115,11 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { extension NotificationStatusTableViewCell { func configure() { backgroundColor = Asset.Colors.Background.systemBackground.color - + selectedBackgroundView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.Cell.highlight.color + return view + }() let containerStackView = UIStackView() containerStackView.axis = .horizontal containerStackView.alignment = .top @@ -259,7 +266,7 @@ extension NotificationStatusTableViewCell: StatusViewDelegate { } func statusView(_ statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { - // do nothing + delegate?.notificationStatusTableViewCell(self, statusView: statusView, metaText: metaText, didSelectMeta: meta) } } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift index fc69155b..30b4ce1b 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift @@ -9,6 +9,11 @@ import Combine import CoreDataStack import Foundation import UIKit +import Meta +import MetaTextView +import ActiveLabel +import FLAnimatedImage +import Nuke protocol NotificationTableViewCellDelegate: AnyObject { var context: AppContext! { get } @@ -16,13 +21,14 @@ protocol NotificationTableViewCellDelegate: AnyObject { func parent() -> UIViewController func userAvatarDidPressed(notification: MastodonNotification) + func userNameLabelDidPressed(notification: MastodonNotification) func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) - + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) + func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) - func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) } @@ -33,9 +39,10 @@ final class NotificationTableViewCell: UITableViewCell { var disposeBag = Set() var delegate: NotificationTableViewCellDelegate? - - let avatatImageView: UIImageView = { - let imageView = UIImageView() + + var avatarImageViewTask: ImageTask? + let avatarImageView: UIImageView = { + let imageView = FLAnimatedImageView() imageView.layer.cornerRadius = 4 imageView.layer.cornerCurve = .continuous imageView.clipsToBounds = true @@ -72,8 +79,8 @@ final class NotificationTableViewCell: UITableViewCell { return label }() - let nameLabel: UILabel = { - let label = UILabel() + let nameLabel: ActiveLabel = { + let label = ActiveLabel() label.textColor = Asset.Colors.brandBlue.color label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20) label.lineBreakMode = .byTruncatingTail @@ -108,7 +115,8 @@ final class NotificationTableViewCell: UITableViewCell { override func prepareForReuse() { super.prepareForReuse() - avatatImageView.af.cancelImageRequest() + avatarImageViewTask?.cancel() + avatarImageViewTask = nil disposeBag.removeAll() } @@ -126,6 +134,11 @@ final class NotificationTableViewCell: UITableViewCell { extension NotificationTableViewCell { func configure() { backgroundColor = Asset.Colors.Background.systemBackground.color + selectedBackgroundView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.Cell.highlight.color + return view + }() let containerStackView = UIStackView() containerStackView.axis = .vertical @@ -153,13 +166,13 @@ extension NotificationTableViewCell { 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) diff --git a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift index 2885fe7e..9e9235ba 100644 --- a/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift +++ b/Mastodon/Scene/Onboarding/ConfirmEmail/MastodonConfirmEmailViewController.swift @@ -171,7 +171,7 @@ extension MastodonConfirmEmailViewController { func showEmailAppAlert() { let clients = ThirdPartyMailClient.clients() let application = UIApplication.shared - let avaliableClients = clients.filter { client -> Bool in + let availableClients = clients.filter { client -> Bool in ThirdPartyMailer.application(application, isMailClientAvailable: client) } let alertController = UIAlertController(title: L10n.Scene.ConfirmEmail.OpenEmailApp.openEmailClient, message: nil, preferredStyle: .alert) @@ -180,9 +180,9 @@ extension MastodonConfirmEmailViewController { UIApplication.shared.open(URL(string: "message://")!, options: [:], completionHandler: nil) } alertController.addAction(alertAction) - _ = avaliableClients.compactMap { client -> UIAlertAction in + _ = availableClients.compactMap { client -> UIAlertAction in let alertAction = UIAlertAction(title: client.name, style: .default) { _ in - _ = ThirdPartyMailer.application(application, openMailClient: client, recipient: nil, subject: nil, body: nil) + _ = ThirdPartyMailer.application(application, openMailClient: client) } alertController.addAction(alertAction) return alertAction diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index abeac985..ad9c3dbc 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -86,7 +86,11 @@ final class ProfileHeaderView: UIView { } static let avatarImageViewOverlayBlurEffect = UIBlurEffect(style: .systemUltraThinMaterialDark) - let avatarImageViewOverlayVisualEffectView = UIVisualEffectView(effect: nil) + let avatarImageViewOverlayVisualEffectView: UIVisualEffectView = { + let visualEffectView = UIVisualEffectView(effect: nil) + visualEffectView.isUserInteractionEnabled = false + return visualEffectView + }() let editAvatarBackgroundView: UIView = { let view = UIView() @@ -343,10 +347,9 @@ extension ProfileHeaderView { nameMetaText.textView.translatesAutoresizingMaskIntoConstraints = false displayNameStackView.addSubview(nameMetaText.textView) NSLayoutConstraint.activate([ - nameMetaText.textView.topAnchor.constraint(equalTo: nameTextField.topAnchor), + nameMetaText.textView.centerYAnchor.constraint(equalTo: nameTextField.centerYAnchor), nameMetaText.textView.leadingAnchor.constraint(equalTo: nameTextField.leadingAnchor), nameMetaText.textView.trailingAnchor.constraint(equalTo: nameTextField.trailingAnchor), - nameMetaText.textView.bottomAnchor.constraint(equalTo: nameTextField.bottomAnchor), ]) nameContainerStackView.addArrangedSubview(displayNameStackView) diff --git a/Mastodon/Scene/Search/SearchViewController.swift b/Mastodon/Scene/Search/SearchViewController.swift index 9a256fb5..51b88e12 100644 --- a/Mastodon/Scene/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/SearchViewController.swift @@ -103,6 +103,7 @@ final class SearchViewController: UIViewController, NeedsDependency { tableView.rowHeight = UITableView.automaticDimension tableView.separatorStyle = .singleLine tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + tableView.separatorColor = UIView.separatorColor return tableView }() diff --git a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift index 2a5ba492..28b13e2f 100644 --- a/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift +++ b/Mastodon/Scene/Share/ContextMenu/ImagePreview/ContextMenuImagePreviewViewController.swift @@ -8,19 +8,27 @@ import func AVFoundation.AVMakeRect import UIKit import Combine +import Nuke +import FLAnimatedImage final class ContextMenuImagePreviewViewController: UIViewController { var disposeBag = Set() var viewModel: ContextMenuImagePreviewViewModel! - + + var imageTask: ImageTask? let imageView: UIImageView = { - let imageView = UIImageView() + let imageView = FLAnimatedImageView() imageView.contentMode = .scaleAspectFill imageView.layer.masksToBounds = true return imageView }() + + deinit { + imageTask?.cancel() + imageTask = nil + } } @@ -47,12 +55,13 @@ extension ContextMenuImagePreviewViewController { .sink { [weak self] url in guard let self = self else { return } guard let url = url else { return } - self.imageView.af.setImage( - withURL: url, - placeholderImage: self.viewModel.thumbnail, - imageTransition: .crossDissolve(0.2), - runImageTransitionIfCached: true, - completion: nil + self.imageTask = Nuke.loadImage( + with: url, + options: ImageLoadingOptions( + placeholder: self.viewModel.thumbnail, + transition: .fadeIn(duration: 0.2) + ), + into: self.imageView ) } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 2b51f028..7ed828fa 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -95,7 +95,12 @@ final class StatusView: UIView { view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile return view }() - let avatarImageView: UIImageView = FLAnimatedImageView() + let avatarImageView: UIImageView = { + let imageView = FLAnimatedImageView() + imageView.layer.shouldRasterize = true + imageView.layer.rasterizationScale = UIScreen.main.scale + return imageView + }() let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() let nameLabel: ActiveLabel = { @@ -217,6 +222,7 @@ final class StatusView: UIView { metaText.textView.textContainer.lineFragmentPadding = 0 metaText.textView.textContainerInset = .zero metaText.textView.layer.masksToBounds = false + let paragraphStyle: NSMutableParagraphStyle = { let style = NSMutableParagraphStyle() style.lineSpacing = 5 @@ -234,7 +240,7 @@ final class StatusView: UIView { ] return metaText }() - + private let headerInfoLabelTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer var isRevealing = true @@ -400,11 +406,6 @@ extension StatusView { statusContainerStackView.addArrangedSubview(contentMetaText.textView) contentMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) - // TODO: - // link preview - // statusContainerStackView.addArrangedSubview(linkPreview) - // linkPreview.setContentHuggingPriority(.defaultHigh, for: .vertical) - // image statusContainerStackView.addArrangedSubview(statusMosaicImageViewContainer) diff --git a/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift b/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift index 965d710d..d16a952f 100644 --- a/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift +++ b/Mastodon/Scene/Share/View/Control/AvatarStackContainerButton.swift @@ -25,7 +25,7 @@ final class AvatarStackContainerButton: UIControl { static let maskOffset: CGFloat = 2 // UIControl.Event - Application: 0x0F000000 - static let primaryAction = UIControl.Event(rawValue: 1 << 25) // 0x01000000 + static let primaryAction = UIControl.Event(rawValue: 1 << 25) // 0x01000000 var primaryActionState: UIControl.State = .normal let topLeadingAvatarStackedImageView = AvatarStackedImageView() @@ -46,6 +46,12 @@ final class AvatarStackContainerButton: UIControl { extension AvatarStackContainerButton { private func _init() { + topLeadingAvatarStackedImageView.layer.shouldRasterize = true + topLeadingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale + + bottomTrailingAvatarStackedImageView.layer.shouldRasterize = true + bottomTrailingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale + topLeadingAvatarStackedImageView.translatesAutoresizingMaskIntoConstraints = false addSubview(topLeadingAvatarStackedImageView) NSLayoutConstraint.activate([ diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index ceb211a7..8656a2b6 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -74,6 +74,7 @@ final class StatusTableViewCell: UITableViewCell, StatusCell { override func prepareForReuse() { super.prepareForReuse() selectionStyle = .default + statusView.statusMosaicImageViewContainer.reset() statusView.contentMetaText.textView.isSelectable = false statusView.updateContentWarningDisplay(isHidden: true, animated: false) statusView.statusMosaicImageViewContainer.contentWarningOverlayView.isUserInteractionEnabled = true @@ -103,7 +104,12 @@ extension StatusTableViewCell { private func _init() { backgroundColor = Asset.Colors.Background.systemBackground.color - + selectedBackgroundView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.Cell.highlight.color + return view + }() + statusView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(statusView) NSLayoutConstraint.activate([ diff --git a/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift b/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift index 265ce245..5ceb8781 100644 --- a/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift +++ b/Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift @@ -23,7 +23,7 @@ struct MosaicImageViewModel { continue } let mosaicMeta = MosaicMeta( - priviewURL: element.previewURL.flatMap { URL(string: $0) }, + previewURL: element.previewURL.flatMap { URL(string: $0) }, url: url, size: CGSize(width: width, height: height), blurhash: element.blurhash, @@ -39,7 +39,7 @@ struct MosaicImageViewModel { struct MosaicMeta { static let edgeMaxLength: CGFloat = 20 - let priviewURL: URL? + let previewURL: URL? let url: URL let size: CGSize let blurhash: String? diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift index 50a518dc..2daeba04 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift @@ -56,7 +56,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { toView.alpha = 0 transitionContext.containerView.addSubview(toView) // set to image hidden - toVC.pagingViewConttroller.view.alpha = 0 + toVC.pagingViewController.view.alpha = 0 // set from image hidden. update hidden when paging. seealso: `MediaPreviewViewController` transitionItem.source.updateAppearance(position: .start, index: toVC.viewModel.currentPage.value) @@ -98,7 +98,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { } animator.addCompletion { position in - toVC.pagingViewConttroller.view.alpha = 1 + toVC.pagingViewController.view.alpha = 1 transitionImageView.removeFromSuperview() UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { toVC.closeButtonBackground.alpha = 1 @@ -112,8 +112,8 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { private func popTransition(using transitionContext: UIViewControllerContextTransitioning, curve: UIView.AnimationCurve = .easeInOut) -> UIViewPropertyAnimator { guard let fromVC = transitionContext.viewController(forKey: .from) as? MediaPreviewViewController, let fromView = transitionContext.view(forKey: .from), - let mediaPreviewImageViewController = fromVC.pagingViewConttroller.currentViewController as? MediaPreviewImageViewController, - let index = fromVC.pagingViewConttroller.currentIndex else { + let mediaPreviewImageViewController = fromVC.pagingViewController.currentViewController as? MediaPreviewImageViewController, + let index = fromVC.pagingViewController.currentIndex else { fatalError() } @@ -154,7 +154,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionItem.targetFrame = targetFrame // disable interaction - fromVC.pagingViewConttroller.isUserInteractionEnabled = false + fromVC.pagingViewController.isUserInteractionEnabled = false let animator = popInteractiveTransitionAnimator @@ -246,8 +246,8 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { private func popInteractiveTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewController(forKey: .from) as? MediaPreviewViewController, let _ = transitionContext.view(forKey: .from), - let mediaPreviewImageViewController = fromVC.pagingViewConttroller.currentViewController as? MediaPreviewImageViewController, - let index = fromVC.pagingViewConttroller.currentIndex else { + let mediaPreviewImageViewController = fromVC.pagingViewController.currentViewController as? MediaPreviewImageViewController, + let index = fromVC.pagingViewController.currentIndex else { fatalError() } @@ -289,7 +289,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionItem.targetFrame = targetFrame ?? snapshot.frame // disable interaction - fromVC.pagingViewConttroller.isUserInteractionEnabled = false + fromVC.pagingViewController.isUserInteractionEnabled = false let animator = popInteractiveTransitionAnimator @@ -315,7 +315,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { } animator.addCompletion { position in - fromVC.pagingViewConttroller.isUserInteractionEnabled = true + fromVC.pagingViewController.isUserInteractionEnabled = true fromVC.closeButtonBackground.alpha = position == .end ? 0 : 1 self.transitionItem.imageView?.isHidden = position == .end self.transitionItem.snapshotRaw?.alpha = position == .start ? 1.0 : 0.0 diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaPreviewTransitionController.swift b/Mastodon/Scene/Transition/MediaPreview/MediaPreviewTransitionController.swift index 225a8320..bd5781b0 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaPreviewTransitionController.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaPreviewTransitionController.swift @@ -56,7 +56,7 @@ extension MediaPreviewTransitionController: UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer === panGestureRecognizer { guard let mediaPreviewViewController = self.mediaPreviewViewController else { return false } - return mediaPreviewViewController.isInteractiveDismissable() + return mediaPreviewViewController.isInteractiveDismissible() } return false diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaPreviewingViewController.swift b/Mastodon/Scene/Transition/MediaPreview/MediaPreviewingViewController.swift index 9c6e56d0..d74c9979 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaPreviewingViewController.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaPreviewingViewController.swift @@ -8,5 +8,5 @@ import Foundation protocol MediaPreviewingViewController: AnyObject { - func isInteractiveDismissable() -> Bool + func isInteractiveDismissible() -> Bool } diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift index b154d17c..f773115d 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift @@ -8,7 +8,6 @@ import os.log import Foundation import GameplayKit -import Kingfisher import MastodonSDK extension MastodonAttachmentService { diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift index ede5c64b..cafa3145 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift @@ -9,7 +9,6 @@ import os.log import UIKit import Combine import PhotosUI -import Kingfisher import GameplayKit import MobileCoreServices import MastodonSDK diff --git a/Mastodon/State/AppContext.swift b/Mastodon/State/AppContext.swift index ef758951..f1de6dd7 100644 --- a/Mastodon/State/AppContext.swift +++ b/Mastodon/State/AppContext.swift @@ -11,7 +11,6 @@ import Combine import CoreData import CoreDataStack import AlamofireImage -import Kingfisher class AppContext: ObservableObject { @@ -124,7 +123,6 @@ extension AppContext { func purgeCache() -> AnyPublisher { Publishers.MergeMany([ AppContext.purgeAlamofireImageCache(), - AppContext.purgeKingfisherCache(), AppContext.purgeTemporaryDirectory(), ]) .reduce(0, +) @@ -146,29 +144,6 @@ extension AppContext { .eraseToAnyPublisher() } - private static func purgeKingfisherCache() -> AnyPublisher { - Future { promise in - KingfisherManager.shared.cache.calculateDiskStorageSize { result in - switch result { - case .success(let diskBytes): - KingfisherManager.shared.cache.clearCache() - KingfisherManager.shared.cache.calculateDiskStorageSize { currentResult in - switch currentResult { - case .success(let currentDiskBytes): - let purgedDiskBytes = max(0, Int(diskBytes) - Int(currentDiskBytes)) - promise(.success(purgedDiskBytes)) - case .failure: - promise(.success(0)) - } - } - case .failure: - promise(.success(0)) - } - } - } - .eraseToAnyPublisher() - } - private static func purgeTemporaryDirectory() -> AnyPublisher { Future { promise in AppContext.purgeCacheWorkingQueue.async { diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 70d259ca..392d2944 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -56,6 +56,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return true + } + } extension AppDelegate {