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)
This commit is contained in:
Nathan Mattes 2024-03-21 08:35:18 +01:00 committed by GitHub
parent 83fd4a89fa
commit ab39c6ef87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 147 additions and 108 deletions

View File

@ -49,6 +49,15 @@ extension ProfileAboutViewController {
view.addSubview(collectionView)
collectionView.pinToParent()
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,
@ -56,11 +65,6 @@ extension ProfileAboutViewController {
profileFieldEditCollectionViewCellDelegate: self
)
let longPressReorderGesture = UILongPressGestureRecognizer(
target: self,
action: #selector(ProfileAboutViewController.longPressReorderGestureHandler(_:))
)
collectionView.addGestureRecognizer(longPressReorderGesture)
}
}

View File

@ -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<ProfileFieldSection, ProfileFieldItem>()

View File

@ -32,7 +32,18 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
var disposeBag = Set<AnyCancellable>()
//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()
@ -182,6 +193,98 @@ extension ProfileViewController {
navigationItem.titleView = titleView
addChild(tabBarPagerController)
tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tabBarPagerController.view)
tabBarPagerController.didMove(toParent: self)
tabBarPagerController.view.pinToParent()
tabBarPagerController.delegate = self
tabBarPagerController.dataSource = self
tabBarPagerController.relayScrollView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(ProfileViewController.refreshControlValueChanged(_:)), for: .valueChanged)
// setup delegate
profileHeaderViewController.delegate = self
profilePagingViewController.viewModel.profileAboutViewController.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
bindViewModel()
bindTitleView()
bindMoreBarButtonItem()
bindPager()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.viewDidAppear.send()
setNeedsStatusBarAppearanceUpdate()
}
}
extension ProfileViewController {
private func bindViewModel() {
// header
let headerViewModel = profileHeaderViewController.viewModel
viewModel.$account
.assign(to: \.account, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$isEditing
.assign(to: \.isEditing, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$isUpdating
.assign(to: \.isUpdating, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$relationship
.assign(to: \.relationship, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$accountForEdit
.assign(to: \.accountForEdit, on: headerViewModel)
.store(in: &disposeBag)
[
viewModel.postsUserTimelineViewModel,
viewModel.repliesUserTimelineViewModel,
viewModel.mediaUserTimelineViewModel,
].forEach { userTimelineViewModel in
viewModel.relationship.publisher
.map { $0.blocking }
.assign(to: \UserTimelineViewModel.isBlocking, on: userTimelineViewModel)
.store(in: &disposeBag)
viewModel.relationship.publisher
.compactMap { $0.blockedBy }
.assign(to: \UserTimelineViewModel.isBlockedBy, on: userTimelineViewModel)
.store(in: &disposeBag)
viewModel.$account
.compactMap { $0.suspended }
.assign(to: \UserTimelineViewModel.isSuspended, on: userTimelineViewModel)
.store(in: &disposeBag)
}
// about
let aboutViewModel = viewModel.profileAboutViewModel
viewModel.$account
.assign(to: \.account, on: aboutViewModel)
.store(in: &disposeBag)
viewModel.$isEditing
.assign(to: \.isEditing, on: aboutViewModel)
.store(in: &disposeBag)
viewModel.$accountForEdit
.assign(to: \.accountForEdit, on: aboutViewModel)
.store(in: &disposeBag)
let editingAndUpdatingPublisher = Publishers.CombineLatest(
viewModel.$isEditing,
viewModel.$isUpdating
@ -259,103 +362,25 @@ extension ProfileViewController {
}
.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)
addChild(tabBarPagerController)
tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tabBarPagerController.view)
tabBarPagerController.didMove(toParent: self)
tabBarPagerController.view.pinToParent()
tabBarPagerController.delegate = self
tabBarPagerController.dataSource = self
tabBarPagerController.relayScrollView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(ProfileViewController.refreshControlValueChanged(_:)), for: .valueChanged)
// 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
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel.viewDidAppear.send()
setNeedsStatusBarAppearanceUpdate()
}
}
extension ProfileViewController {
private func bindViewModel() {
// header
let headerViewModel = profileHeaderViewController.viewModel
viewModel.$account
.assign(to: \.account, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$isEditing
.assign(to: \.isEditing, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$isUpdating
.assign(to: \.isUpdating, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$relationship
.assign(to: \.relationship, on: headerViewModel)
.store(in: &disposeBag)
viewModel.$accountForEdit
.assign(to: \.accountForEdit, on: headerViewModel)
.store(in: &disposeBag)
[
viewModel.postsUserTimelineViewModel,
viewModel.repliesUserTimelineViewModel,
viewModel.mediaUserTimelineViewModel,
].forEach { userTimelineViewModel in
viewModel.relationship.publisher
.map { $0.blocking }
.assign(to: \UserTimelineViewModel.isBlocking, on: userTimelineViewModel)
.store(in: &disposeBag)
viewModel.relationship.publisher
.compactMap { $0.blockedBy }
.assign(to: \UserTimelineViewModel.isBlockedBy, on: userTimelineViewModel)
.store(in: &disposeBag)
viewModel.$account
.compactMap { $0.suspended }
.assign(to: \UserTimelineViewModel.isSuspended, on: userTimelineViewModel)
.store(in: &disposeBag)
}
// about
let aboutViewModel = viewModel.profileAboutViewModel
viewModel.$account
.assign(to: \.account, on: aboutViewModel)
.store(in: &disposeBag)
viewModel.$isEditing
.assign(to: \.isEditing, on: aboutViewModel)
.store(in: &disposeBag)
viewModel.$accountForEdit
.assign(to: \.accountForEdit, on: aboutViewModel)
.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,10 +483,9 @@ extension ProfileViewController {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.profilePagingViewController.becomeFirstResponder()
}
}
// dismiss keyboard if needs
if !isEditing { self.view.endEditing(true) }
self.view.endEditing(true)
}
if isEditing,
let index = self.profilePagingViewController.viewControllers.firstIndex(where: { type(of: $0) is ProfileAboutViewController.Type }),
@ -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