mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Media refactoring
This commit is contained in:
parent
264881f9b0
commit
ff2f813280
18 changed files with 168 additions and 114 deletions
|
@ -1,10 +0,0 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
extension AppPreferences {
|
||||
var shouldReduceMotion: Bool {
|
||||
UIAccessibility.isReduceMotionEnabled && useSystemReduceMotionForMedia
|
||||
}
|
||||
}
|
|
@ -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 = "<group>"; };
|
||||
D08B8D662540DEB200B1EBEF /* ZoomAnimatableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZoomAnimatableView.swift; sourceTree = "<group>"; };
|
||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
D0A3C2F625390A9700739F88 /* AppPreferences+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPreferences+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||
D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
|
||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -23,6 +23,7 @@ public extension AppEnvironment {
|
|||
keychain: keychain,
|
||||
userDefaults: userDefaults,
|
||||
userNotificationClient: userNotificationClient,
|
||||
reduceMotion: { false },
|
||||
uuid: uuid,
|
||||
inMemoryContent: inMemoryContent,
|
||||
fixtureDatabase: fixtureDatabase)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AnyCancellable>()
|
||||
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? {
|
||||
|
|
|
@ -7,9 +7,9 @@ import ServiceLayer
|
|||
|
||||
public struct AccountViewModel: CollectionItemViewModel {
|
||||
public let events: AnyPublisher<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||
public let identification: Identification
|
||||
|
||||
private let accountService: AccountService
|
||||
private let identification: Identification
|
||||
private let eventsSubject = PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, 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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<URL, Never>
|
||||
|
||||
private let profileService: ProfileService
|
||||
private let collectionViewModel: CurrentValueSubject<CollectionItemsViewModel, Never>
|
||||
private let imagePresentationsSubject = PassthroughSubject<URL, Never>()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
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<CollectionUpdate, Never> {
|
||||
collectionViewModel.flatMap(\.updates).eraseToAnyPublisher()
|
||||
|
|
|
@ -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<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||
|
||||
private let statusService: StatusService
|
||||
private let identification: Identification
|
||||
private let eventsSubject = PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, 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 }
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue