Skip to content

Commit

Permalink
multichain asset selection module
Browse files Browse the repository at this point in the history
  • Loading branch information
bnsports committed Aug 19, 2024
1 parent f36ff57 commit 880e219
Show file tree
Hide file tree
Showing 22 changed files with 695 additions and 6 deletions.
108 changes: 108 additions & 0 deletions fearless.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions fearless/Common/Extension/UIImage+ColorsInvert.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import UIKit

extension UIImage {
func invertedImage() -> UIImage? {
guard let cgImage = self.cgImage else { return nil }
let ciImage = CoreImage.CIImage(cgImage: cgImage)
guard let filter = CIFilter(name: "CIColorInvert") else { return nil }
filter.setDefaults()
filter.setValue(ciImage, forKey: kCIInputImageKey)
let context = CIContext(options: nil)
guard let outputImage = filter.outputImage else { return nil }
guard let outputImageCopy = context.createCGImage(outputImage, from: outputImage.extent) else { return nil }
return UIImage(cgImage: outputImageCopy, scale: scale, orientation: .up)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ final class SelectableListViewLayout: UIView {
emptyView.bind(viewModel: viewModel)
}

func bind(isEmbed: Bool) {
indicator.isHidden = isEmbed
layer.cornerRadius = isEmbed ? 0 : Constants.cornerRadius
}

private func setupLayout() {
layer.cornerRadius = Constants.cornerRadius
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation
import SSFModels

protocol MultichainAssetFetching {
func fetchAssets(for chain: ChainModel) async throws -> [ChainAsset]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation
import SSFModels

class OKXMultichainAssetFetching: MultichainAssetFetching {
func fetchAssets(for _: ChainModel) async throws -> [ChainAsset] {
[]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation
import SSFModels

enum MultichainChainFetchingFlow {
case okx
case preset(chainIds: [ChainModel.Id])
}

protocol MultichainChainFetching {
func fetchChains() async throws -> [ChainModel]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import SSFModels
import RobinHood

class OKXMultichainChainFetching: MultichainChainFetching {
private let chainsRepository: AsyncCoreDataRepositoryDefault<ChainModel, CDChain>

init(chainsRepository: AsyncCoreDataRepositoryDefault<ChainModel, CDChain>) {
self.chainsRepository = chainsRepository
}

func fetchChains() async throws -> [ChainModel] {
try await chainsRepository.fetchAll().filter { $0.rank != nil }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import UIKit
import SoraFoundation

final class MultichainAssetSelectionAssembly {
static func configureModule(flow: MultichainChainFetchingFlow, wallet: MetaAccountModel, selectAssetModuleOutput: SelectAssetModuleOutput?) -> MultichainAssetSelectionModuleCreationResult? {
let localizationManager = LocalizationManager.shared

let interactor = MultichainAssetSelectionInteractor(
chainFetching: buildChainFetching(flow: flow),
assetFetching: buildAssetFetching(flow: flow)
)
let router = MultichainAssetSelectionRouter()

let presenter = MultichainAssetSelectionPresenter(
interactor: interactor,
router: router,
localizationManager: localizationManager,
viewModelFactory: MultichainAssetSelectionViewModelFactoryImpl(),
logger: Logger.shared,
selectAssetModuleOutput: selectAssetModuleOutput
)
guard let selectAssetModule = createSelectAssetModule(wallet: wallet, moduleOutput: presenter) else {
return nil
}

presenter.selectAssetModuleInput = selectAssetModule.input

let view = MultichainAssetSelectionViewController(
output: presenter,
localizationManager: localizationManager,
selectAssetViewController: selectAssetModule.view.controller
)

return (view, presenter)
}

private static func buildChainFetching(flow _: MultichainChainFetchingFlow) -> MultichainChainFetching {
let chainsRepository = ChainRepositoryFactory().createAsyncRepository()
return OKXMultichainChainFetching(chainsRepository: chainsRepository)
}

private static func buildAssetFetching(flow _: MultichainChainFetchingFlow) -> MultichainAssetFetching {
OKXMultichainAssetFetching()
}

private static func createSelectAssetModule(wallet: MetaAccountModel, moduleOutput: SelectAssetModuleOutput) -> SelectAssetModuleCreationResult? {
SelectAssetAssembly.configureModule(wallet: wallet, selectedAssetId: nil, chainAssets: nil, searchTextsViewModel: .searchAssetPlaceholder, output: moduleOutput, isEmbed: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import UIKit
import SSFModels

protocol MultichainAssetSelectionInteractorOutput: AnyObject {}

final class MultichainAssetSelectionInteractor {
// MARK: - Private properties

private weak var output: MultichainAssetSelectionInteractorOutput?
private let chainFetching: MultichainChainFetching
private let assetFetching: MultichainAssetFetching

init(chainFetching: MultichainChainFetching, assetFetching: MultichainAssetFetching) {
self.chainFetching = chainFetching
self.assetFetching = assetFetching
}
}

// MARK: - MultichainAssetSelectionInteractorInput

extension MultichainAssetSelectionInteractor: MultichainAssetSelectionInteractorInput {
func setup(with output: MultichainAssetSelectionInteractorOutput) {
self.output = output
}

func fetchChains() async throws -> [ChainModel] {
try await chainFetching.fetchChains()
}

func fetchAssets(for chain: ChainModel) async throws -> [ChainAsset] {
try await assetFetching.fetchAssets(for: chain)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Foundation
import SoraFoundation
import SSFModels

@MainActor
protocol MultichainAssetSelectionViewInput: ControllerBackedProtocol {
func didReceive(viewModels: [ChainSelectionCollectionCellModel])
}

protocol MultichainAssetSelectionInteractorInput: AnyObject {
func setup(with output: MultichainAssetSelectionInteractorOutput)
func fetchChains() async throws -> [ChainModel]
func fetchAssets(for chain: ChainModel) async throws -> [ChainAsset]
}

final class MultichainAssetSelectionPresenter {
// MARK: Private properties

private weak var view: MultichainAssetSelectionViewInput?
private let router: MultichainAssetSelectionRouterInput
private let interactor: MultichainAssetSelectionInteractorInput
private let viewModelFactory: MultichainAssetSelectionViewModelFactory
private let logger: LoggerProtocol
private let selectAssetModuleOutput: SelectAssetModuleOutput?
weak var selectAssetModuleInput: SelectAssetModuleInput?
private var selectedChainId: ChainModel.Id?
private var chains: [ChainModel]?

// MARK: - Constructors

init(
interactor: MultichainAssetSelectionInteractorInput,
router: MultichainAssetSelectionRouterInput,
localizationManager: LocalizationManagerProtocol,
viewModelFactory: MultichainAssetSelectionViewModelFactory,
logger: LoggerProtocol,
selectAssetModuleOutput: SelectAssetModuleOutput?
) {
self.interactor = interactor
self.router = router
self.viewModelFactory = viewModelFactory
self.logger = logger
self.selectAssetModuleOutput = selectAssetModuleOutput

self.localizationManager = localizationManager
}

// MARK: - Private methods

private func provideViewModel() {
Task {
let viewModels = viewModelFactory.buildViewModels(chains: chains.or([]), selectedChainId: selectedChainId)
await view?.didReceive(viewModels: viewModels)
}
}

private func fetchChains() {
Task {
do {
let chains = try await interactor.fetchChains()
self.chains = chains

if selectedChainId == nil {
selectedChainId = chains.first?.chainId
selectAssetModuleInput?.update(with: (chains.first?.chainAssets).or([]))
}

let viewModels = viewModelFactory.buildViewModels(chains: chains, selectedChainId: selectedChainId)
await view?.didReceive(viewModels: viewModels)
} catch {
logger.customError(error)
}
}
}
}

// MARK: - MultichainAssetSelectionViewOutput

extension MultichainAssetSelectionPresenter: MultichainAssetSelectionViewOutput {
func didLoad(view: MultichainAssetSelectionViewInput) {
self.view = view
interactor.setup(with: self)

fetchChains()
}

func didSelect(chain: ChainModel) {
selectAssetModuleInput?.update(with: chain.chainAssets)
selectedChainId = chain.chainId
provideViewModel()
}

func didTapCloseButton() {
router.dismiss(view: view)
}
}

// MARK: - MultichainAssetSelectionInteractorOutput

extension MultichainAssetSelectionPresenter: MultichainAssetSelectionInteractorOutput {}

// MARK: - Localizable

extension MultichainAssetSelectionPresenter: Localizable {
func applyLocalization() {}
}

extension MultichainAssetSelectionPresenter: MultichainAssetSelectionModuleInput {}

extension MultichainAssetSelectionPresenter: SelectAssetModuleOutput {
func assetSelection(didCompleteWith chainAsset: ChainAsset?, contextTag: Int?) {
selectAssetModuleOutput?.assetSelection(didCompleteWith: chainAsset, contextTag: contextTag)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
typealias MultichainAssetSelectionModuleCreationResult = (
view: MultichainAssetSelectionViewInput,
input: MultichainAssetSelectionModuleInput
)

protocol MultichainAssetSelectionRouterInput: AnyObject, AnyDismissable {}

protocol MultichainAssetSelectionModuleInput: AnyObject {}

protocol MultichainAssetSelectionModuleOutput: AnyObject {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Foundation

final class MultichainAssetSelectionRouter: MultichainAssetSelectionRouterInput {}
Loading

0 comments on commit 880e219

Please sign in to comment.