1
0
mirror of https://github.com/mastodon/mastodon-ios.git synced 2025-02-02 18:36:44 +01:00

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

@ -48,21 +48,25 @@ extension ProfileAboutViewController {
collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView) view.addSubview(collectionView)
collectionView.pinToParent() collectionView.pinToParent()
collectionView.delegate = self
viewModel.setupDiffableDataSource(
collectionView: collectionView,
profileFieldCollectionViewCellDelegate: self,
profileFieldEditCollectionViewCellDelegate: self
)
let longPressReorderGesture = UILongPressGestureRecognizer( let longPressReorderGesture = UILongPressGestureRecognizer(
target: self, target: self,
action: #selector(ProfileAboutViewController.longPressReorderGestureHandler(_:)) action: #selector(ProfileAboutViewController.longPressReorderGestureHandler(_:))
) )
collectionView.addGestureRecognizer(longPressReorderGesture) collectionView.addGestureRecognizer(longPressReorderGesture)
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.delegate = self
viewModel.setupDiffableDataSource(
collectionView: collectionView,
profileFieldCollectionViewCellDelegate: self,
profileFieldEditCollectionViewCellDelegate: self
)
}
} }
extension ProfileAboutViewController { extension ProfileAboutViewController {

View File

@ -51,7 +51,7 @@ extension ProfileAboutViewModel {
diffableDataSource.apply(snapshot) diffableDataSource.apply(snapshot)
let fields = Publishers.CombineLatest3( let fields = Publishers.CombineLatest3(
$isEditing.removeDuplicates(), $isEditing,
profileInfo.$fields.removeDuplicates(), profileInfo.$fields.removeDuplicates(),
profileInfoEditing.$fields.removeDuplicates() profileInfoEditing.$fields.removeDuplicates()
).map { isEditing, displayFields, editingFields in ).map { isEditing, displayFields, editingFields in
@ -60,7 +60,7 @@ extension ProfileAboutViewModel {
Publishers.CombineLatest4( Publishers.CombineLatest4(
$isEditing.removeDuplicates(), $isEditing,
$createdAt.removeDuplicates(), $createdAt.removeDuplicates(),
fields, fields,
$emojiMeta.removeDuplicates() $emojiMeta.removeDuplicates()
@ -68,7 +68,7 @@ extension ProfileAboutViewModel {
.throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true) .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isEditing, createdAt, fields, emojiMeta in .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 } guard let diffableDataSource = self.diffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>() var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>()

View File

@ -32,8 +32,19 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
//TODO: Replace with something better than ! //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() let mediaPreviewTransitionController = MediaPreviewTransitionController()
private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = { private(set) lazy var cancelEditingBarButtonItem: UIBarButtonItem = {
@ -182,89 +193,6 @@ extension ProfileViewController {
navigationItem.titleView = titleView 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) addChild(tabBarPagerController)
tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false tabBarPagerController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tabBarPagerController.view) view.addSubview(tabBarPagerController.view)
@ -280,17 +208,17 @@ extension ProfileViewController {
// setup delegate // setup delegate
profileHeaderViewController.delegate = self profileHeaderViewController.delegate = self
profilePagingViewController.viewModel.profileAboutViewController.delegate = self profilePagingViewController.viewModel.profileAboutViewController.delegate = self
bindViewModel()
bindTitleView()
bindMoreBarButtonItem()
bindPager()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = false navigationController?.navigationBar.prefersLargeTitles = false
bindViewModel()
bindTitleView()
bindMoreBarButtonItem()
bindPager()
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
@ -356,6 +284,103 @@ extension ProfileViewController {
viewModel.$accountForEdit viewModel.$accountForEdit
.assign(to: \.accountForEdit, on: aboutViewModel) .assign(to: \.accountForEdit, on: aboutViewModel)
.store(in: &disposeBag) .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() { private func bindTitleView() {
@ -443,7 +468,7 @@ extension ProfileViewController {
viewModel.$isPagingEnabled viewModel.$isPagingEnabled
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isPagingEnabled in .sink { [weak self] isPagingEnabled in
guard let self = self else { return } guard let self else { return }
self.profilePagingViewController.containerView.isScrollEnabled = isPagingEnabled self.profilePagingViewController.containerView.isScrollEnabled = isPagingEnabled
self.profilePagingViewController.buttonBarView.isUserInteractionEnabled = isPagingEnabled self.profilePagingViewController.buttonBarView.isUserInteractionEnabled = isPagingEnabled
} }
@ -458,11 +483,10 @@ extension ProfileViewController {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.profilePagingViewController.becomeFirstResponder() self.profilePagingViewController.becomeFirstResponder()
} }
// dismiss keyboard if needs
self.view.endEditing(true)
} }
// dismiss keyboard if needs
if !isEditing { self.view.endEditing(true) }
if isEditing, if isEditing,
let index = self.profilePagingViewController.viewControllers.firstIndex(where: { type(of: $0) is ProfileAboutViewController.Type }), let index = self.profilePagingViewController.viewControllers.firstIndex(where: { type(of: $0) is ProfileAboutViewController.Type }),
self.profilePagingViewController.canMoveTo(index: index) self.profilePagingViewController.canMoveTo(index: index)
@ -495,8 +519,7 @@ extension ProfileViewController {
extension ProfileViewController { extension ProfileViewController {
@objc private func cancelEditingBarButtonItemPressed(_ sender: UIBarButtonItem) { @objc private func cancelEditingBarButtonItemPressed(_ sender: UIBarButtonItem) {
viewModel.isEditing = false cancelEditing()
profileHeaderViewController.viewModel.isEditing = false
} }
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) { @objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
@ -714,6 +737,8 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
aboutProfileInfo: profileAboutViewModel.profileInfoEditing aboutProfileInfo: profileAboutViewModel.profileInfoEditing
).value ).value
self.viewModel.isEditing = false self.viewModel.isEditing = false
self.profileHeaderViewController.viewModel.isEditing = false
profileAboutViewModel.isEditing = false
self.viewModel.account = updatedAccount self.viewModel.account = updatedAccount
} catch { } catch {
@ -757,15 +782,25 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
// enter editing mode // enter editing mode
self.viewModel.isEditing = true self.viewModel.isEditing = true
self.profileHeaderViewController.viewModel.isEditing = true self.profileHeaderViewController.viewModel.isEditing = true
profileAboutViewModel.isEditing = true
} }
} receiveValue: { [weak self] response in } receiveValue: { [weak self] response in
guard let self = self else { return } guard let self = self else { return }
self.viewModel.accountForEdit = response.value self.viewModel.accountForEdit = response.value
} }
.store(in: &disposeBag) .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() { private func editRelationship() {
guard let relationship = viewModel.relationship, viewModel.isUpdating == false else { guard let relationship = viewModel.relationship, viewModel.isUpdating == false else {
return return