Skip to content

Commit

Permalink
Update ActivateSubscription module to handle subscriptions list itself
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed Jul 25, 2023
1 parent 7217c5a commit ab4e039
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10672,7 +10672,7 @@
repositoryURL = "https://github.com/horizontalsystems/MarketKit.Swift";
requirement = {
kind = exactVersion;
version = 2.1.4;
version = 2.1.5;
};
};
D3604E7128F03B0A0066C366 /* XCRemoteSwiftPackageReference "ScanQrKit.Swift" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import ThemeKit

struct ActivateSubscriptionModule {

static func viewController(address: String) -> UIViewController? {
guard let service = ActivateSubscriptionService(
address: address,
static func viewController() -> UIViewController {
let service = ActivateSubscriptionService(
marketKit: App.shared.marketKit,
subscriptionManager: App.shared.subscriptionManager,
accountManager: App.shared.accountManager
) else {
return nil
}
)

let viewModel = ActivateSubscriptionViewModel(service: service)
let viewController = ActivateSubscriptionViewController(viewModel: viewModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,115 @@ import HsToolKit
import HsExtensions

class ActivateSubscriptionService {
let account: Account
let evmAddress: EvmKit.Address
private let marketKit: MarketKit.Kit
private let subscriptionManager: SubscriptionManager
private let accountManager: AccountManager
private var tasks = Set<AnyTask>()

@PostPublished private(set) var messageItem: MessageItem?
@PostPublished private(set) var state: State = .fetchingMessage
@PostPublished private(set) var state: State = .loading
@PostPublished private(set) var activationState: ActivationState = .ready

init?(address: String, marketKit: MarketKit.Kit, subscriptionManager: SubscriptionManager, accountManager: AccountManager) {
var resolvedAccount: Account?
var resolvedEvmAddress: EvmKit.Address?
private let activatedSubject = PassthroughSubject<Void, Never>()
private let activationErrorSubject = PassthroughSubject<Error, Never>()

for account in accountManager.accounts {
if let evmAddress = account.type.evmAddress(chain: App.shared.evmBlockchainManager.chain(blockchainType: .ethereum)),
evmAddress.hex.caseInsensitiveCompare(address) == .orderedSame {
resolvedAccount = account
resolvedEvmAddress = evmAddress
break
}
}

guard let resolvedAccount, let resolvedEvmAddress else {
return nil
}

account = resolvedAccount
evmAddress = resolvedEvmAddress
init(marketKit: MarketKit.Kit, subscriptionManager: SubscriptionManager, accountManager: AccountManager) {
self.marketKit = marketKit
self.subscriptionManager = subscriptionManager
self.accountManager = accountManager

fetchMessage()
fetchSubscriptions()
}

private func fetchMessage() {
state = .fetchingMessage
private func fetchSubscriptions() {
let addressItems: [AddressItem] = accountManager.accounts.compactMap { account in
guard let address = account.type.evmAddress(chain: App.shared.evmBlockchainManager.chain(blockchainType: .ethereum)) else {
return nil
}

return AddressItem(account: account, address: address)
}

Task { [weak self, marketKit, evmAddress] in
guard !addressItems.isEmpty else {
state = .noSubscriptions
return
}

state = .loading

let addresses = addressItems.map { $0.address.hex }

Task { [weak self, marketKit] in
do {
let message = try await marketKit.authKey(address: evmAddress.hex)
self?.handle(message: message)
let subscriptions = try await marketKit.subscriptions(addresses: addresses)
self?.handle(subscriptions: subscriptions, addressItems: addressItems)
} catch {
self?.state = .failedToFetchMessage(error: error)
self?.state = .failed(error: error)
}
}.store(in: &tasks)
}

private func handle(message: String) {
messageItem = MessageItem(
account: account,
address: evmAddress,
message: message
)
private func handle(subscriptions: [ProSubscription], addressItems: [AddressItem]) {
let address = subscriptions.sorted { lhs, rhs in lhs.deadline > rhs.deadline }.first?.address

state = .readyToActivate
}
guard let address else {
state = .noSubscriptions
return
}

private func handle(token: String) {
subscriptionManager.set(authToken: token)
let addressItem = addressItems.first { addressItem in
addressItem.address.hex.caseInsensitiveCompare(address) == .orderedSame
}

state = .activated
guard let addressItem else {
state = .noSubscriptions
return
}

Task { [weak self, marketKit] in
do {
let message = try await marketKit.authKey(address: addressItem.address.hex)
self?.state = .readyToActivate(message: message, account: addressItem.account, address: addressItem.address)
} catch {
self?.state = .failed(error: error)
}
}.store(in: &tasks)
}

}

extension ActivateSubscriptionService {

var activatedPublisher: AnyPublisher<Void, Never> {
activatedSubject.eraseToAnyPublisher()
}

var activationErrorPublisher: AnyPublisher<Error, Never> {
activationErrorSubject.eraseToAnyPublisher()
}

func retry() {
fetchMessage()
fetchSubscriptions()
}

func sign() {
guard let messageData = messageItem?.message.data(using: .utf8), let signedData = account.type.sign(message: messageData) else {
guard case let .readyToActivate(message, account, address) = state else {
return
}

guard let messageData = message.data(using: .utf8), let signedData = account.type.sign(message: messageData) else {
return
}

state = .activating
activationState = .activating

Task { [weak self, marketKit, evmAddress] in
Task { [weak self, marketKit] in
do {
let token = try await marketKit.authenticate(signature: signedData.hs.hexString, address: evmAddress.hex)
self?.handle(token: token)
let token = try await marketKit.authenticate(signature: signedData.hs.hexString, address: address.hex)
self?.subscriptionManager.set(authToken: token)
self?.activatedSubject.send()
} catch {
self?.state = .failedToActivate(error: error)
self?.activationState = .ready
self?.activationErrorSubject.send(error)
}
}.store(in: &tasks)
}
Expand All @@ -100,19 +123,21 @@ extension ActivateSubscriptionService {

extension ActivateSubscriptionService {

struct MessageItem {
private struct AddressItem {
let account: Account
let address: EvmKit.Address
let message: String
}

enum State {
case fetchingMessage
case readyToActivate
case loading
case noSubscriptions
case readyToActivate(message: String, account: Account, address: EvmKit.Address)
case failed(error: Error)
}

enum ActivationState {
case ready
case activating
case activated
case failedToFetchMessage(error: Error)
case failedToActivate(error: Error)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ class ActivateSubscriptionViewController: ThemeViewController {
private let tableView = SectionsTableView(style: .grouped)
private let spinner = HUDActivityView.create(with: .medium24)
private let errorView = PlaceholderViewModule.reachabilityView()
private let noSubscriptionsView = PlaceholderView()

private let buttonsHolder = BottomGradientHolder()
private let signButton = PrimaryButton()
private let activatingButton = PrimaryButton()
private let cancelButton = PrimaryButton()
private let rejectButton = PrimaryButton()

private var viewItem: ActivateSubscriptionViewModel.ViewItem?

Expand All @@ -37,6 +38,9 @@ class ActivateSubscriptionViewController: ThemeViewController {

title = "activate_subscription.title".localized

navigationItem.largeTitleDisplayMode = .never
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "button.close".localized, style: .plain, target: self, action: #selector(onTapClose))

view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.leading.top.trailing.equalToSuperview()
Expand All @@ -48,7 +52,7 @@ class ActivateSubscriptionViewController: ThemeViewController {

view.addSubview(spinner)
spinner.snp.makeConstraints { make in
make.center.equalTo(tableView)
make.center.equalToSuperview()
}

spinner.startAnimating()
Expand All @@ -60,22 +64,31 @@ class ActivateSubscriptionViewController: ThemeViewController {

errorView.configureSyncError(action: { [weak self] in self?.viewModel.onTapRetry() })

view.addSubview(noSubscriptionsView)
noSubscriptionsView.snp.makeConstraints { maker in
maker.edges.equalToSuperview()
}

noSubscriptionsView.image = UIImage(named: "sync_error_48")?.withTintColor(.themeGray)
noSubscriptionsView.text = "activate_subscription.no_subscriptions".localized
noSubscriptionsView.addPrimaryButton(style: .yellow, title: "subscription_info.get_premium".localized, target: self, action: #selector(onTapGetPremium))

buttonsHolder.add(to: self, under: tableView)
buttonsHolder.addSubview(signButton)

signButton.set(style: .yellow)
signButton.setTitle("activate_subscription.sign".localized, for: .normal)
signButton.addTarget(self, action: #selector(onTapSignButton), for: .touchUpInside)
signButton.addTarget(self, action: #selector(onTapSign), for: .touchUpInside)

buttonsHolder.addSubview(activatingButton)
activatingButton.set(style: .yellow, accessoryType: .spinner)
activatingButton.isEnabled = false
activatingButton.setTitle("activate_subscription.activating".localized, for: .normal)

buttonsHolder.addSubview(cancelButton)
cancelButton.set(style: .gray)
cancelButton.setTitle("button.cancel".localized, for: .normal)
cancelButton.addTarget(self, action: #selector(onTapCancelButton), for: .touchUpInside)
buttonsHolder.addSubview(rejectButton)
rejectButton.set(style: .gray)
rejectButton.setTitle("button.reject".localized, for: .normal)
rejectButton.addTarget(self, action: #selector(onTapReject), for: .touchUpInside)

viewModel.$spinnerVisible
.receive(on: DispatchQueue.main)
Expand All @@ -87,11 +100,23 @@ class ActivateSubscriptionViewController: ThemeViewController {
.sink { [weak self] visible in self?.errorView.isHidden = !visible }
.store(in: &cancellables)

viewModel.$noSubscriptionsVisible
.receive(on: DispatchQueue.main)
.sink { [weak self] visible in self?.noSubscriptionsView.isHidden = !visible }
.store(in: &cancellables)

viewModel.$viewItem
.receive(on: DispatchQueue.main)
.sink { [weak self] viewItem in
self?.viewItem = viewItem
self?.tableView.reload()
if let viewItem {
self?.viewItem = viewItem
self?.tableView.reload()
self?.tableView.isHidden = false
self?.buttonsHolder.isHidden = false
} else {
self?.tableView.isHidden = true
self?.buttonsHolder.isHidden = true
}
}
.store(in: &cancellables)

Expand All @@ -105,6 +130,11 @@ class ActivateSubscriptionViewController: ThemeViewController {
.sink { [weak self] visible in self?.activatingButton.isHidden = !visible }
.store(in: &cancellables)

viewModel.$rejectEnabled
.receive(on: DispatchQueue.main)
.sink { [weak self] enabled in self?.rejectButton.isEnabled = enabled }
.store(in: &cancellables)

viewModel.errorPublisher
.receive(on: DispatchQueue.main)
.sink { text in HudHelper.instance.showErrorBanner(title: text) }
Expand All @@ -117,15 +147,21 @@ class ActivateSubscriptionViewController: ThemeViewController {
self?.dismiss(animated: true)
}
.store(in: &cancellables)

tableView.buildSections()
}

@objc private func onTapSignButton() {
@objc private func onTapSign() {
viewModel.onTapSign()
}

@objc private func onTapCancelButton() {
@objc private func onTapReject() {
dismiss(animated: true)
}

@objc private func onTapGetPremium() {
UrlManager.open(url: AppConfig.analyticsLink, inAppController: self)
}

@objc private func onTapClose() {
dismiss(animated: true)
}

Expand Down
Loading

0 comments on commit ab4e039

Please sign in to comment.