diff --git a/CoreDataStack/Protocol/Managed.swift b/CoreDataStack/Protocol/Managed.swift index 4811b9c6b..4bdff9c3e 100644 --- a/CoreDataStack/Protocol/Managed.swift +++ b/CoreDataStack/Protocol/Managed.swift @@ -8,7 +8,7 @@ import Foundation import CoreData -public protocol Managed: class, NSFetchRequestResult { +public protocol Managed: AnyObject, NSFetchRequestResult { static var entityName: String { get } static var defaultSortDescriptors: [NSSortDescriptor] { get } } diff --git a/Mastodon/Coordinator/NeedsDependency.swift b/Mastodon/Coordinator/NeedsDependency.swift index 70421a822..d6a24cce3 100644 --- a/Mastodon/Coordinator/NeedsDependency.swift +++ b/Mastodon/Coordinator/NeedsDependency.swift @@ -7,7 +7,7 @@ import UIKit -protocol NeedsDependency: class { +protocol NeedsDependency: AnyObject { var context: AppContext! { get set } var coordinator: SceneCoordinator! { get set } } diff --git a/Mastodon/Diffiable/Item/ComposeStatusItem.swift b/Mastodon/Diffiable/Item/ComposeStatusItem.swift index 88bff36c3..d60a76e82 100644 --- a/Mastodon/Diffiable/Item/ComposeStatusItem.swift +++ b/Mastodon/Diffiable/Item/ComposeStatusItem.swift @@ -50,7 +50,7 @@ extension ComposeStatusItem { } } -protocol ComposePollAttributeDelegate: class { +protocol ComposePollAttributeDelegate: AnyObject { func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) } diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 1ed5f0d42..ef5ccb502 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -197,6 +197,20 @@ extension StatusSection { emojiDict: (status.reblog ?? status).emojiDict ) + // set visibility + if let visibility = (status.reblog ?? status).visibility { + cell.statusView.updateVisibility(visibility: visibility) + + cell.statusView.revealContentWarningButton.publisher(for: \.isHidden) + .receive(on: DispatchQueue.main) + .sink { [weak cell] isHidden in + cell?.statusView.visibilityImageView.isHidden = !isHidden + } + .store(in: &cell.disposeBag) + } else { + cell.statusView.visibilityImageView.isHidden = true + } + // prepare media attachments let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending } diff --git a/Mastodon/Protocol/ContentOffsetAdjustableTimelineViewControllerDelegate.swift b/Mastodon/Protocol/ContentOffsetAdjustableTimelineViewControllerDelegate.swift index dbe22c52e..98160eb42 100644 --- a/Mastodon/Protocol/ContentOffsetAdjustableTimelineViewControllerDelegate.swift +++ b/Mastodon/Protocol/ContentOffsetAdjustableTimelineViewControllerDelegate.swift @@ -7,7 +7,7 @@ import UIKit -protocol ContentOffsetAdjustableTimelineViewControllerDelegate: class { +protocol ContentOffsetAdjustableTimelineViewControllerDelegate: AnyObject { func navigationBar() -> UINavigationBar? } diff --git a/Mastodon/Protocol/DisposeBagCollectable.swift b/Mastodon/Protocol/DisposeBagCollectable.swift index a8afde9d4..58bfa8576 100644 --- a/Mastodon/Protocol/DisposeBagCollectable.swift +++ b/Mastodon/Protocol/DisposeBagCollectable.swift @@ -8,6 +8,6 @@ import Foundation import Combine -protocol DisposeBagCollectable: class { +protocol DisposeBagCollectable: AnyObject { var disposeBag: Set { get set } } diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift index 141a944fd..87fe0efaf 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift @@ -9,7 +9,7 @@ import os.log import UIKit import Combine -protocol ComposeStatusAttachmentCollectionViewCellDelegate: class { +protocol ComposeStatusAttachmentCollectionViewCellDelegate: AnyObject { func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) } diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollExpiresOptionCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollExpiresOptionCollectionViewCell.swift index 0abe94ba0..8347f5641 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollExpiresOptionCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollExpiresOptionCollectionViewCell.swift @@ -9,7 +9,7 @@ import os.log import UIKit import Combine -protocol ComposeStatusPollExpiresOptionCollectionViewCellDelegate: class { +protocol ComposeStatusPollExpiresOptionCollectionViewCellDelegate: AnyObject { func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusItem.ComposePollExpiresOptionAttribute.ExpiresOption) } diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionAppendEntryCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionAppendEntryCollectionViewCell.swift index 39a12f954..dbe9ef4ad 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionAppendEntryCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionAppendEntryCollectionViewCell.swift @@ -8,7 +8,7 @@ import os.log import UIKit -protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: class { +protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: AnyObject { func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell) } diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift index 8846e56ed..ab2117f13 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift @@ -9,7 +9,7 @@ import os.log import UIKit import Combine -protocol ComposeStatusPollOptionCollectionViewCellDelegate: class { +protocol ComposeStatusPollOptionCollectionViewCellDelegate: AnyObject { func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField) diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 2940217e5..6aabc4572 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -9,7 +9,7 @@ import os.log import UIKit import MastodonSDK -protocol ComposeToolbarViewDelegate: class { +protocol ComposeToolbarViewDelegate: AnyObject { func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType type: ComposeToolbarView.MediaSelectionType) func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: UIButton) func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: UIButton) @@ -181,6 +181,15 @@ extension ComposeToolbarView { } } + func imageNameForTimeline() -> String { + switch self { + case .public: return "person.3" + case .unlisted: return "eye.slash" + case .private: return "person.crop.circle.badge.plus" + case .direct: return "at" + } + } + var visibility: Mastodon.Entity.Status.Visibility { switch self { case .public: return .public diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift index 242715028..91020f12a 100644 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift @@ -8,7 +8,7 @@ import os.log import UIKit -protocol HomeTimelineNavigationBarTitleViewDelegate: class { +protocol HomeTimelineNavigationBarTitleViewDelegate: AnyObject { func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) } diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift index 85c998b8f..a93dcfebf 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift @@ -12,7 +12,7 @@ import MastodonSDK import AlamofireImage import Kanna -protocol PickServerCellDelegate: class { +protocol PickServerCellDelegate: AnyObject { func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton) } diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift index fb1be2aad..dc048f67a 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift @@ -7,7 +7,7 @@ import UIKit -protocol PickServerSearchCellDelegate: class { +protocol PickServerSearchCellDelegate: AnyObject { func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?) } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 0610ae52f..217ea6584 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -13,7 +13,7 @@ import AlamofireImage import CropViewController import TwitterTextEditor -protocol ProfileHeaderViewControllerDelegate: class { +protocol ProfileHeaderViewControllerDelegate: AnyObject { func profileHeaderViewController(_ viewController: ProfileHeaderViewController, viewLayoutDidUpdate view: UIView) func profileHeaderViewController(_ viewController: ProfileHeaderViewController, pageSegmentedControlValueChanged segmentedControl: UISegmentedControl, selectedSegmentIndex index: Int) } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift b/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift index 4a95fb22f..38c093d1b 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift @@ -8,7 +8,7 @@ import os.log import UIKit -protocol ProfileStatusDashboardViewDelegate: class { +protocol ProfileStatusDashboardViewDelegate: AnyObject { func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, postDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followingDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView) diff --git a/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewController.swift b/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewController.swift index 568369d66..3b00b1c6f 100644 --- a/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewController.swift +++ b/Mastodon/Scene/Profile/Segmented/Paging/ProfilePagingViewController.swift @@ -10,7 +10,7 @@ import UIKit import Pageboy import Tabman -protocol ProfilePagingViewControllerDelegate: class { +protocol ProfilePagingViewControllerDelegate: AnyObject { func profilePagingViewController(_ viewController: ProfilePagingViewController, didScrollToPostCustomScrollViewContainerController customScrollViewContainerController: ScrollViewContainer, atIndex index: Int) } diff --git a/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift index 05bb4d10d..a58bebf8c 100644 --- a/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift +++ b/Mastodon/Scene/Settings/View/Cell/SettingsAppearanceTableViewCell.swift @@ -8,7 +8,7 @@ import UIKit import Combine -protocol SettingsAppearanceTableViewCellDelegate: class { +protocol SettingsAppearanceTableViewCellDelegate: AnyObject { func settingsAppearanceCell(_ cell: SettingsAppearanceTableViewCell, didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode) } diff --git a/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift b/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift index b4a62635b..86698d840 100644 --- a/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift @@ -8,7 +8,7 @@ import UIKit import Combine -protocol SettingsToggleCellDelegate: class { +protocol SettingsToggleCellDelegate: AnyObject { func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch) } diff --git a/Mastodon/Scene/Share/View/Container/PlayerContainerView.swift b/Mastodon/Scene/Share/View/Container/PlayerContainerView.swift index 32a4dc147..d018dfbe5 100644 --- a/Mastodon/Scene/Share/View/Container/PlayerContainerView.swift +++ b/Mastodon/Scene/Share/View/Container/PlayerContainerView.swift @@ -9,7 +9,7 @@ import os.log import AVKit import UIKit -protocol PlayerContainerViewDelegate: class { +protocol PlayerContainerViewDelegate: AnyObject { func playerContainerView(_ playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) } diff --git a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift index 6cf62783f..3dc091aa0 100644 --- a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift +++ b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift @@ -9,7 +9,7 @@ import os.log import Foundation import UIKit -protocol ContentWarningOverlayViewDelegate: class { +protocol ContentWarningOverlayViewDelegate: AnyObject { func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) } diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index 9655940c4..c06c956e0 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -122,6 +122,13 @@ final class StatusView: UIView { return button }() + let visibilityImageView: UIImageView = { + let imageView = UIImageView() + imageView.tintColor = Asset.Colors.Label.secondary.color + imageView.contentMode = .scaleAspectFit + return imageView + }() + let statusContainerStackView = UIStackView() let statusMosaicImageViewContainer = MosaicImageViewContainer() @@ -317,6 +324,10 @@ extension StatusView { authorContainerStackView.addArrangedSubview(revealContentWarningButton) revealContentWarningButton.setContentHuggingPriority(.required - 2, for: .horizontal) + // visibility ImageView + authorContainerStackView.addArrangedSubview(visibilityImageView) + visibilityImageView.setContentHuggingPriority(.required - 2, for: .horizontal) + authorContainerStackView.translatesAutoresizingMaskIntoConstraints = false authorContainerView.addSubview(authorContainerStackView) NSLayoutConstraint.activate([ @@ -479,6 +490,11 @@ extension StatusView { // TODO: a11y } + func updateVisibility(visibility: String) { + guard let visibility = ComposeToolbarView.VisibilitySelectionType(rawValue: visibility) else { return } + visibilityImageView.image = UIImage(systemName: visibility.imageNameForTimeline(), withConfiguration: UIImage.SymbolConfiguration(pointSize: 13, weight: .regular)) + } + } extension StatusView { diff --git a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift index 10ad0c5c8..03359df51 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift @@ -9,7 +9,7 @@ import os.log import UIKit import Combine -protocol ThreadReplyLoaderTableViewCellDelegate: class { +protocol ThreadReplyLoaderTableViewCellDelegate: AnyObject { func threadReplyLoaderTableViewCell(_ cell: ThreadReplyLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton) } diff --git a/Mastodon/Scene/Share/View/TableviewCell/TimelineMiddleLoaderTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/TimelineMiddleLoaderTableViewCell.swift index 7438f5bfd..4a0b623ef 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/TimelineMiddleLoaderTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/TimelineMiddleLoaderTableViewCell.swift @@ -10,7 +10,7 @@ import CoreData import os.log import UIKit -protocol TimelineMiddleLoaderTableViewCellDelegate: class { +protocol TimelineMiddleLoaderTableViewCellDelegate: AnyObject { func configure(cell: TimelineMiddleLoaderTableViewCell, upperTimelineStatusID: String?, timelineIndexobjectID:NSManagedObjectID?) func timelineMiddleLoaderTableViewCell(_ cell: TimelineMiddleLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton) } diff --git a/Mastodon/Scene/Share/View/TextField/DeleteBackwardResponseTextField.swift b/Mastodon/Scene/Share/View/TextField/DeleteBackwardResponseTextField.swift index 21c80dcf8..08c085aa9 100644 --- a/Mastodon/Scene/Share/View/TextField/DeleteBackwardResponseTextField.swift +++ b/Mastodon/Scene/Share/View/TextField/DeleteBackwardResponseTextField.swift @@ -7,7 +7,7 @@ import UIKit -protocol DeleteBackwardResponseTextFieldDelegate: class { +protocol DeleteBackwardResponseTextFieldDelegate: AnyObject { func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?) } diff --git a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift index a2f57dee2..2ed31abb4 100644 --- a/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift +++ b/Mastodon/Scene/Share/View/ToolBar/ActionToolBarContainer.swift @@ -8,7 +8,7 @@ import os.log import UIKit -protocol ActionToolbarContainerDelegate: class { +protocol ActionToolbarContainerDelegate: AnyObject { func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) diff --git a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift index 17ba9c660..eb4a43c1c 100644 --- a/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/MediaPreview/MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift @@ -81,16 +81,28 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionItem.imageView = transitionImageView transitionContext.containerView.addSubview(transitionImageView) - let animator = MediaHostToMediaPreviewViewControllerAnimatedTransitioning.animator(initialVelocity: .zero) + toVC.closeButtonBackground.alpha = 0 + + if UIAccessibility.isReduceTransparencyEnabled { + toVC.visualEffectView.alpha = 0 + } + let animator = MediaHostToMediaPreviewViewControllerAnimatedTransitioning.animator(initialVelocity: .zero) + animator.addAnimations { transitionImageView.frame = transitionTargetFrame toView.alpha = 1 + if UIAccessibility.isReduceTransparencyEnabled { + toVC.visualEffectView.alpha = 1 + } } animator.addCompletion { position in toVC.pagingViewConttroller.view.alpha = 1 transitionImageView.removeFromSuperview() + UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { + toVC.closeButtonBackground.alpha = 1 + } transitionContext.completeTransition(position == .end) } @@ -121,9 +133,20 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionContext.completeTransition(false) fatalError() } - mediaPreviewImageViewController.view.insertSubview(snapshot, aboveSubview: mediaPreviewImageViewController.previewImageView) - - snapshot.center = transitionContext.containerView.center + + let transitionMaskView = UIView(frame: transitionContext.containerView.bounds) + transitionMaskView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + transitionContext.containerView.addSubview(transitionMaskView) + + let maskLayer = CAShapeLayer() + maskLayer.frame = transitionMaskView.bounds + let maskLayerFromPath = UIBezierPath(rect: maskLayer.bounds).cgPath + maskLayer.path = maskLayerFromPath + transitionMaskView.layer.mask = maskLayer + + transitionMaskView.addSubview(snapshot) + snapshot.center = transitionMaskView.center + fromVC.view.bringSubviewToFront(fromVC.closeButtonBackground) transitionItem.imageView = imageView transitionItem.snapshotTransitioning = snapshot @@ -136,6 +159,38 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let animator = popInteractiveTransitionAnimator self.transitionItem.snapshotRaw?.alpha = 0.0 + + var needsMaskWithAnimation = true + let maskLayerToRect: CGRect? = { + guard case .mosaic = transitionItem.source else { return nil } + guard let navigationBar = toVC.navigationController?.navigationBar else { return nil } + let navigationBarFrameInWindow = toVC.view.convert(navigationBar.frame, to: nil) + var rect = transitionMaskView.frame + rect.origin.y = navigationBarFrameInWindow.maxY + UIView.separatorLineHeight(of: toVC.view) // extra hairline + + if rect.minY < snapshot.frame.minY { + needsMaskWithAnimation = false + } + + return rect + }() + let maskLayerToPath = maskLayerToRect.flatMap { UIBezierPath(rect: $0) }?.cgPath + let maskLayerToFinalRect: CGRect? = { + guard case .mosaic = transitionItem.source else { return nil } + guard let tabBarController = toVC.tabBarController else { return nil } + let tabBarFrameInWindow = toVC.view.convert(tabBarController.tabBar.frame, to: nil) + var rect = maskLayerToRect ?? transitionMaskView.frame + let offset = rect.maxY - tabBarFrameInWindow.minY + guard offset > 0 else { return rect } + rect.size.height -= offset + return rect + }() + let maskLayerToFinalPath = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath + + if !needsMaskWithAnimation, let maskLayerToPath = maskLayerToPath { + maskLayer.path = maskLayerToPath + } + animator.addAnimations { if let targetFrame = targetFrame { self.transitionItem.snapshotTransitioning?.frame = targetFrame @@ -145,6 +200,12 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 } fromVC.closeButtonBackground.alpha = 0 fromVC.visualEffectView.effect = nil + if let maskLayerToFinalPath = maskLayerToFinalPath { + maskLayer.path = maskLayerToFinalPath + } + if UIAccessibility.isReduceTransparencyEnabled { + fromVC.visualEffectView.alpha = 0 + } } animator.addCompletion { position in @@ -199,9 +260,21 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { transitionContext.completeTransition(false) return } - mediaPreviewImageViewController.view.insertSubview(snapshot, aboveSubview: mediaPreviewImageViewController.previewImageView) - - snapshot.center = transitionContext.containerView.center + + let transitionMaskView = UIView(frame: transitionContext.containerView.bounds) + transitionMaskView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + transitionContext.containerView.addSubview(transitionMaskView) + transitionItem.interactiveTransitionMaskView = transitionMaskView + + let maskLayer = CAShapeLayer() + maskLayer.frame = transitionMaskView.bounds + maskLayer.path = UIBezierPath(rect: maskLayer.bounds).cgPath + transitionMaskView.layer.mask = maskLayer + transitionItem.interactiveTransitionMaskLayer = maskLayer + + transitionMaskView.addSubview(snapshot) + snapshot.center = transitionMaskView.center + fromVC.view.bringSubviewToFront(fromVC.closeButtonBackground) transitionItem.imageView = imageView transitionItem.snapshotTransitioning = snapshot @@ -216,6 +289,10 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let blurEffect = fromVC.visualEffectView.effect self.transitionItem.snapshotRaw?.alpha = 0.0 + UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { + fromVC.closeButtonBackground.alpha = 0 + } + animator.addAnimations { switch self.transitionItem.source { case .profileBanner: @@ -223,9 +300,11 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { default: break } - fromVC.closeButtonBackground.alpha = 0 fromVC.visualEffectView.effect = nil self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 } + if UIAccessibility.isReduceTransparencyEnabled { + fromVC.visualEffectView.alpha = 0 + } } animator.addCompletion { position in @@ -239,6 +318,13 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { self.transitionItem.source.updateAppearance(position: position, index: nil) } fromVC.visualEffectView.effect = position == .end ? nil : blurEffect + transitionMaskView.removeFromSuperview() + UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseInOut]) { + fromVC.closeButtonBackground.alpha = position == .end ? 0 : 1 + } + if UIAccessibility.isReduceTransparencyEnabled { + fromVC.visualEffectView.alpha = position == .end ? 0 : 1 + } transitionContext.completeTransition(position == .end) } } @@ -315,8 +401,51 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning { let gestureVelocity = panGestureRecognizer.velocity(in: transitionContext.containerView) let velocity = convert(gestureVelocity, for: transitionItem) let itemAnimator = MediaHostToMediaPreviewViewControllerAnimatedTransitioning.animator(initialVelocity: velocity) + + var maskLayerToFinalPath: CGPath? + if toPosition == .end, + let transitionMaskView = transitionItem.interactiveTransitionMaskView, + let snapshot = transitionItem.snapshotTransitioning { + let toVC = transitionItem.previewableViewController + + var needsMaskWithAnimation = true + let maskLayerToRect: CGRect? = { + guard case .mosaic = transitionItem.source else { return nil } + guard let navigationBar = toVC.navigationController?.navigationBar else { return nil } + let navigationBarFrameInWindow = toVC.view.convert(navigationBar.frame, to: nil) + var rect = transitionMaskView.frame + rect.origin.y = navigationBarFrameInWindow.maxY + + if rect.minY < snapshot.frame.minY { + needsMaskWithAnimation = false + } + + return rect + }() + let maskLayerToPath = maskLayerToRect.flatMap { UIBezierPath(rect: $0) }?.cgPath + + if let maskLayer = transitionItem.interactiveTransitionMaskLayer, !needsMaskWithAnimation { + maskLayer.path = maskLayerToPath + } + + let maskLayerToFinalRect: CGRect? = { + guard case .mosaic = transitionItem.source else { return nil } + guard let tabBarController = toVC.tabBarController else { return nil } + let tabBarFrameInWindow = toVC.view.convert(tabBarController.tabBar.frame, to: nil) + var rect = maskLayerToRect ?? transitionMaskView.frame + let offset = rect.maxY - tabBarFrameInWindow.minY + guard offset > 0 else { return rect } + rect.size.height -= offset + return rect + }() + maskLayerToFinalPath = maskLayerToFinalRect.flatMap { UIBezierPath(rect: $0) }?.cgPath + } itemAnimator.addAnimations { + if let maskLayer = self.transitionItem.interactiveTransitionMaskLayer, + let maskLayerToFinalPath = maskLayerToFinalPath { + maskLayer.path = maskLayerToFinalPath + } if toPosition == .end { switch self.transitionItem.source { case .profileBanner where toPosition == .end: diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift index 3a57a9d98..fd95d2634 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift @@ -12,7 +12,7 @@ import Kingfisher import GameplayKit import MastodonSDK -protocol MastodonAttachmentServiceDelegate: class { +protocol MastodonAttachmentServiceDelegate: AnyObject { func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) }