diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 5eee42631..a2ce95de9 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -1028,6 +1028,7 @@ 2D6AED5D2C05E908001A0A15 /* CheckboxListPresenterTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6AED5C2C05E908001A0A15 /* CheckboxListPresenterTrait.swift */; }; 2D6AED5F2C05EB47001A0A15 /* CheckboxListViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6AED5E2C05EB47001A0A15 /* CheckboxListViewModelFactory.swift */; }; 2D6F51A22CDB8C7400564C00 /* GovernanceChainSelectionViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6F51A12CDB8C7400564C00 /* GovernanceChainSelectionViewFactory.swift */; }; + 2D6F51A52CDBCFAC00564C00 /* AssetListStyleSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6F51A42CDBCFAC00564C00 /* AssetListStyleSwitcher.swift */; }; 2D7748772C89B5450059607B /* VoteCardViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7748762C89B5450059607B /* VoteCardViewModelFactory.swift */; }; 2D7748802C8B48C40059607B /* CIKeys.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D77487F2C8B48C40059607B /* CIKeys.generated.swift */; }; 2D7748822C8CFB0B0059607B /* ReferendumAmountOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7748812C8CFB0B0059607B /* ReferendumAmountOperationFactory.swift */; }; @@ -6216,6 +6217,7 @@ 2D6AED5C2C05E908001A0A15 /* CheckboxListPresenterTrait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxListPresenterTrait.swift; sourceTree = ""; }; 2D6AED5E2C05EB47001A0A15 /* CheckboxListViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxListViewModelFactory.swift; sourceTree = ""; }; 2D6F51A12CDB8C7400564C00 /* GovernanceChainSelectionViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceChainSelectionViewFactory.swift; sourceTree = ""; }; + 2D6F51A42CDBCFAC00564C00 /* AssetListStyleSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListStyleSwitcher.swift; sourceTree = ""; }; 2D7748762C89B5450059607B /* VoteCardViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoteCardViewModelFactory.swift; sourceTree = ""; }; 2D77487F2C8B48C40059607B /* CIKeys.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIKeys.generated.swift; sourceTree = SOURCE_ROOT; }; 2D7748812C8CFB0B0059607B /* ReferendumAmountOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumAmountOperationFactory.swift; sourceTree = ""; }; @@ -12829,6 +12831,15 @@ path = CheckboxListPresenter; sourceTree = ""; }; + 2D6F51A32CDBCF9B00564C00 /* AssetListStyleSwitcher */ = { + isa = PBXGroup; + children = ( + 2D1D66032CD89573009C6C2F /* AssetListStyleSwitcherView.swift */, + 2D6F51A42CDBCFAC00564C00 /* AssetListStyleSwitcher.swift */, + ); + path = AssetListStyleSwitcher; + sourceTree = ""; + }; 2D77488A2C9042800059607B /* Model */ = { isa = PBXGroup; children = ( @@ -20754,7 +20765,7 @@ 84D6D7FB27A7F4B40094FC33 /* AssetListNetworkView.swift */, 84D6D7FD27A7F4CD0094FC33 /* AssetListChainView.swift */, 84AEEE6927A9204C005EBA77 /* AssetListSettingsCell.swift */, - 2D1D66032CD89573009C6C2F /* AssetListStyleSwitcherView.swift */, + 2D6F51A32CDBCF9B00564C00 /* AssetListStyleSwitcher */, 847F2D4127A96D9200AFD476 /* AssetListNetworkGroupDecorationView.swift */, 8452585427ABF270004F9082 /* AssetListEmptyCell.swift */, 843E9B2727C83985009C143A /* AssetListNftsCell.swift */, @@ -25787,6 +25798,7 @@ 773BA0782B15F52200929BD4 /* Proxy.swift in Sources */, 0C13DFDF2AFA89EF00E5F355 /* Decimal+Percents.swift in Sources */, AEA0C8BA268113F900F9666F /* YourValidatorList+RecommendedList.swift in Sources */, + 2D6F51A52CDBCFAC00564C00 /* AssetListStyleSwitcher.swift in Sources */, 0C38B5032B7A86CE00882A8B /* TokensPallet.swift in Sources */, 88421056289BBA8D00306F2C /* CurrencyPresenter.swift in Sources */, 84D33996262250B800130A89 /* String+Substrate.swift in Sources */, diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index d76685e5d..1ec520e7a 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -312,7 +312,7 @@ extension ApplicationConfig: ApplicationConfigProtocol { } var whiteAppearanceIconsPath: String { - "https://raw.githubusercontent.com/novasamatech/nova-utils/refs/heads/master/icons/tokens/white/" + "https://raw.githubusercontent.com/novasamatech/nova-utils/refs/heads/master/icons/tokens/white/v1/" } var coloredAppearanceIconsPath: String { diff --git a/novawallet/Modules/AssetList/AssetListProtocols.swift b/novawallet/Modules/AssetList/AssetListProtocols.swift index b6a261c36..927abfde3 100644 --- a/novawallet/Modules/AssetList/AssetListProtocols.swift +++ b/novawallet/Modules/AssetList/AssetListProtocols.swift @@ -17,7 +17,10 @@ protocol AssetListCollectionManagerProtocol { func updateSelectedLocale(with locale: Locale) func updateTokensGroupLayout() - func changeCollectionViewLayout(to style: AssetListGroupsStyle) + func changeCollectionViewLayout( + from oldViewModel: AssetListViewModel, + to newViewModel: AssetListViewModel + ) func updateLoadingState() } diff --git a/novawallet/Modules/AssetList/AssetListViewController.swift b/novawallet/Modules/AssetList/AssetListViewController.swift index f55af7bd7..f1aeccbbb 100644 --- a/novawallet/Modules/AssetList/AssetListViewController.swift +++ b/novawallet/Modules/AssetList/AssetListViewController.swift @@ -106,19 +106,22 @@ extension AssetListViewController: AssetListViewProtocol { } func didReceiveGroups(viewModel: AssetListViewModel) { - let oldGroupsStyle = groupsViewModel.listGroupStyle - let newGroupStyle = viewModel.listGroupStyle + let oldViewModel = groupsViewModel + let newViewModel = viewModel - collectionViewManager.updateGroupsViewModel(with: viewModel) - groupsViewModel = viewModel + groupsViewModel = newViewModel - rootView.collectionView.reloadData() + collectionViewManager.updateGroupsViewModel(with: newViewModel) - if oldGroupsStyle != newGroupStyle { - collectionViewManager.changeCollectionViewLayout(to: newGroupStyle) + if oldViewModel.listGroupStyle != newViewModel.listGroupStyle { + collectionViewManager.changeCollectionViewLayout( + from: oldViewModel, + to: newViewModel + ) + } else { + collectionViewManager.updateTokensGroupLayout() + rootView.collectionView.reloadData() } - - collectionViewManager.updateTokensGroupLayout() } func didReceiveNft(viewModel: AssetListNftsViewModel?) { diff --git a/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListAssetCell.swift b/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListAssetCell.swift index 6801f86d2..51729b16f 100644 --- a/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListAssetCell.swift +++ b/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListAssetCell.swift @@ -40,17 +40,18 @@ class AssetListAssetCell: UICollectionViewCell { view.isHidden = true } + let selectedView: RoundedView = .create { view in + view.applyFilledBackgroundStyle() + view.fillColor = R.color.colorCellBackgroundPressed()! + view.cornerRadius = 0.0 + } + private var iconViewModel: ImageViewModelProtocol? override init(frame: CGRect) { super.init(frame: frame) - let selectedBackgroundView = RoundedView() - selectedBackgroundView.applyFilledBackgroundStyle() - selectedBackgroundView.fillColor = R.color.colorCellBackgroundPressed()! - selectedBackgroundView.cornerRadius = 0.0 - - let rowView = RowView(contentView: selectedBackgroundView) + let rowView = RowView(contentView: selectedView) rowView.isUserInteractionEnabled = false rowView.contentInsets = UIEdgeInsets( top: 0.0, @@ -59,7 +60,7 @@ class AssetListAssetCell: UICollectionViewCell { right: UIConstants.horizontalInset ) - self.selectedBackgroundView = rowView + selectedBackgroundView = rowView setupLayout() } @@ -86,6 +87,16 @@ class AssetListAssetCell: UICollectionViewCell { imageKeyPath: \.token.imageViewModel, nameKeyPath: \.token.symbol ) + + selectedView.cornerRadius = 8.0 + } + + func configureSelectionView(for expanded: Bool) { + selectedView.roundingCorners = if expanded { + [.topLeft, .topRight] + } else { + .allCorners + } } func showDivider() { diff --git a/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListNetworkGroupAssetCell.swift b/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListNetworkGroupAssetCell.swift index fb3ade627..33d99834b 100644 --- a/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListNetworkGroupAssetCell.swift +++ b/novawallet/Modules/AssetList/View/AssetListAssetCell/AssetListNetworkGroupAssetCell.swift @@ -40,6 +40,8 @@ final class AssetListNetworkGroupAssetCell: AssetListAssetCell { imageKeyPath: \.icon, nameKeyPath: \.tokenName ) + + selectedView.cornerRadius = 0 } override func bind( diff --git a/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionManager.swift b/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionManager.swift index aaf798b65..f30418420 100644 --- a/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionManager.swift +++ b/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionManager.swift @@ -23,6 +23,10 @@ final class AssetListCollectionManager { private let collectionViewDataSource: AssetListCollectionViewDataSource private let collectionViewDelegate: AssetListCollectionViewDelegate + private var transitingLayout: Bool = false + + private var pendingLayout: (old: AssetListViewModel, new: AssetListViewModel)? + init( view: AssetListViewLayout, groupsViewModel: AssetListViewModel, @@ -68,6 +72,25 @@ final class AssetListCollectionManager { private func updateLoadingState(for cell: UICollectionViewCell) { (cell as? AnimationUpdatibleView)?.updateLayerAnimationIfActive() } + + func prepareForLayoutTransition() { + transitingLayout = true + tokenGroupsLayout?.animatingTransition = true + networkGroupsLayout?.animatingTransition = true + } + + func endLayoutTransition() { + transitingLayout = false + tokenGroupsLayout?.animatingTransition = false + networkGroupsLayout?.animatingTransition = false + } + + private func replaceViewModel(_ newViewModel: AssetListViewModel) { + groupsViewModel = newViewModel + + collectionViewDataSource.groupsViewModel = newViewModel + collectionViewDelegate.groupsViewModel = newViewModel + } } // MARK: AssetListCollectionManagerPtotocol @@ -98,19 +121,64 @@ extension AssetListCollectionManager: AssetListCollectionManagerProtocol { ) } - func changeCollectionViewLayout(to style: AssetListGroupsStyle) { + func changeCollectionViewLayout( + from oldViewModel: AssetListViewModel, + to newViewModel: AssetListViewModel + ) { guard let view else { return } - view.assetGroupsLayoutStyle = style + guard !transitingLayout else { + pendingLayout = (oldViewModel, newViewModel) - let layout: UICollectionViewLayout = view.collectionViewLayout + return + } + + prepareForLayoutTransition() + updateTokensGroupLayout() - view.collectionView.setCollectionViewLayout( - layout, - animated: false + let removingViewModel = AssetListViewModel( + isFiltered: oldViewModel.isFiltered, + listState: .list(groups: []), + listGroupStyle: oldViewModel.listGroupStyle ) - layout.invalidateLayout() + let removingIndexes = (0 ..< view.collectionView.numberOfSections).filter { section in + section >= AssetListFlowLayout.SectionType.assetsStartingSection + } + + let insertingIndexes = newViewModel.listState.groups.enumerated().map { index, _ in + AssetListFlowLayout.SectionType.assetsStartingSection + index + } + + replaceViewModel(removingViewModel) + + view.collectionView.performBatchUpdates { + view.collectionView.deleteSections(IndexSet(removingIndexes)) + } completion: { [weak self] _ in + guard let self else { return } + + view.assetGroupsLayoutStyle = newViewModel.listGroupStyle + + let newLayout: AssetListFlowLayout = view.collectionViewLayout + + view.collectionView.setCollectionViewLayout( + newLayout, + animated: false + ) + + replaceViewModel(newViewModel) + + view.collectionView.performBatchUpdates { + view.collectionView.insertSections(IndexSet(insertingIndexes)) + } completion: { _ in + self.endLayoutTransition() + + if let pending = self.pendingLayout { + self.pendingLayout = nil + self.changeCollectionViewLayout(from: pending.old, to: pending.new) + } + } + } } func updateTokensGroupLayout() { @@ -141,10 +209,7 @@ extension AssetListCollectionManager: AssetListCollectionManagerProtocol { } func updateGroupsViewModel(with model: AssetListViewModel) { - groupsViewModel = model - - collectionViewDataSource.groupsViewModel = model - collectionViewDelegate.groupsViewModel = model + replaceViewModel(model) } func updateHeaderViewModel(with model: AssetListHeaderViewModel?) { diff --git a/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionViewDataSource.swift b/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionViewDataSource.swift index d6b281548..794811b47 100644 --- a/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionViewDataSource.swift +++ b/novawallet/Modules/AssetList/View/AssetListCollectionManager/AssetListCollectionViewDataSource.swift @@ -106,7 +106,7 @@ private extension AssetListCollectionViewDataSource { settingsCell.locale = selectedLocale - settingsCell.styleSwitcher.setup(with: .init(using: groupsViewModel.listGroupStyle)) + settingsCell.styleSwitcher.controlContentView.setup(with: .init(using: groupsViewModel.listGroupStyle)) settingsCell.manageButton.addTarget( self, @@ -120,9 +120,11 @@ private extension AssetListCollectionViewDataSource { for: .touchUpInside ) - settingsCell.styleSwitcher.addAction { [weak self] _ in - self?.actionsDelegate?.actionChangeAssetListStyle() - } + settingsCell.styleSwitcher.addTarget( + self, + action: #selector(actionSwitchStyle), + for: .valueChanged + ) settingsCell.manageButton.bind(showingBadge: groupsViewModel.isFiltered) @@ -198,6 +200,7 @@ private extension AssetListCollectionViewDataSource { if expanded { cell.showDivider() + cell.configureSelectionView(for: expanded) } assetCell = cell @@ -316,6 +319,10 @@ private extension AssetListCollectionViewDataSource { @objc func actionSwap() { actionsDelegate?.actionSwap() } + + @objc func actionSwitchStyle() { + actionsDelegate?.actionChangeAssetListStyle() + } } // MARK: UICollectionViewDataSource diff --git a/novawallet/Modules/AssetList/View/AssetListSettingsCell.swift b/novawallet/Modules/AssetList/View/AssetListSettingsCell.swift index 5af68e02a..a8ef4696f 100644 --- a/novawallet/Modules/AssetList/View/AssetListSettingsCell.swift +++ b/novawallet/Modules/AssetList/View/AssetListSettingsCell.swift @@ -1,7 +1,7 @@ import UIKit final class AssetListSettingsCell: UICollectionViewCell { - let styleSwitcher = AssetListStyleSwitcherView() + let styleSwitcher = AssetListStyleSwitcher() let manageButton = BadgedManageButton() @@ -38,7 +38,7 @@ final class AssetListSettingsCell: UICollectionViewCell { } private func setupLocalization() { - styleSwitcher.locale = locale + styleSwitcher.controlContentView.locale = locale } private func setupLayout() { diff --git a/novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcher.swift b/novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcher.swift new file mode 100644 index 000000000..1fa01a95b --- /dev/null +++ b/novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcher.swift @@ -0,0 +1,39 @@ +import UIKit +import SoraUI + +class AssetListStyleSwitcher: ControlView { + convenience init() { + self.init(frame: .zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + + setup() + } + + func setup() { + changesContentOpacityWhenHighlighted = true + contentInsets = .zero + + addTarget(self, action: #selector(handleTap), for: .touchUpInside) + + controlContentView.delegate = self + } + + @objc func handleTap() { + controlContentView.handleTap() + + sendActions(for: .valueChanged) + } +} + +extension AssetListStyleSwitcher: AssetListStyleSwitcherAnimationDelegate { + func didStartAnimating() { + isUserInteractionEnabled = false + } + + func didEndAnimating() { + isUserInteractionEnabled = true + } +} diff --git a/novawallet/Modules/AssetList/View/AssetListStyleSwitcherView.swift b/novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcherView.swift similarity index 95% rename from novawallet/Modules/AssetList/View/AssetListStyleSwitcherView.swift rename to novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcherView.swift index 445a7f01f..1a214e837 100644 --- a/novawallet/Modules/AssetList/View/AssetListStyleSwitcherView.swift +++ b/novawallet/Modules/AssetList/View/AssetListStyleSwitcher/AssetListStyleSwitcherView.swift @@ -1,5 +1,10 @@ import UIKit +protocol AssetListStyleSwitcherAnimationDelegate: AnyObject { + func didStartAnimating() + func didEndAnimating() +} + class AssetListStyleSwitcherView: UIView { var locale = Locale.current { didSet { @@ -7,8 +12,9 @@ class AssetListStyleSwitcherView: UIView { } } + weak var delegate: AssetListStyleSwitcherAnimationDelegate? + private var state: State = .tokens - private var valueChanged: ((State) -> Void)? private let label: UILabel = .create { view in view.apply(style: .title3Primary) @@ -48,8 +54,15 @@ class AssetListStyleSwitcherView: UIView { setupLocalizations() } - func addAction(on valueChanged: @escaping (State) -> Void) { - self.valueChanged = valueChanged + func handleTap() { + state.toggle() + + switch state { + case .networks: + animateToNetworks() + case .tokens: + animateToTokens() + } } } @@ -95,10 +108,8 @@ private extension AssetListStyleSwitcherView { } func setupView() { - isUserInteractionEnabled = true setupSubviews() setupConstraints() - setupGesture() setupIndicatorLayers(with: state) } @@ -115,7 +126,6 @@ private extension AssetListStyleSwitcherView { make.width.equalTo(Constants.indicatorContainerWidth) make.height.equalTo(Constants.indicatorContainerHeight) } - labelContainer.snp.makeConstraints { make in make.leading.equalTo(indicatorContainer.snp.trailing).offset(8) make.trailing.equalToSuperview() @@ -123,17 +133,11 @@ private extension AssetListStyleSwitcherView { make.height.equalTo(Constants.labelContainerHeight) make.width.equalTo(Constants.labelContainerWidth) } - label.snp.makeConstraints { make in make.edges.equalToSuperview() } } - func setupGesture() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - addGestureRecognizer(tapGesture) - } - func setupIndicatorLayers(with state: State) { setupSquareLayer(selected: state == .networks) setupCircleLayer(selected: state == .tokens) @@ -177,19 +181,6 @@ private extension AssetListStyleSwitcherView { indicatorContainer.layer.addSublayer(circleLayer) } - - @objc func handleTap() { - state.toggle() - - valueChanged?(state) - - switch state { - case .networks: - animateToNetworks() - case .tokens: - animateToTokens() - } - } } // MARK: Animation @@ -202,7 +193,6 @@ private extension AssetListStyleSwitcherView { ), direction: .up ) - animateIndicators( squareColor: Constants.indicatorInactiveColor, circleColor: Constants.indicatorActiveColor @@ -216,7 +206,6 @@ private extension AssetListStyleSwitcherView { ), direction: .down ) - animateIndicators( squareColor: Constants.indicatorActiveColor, circleColor: Constants.indicatorInactiveColor @@ -257,18 +246,20 @@ private extension AssetListStyleSwitcherView { direction: direction ) - isUserInteractionEnabled = false + delegate?.didStartAnimating() + CATransaction.begin() CATransaction.setCompletionBlock { [weak self] in self?.label.text = newText self?.label.layer.position.y = currentPosition self?.label.layer.opacity = 1.0 + + self?.label.layer.removeAllAnimations() newLabel.removeFromSuperview() - self?.isUserInteractionEnabled = true + self?.delegate?.didEndAnimating() } - CATransaction.begin() label.layer.add( currentSpring, forKey: "rollOut" @@ -316,6 +307,7 @@ private extension AssetListStyleSwitcherView { group.animations = [anticipation, opacity, spring] group.duration = Constants.animationDuration group.fillMode = .forwards + group.isRemovedOnCompletion = false return group } diff --git a/novawallet/Modules/AssetList/View/FlowLayout/AssetListFlowLayout.swift b/novawallet/Modules/AssetList/View/FlowLayout/AssetListFlowLayout.swift index 895aac7e9..ff2c1f019 100644 --- a/novawallet/Modules/AssetList/View/FlowLayout/AssetListFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/FlowLayout/AssetListFlowLayout.swift @@ -25,6 +25,10 @@ class AssetListFlowLayout: UICollectionViewFlowLayout { private(set) var promotionInsets: UIEdgeInsets = .zero private(set) var nftsInsets: UIEdgeInsets = .zero + var needsDecorationUpdate = false + + var animatingTransition: Bool = false + enum SectionType: CaseIterable { case summary case nfts @@ -128,7 +132,7 @@ class AssetListFlowLayout: UICollectionViewFlowLayout { } } - var itemsDecorationAttributes: [UICollectionViewLayoutAttributes] = [] + var itemsDecorationAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:] func updateItemsBackgroundAttributesIfNeeded() { fatalError("Must be overriden by subsclass") @@ -151,11 +155,11 @@ class AssetListFlowLayout: UICollectionViewFlowLayout { in: rect )?.map { $0.copy() } as? [UICollectionViewLayoutAttributes] - let visibleAttributes = itemsDecorationAttributes.filter { attributes in + let visibleAttributes = itemsDecorationAttributes.filter { _, attributes in attributes.frame.intersects(rect) } - return (layoutAttributesObjects ?? []) + visibleAttributes + return (layoutAttributesObjects ?? []) + visibleAttributes.values } override func layoutAttributesForDecorationView( @@ -170,15 +174,13 @@ class AssetListFlowLayout: UICollectionViewFlowLayout { return nil } - let index = indexPath.section - SectionType.assetsStartingSection - - return itemsDecorationAttributes[index] + return itemsDecorationAttributes[indexPath] } override func prepare() { super.prepare() - itemsDecorationAttributes = [] + itemsDecorationAttributes = [:] updateItemsBackgroundAttributesIfNeeded() } @@ -267,4 +269,94 @@ class AssetListFlowLayout: UICollectionViewFlowLayout { func assetSectionIndex(from groupIndex: Int) -> Int { SectionType.assetsStartingSection + groupIndex } + + // MARK: Animation + + func getTransformForAnimation() -> CGAffineTransform { + let scale: CGFloat = 0.65 + return CGAffineTransform( + scaleX: scale, + y: scale + ) + } + + override func initialLayoutAttributesForAppearingItem( + at itemIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)? + .copy() as? UICollectionViewLayoutAttributes + + attributes?.alpha = 0.0 + + if itemIndexPath.row != 0, !animatingTransition { + attributes?.transform = getTransformForAnimation() + } + + return attributes + } + + override func finalLayoutAttributesForDisappearingItem( + at itemIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)? + .copy() as? UICollectionViewLayoutAttributes + + if itemIndexPath.row != 0, !animatingTransition { + attributes?.transform = getTransformForAnimation() + } + + return attributes + } + + override func initialLayoutAttributesForAppearingDecorationElement( + ofKind elementKind: String, + at decorationIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.initialLayoutAttributesForAppearingDecorationElement( + ofKind: elementKind, + at: decorationIndexPath + )?.copy() as? UICollectionViewLayoutAttributes + + return attributes + } + + override func finalLayoutAttributesForDisappearingDecorationElement( + ofKind elementKind: String, + at decorationIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.finalLayoutAttributesForDisappearingDecorationElement( + ofKind: elementKind, + at: decorationIndexPath + )?.copy() as? UICollectionViewLayoutAttributes + + return attributes + } + + override func initialLayoutAttributesForAppearingSupplementaryElement( + ofKind elementKind: String, + at elementIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.initialLayoutAttributesForAppearingSupplementaryElement( + ofKind: elementKind, + at: elementIndexPath + )?.copy() as? UICollectionViewLayoutAttributes + + attributes?.alpha = 0.0 + + return attributes + } + + override func finalLayoutAttributesForDisappearingSupplementaryElement( + ofKind elementKind: String, + at elementIndexPath: IndexPath + ) -> UICollectionViewLayoutAttributes? { + let attributes = super.finalLayoutAttributesForDisappearingSupplementaryElement( + ofKind: elementKind, + at: elementIndexPath + )?.copy() as? UICollectionViewLayoutAttributes + + attributes?.alpha = 0.0 + + return attributes + } } diff --git a/novawallet/Modules/AssetList/View/FlowLayout/AssetListNetworksFlowLayout.swift b/novawallet/Modules/AssetList/View/FlowLayout/AssetListNetworksFlowLayout.swift index 1e725f552..4f0be030f 100644 --- a/novawallet/Modules/AssetList/View/FlowLayout/AssetListNetworksFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/FlowLayout/AssetListNetworksFlowLayout.swift @@ -80,7 +80,7 @@ final class AssetListNetworksFlowLayout: AssetListFlowLayout { return (newAttributes, newPosition) } - itemsDecorationAttributes = attributes + itemsDecorationAttributes = attributes.reduce(into: [:]) { $0[$1.indexPath] = $1 } } override func assetCellHeight(for _: IndexPath) -> CGFloat { diff --git a/novawallet/Modules/AssetList/View/FlowLayout/AssetListTokensFlowLayout.swift b/novawallet/Modules/AssetList/View/FlowLayout/AssetListTokensFlowLayout.swift index ccb773e0e..fc70e0b7c 100644 --- a/novawallet/Modules/AssetList/View/FlowLayout/AssetListTokensFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/FlowLayout/AssetListTokensFlowLayout.swift @@ -174,7 +174,7 @@ class AssetListTokensFlowLayout: AssetListFlowLayout { return (newAttributes, newPosition) } - itemsDecorationAttributes = attributes + itemsDecorationAttributes = attributes.reduce(into: [:]) { $0[$1.indexPath] = $1 } } override func assetCellHeight(for _: IndexPath) -> CGFloat {