Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/asset list animations #1264

Merged
merged 18 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion novawallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -6216,6 +6217,7 @@
2D6AED5C2C05E908001A0A15 /* CheckboxListPresenterTrait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxListPresenterTrait.swift; sourceTree = "<group>"; };
2D6AED5E2C05EB47001A0A15 /* CheckboxListViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxListViewModelFactory.swift; sourceTree = "<group>"; };
2D6F51A12CDB8C7400564C00 /* GovernanceChainSelectionViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GovernanceChainSelectionViewFactory.swift; sourceTree = "<group>"; };
2D6F51A42CDBCFAC00564C00 /* AssetListStyleSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListStyleSwitcher.swift; sourceTree = "<group>"; };
2D7748762C89B5450059607B /* VoteCardViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoteCardViewModelFactory.swift; sourceTree = "<group>"; };
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 = "<group>"; };
Expand Down Expand Up @@ -12829,6 +12831,15 @@
path = CheckboxListPresenter;
sourceTree = "<group>";
};
2D6F51A32CDBCF9B00564C00 /* AssetListStyleSwitcher */ = {
isa = PBXGroup;
children = (
2D1D66032CD89573009C6C2F /* AssetListStyleSwitcherView.swift */,
2D6F51A42CDBCFAC00564C00 /* AssetListStyleSwitcher.swift */,
);
path = AssetListStyleSwitcher;
sourceTree = "<group>";
};
2D77488A2C9042800059607B /* Model */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
2 changes: 1 addition & 1 deletion novawallet/Common/Configs/ApplicationConfigs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion novawallet/Modules/AssetList/AssetListProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
21 changes: 12 additions & 9 deletions novawallet/Modules/AssetList/AssetListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -59,7 +60,7 @@ class AssetListAssetCell: UICollectionViewCell {
right: UIConstants.horizontalInset
)

self.selectedBackgroundView = rowView
selectedBackgroundView = rowView

setupLayout()
}
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ final class AssetListNetworkGroupAssetCell: AssetListAssetCell {
imageKeyPath: \.icon,
nameKeyPath: \.tokenName
)

selectedView.cornerRadius = 0
}

override func bind<T>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)

Expand Down Expand Up @@ -198,6 +200,7 @@ private extension AssetListCollectionViewDataSource {

if expanded {
cell.showDivider()
cell.configureSelectionView(for: expanded)
}

assetCell = cell
Expand Down Expand Up @@ -316,6 +319,10 @@ private extension AssetListCollectionViewDataSource {
@objc func actionSwap() {
actionsDelegate?.actionSwap()
}

@objc func actionSwitchStyle() {
actionsDelegate?.actionChangeAssetListStyle()
}
}

// MARK: UICollectionViewDataSource
Expand Down
4 changes: 2 additions & 2 deletions novawallet/Modules/AssetList/View/AssetListSettingsCell.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit

final class AssetListSettingsCell: UICollectionViewCell {
let styleSwitcher = AssetListStyleSwitcherView()
let styleSwitcher = AssetListStyleSwitcher()

let manageButton = BadgedManageButton()

Expand Down Expand Up @@ -38,7 +38,7 @@ final class AssetListSettingsCell: UICollectionViewCell {
}

private func setupLocalization() {
styleSwitcher.locale = locale
styleSwitcher.controlContentView.locale = locale
}

private func setupLayout() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import UIKit
import SoraUI

class AssetListStyleSwitcher: ControlView<UIView, AssetListStyleSwitcherView> {
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
}
}
Loading
Loading