From ff2f813280b01927cd0b8374ca63d0fabe4e01cb Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Thu, 22 Oct 2020 15:16:06 -0700 Subject: [PATCH] Media refactoring --- Extensions/AppPreferences+Extensions.swift | 10 --- Metatext.xcodeproj/project.pbxproj | 4 - .../Entities/AppEnvironment.swift | 6 +- .../Utilities/AppPreferences.swift | 6 ++ .../MockAppEnvironment.swift | 1 + System/MetatextApp.swift | 4 +- .../ImagePageViewController.swift | 13 +++- View Controllers/ImageViewController.swift | 76 ++++++++++--------- View Controllers/ProfileViewController.swift | 13 ++++ View Controllers/TableViewController.swift | 5 +- .../Sources/ViewModels/AccountViewModel.swift | 26 +++++-- .../ViewModels/AttachmentViewModel.swift | 23 +++++- .../Sources/ViewModels/ProfileViewModel.swift | 11 +++ .../Sources/ViewModels/StatusViewModel.swift | 15 ++-- Views/AccountHeaderView.swift | 32 +++++--- Views/AccountView.swift | 11 +-- Views/Status/StatusAttachmentsView.swift | 16 +--- Views/Status/StatusView.swift | 10 +-- 18 files changed, 168 insertions(+), 114 deletions(-) delete mode 100644 Extensions/AppPreferences+Extensions.swift diff --git a/Extensions/AppPreferences+Extensions.swift b/Extensions/AppPreferences+Extensions.swift deleted file mode 100644 index 22419ec..0000000 --- a/Extensions/AppPreferences+Extensions.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import UIKit -import ViewModels - -extension AppPreferences { - var shouldReduceMotion: Bool { - UIAccessibility.isReduceMotionEnabled && useSystemReduceMotionForMedia - } -} diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 27f36db..d06dbca 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D5F2540DE3A00B1EBEF /* ZoomTransitionController.swift */; }; D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */; }; D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; }; - D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */; }; D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; }; D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; }; D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8510B25259E56004E0744 /* LoadMoreCell.swift */; }; @@ -131,7 +130,6 @@ D08B8D5F2540DE3A00B1EBEF /* ZoomTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomTransitionController.swift; sourceTree = ""; }; D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomAnimatableView.swift; sourceTree = ""; }; D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; }; - D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = ""; }; D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = ""; }; D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = ""; }; D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = ""; }; @@ -384,7 +382,6 @@ D0C7D46824F76169001EBDBB /* Extensions */ = { isa = PBXGroup; children = ( - D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */, D01C6FAB252024BD003D0300 /* Array+Extensions.swift */, D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */, D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */, @@ -614,7 +611,6 @@ D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */, D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */, D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */, - D0A3C2F725390A9700739F88 /* AppPreferences+Extensions.swift in Sources */, D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */, D01C6FAC252024BD003D0300 /* Array+Extensions.swift in Sources */, D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */, diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift index a170500..dbdcf21 100644 --- a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift @@ -13,6 +13,7 @@ public struct AppEnvironment { let keychain: Keychain.Type let userDefaults: UserDefaults let userNotificationClient: UserNotificationClient + let reduceMotion: () -> Bool let uuid: () -> UUID let inMemoryContent: Bool let fixtureDatabase: IdentityDatabase? @@ -22,6 +23,7 @@ public struct AppEnvironment { keychain: Keychain.Type, userDefaults: UserDefaults, userNotificationClient: UserNotificationClient, + reduceMotion: @escaping () -> Bool, uuid: @escaping () -> UUID, inMemoryContent: Bool, fixtureDatabase: IdentityDatabase?) { @@ -30,6 +32,7 @@ public struct AppEnvironment { self.keychain = keychain self.userDefaults = userDefaults self.userNotificationClient = userNotificationClient + self.reduceMotion = reduceMotion self.uuid = uuid self.inMemoryContent = inMemoryContent self.fixtureDatabase = fixtureDatabase @@ -37,13 +40,14 @@ public struct AppEnvironment { } public extension AppEnvironment { - static func live(userNotificationCenter: UNUserNotificationCenter) -> Self { + static func live(userNotificationCenter: UNUserNotificationCenter, reduceMotion: @escaping () -> Bool) -> Self { Self( session: URLSession.shared, webAuthSessionType: LiveWebAuthSession.self, keychain: LiveKeychain.self, userDefaults: .standard, userNotificationClient: .live(userNotificationCenter), + reduceMotion: reduceMotion, uuid: UUID.init, inMemoryContent: false, fixtureDatabase: nil) diff --git a/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift b/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift index 88554fa..68fbd59 100644 --- a/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift +++ b/ServiceLayer/Sources/ServiceLayer/Utilities/AppPreferences.swift @@ -5,9 +5,11 @@ import Foundation public struct AppPreferences { private let userDefaults: UserDefaults + private let systemReduceMotion: () -> Bool public init(environment: AppEnvironment) { self.userDefaults = environment.userDefaults + self.systemReduceMotion = environment.reduceMotion } } @@ -73,6 +75,10 @@ public extension AppPreferences { } set { self[.autoplayVideos] = newValue.rawValue } } + + var shouldReduceMotion: Bool { + systemReduceMotion() && useSystemReduceMotionForMedia + } } extension AppPreferences { diff --git a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift index 7581086..5b0f746 100644 --- a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift @@ -23,6 +23,7 @@ public extension AppEnvironment { keychain: keychain, userDefaults: userDefaults, userNotificationClient: userNotificationClient, + reduceMotion: { false }, uuid: uuid, inMemoryContent: inMemoryContent, fixtureDatabase: fixtureDatabase) diff --git a/System/MetatextApp.swift b/System/MetatextApp.swift index 5008fa9..9e537c0 100644 --- a/System/MetatextApp.swift +++ b/System/MetatextApp.swift @@ -14,7 +14,9 @@ struct MetatextApp: App { RootView( // swiftlint:disable force_try viewModel: try! RootViewModel( - environment: .live(userNotificationCenter: .current()), + environment: .live( + userNotificationCenter: .current(), + reduceMotion: { UIAccessibility.isReduceMotionEnabled }), registerForRemoteNotifications: appDelegate.registerForRemoteNotifications)) // swiftlint:enable force_try } diff --git a/View Controllers/ImagePageViewController.swift b/View Controllers/ImagePageViewController.swift index a037ec8..8c5f1a7 100644 --- a/View Controllers/ImagePageViewController.swift +++ b/View Controllers/ImagePageViewController.swift @@ -7,7 +7,7 @@ class ImagePageViewController: UIPageViewController { let imageViewControllers: [ImageViewController] init(initiallyVisible: AttachmentViewModel, statusViewModel: StatusViewModel) { - imageViewControllers = statusViewModel.attachmentViewModels.map(ImageViewController.init(viewModel:)) + imageViewControllers = statusViewModel.attachmentViewModels.map { ImageViewController(viewModel: $0) } super.init( transitionStyle: .scroll, @@ -21,6 +21,17 @@ class ImagePageViewController: UIPageViewController { setViewControllers([imageViewControllers[index ?? 0]], direction: .forward, animated: false) } + init(imageURL: URL) { + imageViewControllers = [ImageViewController(imageURL: imageURL)] + + super.init( + transitionStyle: .scroll, + navigationOrientation: .horizontal, + options: [.interPageSpacing: CGFloat.defaultSpacing]) + + setViewControllers(imageViewControllers, direction: .forward, animated: false) + } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") diff --git a/View Controllers/ImageViewController.swift b/View Controllers/ImageViewController.swift index 7550790..b710b5b 100644 --- a/View Controllers/ImageViewController.swift +++ b/View Controllers/ImageViewController.swift @@ -9,13 +9,15 @@ class ImageViewController: UIViewController { let imageView = AnimatedImageView() let playerView = PlayerView() - private let viewModel: AttachmentViewModel + private let viewModel: AttachmentViewModel? + private let imageURL: URL? private let contentView = UIView() private let descriptionBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterial)) private let descriptionTextView = UITextView() - init(viewModel: AttachmentViewModel) { + init(viewModel: AttachmentViewModel? = nil, imageURL: URL? = nil) { self.viewModel = viewModel + self.imageURL = imageURL super.init(nibName: nil, bundle: nil) } @@ -51,21 +53,22 @@ class ImageViewController: UIViewController { contentView.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit + imageView.kf.indicatorType = .activity contentView.addSubview(playerView) playerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(descriptionBackgroundView) descriptionBackgroundView.translatesAutoresizingMaskIntoConstraints = false - descriptionBackgroundView.isHidden = viewModel.attachment.description == nil - || viewModel.attachment.description == "" + descriptionBackgroundView.isHidden = viewModel?.attachment.description == nil + || viewModel?.attachment.description == "" descriptionBackgroundView.contentView.addSubview(descriptionTextView) descriptionTextView.translatesAutoresizingMaskIntoConstraints = false descriptionTextView.backgroundColor = .clear descriptionTextView.font = .preferredFont(forTextStyle: .caption1) descriptionTextView.adjustsFontForContentSizeCategory = true - descriptionTextView.text = viewModel.attachment.description + descriptionTextView.text = viewModel?.attachment.description descriptionTextView.isScrollEnabled = false descriptionTextView.isEditable = false @@ -99,37 +102,40 @@ class ImageViewController: UIViewController { descriptionTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) - switch viewModel.attachment.type { - case .image: - imageView.tag = viewModel.tag + if let viewModel = viewModel { + switch viewModel.attachment.type { + case .image: + imageView.tag = viewModel.tag + playerView.isHidden = true + imageView.kf.setImage( + with: viewModel.attachment.previewUrl, + options: [.onlyFromCache], + completionHandler: { [weak self] in + guard let self = self else { return } + + if case .success = $0 { + self.imageView.kf.indicatorType = .none + } + + self.imageView.kf.setImage( + with: viewModel.attachment.url, + options: [.keepCurrentImageWhileLoading]) + }) + case .gifv: + playerView.tag = viewModel.tag + imageView.isHidden = true + let player = PlayerCache.shared.player(url: viewModel.attachment.url) + + player.isMuted = true + + playerView.player = player + player.play() + default: break + } + } else if let imageURL = imageURL { + imageView.tag = imageURL.hashValue playerView.isHidden = true - imageView.isHidden = false - imageView.kf.indicatorType = .activity - imageView.kf.setImage( - with: viewModel.attachment.previewUrl, - options: [.onlyFromCache], - completionHandler: { [weak self] in - guard let self = self else { return } - - if case .success = $0 { - self.imageView.kf.indicatorType = .none - } - - self.imageView.kf.setImage( - with: self.viewModel.attachment.url, - options: [.keepCurrentImageWhileLoading]) - }) - case .gifv: - playerView.tag = viewModel.tag - playerView.isHidden = false - imageView.isHidden = true - let player = PlayerCache.shared.player(url: viewModel.attachment.url) - - player.isMuted = true - - playerView.player = player - player.play() - default: break + imageView.kf.setImage(with: imageURL) } } } diff --git a/View Controllers/ProfileViewController.swift b/View Controllers/ProfileViewController.swift index 97f220d..88b4f25 100644 --- a/View Controllers/ProfileViewController.swift +++ b/View Controllers/ProfileViewController.swift @@ -30,6 +30,19 @@ final class ProfileViewController: TableViewController { } .store(in: &cancellables) + viewModel.imagePresentations.sink { [weak self] in + guard let self = self else { return } + + let imagePageViewController = ImagePageViewController(imageURL: $0) + let imageNavigationController = ImageNavigationController(imagePageViewController: imagePageViewController) + + imageNavigationController.transitionController.fromDelegate = self + self.transitionViewTag = $0.hashValue + + self.present(imageNavigationController, animated: true) + } + .store(in: &cancellables) + tableView.tableHeaderView = accountHeaderView } } diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index 11f4a92..1fcfef4 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -7,13 +7,14 @@ import SwiftUI import ViewModels class TableViewController: UITableViewController { + var transitionViewTag = -1 + private let viewModel: CollectionViewModel private let identification: Identification private let loadingTableFooterView = LoadingTableFooterView() private let webfingerIndicatorView = WebfingerIndicatorView() private var cancellables = Set() private var cellHeightCaches = [CGFloat: [CollectionItem: CGFloat]]() - private var transitionViewTag = -1 private lazy var dataSource: TableViewDataSource = { .init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:)) @@ -201,7 +202,7 @@ extension TableViewController: ZoomAnimatorDelegate { } func referenceView(for zoomAnimator: ZoomAnimator) -> UIView? { - tableView.visibleCells.compactMap { $0.viewWithTag(transitionViewTag) }.first + view.viewWithTag(transitionViewTag) } func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect? { diff --git a/ViewModels/Sources/ViewModels/AccountViewModel.swift b/ViewModels/Sources/ViewModels/AccountViewModel.swift index 479dc83..39a6224 100644 --- a/ViewModels/Sources/ViewModels/AccountViewModel.swift +++ b/ViewModels/Sources/ViewModels/AccountViewModel.swift @@ -7,9 +7,9 @@ import ServiceLayer public struct AccountViewModel: CollectionItemViewModel { public let events: AnyPublisher, Never> - public let identification: Identification private let accountService: AccountService + private let identification: Identification private let eventsSubject = PassthroughSubject, Never>() init(accountService: AccountService, identification: Identification) { @@ -20,13 +20,13 @@ public struct AccountViewModel: CollectionItemViewModel { } public extension AccountViewModel { - var avatarURL: URL { accountService.account.avatar } - - var avatarStaticURL: URL { accountService.account.avatarStatic } - - var headerURL: URL { accountService.account.header } - - var headerStaticURL: URL { accountService.account.headerStatic } + var headerURL: URL { + if !identification.appPreferences.shouldReduceMotion, identification.appPreferences.animateHeaders { + return accountService.account.header + } else { + return accountService.account.headerStatic + } + } var displayName: String { accountService.account.displayName } @@ -36,6 +36,16 @@ public extension AccountViewModel { var emoji: [Emoji] { accountService.account.emojis } + func avatarURL(profile: Bool = false) -> URL { + if !identification.appPreferences.shouldReduceMotion, + (identification.appPreferences.animateAvatars == .everywhere + || identification.appPreferences.animateAvatars == .profiles && profile) { + return accountService.account.avatar + } else { + return accountService.account.avatarStatic + } + } + func urlSelected(_ url: URL) { eventsSubject.send( accountService.navigationService.item(url: url) diff --git a/ViewModels/Sources/ViewModels/AttachmentViewModel.swift b/ViewModels/Sources/ViewModels/AttachmentViewModel.swift index 55876a6..be44fe8 100644 --- a/ViewModels/Sources/ViewModels/AttachmentViewModel.swift +++ b/ViewModels/Sources/ViewModels/AttachmentViewModel.swift @@ -2,15 +2,18 @@ import Foundation import Mastodon +import Network public struct AttachmentViewModel { public let attachment: Attachment private let status: Status + private let identification: Identification - init(attachment: Attachment, status: Status) { + init(attachment: Attachment, status: Status, identification: Identification) { self.attachment = attachment self.status = status + self.identification = identification } } @@ -33,4 +36,22 @@ public extension AttachmentViewModel { return nil } + + var shouldAutoplay: Bool { + switch attachment.type { + case .video: + return identification.appPreferences.autoplayVideos == .always + || (identification.appPreferences.autoplayVideos == .wifi + && Self.wifiMonitor.currentPath.status == .satisfied) + case .gifv: + return identification.appPreferences.autoplayGIFs == .always + || (identification.appPreferences.autoplayGIFs == .wifi + && Self.wifiMonitor.currentPath.status == .satisfied) + default: return false + } + } +} + +private extension AttachmentViewModel { + static let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi) } diff --git a/ViewModels/Sources/ViewModels/ProfileViewModel.swift b/ViewModels/Sources/ViewModels/ProfileViewModel.swift index c043eb9..b9597c1 100644 --- a/ViewModels/Sources/ViewModels/ProfileViewModel.swift +++ b/ViewModels/Sources/ViewModels/ProfileViewModel.swift @@ -9,13 +9,16 @@ final public class ProfileViewModel { @Published public private(set) var accountViewModel: AccountViewModel? @Published public var collection = ProfileCollection.statuses @Published public var alertItem: AlertItem? + public let imagePresentations: AnyPublisher private let profileService: ProfileService private let collectionViewModel: CurrentValueSubject + private let imagePresentationsSubject = PassthroughSubject() private var cancellables = Set() public init(profileService: ProfileService, identification: Identification) { self.profileService = profileService + imagePresentations = imagePresentationsSubject.eraseToAnyPublisher() collectionViewModel = CurrentValueSubject( CollectionItemsViewModel( @@ -40,6 +43,14 @@ final public class ProfileViewModel { } } +public extension ProfileViewModel { + func presentHeader() { + guard let accountViewModel = accountViewModel else { return } + + imagePresentationsSubject.send(accountViewModel.headerURL) + } +} + extension ProfileViewModel: CollectionViewModel { public var updates: AnyPublisher { collectionViewModel.flatMap(\.updates).eraseToAnyPublisher() diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index 1c3829d..31a1375 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -18,10 +18,10 @@ public struct StatusViewModel: CollectionItemViewModel { public let pollOptionTitles: [String] public let pollEmoji: [Emoji] public var configuration = CollectionItem.StatusConfiguration.default - public let identification: Identification public let events: AnyPublisher, Never> private let statusService: StatusService + private let identification: Identification private let eventsSubject = PassthroughSubject, Never>() init(statusService: StatusService, identification: Identification) { @@ -40,7 +40,7 @@ public struct StatusViewModel: CollectionItemViewModel { : statusService.status.account.displayName rebloggedByDisplayNameEmoji = statusService.status.account.emojis attachmentViewModels = statusService.status.displayStatus.mediaAttachments - .map { AttachmentViewModel(attachment: $0, status: statusService.status) } + .map { AttachmentViewModel(attachment: $0, status: statusService.status, identification: identification) } pollOptionTitles = statusService.status.displayStatus.poll?.options.map { $0.title } ?? [] pollEmoji = statusService.status.displayStatus.poll?.emojis ?? [] events = eventsSubject.eraseToAnyPublisher() @@ -75,9 +75,14 @@ public extension StatusViewModel { var accountName: String { "@" + statusService.status.displayStatus.account.acct } - var avatarURL: URL { statusService.status.displayStatus.account.avatar } - - var avatarStaticURL: URL { statusService.status.displayStatus.account.avatarStatic } + var avatarURL: URL { + if !identification.appPreferences.shouldReduceMotion, + identification.appPreferences.animateAvatars == .everywhere { + return statusService.status.displayStatus.account.avatar + } else { + return statusService.status.displayStatus.account.avatarStatic + } + } var time: String? { statusService.status.displayStatus.createdAt.timeAgo } diff --git a/Views/AccountHeaderView.swift b/Views/AccountHeaderView.swift index 242acf2..0f264c3 100644 --- a/Views/AccountHeaderView.swift +++ b/Views/AccountHeaderView.swift @@ -5,23 +5,16 @@ import UIKit import ViewModels class AccountHeaderView: UIView { - let headerImageView = UIImageView() + let headerImageView = AnimatedImageView() + let headerButton = UIButton() let noteTextView = TouchFallthroughTextView() let segmentedControl = UISegmentedControl() var viewModel: ProfileViewModel? { didSet { if let accountViewModel = viewModel?.accountViewModel { - let appPreferences = accountViewModel.identification.appPreferences - let headerURL: URL - - if !appPreferences.shouldReduceMotion, appPreferences.animateHeaders { - headerURL = accountViewModel.headerURL - } else { - headerURL = accountViewModel.headerStaticURL - } - - headerImageView.kf.setImage(with: headerURL) + headerImageView.kf.setImage(with: accountViewModel.headerURL) + headerImageView.tag = accountViewModel.headerURL.hashValue let noteFont = UIFont.preferredFont(forTextStyle: .callout) let mutableNote = NSMutableAttributedString(attributedString: accountViewModel.note) @@ -71,14 +64,25 @@ extension AccountHeaderView: UITextViewDelegate { } private extension AccountHeaderView { + // swiftlint:disable:next function_body_length func initialSetup() { let baseStackView = UIStackView() addSubview(headerImageView) - addSubview(baseStackView) headerImageView.translatesAutoresizingMaskIntoConstraints = false headerImageView.contentMode = .scaleAspectFill headerImageView.clipsToBounds = true + headerImageView.isUserInteractionEnabled = true + + headerImageView.addSubview(headerButton) + headerButton.translatesAutoresizingMaskIntoConstraints = false + headerButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted) + + headerButton.addAction( + UIAction { [weak self] _ in self?.viewModel?.presentHeader() }, + for: .touchUpInside) + + addSubview(baseStackView) baseStackView.translatesAutoresizingMaskIntoConstraints = false baseStackView.axis = .vertical @@ -111,6 +115,10 @@ private extension AccountHeaderView { headerImageView.topAnchor.constraint(equalTo: topAnchor), headerImageView.leadingAnchor.constraint(equalTo: leadingAnchor), headerImageView.trailingAnchor.constraint(equalTo: trailingAnchor), + headerButton.leadingAnchor.constraint(equalTo: headerImageView.leadingAnchor), + headerButton.topAnchor.constraint(equalTo: headerImageView.topAnchor), + headerButton.bottomAnchor.constraint(equalTo: headerImageView.bottomAnchor), + headerButton.trailingAnchor.constraint(equalTo: headerImageView.trailingAnchor), baseStackView.topAnchor.constraint(equalTo: headerImageView.bottomAnchor), baseStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), baseStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), diff --git a/Views/AccountView.swift b/Views/AccountView.swift index a9881a1..f8ba210 100644 --- a/Views/AccountView.swift +++ b/Views/AccountView.swift @@ -96,16 +96,7 @@ private extension AccountView { } func applyAccountConfiguration() { - let appPreferences = accountConfiguration.viewModel.identification.appPreferences - let avatarURL: URL - - if !appPreferences.shouldReduceMotion && appPreferences.animateAvatars == .everywhere { - avatarURL = accountConfiguration.viewModel.avatarURL - } else { - avatarURL = accountConfiguration.viewModel.avatarStaticURL - } - - avatarImageView.kf.setImage(with: avatarURL) + avatarImageView.kf.setImage(with: accountConfiguration.viewModel.avatarURL(profile: false)) if accountConfiguration.viewModel.displayName == "" { displayNameLabel.isHidden = true diff --git a/Views/Status/StatusAttachmentsView.swift b/Views/Status/StatusAttachmentsView.swift index b7d7215..76591ce 100644 --- a/Views/Status/StatusAttachmentsView.swift +++ b/Views/Status/StatusAttachmentsView.swift @@ -1,7 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. import Combine -import Network import UIKit import ViewModels @@ -86,20 +85,7 @@ extension StatusAttachmentsView { var shouldAutoplay: Bool { guard !isHidden, let viewModel = viewModel, viewModel.shouldShowAttachments else { return false } - let appPreferences = viewModel.identification.appPreferences - let onWifi = NWPathMonitor(requiredInterfaceType: .wifi).currentPath.status == .satisfied - let hasVideoAttachment = viewModel.attachmentViewModels.contains { $0.attachment.type == .video } - let shouldAutoplayVideo = appPreferences.autoplayVideos == .always - || appPreferences.autoplayVideos == .wifi && onWifi - - if hasVideoAttachment && shouldAutoplayVideo { - return true - } - - let hasGIFAttachment = viewModel.attachmentViewModels.contains { $0.attachment.type == .gifv } - let shouldAutoplayGIF = appPreferences.autoplayGIFs == .always || appPreferences.autoplayGIFs == .wifi && onWifi - - return hasGIFAttachment && shouldAutoplayGIF + return viewModel.attachmentViewModels.allSatisfy(\.shouldAutoplay) } } diff --git a/Views/Status/StatusView.swift b/Views/Status/StatusView.swift index dd7c256..556dee0 100644 --- a/Views/Status/StatusView.swift +++ b/Views/Status/StatusView.swift @@ -292,22 +292,14 @@ private extension StatusView { func applyStatusConfiguration() { let viewModel = statusConfiguration.viewModel - let appPreferences = viewModel.identification.appPreferences let isContextParent = viewModel.configuration.isContextParent let mutableContent = NSMutableAttributedString(attributedString: viewModel.content) let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName) let mutableSpoilerText = NSMutableAttributedString(string: viewModel.spoilerText) let contentFont = UIFont.preferredFont(forTextStyle: isContextParent ? .title3 : .callout) let contentRange = NSRange(location: 0, length: mutableContent.length) - let avatarURL: URL - if !appPreferences.shouldReduceMotion && appPreferences.animateAvatars == .everywhere { - avatarURL = viewModel.avatarURL - } else { - avatarURL = viewModel.avatarStaticURL - } - - avatarImageView.kf.setImage(with: avatarURL) + avatarImageView.kf.setImage(with: viewModel.avatarURL) contentTextView.shouldFallthrough = !isContextParent sideStackView.isHidden = isContextParent