Merge commit 'develop' into release/0.4.0

This commit is contained in:
CMK 2021-05-11 16:11:22 +08:00
commit ebc25be6a5
28 changed files with 201 additions and 33 deletions

View File

@ -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 }
}

View File

@ -7,7 +7,7 @@
import UIKit
protocol NeedsDependency: class {
protocol NeedsDependency: AnyObject {
var context: AppContext! { get set }
var coordinator: SceneCoordinator! { get set }
}

View File

@ -50,7 +50,7 @@ extension ComposeStatusItem {
}
}
protocol ComposePollAttributeDelegate: class {
protocol ComposePollAttributeDelegate: AnyObject {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?)
}

View File

@ -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 }

View File

@ -7,7 +7,7 @@
import UIKit
protocol ContentOffsetAdjustableTimelineViewControllerDelegate: class {
protocol ContentOffsetAdjustableTimelineViewControllerDelegate: AnyObject {
func navigationBar() -> UINavigationBar?
}

View File

@ -8,6 +8,6 @@
import Foundation
import Combine
protocol DisposeBagCollectable: class {
protocol DisposeBagCollectable: AnyObject {
var disposeBag: Set<AnyCancellable> { get set }
}

View File

@ -9,7 +9,7 @@ import os.log
import UIKit
import Combine
protocol ComposeStatusAttachmentCollectionViewCellDelegate: class {
protocol ComposeStatusAttachmentCollectionViewCellDelegate: AnyObject {
func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton)
}

View File

@ -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)
}

View File

@ -8,7 +8,7 @@
import os.log
import UIKit
protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: class {
protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: AnyObject {
func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell)
}

View File

@ -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)

View File

@ -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

View File

@ -8,7 +8,7 @@
import os.log
import UIKit
protocol HomeTimelineNavigationBarTitleViewDelegate: class {
protocol HomeTimelineNavigationBarTitleViewDelegate: AnyObject {
func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton)
}

View File

@ -12,7 +12,7 @@ import MastodonSDK
import AlamofireImage
import Kanna
protocol PickServerCellDelegate: class {
protocol PickServerCellDelegate: AnyObject {
func pickServerCell(_ cell: PickServerCell, expandButtonPressed button: UIButton)
}

View File

@ -7,7 +7,7 @@
import UIKit
protocol PickServerSearchCellDelegate: class {
protocol PickServerSearchCellDelegate: AnyObject {
func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -8,7 +8,7 @@
import UIKit
import Combine
protocol SettingsAppearanceTableViewCellDelegate: class {
protocol SettingsAppearanceTableViewCellDelegate: AnyObject {
func settingsAppearanceCell(_ cell: SettingsAppearanceTableViewCell, didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode)
}

View File

@ -8,7 +8,7 @@
import UIKit
import Combine
protocol SettingsToggleCellDelegate: class {
protocol SettingsToggleCellDelegate: AnyObject {
func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch)
}

View File

@ -9,7 +9,7 @@ import os.log
import AVKit
import UIKit
protocol PlayerContainerViewDelegate: class {
protocol PlayerContainerViewDelegate: AnyObject {
func playerContainerView(_ playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
}

View File

@ -9,7 +9,7 @@ import os.log
import Foundation
import UIKit
protocol ContentWarningOverlayViewDelegate: class {
protocol ContentWarningOverlayViewDelegate: AnyObject {
func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView)
}

View File

@ -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 {

View File

@ -9,7 +9,7 @@ import os.log
import UIKit
import Combine
protocol ThreadReplyLoaderTableViewCellDelegate: class {
protocol ThreadReplyLoaderTableViewCellDelegate: AnyObject {
func threadReplyLoaderTableViewCell(_ cell: ThreadReplyLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton)
}

View File

@ -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)
}

View File

@ -7,7 +7,7 @@
import UIKit
protocol DeleteBackwardResponseTextFieldDelegate: class {
protocol DeleteBackwardResponseTextFieldDelegate: AnyObject {
func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?)
}

View File

@ -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)

View File

@ -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:

View File

@ -12,7 +12,7 @@ import Kingfisher
import GameplayKit
import MastodonSDK
protocol MastodonAttachmentServiceDelegate: class {
protocol MastodonAttachmentServiceDelegate: AnyObject {
func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?)
}