Swiftgram/submodules/WalletUI/Sources/WalletConfgurationScreen.swift
2019-10-29 23:26:08 +04:00

299 lines
12 KiB
Swift

import Foundation
import UIKit
import AppBundle
import AsyncDisplayKit
import Display
import SwiftSignalKit
import OverlayStatusController
import WalletCore
private final class WalletConfigurationScreenArguments {
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void
let dismissInput: () -> Void
let updateSelectedMode: (WalletConfigurationScreenMode) -> Void
let updateBlockchainName: (String) -> Void
init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void, updateSelectedMode: @escaping (WalletConfigurationScreenMode) -> Void, updateBlockchainName: @escaping (String) -> Void) {
self.updateState = updateState
self.dismissInput = dismissInput
self.updateSelectedMode = updateSelectedMode
self.updateBlockchainName = updateBlockchainName
}
}
private enum WalletConfigurationScreenMode {
case url
case customString
}
private enum WalletConfigurationScreenSection: Int32 {
case mode
case configString
case blockchainName
}
private enum WalletConfigurationScreenEntryTag: ItemListItemTag {
case configStringText
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? WalletConfigurationScreenEntryTag {
return self == other
} else {
return false
}
}
}
private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
case modeUrl(WalletTheme, String, Bool)
case modeCustomString(WalletTheme, String, Bool)
case configUrl(WalletTheme, WalletStrings, String, String)
case configString(WalletTheme, String, String)
case blockchainNameHeader(WalletTheme, String)
case blockchainName(WalletTheme, WalletStrings, String, String)
var section: ItemListSectionId {
switch self {
case .modeUrl, .modeCustomString:
return WalletConfigurationScreenSection.mode.rawValue
case .configUrl, .configString:
return WalletConfigurationScreenSection.configString.rawValue
case .blockchainNameHeader, .blockchainName:
return WalletConfigurationScreenSection.blockchainName.rawValue
}
}
var stableId: Int32 {
switch self {
case .modeUrl:
return 0
case .modeCustomString:
return 1
case .configUrl:
return 2
case .configString:
return 3
case .blockchainNameHeader:
return 4
case .blockchainName:
return 5
}
}
static func <(lhs: WalletConfigurationScreenEntry, rhs: WalletConfigurationScreenEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! WalletConfigurationScreenArguments
switch self {
case let .modeUrl(theme, text, isSelected):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateSelectedMode(.url)
})
case let .modeCustomString(theme, text, isSelected):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateSelectedMode(.customString)
})
case let .configUrl(theme, strings, placeholder, text):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in
arguments.updateState { state in
var state = state
state.configUrl = value
return state
}
}, action: {})
case let .configString(theme, placeholder, text):
return ItemListMultilineInputItem(theme: theme, text: text, placeholder: placeholder, maxLength: nil, sectionId: self.section, style: .blocks, capitalization: false, autocorrection: false, returnKeyType: .done, minimalHeight: nil, textUpdated: { value in
arguments.updateState { state in
var state = state
state.configString = value
return state
}
}, shouldUpdateText: { _ in
return true
}, processPaste: nil, updatedFocus: nil, tag: WalletConfigurationScreenEntryTag.configStringText, action: nil, inlineAction: nil)
case let .blockchainNameHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .blockchainName(theme, strings, title, value):
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: value, placeholder: title, sectionId: self.section, textUpdated: { value in
arguments.updateBlockchainName(value)
}, action: {})
}
}
}
private struct WalletConfigurationScreenState: Equatable {
var mode: WalletConfigurationScreenMode
var configUrl: String
var configString: String
var blockchainName: String
var isEmpty: Bool {
if self.blockchainName.isEmpty {
return true
}
switch self.mode {
case .url:
return self.configUrl.isEmpty || URL(string: self.configUrl) == nil
case .customString:
return self.configString.isEmpty
}
}
}
private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] {
var entries: [WalletConfigurationScreenEntry] = []
entries.append(.modeUrl(presentationData.theme, "URL", state.mode == .url))
entries.append(.modeCustomString(presentationData.theme, "Custom", state.mode == .customString))
switch state.mode {
case .url:
entries.append(.configUrl(presentationData.theme, presentationData.strings, "URL", state.configUrl))
case .customString:
entries.append(.configString(presentationData.theme, "JSON", state.configString))
}
entries.append(.blockchainNameHeader(presentationData.theme, "BLOCKCHAIN NAME"))
entries.append(.blockchainName(presentationData.theme, presentationData.strings, "Blockchain Name", state.blockchainName))
return entries
}
protocol WalletConfigurationScreen {
}
private final class WalletConfigurationScreenImpl: ItemListController, WalletConfigurationScreen {
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
return CGSize(width: layout.size.width, height: layout.size.height - 174.0)
}
}
func walletConfigurationScreen(context: WalletContext, currentConfiguration: LocalWalletConfiguration) -> ViewController {
var configUrl = ""
var configString = ""
switch currentConfiguration.source {
case let .url(url):
configUrl = url
case let .string(string):
configString = string
}
let initialState = WalletConfigurationScreenState(mode: configString.isEmpty ? .url : .customString, configUrl: configUrl, configString: configString, blockchainName: currentConfiguration.blockchainName)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
var ensureItemVisibleImpl: ((WalletConfigurationScreenEntryTag, Bool) -> Void)?
weak var currentStatusController: ViewController?
let arguments = WalletConfigurationScreenArguments(updateState: { f in
updateState(f)
}, dismissInput: {
dismissInputImpl?()
}, updateSelectedMode: { mode in
updateState { state in
var state = state
state.mode = mode
return state
}
}, updateBlockchainName: { value in
updateState { state in
var state = state
state.blockchainName = value
return state
}
})
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Configuration_Apply), style: .bold, enabled: !state.isEmpty, action: {
let state = stateValue.with { $0 }
let source: LocalWalletConfigurationSource
let blockchainName = state.blockchainName
if blockchainName.isEmpty {
return
}
switch state.mode {
case .url:
if state.configUrl.isEmpty {
return
} else {
source = .url(state.configUrl)
}
case .customString:
if state.configString.isEmpty {
return
} else {
source = .string(state.configString)
}
}
let _ = (context.storage.updateLocalWalletConfiguration { current in
var current = current
current.source = source
current.blockchainName = blockchainName
return current
}
|> deliverOnMainQueue).start(completed: {
dismissImpl?()
})
})
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_Configuration_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
let listState = ItemListNodeState(entries: walletConfigurationScreenEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
let controller = WalletConfigurationScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
controller.navigationPresentation = .modal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
controller.experimentalSnapScrollToItem = true
controller.didAppear = { _ in
}
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
pushImpl = { [weak controller] c in
controller?.push(c)
}
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
let _ = controller?.dismiss()
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
}
ensureItemVisibleImpl = { [weak controller] targetTag, animated in
controller?.afterLayout({
guard let controller = controller else {
return
}
var resultItemNode: ListViewItemNode?
let state = stateValue.with({ $0 })
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListItemNode {
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
resultItemNode = itemNode as? ListViewItemNode
return true
}
}
return false
})
if let resultItemNode = resultItemNode {
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
}
})
}
return controller
}