feat: handle profile banner preview
This commit is contained in:
parent
acbbafb18f
commit
7a13b39c51
|
@ -164,7 +164,7 @@ struct AvatarConfigurableViewConfiguration {
|
||||||
placeholderImage: UIImage? = nil,
|
placeholderImage: UIImage? = nil,
|
||||||
borderColor: UIColor? = nil,
|
borderColor: UIColor? = nil,
|
||||||
borderWidth: CGFloat? = nil,
|
borderWidth: CGFloat? = nil,
|
||||||
keepImageCorner: Bool = true
|
keepImageCorner: Bool = false // default clip corner on image
|
||||||
) {
|
) {
|
||||||
self.avatarImageURL = avatarImageURL
|
self.avatarImageURL = avatarImageURL
|
||||||
self.placeholderImage = placeholderImage
|
self.placeholderImage = placeholderImage
|
||||||
|
|
|
@ -106,7 +106,7 @@ extension MediaPreviewViewController {
|
||||||
mosaicImageViewContainer.setImageViews(alpha: 1)
|
mosaicImageViewContainer.setImageViews(alpha: 1)
|
||||||
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
|
mosaicImageViewContainer.setImageView(alpha: 0, index: index)
|
||||||
}
|
}
|
||||||
case .profileAvatar:
|
case .profileAvatar, .profileBanner:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,26 @@ final class MediaPreviewViewModel: NSObject {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(context: AppContext, meta: ProfileBannerImagePreviewMeta, pushTransitionItem: MediaPreviewTransitionItem) {
|
||||||
|
self.context = context
|
||||||
|
self.initialItem = .profileBanner(meta)
|
||||||
|
var viewControllers: [UIViewController] = []
|
||||||
|
let managedObjectContext = self.context.managedObjectContext
|
||||||
|
managedObjectContext.performAndWait {
|
||||||
|
let account = managedObjectContext.object(with: meta.accountObjectID) as! MastodonUser
|
||||||
|
let avatarURL = account.headerImageURL() ?? URL(string: "https://example.com")! // assert URL exist
|
||||||
|
let meta = MediaPreviewImageViewModel.RemoteImagePreviewMeta(url: avatarURL, thumbnail: meta.preloadThumbnailImage)
|
||||||
|
let mediaPreviewImageModel = MediaPreviewImageViewModel(meta: meta)
|
||||||
|
let mediaPreviewImageViewController = MediaPreviewImageViewController()
|
||||||
|
mediaPreviewImageViewController.viewModel = mediaPreviewImageModel
|
||||||
|
viewControllers.append(mediaPreviewImageViewController)
|
||||||
|
}
|
||||||
|
self.viewControllers = viewControllers
|
||||||
|
self.currentPage = CurrentValueSubject(0)
|
||||||
|
self.pushTransitionItem = pushTransitionItem
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
init(context: AppContext, meta: ProfileAvatarImagePreviewMeta, pushTransitionItem: MediaPreviewTransitionItem) {
|
init(context: AppContext, meta: ProfileAvatarImagePreviewMeta, pushTransitionItem: MediaPreviewTransitionItem) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.initialItem = .profileAvatar(meta)
|
self.initialItem = .profileAvatar(meta)
|
||||||
|
@ -79,6 +99,7 @@ extension MediaPreviewViewModel {
|
||||||
enum PreviewItem {
|
enum PreviewItem {
|
||||||
case status(StatusImagePreviewMeta)
|
case status(StatusImagePreviewMeta)
|
||||||
case profileAvatar(ProfileAvatarImagePreviewMeta)
|
case profileAvatar(ProfileAvatarImagePreviewMeta)
|
||||||
|
case profileBanner(ProfileBannerImagePreviewMeta)
|
||||||
case local(LocalImagePreviewMeta)
|
case local(LocalImagePreviewMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +114,11 @@ extension MediaPreviewViewModel {
|
||||||
let preloadThumbnailImage: UIImage?
|
let preloadThumbnailImage: UIImage?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProfileBannerImagePreviewMeta {
|
||||||
|
let accountObjectID: NSManagedObjectID
|
||||||
|
let preloadThumbnailImage: UIImage?
|
||||||
|
}
|
||||||
|
|
||||||
struct LocalImagePreviewMeta {
|
struct LocalImagePreviewMeta {
|
||||||
let image: UIImage
|
let image: UIImage
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import TwitterTextEditor
|
||||||
|
|
||||||
protocol ProfileHeaderViewDelegate: AnyObject {
|
protocol ProfileHeaderViewDelegate: AnyObject {
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarImageViewDidPressed imageView: UIImageView)
|
||||||
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton)
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity)
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, activeLabel: ActiveLabel, entityDidPressed entity: ActiveEntity)
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ final class ProfileHeaderView: UIView {
|
||||||
imageView.image = .placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor)
|
imageView.image = .placeholder(color: ProfileHeaderView.bannerImageViewPlaceholderColor)
|
||||||
imageView.backgroundColor = ProfileHeaderView.bannerImageViewPlaceholderColor
|
imageView.backgroundColor = ProfileHeaderView.bannerImageViewPlaceholderColor
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
// #if DEBUG
|
// #if DEBUG
|
||||||
// imageView.image = .placeholder(color: .red)
|
// imageView.image = .placeholder(color: .red)
|
||||||
// #endif
|
// #endif
|
||||||
|
@ -338,6 +340,11 @@ extension ProfileHeaderView {
|
||||||
let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
let avatarImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer)
|
avatarImageView.addGestureRecognizer(avatarImageViewSingleTapGestureRecognizer)
|
||||||
avatarImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.avatarImageViewDidPressed(_:)))
|
avatarImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.avatarImageViewDidPressed(_:)))
|
||||||
|
|
||||||
|
let bannerImageViewSingleTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
bannerImageView.addGestureRecognizer(bannerImageViewSingleTapGestureRecognizer)
|
||||||
|
bannerImageViewSingleTapGestureRecognizer.addTarget(self, action: #selector(ProfileHeaderView.bannerImageViewDidPressed(_:)))
|
||||||
|
|
||||||
relationshipActionButton.addTarget(self, action: #selector(ProfileHeaderView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside)
|
relationshipActionButton.addTarget(self, action: #selector(ProfileHeaderView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
configure(state: .normal)
|
configure(state: .normal)
|
||||||
|
@ -402,6 +409,11 @@ extension ProfileHeaderView {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
delegate?.profileHeaderView(self, avatarImageViewDidPressed: avatarImageView)
|
delegate?.profileHeaderView(self, avatarImageViewDidPressed: avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func bannerImageViewDidPressed(_ sender: UITapGestureRecognizer) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
delegate?.profileHeaderView(self, bannerImageViewDidPressed: bannerImageView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - ActiveLabelDelegate
|
// MARK: - ActiveLabelDelegate
|
||||||
|
|
|
@ -679,6 +679,37 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView) {
|
||||||
|
guard let mastodonUser = viewModel.mastodonUser.value else { return }
|
||||||
|
guard let header = imageView.image else { return }
|
||||||
|
|
||||||
|
let meta = MediaPreviewViewModel.ProfileBannerImagePreviewMeta(
|
||||||
|
accountObjectID: mastodonUser.objectID,
|
||||||
|
preloadThumbnailImage: header
|
||||||
|
)
|
||||||
|
let pushTransitionItem = MediaPreviewTransitionItem(
|
||||||
|
source: .profileBanner(profileHeaderView),
|
||||||
|
previewableViewController: self
|
||||||
|
)
|
||||||
|
pushTransitionItem.aspectRatio = header.size
|
||||||
|
pushTransitionItem.sourceImageView = imageView
|
||||||
|
pushTransitionItem.initialFrame = {
|
||||||
|
let initialFrame = imageView.superview!.convert(imageView.frame, to: nil)
|
||||||
|
assert(initialFrame != .zero)
|
||||||
|
return initialFrame
|
||||||
|
}()
|
||||||
|
pushTransitionItem.image = header
|
||||||
|
|
||||||
|
let mediaPreviewViewModel = MediaPreviewViewModel(
|
||||||
|
context: context,
|
||||||
|
meta: meta,
|
||||||
|
pushTransitionItem: pushTransitionItem
|
||||||
|
)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.coordinator.present(scene: .mediaPreview(viewModel: mediaPreviewViewModel), from: self, transition: .custom(transitioningDelegate: self.mediaPreviewTransitionController))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) {
|
||||||
let relationshipActionSet = viewModel.relationshipActionOptionSet.value
|
let relationshipActionSet = viewModel.relationshipActionOptionSet.value
|
||||||
if relationshipActionSet.contains(.edit) {
|
if relationshipActionSet.contains(.edit) {
|
||||||
|
|
|
@ -204,7 +204,7 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
transitionItem.imageView = imageView
|
transitionItem.imageView = imageView
|
||||||
transitionItem.snapshotTransitioning = snapshot
|
transitionItem.snapshotTransitioning = snapshot
|
||||||
transitionItem.initialFrame = snapshot.frame
|
transitionItem.initialFrame = snapshot.frame
|
||||||
transitionItem.targetFrame = targetFrame
|
transitionItem.targetFrame = targetFrame ?? snapshot.frame
|
||||||
|
|
||||||
// disable interaction
|
// disable interaction
|
||||||
fromVC.pagingViewConttroller.isUserInteractionEnabled = false
|
fromVC.pagingViewConttroller.isUserInteractionEnabled = false
|
||||||
|
@ -215,6 +215,12 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
self.transitionItem.snapshotRaw?.alpha = 0.0
|
self.transitionItem.snapshotRaw?.alpha = 0.0
|
||||||
|
|
||||||
animator.addAnimations {
|
animator.addAnimations {
|
||||||
|
switch self.transitionItem.source {
|
||||||
|
case .profileBanner:
|
||||||
|
self.transitionItem.snapshotTransitioning?.alpha = 0.4
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
fromVC.closeButtonBackground.alpha = 0
|
fromVC.closeButtonBackground.alpha = 0
|
||||||
fromVC.visualEffectView.effect = nil
|
fromVC.visualEffectView.effect = nil
|
||||||
self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 }
|
self.transitionItem.sourceImageViewCornerRadius.flatMap { self.transitionItem.snapshotTransitioning?.layer.cornerRadius = $0 }
|
||||||
|
@ -310,11 +316,18 @@ extension MediaHostToMediaPreviewViewControllerAnimatedTransitioning {
|
||||||
|
|
||||||
itemAnimator.addAnimations {
|
itemAnimator.addAnimations {
|
||||||
if toPosition == .end {
|
if toPosition == .end {
|
||||||
if let targetFrame = self.transitionItem.targetFrame {
|
switch self.transitionItem.source {
|
||||||
self.transitionItem.snapshotTransitioning?.frame = targetFrame
|
case .profileBanner where toPosition == .end:
|
||||||
} else {
|
// fade transition for banner
|
||||||
self.transitionItem.snapshotTransitioning?.alpha = 0
|
self.transitionItem.snapshotTransitioning?.alpha = 0
|
||||||
|
default:
|
||||||
|
if let targetFrame = self.transitionItem.targetFrame {
|
||||||
|
self.transitionItem.snapshotTransitioning?.frame = targetFrame
|
||||||
|
} else {
|
||||||
|
self.transitionItem.snapshotTransitioning?.alpha = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if let initialFrame = self.transitionItem.initialFrame {
|
if let initialFrame = self.transitionItem.initialFrame {
|
||||||
self.transitionItem.snapshotTransitioning?.frame = initialFrame
|
self.transitionItem.snapshotTransitioning?.frame = initialFrame
|
||||||
|
|
|
@ -43,6 +43,7 @@ extension MediaPreviewTransitionItem {
|
||||||
enum Source {
|
enum Source {
|
||||||
case mosaic(MosaicImageViewContainer)
|
case mosaic(MosaicImageViewContainer)
|
||||||
case profileAvatar(ProfileHeaderView)
|
case profileAvatar(ProfileHeaderView)
|
||||||
|
case profileBanner(ProfileHeaderView)
|
||||||
|
|
||||||
func updateAppearance(position: UIViewAnimatingPosition, index: Int?) {
|
func updateAppearance(position: UIViewAnimatingPosition, index: Int?) {
|
||||||
let alpha: CGFloat = position == .end ? 1 : 0
|
let alpha: CGFloat = position == .end ? 1 : 0
|
||||||
|
@ -55,6 +56,8 @@ extension MediaPreviewTransitionItem {
|
||||||
}
|
}
|
||||||
case .profileAvatar(let profileHeaderView):
|
case .profileAvatar(let profileHeaderView):
|
||||||
profileHeaderView.avatarImageView.alpha = alpha
|
profileHeaderView.avatarImageView.alpha = alpha
|
||||||
|
case .profileBanner:
|
||||||
|
break // keep source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ extension MediaPreviewableViewController {
|
||||||
return imageView.superview!.convert(imageView.frame, to: nil)
|
return imageView.superview!.convert(imageView.frame, to: nil)
|
||||||
case .profileAvatar(let profileHeaderView):
|
case .profileAvatar(let profileHeaderView):
|
||||||
return profileHeaderView.avatarImageView.superview!.convert(profileHeaderView.avatarImageView.frame, to: nil)
|
return profileHeaderView.avatarImageView.superview!.convert(profileHeaderView.avatarImageView.frame, to: nil)
|
||||||
|
case .profileBanner:
|
||||||
|
return nil // fallback to snapshot.frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue