mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Wallet improvements
This commit is contained in:
parent
4d5f28aa4c
commit
5bb1ba67b7
@ -44,6 +44,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -65,6 +67,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -44,6 +44,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -65,6 +67,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -44,6 +44,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -65,6 +67,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -44,6 +44,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -65,6 +67,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -44,6 +44,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -65,6 +67,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -2549,6 +2549,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -2570,6 +2572,8 @@
|
||||
ReferencedContainer = "container:Project.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -167,6 +167,7 @@ public enum ResolvedUrl {
|
||||
case share(url: String?, text: String?, to: String?)
|
||||
case wallpaper(WallpaperUrlParameter)
|
||||
case theme(String)
|
||||
case wallet(address: String, amount: Int64?, comment: String?)
|
||||
}
|
||||
|
||||
public enum NavigateToChatKeepStack {
|
||||
@ -368,6 +369,11 @@ public final class ContactSelectionControllerParams {
|
||||
}
|
||||
}
|
||||
|
||||
public enum OpenWalletContext {
|
||||
case generic
|
||||
case send(address: String, amount: Int64?, comment: String?)
|
||||
}
|
||||
|
||||
public let defaultContactLabel: String = "_$!<Mobile>!$_"
|
||||
|
||||
public enum CreateGroupMode {
|
||||
@ -434,6 +440,7 @@ public protocol SharedAccountContext: class {
|
||||
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
|
||||
func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void)
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
|
@ -60,11 +60,13 @@ private final class CameraContext {
|
||||
self.session.startRunning()
|
||||
}
|
||||
|
||||
func stopCapture() {
|
||||
self.session.beginConfiguration()
|
||||
self.input.invalidate(for: self.session)
|
||||
self.output.invalidate(for: self.session)
|
||||
self.session.commitConfiguration()
|
||||
func stopCapture(invalidate: Bool = false) {
|
||||
if invalidate {
|
||||
self.session.beginConfiguration()
|
||||
self.input.invalidate(for: self.session)
|
||||
self.output.invalidate(for: self.session)
|
||||
self.session.commitConfiguration()
|
||||
}
|
||||
|
||||
self.session.stopRunning()
|
||||
}
|
||||
@ -143,10 +145,10 @@ public final class Camera {
|
||||
}
|
||||
}
|
||||
|
||||
public func stopCapture() {
|
||||
public func stopCapture(invalidate: Bool = false) {
|
||||
self.queue.async {
|
||||
if let context = self.contextRef?.takeUnretainedValue() {
|
||||
context.stopCapture()
|
||||
context.stopCapture(invalidate: invalidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -813,9 +813,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor * UIView.animationDurationFactor(), from: 0.0, to: 0.999, update: { [weak self] value in
|
||||
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
|
||||
}, completion: {
|
||||
completedEffect = true
|
||||
intermediateCompletion()
|
||||
}, completion: {
|
||||
completedEffect = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
}
|
||||
self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
|
@ -524,8 +524,8 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
||||
}
|
||||
}
|
||||
|
||||
public func ensureItemNodeVisible(_ itemNode: ListViewItemNode) {
|
||||
(self.displayNode as! ItemListControllerNode<Entry>).listNode.ensureItemNodeVisible(itemNode)
|
||||
public func ensureItemNodeVisible(_ itemNode: ListViewItemNode, animated: Bool = true) {
|
||||
(self.displayNode as! ItemListControllerNode<Entry>).listNode.ensureItemNodeVisible(itemNode, animated: animated)
|
||||
}
|
||||
|
||||
public func afterLayout(_ f: @escaping () -> Void) {
|
||||
|
@ -37,12 +37,14 @@ public class ItemListMultilineInputItem: ListViewItem, ItemListItem {
|
||||
let action: (() -> Void)?
|
||||
let textUpdated: (String) -> Void
|
||||
let shouldUpdateText: (String) -> Bool
|
||||
let processPaste: ((String) -> Void)?
|
||||
let updatedFocus: ((Bool) -> Void)?
|
||||
let maxLength: ItemListMultilineInputItemTextLimit?
|
||||
let minimalHeight: CGFloat?
|
||||
let inlineAction: ItemListMultilineInputInlineAction?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(theme: PresentationTheme, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil) {
|
||||
public init(theme: PresentationTheme, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil) {
|
||||
self.theme = theme
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
@ -55,6 +57,8 @@ public class ItemListMultilineInputItem: ListViewItem, ItemListItem {
|
||||
self.minimalHeight = minimalHeight
|
||||
self.textUpdated = textUpdated
|
||||
self.shouldUpdateText = shouldUpdateText
|
||||
self.processPaste = processPaste
|
||||
self.updatedFocus = updatedFocus
|
||||
self.tag = tag
|
||||
self.action = action
|
||||
self.inlineAction = inlineAction
|
||||
@ -365,15 +369,27 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
||||
self.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: max(0.0, params.width - leftInset - params.rightInset), height: max(0.0, contentSize.height - textTopInset - textBottomInset)))
|
||||
}
|
||||
|
||||
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.item?.updatedFocus?(true)
|
||||
}
|
||||
|
||||
public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.item?.updatedFocus?(false)
|
||||
}
|
||||
|
||||
public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
if let item = self.item {
|
||||
if text.count > 1, let processPaste = item.processPaste {
|
||||
processPaste(text)
|
||||
return false
|
||||
}
|
||||
|
||||
if let action = item.action, text == "\n" {
|
||||
action()
|
||||
return false
|
||||
}
|
||||
|
||||
var newText: String = editableTextNode.textView.text
|
||||
newText.replaceSubrange(newText.index(newText.startIndex, offsetBy: range.lowerBound) ..< newText.index(newText.startIndex, offsetBy: range.upperBound), with: text)
|
||||
let newText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if !item.shouldUpdateText(newText) {
|
||||
return false
|
||||
}
|
||||
|
@ -400,8 +400,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
|
||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
if let item = self.item {
|
||||
var newText = textField.text ?? ""
|
||||
newText.replaceSubrange(newText.index(newText.startIndex, offsetBy: range.lowerBound) ..< newText.index(newText.startIndex, offsetBy: range.upperBound), with: string)
|
||||
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||
if !item.shouldUpdateText(newText) {
|
||||
return false
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
|
||||
_backgroundView.image = backgroundImage;
|
||||
[_wrapperView addSubview:_backgroundView];
|
||||
|
||||
_countLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -0.5f, frame.size.width, frame.size.height)];
|
||||
_countLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -0.5f, frame.size.width + 1.0, frame.size.height)];
|
||||
_countLabel.backgroundColor = [UIColor clearColor];
|
||||
_countLabel.font = [TGFont roundedFontOfSize:17];
|
||||
_countLabel.text = [TGStringUtils stringWithLocalizedNumber:0];
|
||||
@ -292,7 +292,7 @@ const CGFloat TGPhotoCounterButtonMaskFade = 18;
|
||||
if (sizeToFit)
|
||||
[_countLabel sizeToFit];
|
||||
|
||||
CGFloat labelWidth = CGRound(_countLabel.frame.size.width);
|
||||
CGFloat labelWidth = ceilf(_countLabel.frame.size.width);
|
||||
CGFloat labelOrigin = 0.0f;
|
||||
|
||||
if (![self _useRtlLayout])
|
||||
|
@ -10,7 +10,7 @@ public enum QrCodeIcon {
|
||||
case custom(UIImage?)
|
||||
}
|
||||
|
||||
public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = nil, icon: QrCodeIcon, ecl: String = "M", scale: CGFloat = 0.0) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = nil, icon: QrCodeIcon, ecl: String = "M") -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return Signal<CIImage, NoError> { subscriber in
|
||||
if let data = string.data(using: .isoLatin1, allowLossyConversion: false), let filter = CIFilter(name: "CIQRCodeGenerator") {
|
||||
filter.setValue(data, forKey: "inputMessage")
|
||||
@ -25,7 +25,7 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|
||||
}
|
||||
|> map { inputImage in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
|
||||
|
||||
let drawingRect = arguments.drawingRect
|
||||
let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
|
||||
|
@ -29,6 +29,8 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@ -40,6 +42,17 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E66DC04E89A74F8D00000000"
|
||||
BuildableName = "libSwiftSignalKit.dylib"
|
||||
BlueprintName = "SwiftSignalKit#shared"
|
||||
ReferencedContainer = "container:SwiftSignalKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -38,10 +38,10 @@ public final class ShareProxyServerActionSheetController: ActionSheetController
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: strings.SocksProxySetup_ShareQRCode, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy, scale: 1.0)
|
||||
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy)
|
||||
|> map { generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 512.0, height: 512.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
|
@ -12,6 +12,7 @@ import AccountContext
|
||||
import ShareController
|
||||
import SearchBarNode
|
||||
import SearchUI
|
||||
import ActivityIndicator
|
||||
|
||||
private enum LanguageListSection: ItemListSectionId {
|
||||
case official
|
||||
@ -194,12 +195,12 @@ private final class LocalizationListSearchContainerNode: SearchDisplayController
|
||||
|
||||
if self.hasValidLayout {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransition() {
|
||||
private func dequeueTransitions() {
|
||||
if let transition = self.enqueuedTransitions.first {
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
@ -248,7 +249,7 @@ private final class LocalizationListSearchContainerNode: SearchDisplayController
|
||||
if !self.hasValidLayout {
|
||||
self.hasValidLayout = true
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,17 +266,18 @@ private struct LanguageListNodeTransition {
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let firstTime: Bool
|
||||
let isLoading: Bool
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
private func preparedLanguageListNodeTransition(theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [LanguageListEntry], to toEntries: [LanguageListEntry], openSearch: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void, firstTime: Bool, forceUpdate: Bool, animated: Bool) -> LanguageListNodeTransition {
|
||||
private func preparedLanguageListNodeTransition(theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [LanguageListEntry], to toEntries: [LanguageListEntry], openSearch: @escaping () -> Void, selectLocalization: @escaping (LocalizationInfo) -> Void, setItemWithRevealedOptions: @escaping (String?, String?) -> Void, removeItem: @escaping (String) -> Void, firstTime: Bool, isLoading: Bool, forceUpdate: Bool, animated: Bool) -> LanguageListNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: theme, strings: strings, searchMode: false, openSearch: openSearch, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: theme, strings: strings, searchMode: false, openSearch: openSearch, selectLocalization: selectLocalization, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem), directionHint: nil) }
|
||||
|
||||
return LanguageListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, animated: animated)
|
||||
return LanguageListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, firstTime: firstTime, isLoading: isLoading, animated: animated)
|
||||
}
|
||||
|
||||
final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
@ -292,7 +294,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
let listNode: ListView
|
||||
private var queuedTransitions: [LanguageListNodeTransition] = []
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
private let presentationDataValue = Promise<(PresentationTheme, PresentationStrings)>()
|
||||
@ -408,7 +410,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
}
|
||||
let previousEntriesAndPresentationData = previousEntriesHolder.swap((entries, presentationData.0, presentationData.1))
|
||||
let transition = preparedLanguageListNodeTransition(theme: presentationData.0, strings: presentationData.1, from: previousEntriesAndPresentationData?.0 ?? [], to: entries, openSearch: openSearch, selectLocalization: { [weak self] info in self?.selectLocalization(info) }, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem, firstTime: previousEntriesAndPresentationData == nil, forceUpdate: previousEntriesAndPresentationData?.1 !== presentationData.0 || previousEntriesAndPresentationData?.2 !== presentationData.1, animated: (previousEntriesAndPresentationData?.0.count ?? 0) >= entries.count)
|
||||
let transition = preparedLanguageListNodeTransition(theme: presentationData.0, strings: presentationData.1, from: previousEntriesAndPresentationData?.0 ?? [], to: entries, openSearch: openSearch, selectLocalization: { [weak self] info in self?.selectLocalization(info) }, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem, firstTime: previousEntriesAndPresentationData == nil, isLoading: entries.isEmpty, forceUpdate: previousEntriesAndPresentationData?.1 !== presentationData.0 || previousEntriesAndPresentationData?.2 !== presentationData.1, animated: (previousEntriesAndPresentationData?.0.count ?? 0) >= entries.count)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
})
|
||||
self.updatedDisposable = synchronizedLocalizationListState(postbox: context.account.postbox, network: context.account.network).start()
|
||||
@ -469,6 +471,11 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
if let activityIndicator = self.activityIndicator {
|
||||
let indicatorSize = activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: updateSizeAndInsets.insets.top + 50.0 + floor((layout.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom - indicatorSize.height - 50.0) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
|
||||
if !hadValidLayout {
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
@ -483,26 +490,38 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
private func dequeueTransitions() {
|
||||
if self.containerLayout != nil {
|
||||
while !self.queuedTransitions.isEmpty {
|
||||
let transition = self.queuedTransitions.removeFirst()
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if transition.firstTime {
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.LowLatency)
|
||||
} else if transition.animated {
|
||||
options.insert(.AnimateInsertion)
|
||||
}
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
guard let (layout, navigationBarHeight) = self.containerLayout else {
|
||||
return
|
||||
}
|
||||
while !self.queuedTransitions.isEmpty {
|
||||
let transition = self.queuedTransitions.removeFirst()
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if transition.firstTime {
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.LowLatency)
|
||||
} else if transition.animated {
|
||||
options.insert(.AnimateInsertion)
|
||||
}
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf._ready.set(true)
|
||||
}
|
||||
|
||||
if transition.isLoading, strongSelf.activityIndicator == nil {
|
||||
let activityIndicator = ActivityIndicator(type: .custom(strongSelf.presentationData.theme.list.itemAccentColor, 22.0, 1.0, false))
|
||||
strongSelf.activityIndicator = activityIndicator
|
||||
strongSelf.insertSubnode(activityIndicator, aboveSubnode: strongSelf.listNode)
|
||||
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
} else if !transition.isLoading, let activityIndicator = strongSelf.activityIndicator {
|
||||
strongSelf.activityIndicator = nil
|
||||
activityIndicator.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,8 @@ extension SettingsSearchableItemIcon {
|
||||
return PresentationResourcesSettings.watch
|
||||
case .passport:
|
||||
return PresentationResourcesSettings.passport
|
||||
case .wallet:
|
||||
return PresentationResourcesSettings.wallet
|
||||
case .support:
|
||||
return PresentationResourcesSettings.support
|
||||
case .faq:
|
||||
|
@ -27,6 +27,7 @@ enum SettingsSearchableItemIcon {
|
||||
case appearance
|
||||
case language
|
||||
case watch
|
||||
case wallet
|
||||
case passport
|
||||
case support
|
||||
case faq
|
||||
@ -858,9 +859,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
|
||||
})
|
||||
allItems.append(passport)
|
||||
|
||||
if true || hasWallet {
|
||||
let wallet = SettingsSearchableItem(id: .wallet(0), title: "Wallet", alternate: synonyms("Wallet"), icon: .passport, breadcrumbs: [], present: { context, _, present in
|
||||
openWallet(context: context, push: { c in
|
||||
if hasWallet {
|
||||
let wallet = SettingsSearchableItem(id: .wallet(0), title: "Gram Wallet", alternate: synonyms(""), icon: .wallet, breadcrumbs: [], present: { context, _, present in
|
||||
context.sharedContext.openWallet(context: context, walletContext: .generic, present: { c in
|
||||
present(.push, c)
|
||||
})
|
||||
})
|
||||
|
@ -574,12 +574,12 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
|
||||
let languageName = presentationData.strings.primaryComponent.localizedName
|
||||
entries.append(.language(presentationData.theme, PresentationResourcesSettings.language, presentationData.strings.Settings_AppLanguage, languageName.isEmpty ? presentationData.strings.Localization_LanguageName : languageName))
|
||||
|
||||
if hasWallet || experimentalUISettings.wallets {
|
||||
entries.append(.wallet(presentationData.theme, PresentationResourcesSettings.wallet, "Gram Wallet", ""))
|
||||
}
|
||||
if hasPassport {
|
||||
entries.append(.passport(presentationData.theme, PresentationResourcesSettings.passport, presentationData.strings.Settings_Passport, ""))
|
||||
}
|
||||
if hasWallet || experimentalUISettings.wallets {
|
||||
entries.append(.wallet(presentationData.theme, PresentationResourcesSettings.passport, "Wallet", ""))
|
||||
}
|
||||
|
||||
if hasWatchApp {
|
||||
entries.append(.watch(presentationData.theme, PresentationResourcesSettings.watch, presentationData.strings.Settings_AppleWatch, ""))
|
||||
@ -850,7 +850,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
let _ = (contextValue.get()
|
||||
|> deliverOnMainQueue
|
||||
|> take(1)).start(next: { context in
|
||||
openWallet(context: context, push: { c in
|
||||
context.sharedContext.openWallet(context: context, walletContext: .generic, present: { c in
|
||||
pushControllerImpl?(c)
|
||||
})
|
||||
})
|
||||
@ -1098,8 +1098,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
)
|
||||
)
|
||||
|
||||
let hasWallet = .single(false)
|
||||
|> then(contextValue.get()
|
||||
let hasWallet = contextValue.get()
|
||||
|> mapToSignal { context in
|
||||
return context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> Bool in
|
||||
@ -1107,7 +1106,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
let configuration = WalletConfiguration.with(appConfiguration: appConfiguration)
|
||||
return configuration.enabled
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let hasPassport = ValuePromise<Bool>(false)
|
||||
let updatePassport: () -> Void = {
|
||||
@ -1618,36 +1617,3 @@ private func accountContextMenuItems(context: AccountContext, logout: @escaping
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
func openWallet(context: AccountContext, push: @escaping (ViewController) -> Void) {
|
||||
guard let tonContext = context.tonContext else {
|
||||
return
|
||||
}
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
availableWallets(postbox: context.account.postbox),
|
||||
tonContext.keychain.encryptionPublicKey()
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey in
|
||||
if wallets.wallets.isEmpty {
|
||||
if let _ = currentPublicKey {
|
||||
push(WalletSplashScreen(context: context, tonContext: tonContext, mode: .intro))
|
||||
} else {
|
||||
push(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageNotAvailable))
|
||||
}
|
||||
} else {
|
||||
let walletInfo = wallets.wallets[0].info
|
||||
if let currentPublicKey = currentPublicKey {
|
||||
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
|
||||
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { address in
|
||||
push(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address))
|
||||
})
|
||||
} else {
|
||||
push(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageReset(.changed)))
|
||||
}
|
||||
} else {
|
||||
push(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageReset(.notAvailable)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -384,7 +384,7 @@ public class WallpaperGalleryController: ViewController {
|
||||
let wallpaper = wallpaper.withUpdatedSettings(updatedSettings)
|
||||
|
||||
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers
|
||||
themeSpecificChatWallpapers[current.theme.index] = wallpaper
|
||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations)
|
||||
}) |> deliverOnMainQueue).start(completed: {
|
||||
|
@ -15,7 +15,8 @@ public struct PresentationResourcesSettings {
|
||||
public static let dataAndStorage = UIImage(bundleImageName: "Settings/MenuIcons/DataAndStorage")?.precomposed()
|
||||
public static let appearance = UIImage(bundleImageName: "Settings/MenuIcons/Appearance")?.precomposed()
|
||||
public static let language = UIImage(bundleImageName: "Settings/MenuIcons/Language")?.precomposed()
|
||||
|
||||
|
||||
public static let wallet = UIImage(bundleImageName: "Settings/MenuIcons/Wallet")?.precomposed()
|
||||
public static let passport = UIImage(bundleImageName: "Settings/MenuIcons/Passport")?.precomposed()
|
||||
public static let watch = UIImage(bundleImageName: "Settings/MenuIcons/Watch")?.precomposed()
|
||||
|
||||
|
@ -20,6 +20,7 @@ public func normalizeArabicNumeralString(_ string: String, type: ArabicNumeralSt
|
||||
("7", "٧", "۷"),
|
||||
("8", "٨", "۸"),
|
||||
("9", "٩", "۹"),
|
||||
(",", "٫", "٫")
|
||||
]
|
||||
for (western, arabic, persian) in numerals {
|
||||
switch type {
|
||||
|
22
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_ton@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_ton@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/ic_ton@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/ic_ton@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/ic_ton@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Wallet.imageset/ic_ton@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
12
submodules/TelegramUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_gallery (3).pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/ic_gallery (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/CameraGalleryIcon.imageset/ic_gallery (3).pdf
vendored
Normal file
Binary file not shown.
22
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "QrGem@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "QrGem@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Wallet/QrGem.imageset/QrGem@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -24,6 +24,8 @@ import WatchBridge
|
||||
import LegacyDataImport
|
||||
import SettingsUI
|
||||
import AppBundle
|
||||
import WalletUI
|
||||
import UrlHandling
|
||||
|
||||
private let handleVoipNotifications = false
|
||||
|
||||
@ -1479,6 +1481,9 @@ final class SharedApplicationContext {
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {})
|
||||
} else if let confirmationCode = parseConfirmationCodeUrl(url) {
|
||||
authContext.rootController.applyConfirmationCode(confirmationCode)
|
||||
} else if let _ = parseWalletUrl(url) {
|
||||
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
|
||||
authContext.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "Please log in to your account to use Gram Wallet.", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -46,6 +46,7 @@ import UrlHandling
|
||||
import ReactionSelectionNode
|
||||
import MessageReactionListUI
|
||||
import AppBundle
|
||||
import WalletUI
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -5078,8 +5079,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}, recognizedQRCode: { [weak self] code in
|
||||
if let strongSelf = self, let (host, port, username, password, secret) = parseProxyUrl(code) {
|
||||
strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret))
|
||||
if let strongSelf = self {
|
||||
if let (host, port, username, password, secret) = parseProxyUrl(code) {
|
||||
strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret))
|
||||
} else if let url = URL(string: code), let parsedWalletUrl = parseWalletUrl(url) {
|
||||
strongSelf.openResolved(ResolvedUrl.wallet(address: parsedWalletUrl.address, amount: parsedWalletUrl.amount, comment: parsedWalletUrl.comment))
|
||||
}
|
||||
}
|
||||
}, presentSchedulePicker: { [weak self] done in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -822,6 +822,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
break
|
||||
case .theme:
|
||||
break
|
||||
case .wallet:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -324,5 +324,9 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
controller?.dismiss()
|
||||
}))
|
||||
dismissInput()
|
||||
case let .wallet(address, amount, comment):
|
||||
context.sharedContext.openWallet(context: context, walletContext: .send(address: address, amount: amount, comment: comment)) { c in
|
||||
navigationController?.pushViewController(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import AccountContext
|
||||
import UrlEscaping
|
||||
import PassportUI
|
||||
import UrlHandling
|
||||
import WalletUI
|
||||
|
||||
public struct ParsedSecureIdUrl {
|
||||
public let peerId: PeerId
|
||||
@ -140,6 +141,12 @@ func formattedConfirmationCode(_ code: Int) -> String {
|
||||
|
||||
func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
||||
if url.hasPrefix("ton://") {
|
||||
if let url = URL(string: url), let parsedUrl = parseWalletUrl(url) {
|
||||
context.sharedContext.openWallet(context: context, walletContext: .send(address: parsedUrl.address, amount: parsedUrl.amount, comment: parsedUrl.comment)) { c in
|
||||
navigationController?.pushViewController(c)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import PeersNearbyUI
|
||||
import PeerInfoUI
|
||||
import SettingsUI
|
||||
import UrlHandling
|
||||
import WalletUI
|
||||
|
||||
private enum CallStatusText: Equatable {
|
||||
case none
|
||||
@ -1016,6 +1017,45 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public func makeChatMessagePreviewItem(context: AccountContext, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?) -> ListViewItem {
|
||||
return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: .peer(message.id.peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, isScheduledMessages: false, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus), controllerInteraction: defaultChatControllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), disableDate: true, additionalContent: nil)
|
||||
}
|
||||
|
||||
public func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void) {
|
||||
guard let tonContext = context.tonContext else {
|
||||
return
|
||||
}
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
availableWallets(postbox: context.account.postbox),
|
||||
tonContext.keychain.encryptionPublicKey()
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { wallets, currentPublicKey in
|
||||
if wallets.wallets.isEmpty {
|
||||
if let _ = currentPublicKey {
|
||||
present(WalletSplashScreen(context: context, tonContext: tonContext, mode: .intro))
|
||||
} else {
|
||||
present(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageNotAvailable))
|
||||
}
|
||||
} else {
|
||||
let walletInfo = wallets.wallets[0].info
|
||||
if let currentPublicKey = currentPublicKey {
|
||||
if currentPublicKey == walletInfo.encryptedSecret.publicKey {
|
||||
let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { address in
|
||||
switch walletContext {
|
||||
case .generic:
|
||||
present(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address))
|
||||
case let .send(address, amount, comment):
|
||||
present(walletSendScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address, amount: amount, comment: comment))
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
present(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageReset(.changed)))
|
||||
}
|
||||
} else {
|
||||
present(WalletSplashScreen(context: context, tonContext: tonContext, mode: .secureStorageReset(.notAvailable)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultChatControllerInteraction = ChatControllerInteraction.default
|
||||
|
@ -372,6 +372,11 @@ public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
|
||||
}
|
||||
|
||||
public func resolveUrlImpl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||
if url.hasPrefix("ton://") {
|
||||
if let url = URL(string: url), let parsedUrl = parseWalletUrl(url) {
|
||||
return .single(.wallet(address: parsedUrl.address, amount: parsedUrl.amount, comment: parsedUrl.comment))
|
||||
}
|
||||
}
|
||||
let schemes = ["http://", "https://", ""]
|
||||
let baseTelegramMePaths = ["telegram.me", "t.me"]
|
||||
for basePath in baseTelegramMePaths {
|
||||
@ -430,3 +435,42 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal<Resol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ParsedWalletUrl {
|
||||
public let address: String
|
||||
public let amount: Int64?
|
||||
public let comment: String?
|
||||
}
|
||||
|
||||
private let invalidWalletAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted
|
||||
|
||||
private func isValidWalletAddress(_ address: String) -> Bool {
|
||||
if address.count != 48 || address.rangeOfCharacter(from: invalidWalletAddressCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func parseWalletUrl(_ url: URL) -> ParsedWalletUrl? {
|
||||
guard url.scheme == "ton" else {
|
||||
return nil
|
||||
}
|
||||
var address: String?
|
||||
if let host = url.host, isValidWalletAddress(host) {
|
||||
address = host.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
||||
}
|
||||
var amount: Int64?
|
||||
var comment: String?
|
||||
if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "amount", !value.isEmpty, let amountValue = Int64(value) {
|
||||
amount = amountValue
|
||||
} else if queryItem.name == "text", !value.isEmpty {
|
||||
comment = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return address.flatMap { ParsedWalletUrl(address: $0, amount: amount, comment: comment) }
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ static_library(
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/GlassButtonNode:GlassButtonNode",
|
||||
"//submodules/UrlHandling:UrlHandling",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -115,13 +115,15 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
|
||||
let title = "Wallet Created"
|
||||
let text = "Your wallet address"
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.bold(32.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
let textColor = UIColor.black
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: Font.regular(16.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.bold(32.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: Font.regular(16.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var addressString = item.address
|
||||
addressString.insert("\n", at: addressString.index(addressString.startIndex, offsetBy: addressString.count / 2))
|
||||
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: addressString, font: Font.monospace(16.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: addressString, font: Font.monospace(16.0), textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentVerticalOrigin: CGFloat = 32.0
|
||||
|
||||
|
@ -513,7 +513,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
self.headerNode = WalletInfoHeaderNode(account: account, theme: presentationData.theme, sendAction: sendAction, receiveAction: receiveAction)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
||||
self.listNode.isHidden = true
|
||||
|
||||
@ -869,26 +869,3 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatBalanceText(_ value: Int64, decimalSeparator: String) -> String {
|
||||
var balanceText = "\(abs(value))"
|
||||
while balanceText.count < 10 {
|
||||
balanceText.insert("0", at: balanceText.startIndex)
|
||||
}
|
||||
balanceText.insert(contentsOf: decimalSeparator, at: balanceText.index(balanceText.endIndex, offsetBy: -9))
|
||||
while true {
|
||||
if balanceText.hasSuffix("0") {
|
||||
if balanceText.hasSuffix("\(decimalSeparator)0") {
|
||||
break
|
||||
} else {
|
||||
balanceText.removeLast()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if value < 0 {
|
||||
balanceText.insert("-", at: balanceText.startIndex)
|
||||
}
|
||||
return balanceText
|
||||
}
|
||||
|
@ -86,12 +86,6 @@ private let descriptionFont = Font.regular(15.0)
|
||||
private let dateFont = Font.regular(14.0)
|
||||
private let directionFont = Font.regular(15.0)
|
||||
|
||||
private func formatAddress(_ address: String) -> String {
|
||||
var address = address
|
||||
address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2))
|
||||
return address
|
||||
}
|
||||
|
||||
class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
|
@ -12,12 +12,17 @@ class WalletQrCodeItem: ListViewItem, ItemListItem {
|
||||
let address: String
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let action: (() -> Void)?
|
||||
let longTapAction: (() -> Void)?
|
||||
public let isAlwaysPlain: Bool = true
|
||||
|
||||
init(theme: PresentationTheme, address: String, sectionId: ItemListSectionId, style: ItemListStyle) {
|
||||
init(theme: PresentationTheme, address: String, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.address = address
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -55,10 +60,6 @@ class WalletQrCodeItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
|
||||
class WalletQrCodeItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
private var item: WalletQrCodeItem?
|
||||
@ -68,16 +69,6 @@ class WalletQrCodeItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
@ -85,6 +76,37 @@ class WalletQrCodeItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
self?.imageNode.alpha = point != nil ? 0.4 : 1.0
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
self.item?.action?()
|
||||
case .longTap:
|
||||
self.item?.longTapAction?()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletQrCodeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
|
||||
@ -98,7 +120,7 @@ class WalletQrCodeItemNode: ListViewItemNode {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
|
||||
if currentItem?.address != item.address {
|
||||
if currentItem?.address != item.address || updatedTheme != nil {
|
||||
updatedAddress = item.address
|
||||
}
|
||||
|
||||
@ -106,22 +128,16 @@ class WalletQrCodeItemNode: ListViewItemNode {
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let inset: CGFloat = 12.0
|
||||
var imageSize = CGSize(width: 256.0, height: 256.0)
|
||||
let inset: CGFloat = 0.0
|
||||
var imageSize = CGSize(width: 128.0, height: 128.0)
|
||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
||||
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemSeparatorColor: UIColor
|
||||
switch item.style {
|
||||
case .plain:
|
||||
itemBackgroundColor = item.theme.list.plainBackgroundColor
|
||||
itemSeparatorColor = item.theme.list.itemPlainSeparatorColor
|
||||
contentSize = CGSize(width: params.width, height: imageSize.height + inset * 2.0)
|
||||
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
|
||||
insets = itemListNeighborsPlainInsets(neighbors)
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
|
||||
contentSize = CGSize(width: params.width, height: imageSize.height + inset * 2.0)
|
||||
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
}
|
||||
|
||||
@ -131,69 +147,13 @@ class WalletQrCodeItemNode: ListViewItemNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
}
|
||||
|
||||
if let updatedAddress = updatedAddress {
|
||||
strongSelf.imageNode.setSignal(qrCode(string: updatedAddress, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Settings/Wallet/IntroIcon")), ecl: "Q"), attemptSynchronously: true)
|
||||
strongSelf.imageNode.setSignal(qrCode(string: updatedAddress, color: item.theme.list.itemPrimaryTextColor.withAlphaComponent(0.77), backgroundColor: item.theme.list.blocksBackgroundColor, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem")), ecl: "Q"), attemptSynchronously: true)
|
||||
}
|
||||
|
||||
let _ = imageApply()
|
||||
|
||||
let leftInset: CGFloat
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
leftInset = 35.0 + params.leftInset
|
||||
|
||||
if strongSelf.backgroundNode.supernode != nil {
|
||||
strongSelf.backgroundNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode != nil {
|
||||
strongSelf.topStripeNode.removeFromSupernode()
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||
}
|
||||
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||
case .blocks:
|
||||
leftInset = 16.0 + params.leftInset
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topStripeNode.isHidden = false
|
||||
}
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
}
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||
}
|
||||
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: (params.width - imageSize.width) / 2.0, y: 12.0), size: imageSize)
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: (params.width - imageSize.width) / 2.0, y: 0.0), size: imageSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Camera
|
||||
import GlassButtonNode
|
||||
import UrlHandling
|
||||
|
||||
private func generateFrameImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in
|
||||
@ -47,12 +48,13 @@ private func generateFrameImage() -> UIImage? {
|
||||
|
||||
public final class WalletQrScanScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let completion: (String, Int64?, String?) -> Void
|
||||
private let completion: (ParsedWalletUrl) -> Void
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var codeDisposable: Disposable?
|
||||
private var inForegroundDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, completion: @escaping (String, Int64?, String?) -> Void) {
|
||||
public init(context: AccountContext, completion: @escaping (ParsedWalletUrl) -> Void) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
||||
@ -70,6 +72,14 @@ public final class WalletQrScanScreen: ViewController {
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
(strongSelf.displayNode as! WalletQrScanScreenNode).updateInForeground(inForeground)
|
||||
})
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
@ -77,7 +87,8 @@ public final class WalletQrScanScreen: ViewController {
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.codeDisposable?.dispose()
|
||||
self.inForegroundDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func backPressed() {
|
||||
@ -89,12 +100,7 @@ public final class WalletQrScanScreen: ViewController {
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
// (self.displayNode as! WalletQrScanScreenNode).focusedCode.get()
|
||||
// |> map { code -> String? in
|
||||
// return code?.message
|
||||
// } |> distinctUntilChanged
|
||||
|
||||
self.disposable = (((self.displayNode as! WalletQrScanScreenNode).focusedCode.get()
|
||||
self.codeDisposable = (((self.displayNode as! WalletQrScanScreenNode).focusedCode.get()
|
||||
|> map { code -> String? in
|
||||
return code?.message
|
||||
}
|
||||
@ -106,8 +112,9 @@ public final class WalletQrScanScreen: ViewController {
|
||||
guard let strongSelf = self, let code = code else {
|
||||
return
|
||||
}
|
||||
let cleanString = code.replacingOccurrences(of: "ton://", with: "")
|
||||
strongSelf.completion(cleanString, nil, nil)
|
||||
if let url = URL(string: code), let parsedWalletUrl = parseWalletUrl(url) {
|
||||
strongSelf.completion(parsedWalletUrl)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -128,6 +135,7 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
|
||||
private let leftDimNode: ASDisplayNode
|
||||
private let rightDimNode: ASDisplayNode
|
||||
private let frameNode: ASImageNode
|
||||
private let galleryButtonNode: GlassButtonNode
|
||||
private let torchButtonNode: GlassButtonNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
|
||||
@ -168,6 +176,7 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
|
||||
self.frameNode = ASImageNode()
|
||||
self.frameNode.image = generateFrameImage()
|
||||
|
||||
self.galleryButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraGalleryIcon")!, label: nil)
|
||||
self.torchButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraFlashIcon")!, label: nil)
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
@ -189,14 +198,25 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
|
||||
self.addSubnode(self.leftDimNode)
|
||||
self.addSubnode(self.rightDimNode)
|
||||
self.addSubnode(self.frameNode)
|
||||
self.addSubnode(self.galleryButtonNode)
|
||||
self.addSubnode(self.torchButtonNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
|
||||
|
||||
self.galleryButtonNode.addTarget(self, action: #selector(self.galleryPressed), forControlEvents: .touchUpInside)
|
||||
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.codeDisposable.dispose()
|
||||
self.camera.stopCapture(invalidate: true)
|
||||
}
|
||||
|
||||
fileprivate func updateInForeground(_ inForeground: Bool) {
|
||||
if !inForeground {
|
||||
self.camera.stopCapture(invalidate: false)
|
||||
} else {
|
||||
self.camera.startCapture()
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -272,14 +292,19 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
|
||||
|
||||
transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0))
|
||||
|
||||
let torchButtonSize = CGSize(width: 72.0, height: 72.0)
|
||||
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - torchButtonSize.width) / 2.0), y: dimHeight + frameSide + 50.0), size: torchButtonSize))
|
||||
let buttonSize = CGSize(width: 72.0, height: 72.0)
|
||||
transition.updateFrame(node: self.galleryButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0) - buttonSize.width - 28.0, y: dimHeight + frameSide + 50.0), size: buttonSize))
|
||||
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0) + 28.0, y: dimHeight + frameSide + 50.0), size: buttonSize))
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: dimHeight - titleSize.height - titleSpacing), size: titleSize)
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
||||
}
|
||||
|
||||
@objc private func galleryPressed() {
|
||||
|
||||
}
|
||||
|
||||
@objc private func torchPressed() {
|
||||
self.torchButtonNode.isSelected = !self.torchButtonNode.isSelected
|
||||
self.camera.setTorchActive(self.torchButtonNode.isSelected)
|
||||
|
146
submodules/WalletUI/Sources/WalletQrViewScreen.swift
Normal file
146
submodules/WalletUI/Sources/WalletQrViewScreen.swift
Normal file
@ -0,0 +1,146 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import AppBundle
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import QrCode
|
||||
import ShareController
|
||||
|
||||
func shareInvoiceQrCode(context: AccountContext, invoice: String) {
|
||||
let _ = (qrCode(string: invoice, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem")), ecl: "Q")
|
||||
|> map { generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
||||
context.sharedContext.applicationBindings.presentNativeController(activityController)
|
||||
})
|
||||
}
|
||||
|
||||
public final class WalletQrViewScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let invoice: String
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var previousScreenBrightness: CGFloat?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
private let idleTimerExtensionDisposable: Disposable
|
||||
|
||||
public init(context: AccountContext, invoice: String) {
|
||||
self.context = context
|
||||
self.invoice = invoice
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let defaultNavigationPresentationData = NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultNavigationPresentationData.theme.buttonColor, disabledButtonColor: defaultNavigationPresentationData.theme.disabledButtonColor, primaryTextColor: defaultNavigationPresentationData.theme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultNavigationPresentationData.theme.badgeBackgroundColor, badgeStrokeColor: defaultNavigationPresentationData.theme.badgeStrokeColor, badgeTextColor: defaultNavigationPresentationData.theme.badgeTextColor)
|
||||
|
||||
self.idleTimerExtensionDisposable = context.sharedContext.applicationBindings.pushIdleTimerExtension()
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
self.title = "QR Code"
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.shareButtonPressed))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.idleTimerExtensionDisposable.dispose()
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = WalletQrViewScreenNode(context: self.context, presentationData: self.presentationData, message: self.invoice)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let screenBrightness = UIScreen.main.brightness
|
||||
if screenBrightness < 0.85 {
|
||||
self.previousScreenBrightness = screenBrightness
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.5, from: screenBrightness, to: 0.85, update: { value in
|
||||
UIScreen.main.brightness = value
|
||||
}, completion: {
|
||||
self.displayLinkAnimator = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
let screenBrightness = UIScreen.main.brightness
|
||||
if let previousScreenBrightness = self.previousScreenBrightness, screenBrightness > previousScreenBrightness {
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2, from: screenBrightness, to: previousScreenBrightness, update: { value in
|
||||
UIScreen.main.brightness = value
|
||||
}, completion: {
|
||||
self.displayLinkAnimator = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! WalletQrViewScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
@objc private func shareButtonPressed() {
|
||||
shareInvoiceQrCode(context: self.context, invoice: self.invoice)
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletQrViewScreenNode: ViewControllerTracingNode {
|
||||
private var presentationData: PresentationData
|
||||
private let invoice: String
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, message: String) {
|
||||
self.presentationData = presentationData
|
||||
self.invoice = message
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.imageNode.setSignal(qrCode(string: self.invoice, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem")), ecl: "Q"), attemptSynchronously: true)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
|
||||
let imageSide = layout.size.width - 48.0 * 2.0
|
||||
var imageSize = CGSize(width: imageSide, height: imageSide)
|
||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
||||
|
||||
let _ = imageApply()
|
||||
|
||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: floor((layout.size.height - imageSize.height - layout.intrinsicInsets.bottom) / 2.0)), size: imageSize))
|
||||
}
|
||||
}
|
@ -12,48 +12,79 @@ import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import ShareController
|
||||
|
||||
private func formatAddress(_ address: String) -> String {
|
||||
var address = address
|
||||
address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2))
|
||||
return address
|
||||
}
|
||||
|
||||
private final class WalletReceiveScreenArguments {
|
||||
let context: AccountContext
|
||||
let updateState: ((WalletReceiveScreenState) -> WalletReceiveScreenState) -> Void
|
||||
let updateText: (WalletReceiveScreenEntryTag, String) -> Void
|
||||
let selectNextInputItem: (WalletReceiveScreenEntryTag) -> Void
|
||||
let dismissInput: () -> Void
|
||||
let copyAddress: () -> Void
|
||||
let shareAddressLink: () -> Void
|
||||
let openQrCode: () -> Void
|
||||
let displayQrCodeContextMenu: () -> Void
|
||||
let scrollToBottom: () -> Void
|
||||
|
||||
init(context: AccountContext, copyAddress: @escaping () -> Void, shareAddressLink: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateState: @escaping ((WalletReceiveScreenState) -> WalletReceiveScreenState) -> Void, updateText: @escaping (WalletReceiveScreenEntryTag, String) -> Void, selectNextInputItem: @escaping (WalletReceiveScreenEntryTag) -> Void, dismissInput: @escaping () -> Void, copyAddress: @escaping () -> Void, shareAddressLink: @escaping () -> Void, openQrCode: @escaping () -> Void, displayQrCodeContextMenu: @escaping () -> Void, scrollToBottom: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.updateText = updateText
|
||||
self.selectNextInputItem = selectNextInputItem
|
||||
self.dismissInput = dismissInput
|
||||
self.copyAddress = copyAddress
|
||||
self.shareAddressLink = shareAddressLink
|
||||
self.openQrCode = openQrCode
|
||||
self.displayQrCodeContextMenu = displayQrCodeContextMenu
|
||||
self.scrollToBottom = scrollToBottom
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletReceiveScreenSection: Int32 {
|
||||
case address
|
||||
case amount
|
||||
case comment
|
||||
}
|
||||
|
||||
private enum WalletReceiveScreenEntryTag: ItemListItemTag {
|
||||
case amount
|
||||
case comment
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? WalletReceiveScreenEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
case addressHeader(PresentationTheme, String)
|
||||
case addressCode(PresentationTheme, String)
|
||||
case address(PresentationTheme, String)
|
||||
case addressHeader(PresentationTheme, String)
|
||||
case address(PresentationTheme, String, Bool)
|
||||
case copyAddress(PresentationTheme, String)
|
||||
case shareAddressLink(PresentationTheme, String)
|
||||
case addressInfo(PresentationTheme, String)
|
||||
case amountHeader(PresentationTheme, String)
|
||||
case amount(PresentationTheme, PresentationStrings, String, String)
|
||||
case commentHeader(PresentationTheme, String)
|
||||
case comment(PresentationTheme, String, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .addressHeader, .addressCode, .address, .copyAddress, .shareAddressLink, .addressInfo:
|
||||
case .addressCode, .addressHeader, .address, .copyAddress, .shareAddressLink, .addressInfo:
|
||||
return WalletReceiveScreenSection.address.rawValue
|
||||
case .amountHeader, .amount:
|
||||
return WalletReceiveScreenSection.amount.rawValue
|
||||
case .commentHeader, .comment:
|
||||
return WalletReceiveScreenSection.comment.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .addressHeader:
|
||||
return 0
|
||||
case .addressCode:
|
||||
return 0
|
||||
case .addressHeader:
|
||||
return 1
|
||||
case .address:
|
||||
return 2
|
||||
@ -63,25 +94,33 @@ private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
return 4
|
||||
case .addressInfo:
|
||||
return 5
|
||||
case .amountHeader:
|
||||
return 6
|
||||
case .amount:
|
||||
return 7
|
||||
case .commentHeader:
|
||||
return 8
|
||||
case .comment:
|
||||
return 9
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: WalletReceiveScreenEntry, rhs: WalletReceiveScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .addressHeader(lhsTheme, lhsText):
|
||||
if case let .addressHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressCode(lhsTheme, lhsAddress):
|
||||
if case let .addressCode(rhsTheme, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .address(lhsTheme, lhsAddress):
|
||||
if case let .address(rhsTheme, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress {
|
||||
case let .addressHeader(lhsTheme, lhsText):
|
||||
if case let .addressHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .address(lhsTheme, lhsAddress, lhsMonospace):
|
||||
if case let .address(rhsTheme, rhsAddress, rhsMonospace) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress, lhsMonospace == rhsMonospace {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -104,6 +143,30 @@ private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amountHeader(lhsTheme, lhsText):
|
||||
if case let .amountHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amount(lhsTheme, lhsStrings, lhsPlaceholder, lhsBalance):
|
||||
if case let .amount(rhsTheme, rhsStrings, rhsPlaceholder, rhsBalance) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsBalance == rhsBalance {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .commentHeader(lhsTheme, lhsText):
|
||||
if case let .commentHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .comment(lhsTheme, lhsPlaceholder, lhsText):
|
||||
if case let .comment(rhsTheme, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,12 +176,16 @@ private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
|
||||
func item(_ arguments: WalletReceiveScreenArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .addressCode(theme, text):
|
||||
return WalletQrCodeItem(theme: theme, address: text, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openQrCode()
|
||||
}, longTapAction: {
|
||||
arguments.displayQrCodeContextMenu()
|
||||
})
|
||||
case let .addressHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .addressCode(theme, address):
|
||||
return WalletQrCodeItem(theme: theme, address: "ton://\(address)", sectionId: self.section, style: .blocks)
|
||||
case let .address(theme, address):
|
||||
return ItemListMultilineTextItem(theme: theme, text: address, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks)
|
||||
case let .address(theme, text, monospace):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks)
|
||||
case let .copyAddress(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.copyAddress()
|
||||
@ -129,18 +196,97 @@ private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .addressInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
case let .amountHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .amount(theme, strings, placeholder, text):
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, type: .decimal, returnKeyType: .next, tag: WalletReceiveScreenEntryTag.amount, sectionId: self.section, textUpdated: { text in
|
||||
let text = formatAmountText(text, decimalSeparator: arguments.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat.decimalSeparator)
|
||||
arguments.updateText(WalletReceiveScreenEntryTag.amount, text)
|
||||
}, shouldUpdateText: { text in
|
||||
return isValidAmount(text)
|
||||
}, processPaste: { pastedText in
|
||||
if isValidAmount(pastedText) {
|
||||
return normalizedStringForGramsString(pastedText)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}, updatedFocus: { focus in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.focusItemTag = focus ? WalletReceiveScreenEntryTag.amount : nil
|
||||
return state
|
||||
}
|
||||
if focus {
|
||||
arguments.scrollToBottom()
|
||||
} else {
|
||||
let presentationData = arguments.context.sharedContext.currentPresentationData.with { $0 }
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
if !state.amount.isEmpty {
|
||||
state.amount = normalizedStringForGramsString(state.amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, action: {
|
||||
arguments.selectNextInputItem(WalletReceiveScreenEntryTag.amount)
|
||||
})
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, placeholder, value):
|
||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 128, display: true), sectionId: self.section, style: .blocks, returnKeyType: .done, textUpdated: { text in
|
||||
arguments.updateText(WalletReceiveScreenEntryTag.comment, text)
|
||||
}, updatedFocus: { focus in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.focusItemTag = focus ? WalletReceiveScreenEntryTag.comment : nil
|
||||
return state
|
||||
}
|
||||
if focus {
|
||||
arguments.scrollToBottom()
|
||||
}
|
||||
}, tag: WalletReceiveScreenEntryTag.comment, action: {
|
||||
arguments.dismissInput()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func walletReceiveScreenEntries(presentationData: PresentationData, address: String) -> [WalletReceiveScreenEntry] {
|
||||
private struct WalletReceiveScreenState: Equatable {
|
||||
var amount: String
|
||||
var comment: String
|
||||
var focusItemTag: WalletReceiveScreenEntryTag?
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.amount.isEmpty && self.comment.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
private func walletReceiveScreenEntries(presentationData: PresentationData, address: String, state: WalletReceiveScreenState) -> [WalletReceiveScreenEntry] {
|
||||
var entries: [WalletReceiveScreenEntry] = []
|
||||
entries.append(.addressHeader(presentationData.theme, "YOUR WALLET ADDRESS"))
|
||||
entries.append(.addressCode(presentationData.theme, address))
|
||||
entries.append(.address(presentationData.theme, formatAddress(address)))
|
||||
entries.append(.copyAddress(presentationData.theme, "Copy Wallet Address"))
|
||||
entries.append(.shareAddressLink(presentationData.theme, "Share Wallet Address"))
|
||||
entries.append(.addressCode(presentationData.theme, invoiceUrl(address: address, state: state, escapeComment: true)))
|
||||
entries.append(.addressHeader(presentationData.theme, state.isEmpty ? "YOUR WALLET ADDRESS" : "INVOICE URL"))
|
||||
|
||||
let addressText: String
|
||||
var addressMonospace = false
|
||||
if state.isEmpty {
|
||||
addressText = formatAddress(address)
|
||||
addressMonospace = true
|
||||
} else {
|
||||
addressText = invoiceUrl(address: address, state: state, escapeComment: false)
|
||||
}
|
||||
entries.append(.address(presentationData.theme, addressText, addressMonospace))
|
||||
entries.append(.copyAddress(presentationData.theme, state.isEmpty ? "Copy Wallet Address" : "Copy Invoice URL"))
|
||||
entries.append(.shareAddressLink(presentationData.theme, state.isEmpty ? "Share Wallet Address" : "Share Invoice URL"))
|
||||
entries.append(.addressInfo(presentationData.theme, "Share this link with other Gram wallet owners to receive Grams from them."))
|
||||
|
||||
let amount = amountValue(state.amount)
|
||||
entries.append(.amountHeader(presentationData.theme, "AMOUNT"))
|
||||
entries.append(.amount(presentationData.theme, presentationData.strings, "Grams to receive", state.amount ?? ""))
|
||||
|
||||
entries.append(.commentHeader(presentationData.theme, "COMMENT (OPTIONAL)"))
|
||||
entries.append(.comment(presentationData.theme, "Description of the payment", state.comment))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -152,42 +298,176 @@ private final class WalletReceiveScreenImpl: ItemListController<WalletReceiveScr
|
||||
|
||||
}
|
||||
|
||||
func walletReceiveScreen(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let arguments = WalletReceiveScreenArguments(context: context, copyAddress: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
UIPasteboard.general.string = address
|
||||
private func invoiceUrl(address: String, state: WalletReceiveScreenState, escapeComment: Bool = true) -> String {
|
||||
let escapedAddress = address.replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_")
|
||||
var arguments = ""
|
||||
if !state.amount.isEmpty {
|
||||
arguments += arguments.isEmpty ? "/?" : "&"
|
||||
arguments += "amount=\(amountValue(state.amount))"
|
||||
}
|
||||
if !state.comment.isEmpty, let escapedComment = state.comment.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
|
||||
arguments += arguments.isEmpty ? "/?" : "&"
|
||||
if escapeComment {
|
||||
arguments += "text=\(escapedComment)"
|
||||
} else {
|
||||
arguments += "text=\(state.comment)"
|
||||
}
|
||||
}
|
||||
return "ton://\(escapedAddress)\(arguments)"
|
||||
}
|
||||
|
||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess("Address copied to clipboard.", false)), nil)
|
||||
func walletReceiveScreen(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String) -> ViewController {
|
||||
let initialState = WalletReceiveScreenState(amount: "", comment: "", focusItemTag: nil)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((WalletReceiveScreenState) -> WalletReceiveScreenState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var selectNextInputItemImpl: ((WalletReceiveScreenEntryTag) -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var ensureItemVisibleImpl: ((WalletReceiveScreenEntryTag, Bool) -> Void)?
|
||||
var displayQrCodeContextMenuImpl: (() -> Void)?
|
||||
|
||||
weak var currentStatusController: ViewController?
|
||||
let arguments = WalletReceiveScreenArguments(context: context, updateState: { f in
|
||||
updateState(f)
|
||||
}, updateText: { tag, value in
|
||||
updateState { state in
|
||||
var state = state
|
||||
switch tag {
|
||||
case .amount:
|
||||
state.amount = value
|
||||
case .comment:
|
||||
state.comment = value
|
||||
}
|
||||
return state
|
||||
}
|
||||
ensureItemVisibleImpl?(tag, false)
|
||||
}, selectNextInputItem: { tag in
|
||||
selectNextInputItemImpl?(tag)
|
||||
}, dismissInput: {
|
||||
dismissInputImpl?()
|
||||
}, copyAddress: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let state = stateValue.with { $0 }
|
||||
|
||||
let successText: String
|
||||
if state.isEmpty {
|
||||
UIPasteboard.general.string = address
|
||||
successText = "Address copied to clipboard."
|
||||
} else {
|
||||
UIPasteboard.general.string = invoiceUrl(address: address, state: state)
|
||||
successText = "Invoice URL copied to clipboard."
|
||||
}
|
||||
|
||||
if currentStatusController == nil {
|
||||
let statusController = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(successText, false))
|
||||
presentControllerImpl?(statusController, nil)
|
||||
currentStatusController = statusController
|
||||
}
|
||||
}, shareAddressLink: {
|
||||
let controller = ShareController(context: context, subject: .url("ton://\(address)"), preferredAction: .default)
|
||||
dismissInputImpl?()
|
||||
let state = stateValue.with { $0 }
|
||||
let url = invoiceUrl(address: address, state: state)
|
||||
let controller = ShareController(context: context, subject: .url(url), preferredAction: .default)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}, openQrCode: {
|
||||
dismissInputImpl?()
|
||||
let state = stateValue.with { $0 }
|
||||
let url = invoiceUrl(address: address, state: state)
|
||||
pushImpl?(WalletQrViewScreen(context: context, invoice: url))
|
||||
}, displayQrCodeContextMenu: {
|
||||
dismissInputImpl?()
|
||||
displayQrCodeContextMenuImpl?()
|
||||
}, scrollToBottom: {
|
||||
ensureItemVisibleImpl?(WalletReceiveScreenEntryTag.comment, true)
|
||||
})
|
||||
|
||||
let address: Signal<String, NoError> = .single(address)
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, address)
|
||||
|> map { presentationData, address -> (ItemListControllerState, (ItemListNodeState<WalletReceiveScreenEntry>, WalletReceiveScreenEntry.ItemGenerationArguments)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<WalletReceiveScreenEntry>, WalletReceiveScreenEntry.ItemGenerationArguments)) in
|
||||
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
var ensureVisibleItemTag: ItemListItemTag?
|
||||
if let focusItemTag = state.focusItemTag {
|
||||
ensureVisibleItemTag = focusItemTag
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Receive Grams"), leftNavigationButton: rightNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletReceiveScreenEntries(presentationData: presentationData, address: address), style: .blocks, animateChanges: false)
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Receive Grams"), leftNavigationButton: ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}), rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletReceiveScreenEntries(presentationData: presentationData, address: address, state: state), style: .blocks, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = WalletReceiveScreenImpl(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
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()
|
||||
}
|
||||
selectNextInputItemImpl = { [weak controller] currentTag in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
var resultItemNode: ItemListItemFocusableNode?
|
||||
var focusOnNext = false
|
||||
let _ = controller.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, let focusableItemNode = itemNode as? ItemListItemFocusableNode {
|
||||
if focusOnNext && resultItemNode == nil {
|
||||
resultItemNode = focusableItemNode
|
||||
return true
|
||||
} else if currentTag.isEqual(to: tag) {
|
||||
focusOnNext = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
resultItemNode.focus()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
displayQrCodeContextMenuImpl = { [weak controller] in
|
||||
let state = stateValue.with { $0 }
|
||||
let url = invoiceUrl(address: address, state: state)
|
||||
shareInvoiceQrCode(context: context, invoice: url)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -11,21 +11,27 @@ import ItemListUI
|
||||
import SwiftSignalKit
|
||||
import AlertUI
|
||||
import TextFormat
|
||||
import DeviceAccess
|
||||
import TelegramStringFormatting
|
||||
import UrlHandling
|
||||
|
||||
private let walletAddressLength: Int = 48
|
||||
private let balanceIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
|
||||
|
||||
private final class WalletSendScreenArguments {
|
||||
let context: AccountContext
|
||||
let updateState: ((WalletSendScreenState) -> WalletSendScreenState) -> Void
|
||||
let updateText: (WalletSendScreenEntryTag, String) -> Void
|
||||
let selectNextInputItem: (WalletSendScreenEntryTag) -> Void
|
||||
let dismissInput: () -> Void
|
||||
let openQrScanner: () -> Void
|
||||
let proceed: () -> Void
|
||||
|
||||
init(context: AccountContext, updateState: @escaping ((WalletSendScreenState) -> WalletSendScreenState) -> Void, selectNextInputItem: @escaping (WalletSendScreenEntryTag) -> Void, openQrScanner: @escaping () -> Void, proceed: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateState: @escaping ((WalletSendScreenState) -> WalletSendScreenState) -> Void, updateText: @escaping (WalletSendScreenEntryTag, String) -> Void, selectNextInputItem: @escaping (WalletSendScreenEntryTag) -> Void, dismissInput: @escaping () -> Void, openQrScanner: @escaping () -> Void, proceed: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.updateText = updateText
|
||||
self.selectNextInputItem = selectNextInputItem
|
||||
self.dismissInput = dismissInput
|
||||
self.openQrScanner = openQrScanner
|
||||
self.proceed = proceed
|
||||
}
|
||||
@ -51,76 +57,12 @@ private enum WalletSendScreenEntryTag: ItemListItemTag {
|
||||
}
|
||||
}
|
||||
|
||||
private let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=").inverted
|
||||
private func isValidAddress(_ address: String, exactLength: Bool = false) -> Bool {
|
||||
if address.count > walletAddressLength || address.rangeOfCharacter(from: invalidAddressCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
if exactLength && address.count != walletAddressLength {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private let invalidAmountCharacters = CharacterSet(charactersIn: "01234567890.,").inverted
|
||||
private func isValidAmount(_ amount: String) -> Bool {
|
||||
if amount.rangeOfCharacter(from: invalidAmountCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
var hasDecimalSeparator = false
|
||||
var hasLeadingZero = false
|
||||
var index = 0
|
||||
for c in amount {
|
||||
if c == "." || c == "," {
|
||||
if !hasDecimalSeparator {
|
||||
hasDecimalSeparator = true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
var decimalIndex: String.Index?
|
||||
if let index = amount.firstIndex(of: ".") {
|
||||
decimalIndex = index
|
||||
} else if let index = amount.firstIndex(of: ",") {
|
||||
decimalIndex = index
|
||||
}
|
||||
|
||||
if let decimalIndex = decimalIndex, amount.distance(from: decimalIndex, to: amount.endIndex) > 10 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func formatAmountText(_ amount: Int64, decimalSeparator: String = ".") -> String {
|
||||
if amount < 1000000000 {
|
||||
return "0\(decimalSeparator)\(String(amount).rightJustified(width: 9, pad: "0"))"
|
||||
} else {
|
||||
var string = String(amount)
|
||||
string.insert(contentsOf: decimalSeparator, at: string.index(string.endIndex, offsetBy: -9))
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
private func amountValue(_ string: String) -> Int64 {
|
||||
return Int64((Double(string.replacingOccurrences(of: ",", with: ".")) ?? 0.0) * 1000000000.0)
|
||||
}
|
||||
|
||||
private func normalizedStringForGramsString(_ string: String, decimalSeparator: String = ".") -> String {
|
||||
return formatAmountText(amountValue(string), decimalSeparator: decimalSeparator)
|
||||
}
|
||||
|
||||
private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
case addressHeader(PresentationTheme, String)
|
||||
case address(PresentationTheme, String, String)
|
||||
case addressInfo(PresentationTheme, String)
|
||||
|
||||
case amountHeader(PresentationTheme, String, String?, Bool)
|
||||
case amount(PresentationTheme, PresentationStrings, String, String)
|
||||
|
||||
case commentHeader(PresentationTheme, String)
|
||||
case comment(PresentationTheme, String, String)
|
||||
|
||||
@ -180,8 +122,8 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amount(lhsTheme, lhsStrings, lhsPlaceholder, lhsBalance):
|
||||
if case let .amount(rhsTheme, rhsStrings, rhsPlaceholder, rhsBalance) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsBalance == rhsBalance {
|
||||
case let .amount(lhsTheme, lhsStrings, lhsPlaceholder, lhsAmount):
|
||||
if case let .amount(rhsTheme, rhsStrings, rhsPlaceholder, rhsAmount) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsAmount == rhsAmount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -210,14 +152,44 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
case let .addressHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .address(theme, placeholder, address):
|
||||
return ItemListMultilineInputItem(theme: theme, text: address, placeholder: placeholder, maxLength: .init(value: walletAddressLength, display: false), sectionId: self.section, style: .blocks, capitalization: false, autocorrection: false, returnKeyType: .next, minimalHeight: 68.0, textUpdated: { address in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.address = address.replacingOccurrences(of: "\n", with: "")
|
||||
return state
|
||||
}
|
||||
return ItemListMultilineInputItem(theme: theme, text: address, placeholder: placeholder, maxLength: .init(value: walletAddressLength, display: false), sectionId: self.section, style: .blocks, capitalization: false, autocorrection: false, returnKeyType: .next, minimalHeight: 68.0, textUpdated: { text in
|
||||
arguments.updateText(WalletSendScreenEntryTag.address, text.replacingOccurrences(of: "\n", with: ""))
|
||||
}, shouldUpdateText: { text in
|
||||
return isValidAddress(text)
|
||||
}, processPaste: { text in
|
||||
if let url = URL(string: text), let parsedUrl = parseWalletUrl(url) {
|
||||
var focusItemTag: WalletSendScreenEntryTag?
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.address = parsedUrl.address
|
||||
if let amount = parsedUrl.amount {
|
||||
state.amount = formatBalanceText(amount, decimalSeparator: arguments.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat.decimalSeparator)
|
||||
} else if state.amount.isEmpty {
|
||||
focusItemTag = WalletSendScreenEntryTag.address
|
||||
}
|
||||
if let comment = parsedUrl.comment {
|
||||
state.comment = comment
|
||||
} else if state.comment.isEmpty && focusItemTag == nil {
|
||||
focusItemTag = WalletSendScreenEntryTag.amount
|
||||
}
|
||||
return state
|
||||
}
|
||||
if let focusItemTag = focusItemTag {
|
||||
arguments.selectNextInputItem(focusItemTag)
|
||||
} else {
|
||||
arguments.dismissInput()
|
||||
}
|
||||
} else if isValidAddress(text) {
|
||||
arguments.updateText(WalletSendScreenEntryTag.address, text)
|
||||
if isValidAddress(text, exactLength: true, url: false) {
|
||||
arguments.selectNextInputItem(WalletSendScreenEntryTag.address)
|
||||
}
|
||||
} else if isValidAddress(text, url: true) {
|
||||
arguments.updateText(WalletSendScreenEntryTag.address, convertedAddress(text, url: false))
|
||||
if isValidAddress(text, exactLength: true, url: true) {
|
||||
arguments.selectNextInputItem(WalletSendScreenEntryTag.address)
|
||||
}
|
||||
}
|
||||
}, tag: WalletSendScreenEntryTag.address, action: {
|
||||
arguments.selectNextInputItem(WalletSendScreenEntryTag.address)
|
||||
}, inlineAction: ItemListMultilineInputInlineAction(icon: UIImage(bundleImageName: "Wallet/QrIcon")!, action: {
|
||||
@ -229,16 +201,14 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, activityIndicator: balance == nil ? .right : .none, accessoryText: balance.flatMap { ItemListSectionHeaderAccessoryText(value: $0, color: insufficient ? .destructive : .generic, icon: balanceIcon) }, sectionId: self.section)
|
||||
case let .amount(theme, strings, placeholder, text):
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, type: .decimal, returnKeyType: .next, tag: WalletSendScreenEntryTag.amount, sectionId: self.section, textUpdated: { text in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.amount = text
|
||||
return state
|
||||
}
|
||||
let text = formatAmountText(text, decimalSeparator: arguments.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat.decimalSeparator)
|
||||
arguments.updateText(WalletSendScreenEntryTag.amount, text)
|
||||
}, shouldUpdateText: { text in
|
||||
return isValidAmount(text)
|
||||
}, processPaste: { pastedText in
|
||||
if isValidAmount(pastedText) {
|
||||
return normalizedStringForGramsString(pastedText)
|
||||
let presentationData = arguments.context.sharedContext.currentPresentationData.with { $0 }
|
||||
return normalizedStringForGramsString(pastedText, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
@ -259,12 +229,8 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, placeholder, value):
|
||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 128, display: true), sectionId: self.section, style: .blocks, returnKeyType: .send, textUpdated: { comment in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.text = comment
|
||||
return state
|
||||
}
|
||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 128, display: true), sectionId: self.section, style: .blocks, returnKeyType: .send, textUpdated: { text in
|
||||
arguments.updateText(WalletSendScreenEntryTag.comment, text)
|
||||
}, tag: WalletSendScreenEntryTag.comment, action: {
|
||||
arguments.proceed()
|
||||
})
|
||||
@ -275,7 +241,7 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
private struct WalletSendScreenState: Equatable {
|
||||
var address: String
|
||||
var amount: String
|
||||
var text: String
|
||||
var comment: String
|
||||
}
|
||||
|
||||
private func walletSendScreenEntries(presentationData: PresentationData, balance: Int64?, state: WalletSendScreenState) -> [WalletSendScreenEntry] {
|
||||
@ -289,8 +255,8 @@ private func walletSendScreenEntries(presentationData: PresentationData, balance
|
||||
entries.append(.amountHeader(presentationData.theme, "AMOUNT", balance.flatMap { "BALANCE: \(formatBalanceText($0, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" }, amount > 0 && (balance ?? 0) < amount))
|
||||
entries.append(.amount(presentationData.theme, presentationData.strings, "Grams to send", state.amount ?? ""))
|
||||
|
||||
entries.append(.commentHeader(presentationData.theme, "COMMENT"))
|
||||
entries.append(.comment(presentationData.theme, "Optional description of the payment", state.text))
|
||||
entries.append(.commentHeader(presentationData.theme, "COMMENT (OPTIONAL)"))
|
||||
entries.append(.comment(presentationData.theme, "Description of the payment", state.comment))
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -302,10 +268,10 @@ private final class WalletSendScreenImpl: ItemListController<WalletSendScreenEnt
|
||||
|
||||
}
|
||||
|
||||
func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String? = nil, amount: Int64? = nil, text: String? = nil) -> ViewController {
|
||||
public func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String? = nil, amount: Int64? = nil, comment: String? = nil) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let initialState = WalletSendScreenState(address: address ?? "", amount: amount.flatMap { formatAmountText($0, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) } ?? "", text: text ?? "")
|
||||
|
||||
|
||||
let initialState = WalletSendScreenState(address: address ?? "", amount: amount.flatMap { formatBalanceText($0, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) } ?? "", comment: comment ?? "")
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((WalletSendScreenState) -> WalletSendScreenState) -> Void = { f in
|
||||
@ -319,36 +285,63 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
var dismissImpl: (() -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var selectNextInputItemImpl: ((WalletSendScreenEntryTag) -> Void)?
|
||||
var ensureItemVisibleImpl: ((WalletSendScreenEntryTag) -> Void)?
|
||||
|
||||
let arguments = WalletSendScreenArguments(context: context, updateState: { f in
|
||||
updateState(f)
|
||||
}, updateText: { tag, value in
|
||||
updateState { state in
|
||||
var state = state
|
||||
switch tag {
|
||||
case .address:
|
||||
state.address = value
|
||||
case .amount:
|
||||
state.amount = value
|
||||
case .comment:
|
||||
state.comment = value
|
||||
}
|
||||
return state
|
||||
}
|
||||
ensureItemVisibleImpl?(tag)
|
||||
}, selectNextInputItem: { tag in
|
||||
selectNextInputItemImpl?(tag)
|
||||
}, dismissInput: {
|
||||
dismissInputImpl?()
|
||||
}, openQrScanner: {
|
||||
dismissInputImpl?()
|
||||
pushImpl?(WalletQrScanScreen(context: context, completion: { address, amount, comment in
|
||||
var updatedState: WalletSendScreenState?
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.address = address
|
||||
if let amount = amount {
|
||||
state.amount = formatAmountText(amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
if let comment = comment {
|
||||
state.text = comment
|
||||
}
|
||||
updatedState = state
|
||||
return state
|
||||
|
||||
DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, openSettings: {
|
||||
context.sharedContext.applicationBindings.openSettings()
|
||||
}, { granted in
|
||||
guard granted else {
|
||||
return
|
||||
}
|
||||
popImpl?()
|
||||
if let updatedState = updatedState {
|
||||
if updatedState.amount.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.address)
|
||||
} else if updatedState.text.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.amount)
|
||||
pushImpl?(WalletQrScanScreen(context: context, completion: { parsedUrl in
|
||||
var updatedState: WalletSendScreenState?
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.address = parsedUrl.address
|
||||
if let amount = parsedUrl.amount {
|
||||
state.amount = formatBalanceText(amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
if let comment = parsedUrl.comment {
|
||||
state.comment = comment
|
||||
}
|
||||
updatedState = state
|
||||
return state
|
||||
}
|
||||
}
|
||||
}))
|
||||
popImpl?()
|
||||
if let updatedState = updatedState {
|
||||
if updatedState.amount.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.address)
|
||||
} else if updatedState.comment.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.amount)
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
}, proceed: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let state = stateValue.with { $0 }
|
||||
@ -356,7 +349,7 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.amount = formatAmountText(amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
state.amount = formatBalanceText(amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
return state
|
||||
}
|
||||
|
||||
@ -375,7 +368,7 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
dismissAlertImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: "Confirm", action: {
|
||||
dismissAlertImpl?(false)
|
||||
pushImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .sending(walletInfo, state.address, amount, state.text)))
|
||||
pushImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .sending(walletInfo, state.address, amount, state.comment)))
|
||||
})], dismissAutomatically: false)
|
||||
presentInGlobalOverlayImpl?(controller, nil)
|
||||
|
||||
@ -419,7 +412,7 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
let amount = amountValue(state.amount)
|
||||
var sendEnabled = false
|
||||
if let balance = balance {
|
||||
sendEnabled = isValidAddress(state.address, exactLength: true) && amount > 0 && amount <= balance.balance
|
||||
sendEnabled = isValidAddress(state.address, exactLength: true) && amount > 0 && amount <= balance.balance && state.comment.count <= 128
|
||||
}
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text("Send"), style: .bold, enabled: sendEnabled, action: {
|
||||
arguments.proceed()
|
||||
@ -432,7 +425,8 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
}
|
||||
|
||||
let controller = WalletSendScreenImpl(context: context, state: signal)
|
||||
controller.navigationPresentation = .modalInLargeLayout
|
||||
controller.navigationPresentation = .modal
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
@ -473,5 +467,28 @@ func walletSendScreen(context: AccountContext, tonContext: TonContext, walletInf
|
||||
resultItemNode.focus()
|
||||
}
|
||||
}
|
||||
ensureItemVisibleImpl = { [weak controller] targetTag 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -17,10 +17,12 @@ import TelegramStringFormatting
|
||||
private final class WalletTransactionInfoControllerArguments {
|
||||
let copyWalletAddress: () -> Void
|
||||
let sendGrams: () -> Void
|
||||
let displayContextMenu: (WalletTransactionInfoEntryTag, String) -> Void
|
||||
|
||||
init(copyWalletAddress: @escaping () -> Void, sendGrams: @escaping () -> Void) {
|
||||
init(copyWalletAddress: @escaping () -> Void, sendGrams: @escaping () -> Void, displayContextMenu: @escaping (WalletTransactionInfoEntryTag, String) -> Void) {
|
||||
self.copyWalletAddress = copyWalletAddress
|
||||
self.sendGrams = sendGrams
|
||||
self.displayContextMenu = displayContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +32,18 @@ private enum WalletTransactionInfoSection: Int32 {
|
||||
case comment
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoEntryTag: ItemListItemTag {
|
||||
case comment
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? WalletTransactionInfoEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
||||
case amount(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, WalletTransaction)
|
||||
case infoHeader(PresentationTheme, String)
|
||||
@ -92,7 +106,9 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, text):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks)
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks, longTapAction: {
|
||||
arguments.displayContextMenu(WalletTransactionInfoEntryTag.comment, text)
|
||||
}, tag: WalletTransactionInfoEntryTag.comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,12 +173,6 @@ private func extractDescription(_ walletTransaction: WalletTransaction) -> Strin
|
||||
return text
|
||||
}
|
||||
|
||||
private func formatAddress(_ address: String) -> String {
|
||||
var address = address
|
||||
address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2))
|
||||
return address
|
||||
}
|
||||
|
||||
private func walletTransactionInfoControllerEntries(presentationData: PresentationData, walletTransaction: WalletTransaction, state: WalletTransactionInfoControllerState) -> [WalletTransactionInfoEntry] {
|
||||
var entries: [WalletTransactionInfoEntry] = []
|
||||
|
||||
@ -203,6 +213,7 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var displayContextMenuImpl: ((WalletTransactionInfoEntryTag, String) -> Void)?
|
||||
|
||||
let arguments = WalletTransactionInfoControllerArguments(copyWalletAddress: {
|
||||
let address = extractAddress(walletTransaction)
|
||||
@ -217,6 +228,8 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
||||
dismissImpl?()
|
||||
pushImpl?(walletSendScreen(context: context, tonContext: tonContext, walletInfo: walletInfo, address: address))
|
||||
}
|
||||
}, displayContextMenu: { tag, text in
|
||||
displayContextMenuImpl?(tag, text)
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get())
|
||||
@ -242,6 +255,35 @@ func walletTransactionInfoController(context: AccountContext, tonContext: TonCon
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
displayContextMenuImpl = { [weak controller] tag, value in
|
||||
if let strongController = controller {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let _ = strongController.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListMultilineTextItemNode {
|
||||
if let itemTag = itemNode.tag as? WalletTransactionInfoEntryTag {
|
||||
if itemTag == tag {
|
||||
resultItemNode = itemNode
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = value
|
||||
})])
|
||||
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
||||
if let strongController = controller, let resultItemNode = resultItemNode {
|
||||
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
112
submodules/WalletUI/Sources/WalletUtils.swift
Normal file
112
submodules/WalletUI/Sources/WalletUtils.swift
Normal file
@ -0,0 +1,112 @@
|
||||
import Foundation
|
||||
import TelegramStringFormatting
|
||||
|
||||
let walletAddressLength: Int = 48
|
||||
|
||||
func formatAddress(_ address: String) -> String {
|
||||
var address = address
|
||||
address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2))
|
||||
return address
|
||||
}
|
||||
|
||||
func formatBalanceText(_ value: Int64, decimalSeparator: String) -> String {
|
||||
var balanceText = "\(abs(value))"
|
||||
while balanceText.count < 10 {
|
||||
balanceText.insert("0", at: balanceText.startIndex)
|
||||
}
|
||||
balanceText.insert(contentsOf: decimalSeparator, at: balanceText.index(balanceText.endIndex, offsetBy: -9))
|
||||
while true {
|
||||
if balanceText.hasSuffix("0") {
|
||||
if balanceText.hasSuffix("\(decimalSeparator)0") {
|
||||
break
|
||||
} else {
|
||||
balanceText.removeLast()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if value < 0 {
|
||||
balanceText.insert("-", at: balanceText.startIndex)
|
||||
}
|
||||
return balanceText
|
||||
}
|
||||
|
||||
private let invalidAmountCharacters = CharacterSet(charactersIn: "01234567890.,").inverted
|
||||
func isValidAmount(_ amount: String) -> Bool {
|
||||
let amount = normalizeArabicNumeralString(amount, type: .western)
|
||||
if amount.rangeOfCharacter(from: invalidAmountCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
var hasDecimalSeparator = false
|
||||
var hasLeadingZero = false
|
||||
var index = 0
|
||||
for c in amount {
|
||||
if c == "." || c == "," {
|
||||
if !hasDecimalSeparator {
|
||||
hasDecimalSeparator = true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
var decimalIndex: String.Index?
|
||||
if let index = amount.firstIndex(of: ".") {
|
||||
decimalIndex = index
|
||||
} else if let index = amount.firstIndex(of: ",") {
|
||||
decimalIndex = index
|
||||
}
|
||||
|
||||
if let decimalIndex = decimalIndex, amount.distance(from: decimalIndex, to: amount.endIndex) > 10 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func amountValue(_ string: String) -> Int64 {
|
||||
return Int64((Double(string.replacingOccurrences(of: ",", with: ".")) ?? 0.0) * 1000000000.0)
|
||||
}
|
||||
|
||||
func normalizedStringForGramsString(_ string: String, decimalSeparator: String = ".") -> String {
|
||||
return formatBalanceText(amountValue(string), decimalSeparator: decimalSeparator)
|
||||
}
|
||||
|
||||
func formatAmountText(_ text: String, decimalSeparator: String) -> String {
|
||||
var text = normalizeArabicNumeralString(text, type: .western)
|
||||
if text == "." || text == "," {
|
||||
text = "0\(decimalSeparator)"
|
||||
} else if text == "0" {
|
||||
text = "0\(decimalSeparator)"
|
||||
} else if text.hasPrefix("0") && text.firstIndex(of: ".") == nil && text.firstIndex(of: ",") == nil {
|
||||
var trimmedText = text
|
||||
while trimmedText.first == "0" {
|
||||
trimmedText.removeFirst()
|
||||
}
|
||||
text = trimmedText
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
private let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=").inverted
|
||||
private let invalidUrlAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted
|
||||
|
||||
func isValidAddress(_ address: String, exactLength: Bool = false, url: Bool = false) -> Bool {
|
||||
if address.count > walletAddressLength || address.rangeOfCharacter(from: url ? invalidUrlAddressCharacters : invalidAddressCharacters) != nil {
|
||||
return false
|
||||
}
|
||||
if exactLength && address.count != walletAddressLength {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func convertedAddress(_ address: String, url: Bool) -> String {
|
||||
if url {
|
||||
return address.replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_")
|
||||
} else {
|
||||
return address.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
||||
}
|
||||
}
|
@ -2250,6 +2250,8 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let inputNode: TextFieldNode
|
||||
private let clearButtonNode: HighlightableButtonNode
|
||||
|
||||
public private(set) var isLast: Bool
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.inputNode.textField.text ?? ""
|
||||
@ -2263,6 +2265,7 @@ private final class WordCheckInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
self.next = next
|
||||
self.focused = focused
|
||||
self.pasteWords = pasteWords
|
||||
self.isLast = isLast
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
@ -2710,7 +2713,13 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
|
||||
if node.isLast {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(strongSelf.buttonNode.frame.insetBy(dx: 0.0, dy: -10.0), animated: false)
|
||||
})
|
||||
} else {
|
||||
strongSelf.scrollNode.view.scrollRectToVisible(node.frame.insetBy(dx: 0.0, dy: -10.0), animated: true)
|
||||
}
|
||||
}
|
||||
pasteWords = { [weak self] wordList in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AppBundle
|
||||
import AccountContext
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
@ -19,6 +20,7 @@ public final class WalletWordDisplayScreen: ViewController {
|
||||
private let wordList: [String]
|
||||
|
||||
private let startTime: Double
|
||||
private let idleTimerExtensionDisposable: Disposable
|
||||
|
||||
public init(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, wordList: [String]) {
|
||||
self.context = context
|
||||
@ -32,6 +34,7 @@ public final class WalletWordDisplayScreen: ViewController {
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultNavigationPresentationData.theme.buttonColor, disabledButtonColor: defaultNavigationPresentationData.theme.disabledButtonColor, primaryTextColor: defaultNavigationPresentationData.theme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultNavigationPresentationData.theme.badgeBackgroundColor, badgeStrokeColor: defaultNavigationPresentationData.theme.badgeStrokeColor, badgeTextColor: defaultNavigationPresentationData.theme.badgeTextColor)
|
||||
|
||||
self.startTime = Date().timeIntervalSince1970
|
||||
self.idleTimerExtensionDisposable = context.sharedContext.applicationBindings.pushIdleTimerExtension()
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
|
||||
|
||||
@ -46,6 +49,10 @@ public final class WalletWordDisplayScreen: ViewController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.idleTimerExtensionDisposable.dispose()
|
||||
}
|
||||
|
||||
@objc private func backPressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
|
@ -741,6 +741,19 @@
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>1DD70E29D81471E200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>libUrlHandling.a</string>
|
||||
<key>path</key>
|
||||
<string>libUrlHandling.a</string>
|
||||
<key>sourceTree</key>
|
||||
<string>BUILT_PRODUCTS_DIR</string>
|
||||
<key>explicitFileType</key>
|
||||
<string>archive.ar</string>
|
||||
</dict>
|
||||
<key>B401C97968022A5500000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -802,6 +815,7 @@
|
||||
<string>1DD70E29AE67341000000000</string>
|
||||
<string>1DD70E2951398CF200000000</string>
|
||||
<string>1DD70E29597BAFBB00000000</string>
|
||||
<string>1DD70E29D81471E200000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>1DD70E29D097476500000000</key>
|
||||
@ -920,6 +934,17 @@
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E298815219000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletQrViewScreen.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletQrViewScreen.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2979DDEBBB00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -975,6 +1000,17 @@
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E298710C0BD00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXFileReference</string>
|
||||
<key>name</key>
|
||||
<string>WalletUtils.swift</string>
|
||||
<key>path</key>
|
||||
<string>Sources/WalletUtils.swift</string>
|
||||
<key>sourceTree</key>
|
||||
<string>SOURCE_ROOT</string>
|
||||
</dict>
|
||||
<key>1DD70E2936794EB600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -1014,11 +1050,13 @@
|
||||
<string>1DD70E29E336006800000000</string>
|
||||
<string>1DD70E296D49CFFF00000000</string>
|
||||
<string>1DD70E29DE28A96800000000</string>
|
||||
<string>1DD70E298815219000000000</string>
|
||||
<string>1DD70E2979DDEBBB00000000</string>
|
||||
<string>1DD70E2948FA33F200000000</string>
|
||||
<string>1DD70E2986544B8D00000000</string>
|
||||
<string>1DD70E2964068E1100000000</string>
|
||||
<string>1DD70E290467090400000000</string>
|
||||
<string>1DD70E298710C0BD00000000</string>
|
||||
<string>1DD70E2936794EB600000000</string>
|
||||
<string>1DD70E290678D03000000000</string>
|
||||
</array>
|
||||
@ -1102,6 +1140,13 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29DE28A96800000000</string>
|
||||
</dict>
|
||||
<key>E7A30F048815219000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E298815219000000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0479DDEBBB00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -1137,6 +1182,13 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E290467090400000000</string>
|
||||
</dict>
|
||||
<key>E7A30F048710C0BD00000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E298710C0BD00000000</string>
|
||||
</dict>
|
||||
<key>E7A30F0436794EB600000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -1164,11 +1216,13 @@
|
||||
<string>E7A30F04E336006800000000</string>
|
||||
<string>E7A30F046D49CFFF00000000</string>
|
||||
<string>E7A30F04DE28A96800000000</string>
|
||||
<string>E7A30F048815219000000000</string>
|
||||
<string>E7A30F0479DDEBBB00000000</string>
|
||||
<string>E7A30F0448FA33F200000000</string>
|
||||
<string>E7A30F0486544B8D00000000</string>
|
||||
<string>E7A30F0464068E1100000000</string>
|
||||
<string>E7A30F040467090400000000</string>
|
||||
<string>E7A30F048710C0BD00000000</string>
|
||||
<string>E7A30F0436794EB600000000</string>
|
||||
<string>E7A30F040678D03000000000</string>
|
||||
</array>
|
||||
@ -1530,6 +1584,13 @@
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29AE67341000000000</string>
|
||||
</dict>
|
||||
<key>E7A30F04D81471E200000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
<string>PBXBuildFile</string>
|
||||
<key>fileRef</key>
|
||||
<string>1DD70E29D81471E200000000</string>
|
||||
</dict>
|
||||
<key>FAF5FAC90000000000000000</key>
|
||||
<dict>
|
||||
<key>isa</key>
|
||||
@ -1587,6 +1648,7 @@
|
||||
<string>E7A30F0481AE180900000000</string>
|
||||
<string>E7A30F04524F478E00000000</string>
|
||||
<string>E7A30F04AE67341000000000</string>
|
||||
<string>E7A30F04D81471E200000000</string>
|
||||
</array>
|
||||
<key>name</key>
|
||||
<string>Fake Swift Dependencies (Copy Files Phase)</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user