From ab39c6ef87ff2c2e15826b517f88ff9bf19e1fff Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 21 Mar 2024 08:35:18 +0100 Subject: [PATCH] Fix Profile Editing again (IOS-239, #1244) (#1259) * Fix Profile Editing again (IOS-239, #1244) This needs a bit of explanation, I guess, so please don't squash if possible? I didn't take into consideration, that the `ProfileViewController.viewModel` changes. And when it changes, all the combine-connections just ... disappear. This PR changes that (but probably I oversaw something again). * Disable pull to refresh for editing mode (IOS-239) * In case of nothing change, cancel editing (IOS-239) --- .../About/ProfileAboutViewController.swift | 22 +- .../ProfileAboutViewModel+Diffable.swift | 6 +- .../Scene/Profile/ProfileViewController.swift | 227 ++++++++++-------- 3 files changed, 147 insertions(+), 108 deletions(-) diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift index 8881f894e..473c22d52 100644 --- a/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift +++ b/Mastodon/Scene/Profile/About/ProfileAboutViewController.swift @@ -48,21 +48,25 @@ extension ProfileAboutViewController { collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) collectionView.pinToParent() - - collectionView.delegate = self - viewModel.setupDiffableDataSource( - collectionView: collectionView, - profileFieldCollectionViewCellDelegate: self, - profileFieldEditCollectionViewCellDelegate: self - ) - + let longPressReorderGesture = UILongPressGestureRecognizer( target: self, action: #selector(ProfileAboutViewController.longPressReorderGestureHandler(_:)) ) collectionView.addGestureRecognizer(longPressReorderGesture) } - + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + collectionView.delegate = self + viewModel.setupDiffableDataSource( + collectionView: collectionView, + profileFieldCollectionViewCellDelegate: self, + profileFieldEditCollectionViewCellDelegate: self + ) + + } + } extension ProfileAboutViewController { diff --git a/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift b/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift index 7c69c9669..43e4a301b 100644 --- a/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/About/ProfileAboutViewModel+Diffable.swift @@ -51,7 +51,7 @@ extension ProfileAboutViewModel { diffableDataSource.apply(snapshot) let fields = Publishers.CombineLatest3( - $isEditing.removeDuplicates(), + $isEditing, profileInfo.$fields.removeDuplicates(), profileInfoEditing.$fields.removeDuplicates() ).map { isEditing, displayFields, editingFields in @@ -60,7 +60,7 @@ extension ProfileAboutViewModel { Publishers.CombineLatest4( - $isEditing.removeDuplicates(), + $isEditing, $createdAt.removeDuplicates(), fields, $emojiMeta.removeDuplicates() @@ -68,7 +68,7 @@ extension ProfileAboutViewModel { .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true) .receive(on: DispatchQueue.main) .sink { [weak self] isEditing, createdAt, fields, emojiMeta in - guard let self = self else { return } + guard let self else { return } guard let diffableDataSource = self.diffableDataSource else { return } var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 992e7d9b1..71b8b6a21 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -32,8 +32,19 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi var disposeBag = Set() //TODO: Replace with something better than ! - var viewModel: ProfileViewModel! - + var viewModel: ProfileViewModel! { + didSet { + if isViewLoaded { + bindViewModel() + + viewModel.isEditing = false + profileHeaderViewController.viewModel.isEditing = false + profilePagingViewController.viewModel.profileAboutViewController.viewModel.isEditing = false + viewModel.profileAboutViewModel.isEditing = false + } + } + } + let mediaPreviewTransitionController = MediaPreviewTransitionController() private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = { @@ -182,89 +193,6 @@ extension ProfileViewController { navigationItem.titleView = titleView - let editingAndUpdatingPublisher = Publishers.CombineLatest( - viewModel.$isEditing, - viewModel.$isUpdating - ) - // note: not add .share() here - - let barButtonItemHiddenPublisher = Publishers.CombineLatest3( - viewModel.$isMeBarButtonItemsHidden, - viewModel.$isReplyBarButtonItemHidden, - viewModel.$isMoreMenuBarButtonItemHidden - ) - - editingAndUpdatingPublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] isEditing, isUpdating in - guard let self = self else { return } - self.cancelEditingBarButtonItem.isEnabled = !isUpdating - } - .store(in: &disposeBag) - - // build items - Publishers.CombineLatest4( - viewModel.$relationship, - profileHeaderViewController.viewModel.$isTitleViewDisplaying, - editingAndUpdatingPublisher, - barButtonItemHiddenPublisher - ) - .receive(on: DispatchQueue.main) - .sink { [weak self] account, isTitleViewDisplaying, tuple1, tuple2 in - guard let self else { return } - let (isEditing, _) = tuple1 - let (isMeBarButtonItemsHidden, isReplyBarButtonItemHidden, isMoreMenuBarButtonItemHidden) = tuple2 - - var items: [UIBarButtonItem] = [] - defer { - if items.isNotEmpty { - self.navigationItem.rightBarButtonItems = items - } else { - self.navigationItem.rightBarButtonItems = nil - } - } - - if let suspended = self.viewModel.account.suspended, suspended == true { - return - } - - guard isEditing == false else { - items.append(self.cancelEditingBarButtonItem) - return - } - - guard isTitleViewDisplaying == false else { - return - } - - guard isMeBarButtonItemsHidden else { - items.append(self.settingBarButtonItem) - items.append(self.shareBarButtonItem) - items.append(self.favoriteBarButtonItem) - items.append(self.bookmarkBarButtonItem) - - if self.currentInstance?.canFollowTags == true { - items.append(self.followedTagsBarButtonItem) - } - - return - } - - if !isMoreMenuBarButtonItemHidden { - items.append(self.moreMenuBarButtonItem) - } - if !isReplyBarButtonItemHidden { - items.append(self.replyBarButtonItem) - } - } - .store(in: &disposeBag) - - context.publisherService.statusPublishResult.sink { [weak self] result in - if case .success(.edit(let status)) = result { - self?.updateViewModelsWithDataControllers(status: .fromEntity(status.value), intent: .edit) - } - }.store(in: &disposeBag) - addChild(tabBarPagerController) tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tabBarPagerController.view) @@ -280,17 +208,17 @@ extension ProfileViewController { // setup delegate profileHeaderViewController.delegate = self profilePagingViewController.viewModel.profileAboutViewController.delegate = self - - bindViewModel() - bindTitleView() - bindMoreBarButtonItem() - bindPager() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.navigationBar.prefersLargeTitles = false + + bindViewModel() + bindTitleView() + bindMoreBarButtonItem() + bindPager() } override func viewDidAppear(_ animated: Bool) { @@ -356,6 +284,103 @@ extension ProfileViewController { viewModel.$accountForEdit .assign(to: \.accountForEdit, on: aboutViewModel) .store(in: &disposeBag) + + let editingAndUpdatingPublisher = Publishers.CombineLatest( + viewModel.$isEditing, + viewModel.$isUpdating + ) + // note: not add .share() here + + let barButtonItemHiddenPublisher = Publishers.CombineLatest3( + viewModel.$isMeBarButtonItemsHidden, + viewModel.$isReplyBarButtonItemHidden, + viewModel.$isMoreMenuBarButtonItemHidden + ) + + editingAndUpdatingPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isEditing, isUpdating in + guard let self = self else { return } + self.cancelEditingBarButtonItem.isEnabled = !isUpdating + } + .store(in: &disposeBag) + + // build items + Publishers.CombineLatest4( + viewModel.$relationship, + profileHeaderViewController.viewModel.$isTitleViewDisplaying, + editingAndUpdatingPublisher, + barButtonItemHiddenPublisher + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] account, isTitleViewDisplaying, tuple1, tuple2 in + guard let self else { return } + let (isEditing, _) = tuple1 + let (isMeBarButtonItemsHidden, isReplyBarButtonItemHidden, isMoreMenuBarButtonItemHidden) = tuple2 + + var items: [UIBarButtonItem] = [] + defer { + if items.isNotEmpty { + self.navigationItem.rightBarButtonItems = items + } else { + self.navigationItem.rightBarButtonItems = nil + } + } + + if let suspended = self.viewModel.account.suspended, suspended == true { + return + } + + guard isEditing == false else { + items.append(self.cancelEditingBarButtonItem) + return + } + + guard isTitleViewDisplaying == false else { + return + } + + guard isMeBarButtonItemsHidden else { + items.append(self.settingBarButtonItem) + items.append(self.shareBarButtonItem) + items.append(self.favoriteBarButtonItem) + items.append(self.bookmarkBarButtonItem) + + if self.currentInstance?.canFollowTags == true { + items.append(self.followedTagsBarButtonItem) + } + + return + } + + if !isMoreMenuBarButtonItemHidden { + items.append(self.moreMenuBarButtonItem) + } + if !isReplyBarButtonItemHidden { + items.append(self.replyBarButtonItem) + } + } + .store(in: &disposeBag) + + viewModel.$isEditing + .receive(on: DispatchQueue.main) + .sink { [weak self] isEditing in + guard let self else { return } + + if isEditing { + tabBarPagerController.relayScrollView.refreshControl = nil + } else { + tabBarPagerController.relayScrollView.refreshControl = refreshControl + } + } + .store(in: &disposeBag) + + context.publisherService.statusPublishResult.sink { [weak self] result in + if case .success(.edit(let status)) = result { + self?.updateViewModelsWithDataControllers(status: .fromEntity(status.value), intent: .edit) + } + }.store(in: &disposeBag) + } private func bindTitleView() { @@ -443,7 +468,7 @@ extension ProfileViewController { viewModel.$isPagingEnabled .receive(on: DispatchQueue.main) .sink { [weak self] isPagingEnabled in - guard let self = self else { return } + guard let self else { return } self.profilePagingViewController.containerView.isScrollEnabled = isPagingEnabled self.profilePagingViewController.buttonBarView.isUserInteractionEnabled = isPagingEnabled } @@ -458,11 +483,10 @@ extension ProfileViewController { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.profilePagingViewController.becomeFirstResponder() } + // dismiss keyboard if needs + self.view.endEditing(true) } - // dismiss keyboard if needs - if !isEditing { self.view.endEditing(true) } - if isEditing, let index = self.profilePagingViewController.viewControllers.firstIndex(where: { type(of: $0) is ProfileAboutViewController.Type }), self.profilePagingViewController.canMoveTo(index: index) @@ -495,8 +519,7 @@ extension ProfileViewController { extension ProfileViewController { @objc private func cancelEditingBarButtonItemPressed(_ sender: UIBarButtonItem) { - viewModel.isEditing = false - profileHeaderViewController.viewModel.isEditing = false + cancelEditing() } @objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) { @@ -714,6 +737,8 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { aboutProfileInfo: profileAboutViewModel.profileInfoEditing ).value self.viewModel.isEditing = false + self.profileHeaderViewController.viewModel.isEditing = false + profileAboutViewModel.isEditing = false self.viewModel.account = updatedAccount } catch { @@ -757,15 +782,25 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { // enter editing mode self.viewModel.isEditing = true self.profileHeaderViewController.viewModel.isEditing = true + profileAboutViewModel.isEditing = true } } receiveValue: { [weak self] response in guard let self = self else { return } self.viewModel.accountForEdit = response.value } .store(in: &disposeBag) + } else if isEdited == false { + cancelEditing() } } + private func cancelEditing() { + viewModel.isEditing = false + profileHeaderViewController.viewModel.isEditing = false + profilePagingViewController.viewModel.profileAboutViewController.viewModel.isEditing = false + viewModel.profileAboutViewModel.isEditing = false + } + private func editRelationship() { guard let relationship = viewModel.relationship, viewModel.isUpdating == false else { return