Fix layout

This commit is contained in:
Peter 2019-09-22 00:06:59 +04:00
parent ee32596ebd
commit 053187ec4a
14 changed files with 393 additions and 140 deletions

View File

@ -357,8 +357,11 @@ API_AVAILABLE(ios(10))
if (baseAppBundleId != nil) {
_dataDict[@"bundleId"] = baseAppBundleId;
}
if (signature.name != nil) {
_dataDict[@"name"] = signature.name;
if (signature.issuerName != nil) {
_dataDict[@"issuerName"] = signature.issuerName;
}
if (signature.subjectName != nil) {
_dataDict[@"subjectName"] = signature.subjectName;
}
if (signature.data != nil) {
_dataDict[@"data"] = [MTSha1(signature.data) base64EncodedStringWithOptions:0];

View File

@ -69,7 +69,7 @@ static NSData *base64_decode(NSString *str) {
}
}
+ (MTSignal *)fetchBackupIpsResolveGoogle:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext {
+ (MTSignal *)fetchBackupIpsResolveGoogle:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext addressOverride:(NSString *)addressOverride {
NSArray *hosts = @[
@"google.com",
@"www.google.com",
@ -79,7 +79,11 @@ static NSData *base64_decode(NSString *str) {
NSMutableArray *signals = [[NSMutableArray alloc] init];
for (NSString *host in hosts) {
MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/resolve?name=%@&type=16", host, isTesting ? @"tapv3.stel.com" : @"apv3.stel.com"]] headers:headers] mapToSignal:^MTSignal *(NSData *data) {
NSString *apvHost = @"apv3.stel.com";
if (addressOverride != nil) {
apvHost = addressOverride;
}
MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/resolve?name=%@&type=16", host, isTesting ? @"tapv3.stel.com" : apvHost]] headers:headers] mapToSignal:^MTSignal *(NSData *data) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([dict respondsToSelector:@selector(objectForKey:)]) {
NSArray *answer = dict[@"Answer"];
@ -205,7 +209,7 @@ static NSData *base64_decode(NSString *str) {
+ (MTSignal * _Nonnull)fetchBackupIps:(bool)isTestingEnvironment currentContext:(MTContext * _Nonnull)currentContext additionalSource:(MTSignal * _Nullable)additionalSource phoneNumber:(NSString * _Nullable)phoneNumber {
NSMutableArray *signals = [[NSMutableArray alloc] init];
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext]];
[signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
if (additionalSource != nil) {
[signals addObject:additionalSource];
}

View File

@ -4,7 +4,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface MTPKCS : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *issuerName;
@property (nonatomic, strong, readonly) NSString *subjectName;
@property (nonatomic, strong, readonly) NSData *data;
+ (MTPKCS * _Nullable)parse:(const unsigned char *)buffer size:(int)size;

View File

@ -3,12 +3,23 @@
#include <openssl/x509.h>
#include <openssl/pkcs7.h>
static NSString * _Nullable readName(X509_NAME *subject) {
BIO *subjectBio = BIO_new(BIO_s_mem());
X509_NAME_print_ex(subjectBio, subject, 0, XN_FLAG_RFC2253);
char *dataStart = NULL;
long nameLength = BIO_get_mem_data(subjectBio, &dataStart);
NSString *result = [[NSString alloc] initWithBytes:dataStart length:nameLength encoding:NSUTF8StringEncoding];
BIO_free(subjectBio);
return result;
}
@implementation MTPKCS
- (instancetype)initWithName:(NSString *)name data:(NSData *)data {
- (instancetype)initWithIssuerName:(NSString *)issuerName subjectName:(NSString *)subjectName data:(NSData *)data {
self = [super init];
if (self != nil) {
_name = name;
_issuerName = issuerName;
_subjectName = subjectName;
_data = data;
}
return self;
@ -51,10 +62,13 @@
return nil;
}
X509_NAME *name = X509_get_subject_name(cert);
EVP_PKEY *publicKey = X509_get_pubkey(cert);
X509_NAME *issuerName = X509_get_issuer_name(cert);
X509_NAME *subjectName = X509_get_subject_name(cert);
//result = [[MTPKCS alloc] initWithName:[NSString stringWithUTF8String:cert->name] data:[NSData dataWithBytes:cert->cert_info->key->public_key->data length:cert->cert_info->key->public_key->length]];
NSString *issuerNameString = readName(issuerName);
NSString *subjectNameString = readName(subjectName);
result = [[MTPKCS alloc] initWithIssuerName:issuerNameString subjectName:subjectNameString data:[NSData data]];
return result;
}

View File

@ -69,6 +69,7 @@
@property (nonatomic) bool disableUpdates;
@property (nonatomic) NSData *tcpPayloadPrefix;
@property (nonatomic) NSDictionary *datacenterAddressOverrides;
@property (nonatomic) NSString *accessHostOverride;
@property (nonatomic, strong, readonly) MTSocksProxySettings *socksProxySettings;
@property (nonatomic, strong, readonly) MTNetworkSettings *networkSettings;

View File

@ -785,6 +785,7 @@ NSString *suffix = @"";
result.disableUpdates = self.disableUpdates;
result.tcpPayloadPrefix = self.tcpPayloadPrefix;
result.datacenterAddressOverrides = self.datacenterAddressOverrides;
result.accessHostOverride = self.accessHostOverride;
result->_socksProxySettings = self.socksProxySettings;
result->_networkSettings = self.networkSettings;
result->_systemCode = self.systemCode;
@ -811,6 +812,7 @@ NSString *suffix = @"";
result.disableUpdates = self.disableUpdates;
result.tcpPayloadPrefix = self.tcpPayloadPrefix;
result.datacenterAddressOverrides = self.datacenterAddressOverrides;
result.accessHostOverride = self.accessHostOverride;
[result _updateApiInitializationHash];
@ -834,6 +836,7 @@ NSString *suffix = @"";
result.disableUpdates = self.disableUpdates;
result.tcpPayloadPrefix = self.tcpPayloadPrefix;
result.datacenterAddressOverrides = self.datacenterAddressOverrides;
result.accessHostOverride = self.accessHostOverride;
[result _updateApiInitializationHash];
@ -857,6 +860,7 @@ NSString *suffix = @"";
result.disableUpdates = self.disableUpdates;
result.tcpPayloadPrefix = self.tcpPayloadPrefix;
result.datacenterAddressOverrides = self.datacenterAddressOverrides;
result.accessHostOverride = self.accessHostOverride;
[result _updateApiInitializationHash];
@ -880,6 +884,7 @@ NSString *suffix = @"";
result.disableUpdates = self.disableUpdates;
result.tcpPayloadPrefix = self.tcpPayloadPrefix;
result.datacenterAddressOverrides = self.datacenterAddressOverrides;
result.accessHostOverride = self.accessHostOverride;
[result _updateApiInitializationHash];

View File

@ -340,7 +340,6 @@
}
}
[_apiEnvironment.datacenterAddressOverrides enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, MTDatacenterAddress *address, __unused BOOL *stop) {
_datacenterAddressSetById[nDatacenterId] = [[MTDatacenterAddressSet alloc] initWithAddressList:@[address]];
}];

View File

@ -73,6 +73,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case photoPreview(PresentationTheme, Bool)
case knockoutWallpaper(PresentationTheme, Bool)
case gradientBubbles(PresentationTheme, Bool)
case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme)
var section: ItemListSectionId {
@ -87,7 +88,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .resetBiometricsData, .openDebugWallet, .getGrams, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .gradientBubbles:
return DebugControllerSection.experiments.rawValue
case .versionInfo:
case .hostInfo, .versionInfo:
return DebugControllerSection.info.rawValue
}
}
@ -142,8 +143,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 22
case .gradientBubbles:
return 23
case .versionInfo:
case .hostInfo:
return 24
case .versionInfo:
return 25
}
}
@ -556,6 +559,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .hostInfo(theme, string):
return ItemListTextItem(theme: theme, text: .plain(string), sectionId: self.section)
case let .versionInfo(theme):
let bundle = Bundle.main
let bundleId = bundle.bundleIdentifier ?? ""
@ -566,7 +571,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
}
private func debugControllerEntries(presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, hasLegacyAppData: Bool) -> [DebugControllerEntry] {
private func debugControllerEntries(presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, networkSettings: NetworkSettings?, hasLegacyAppData: Bool) -> [DebugControllerEntry] {
var entries: [DebugControllerEntry] = []
entries.append(.sendLogs(presentationData.theme))
@ -599,6 +604,9 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.gradientBubbles(presentationData.theme, experimentalSettings.gradientBubbles))
if let backupHostOverride = networkSettings?.backupHostOverride {
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))
}
entries.append(.versionInfo(presentationData.theme))
return entries
@ -627,35 +635,45 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
hasLegacyAppData = FileManager.default.fileExists(atPath: statusPath)
}
let signal = combineLatest(sharedContext.presentationData, sharedContext.accountManager.sharedData(keys: Set([SharedDataKeys.loggingSettings, ApplicationSpecificSharedDataKeys.mediaInputSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings])))
|> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState<DebugControllerEntry>, DebugControllerEntry.ItemGenerationArguments)) in
let loggingSettings: LoggingSettings
if let value = sharedData.entries[SharedDataKeys.loggingSettings] as? LoggingSettings {
loggingSettings = value
} else {
loggingSettings = LoggingSettings.defaultSettings
}
let mediaInputSettings: MediaInputSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaInputSettings] as? MediaInputSettings {
mediaInputSettings = value
} else {
mediaInputSettings = MediaInputSettings.defaultSettings
}
let experimentalSettings: ExperimentalUISettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings) ?? ExperimentalUISettings.defaultSettings
var leftNavigationButton: ItemListNavigationButton?
if modal {
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Debug"), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: debugControllerEntries(presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, hasLegacyAppData: hasLegacyAppData), style: .blocks)
return (controllerState, (listState, arguments))
let preferencesSignal: Signal<PreferencesView?, NoError>
if let context = context {
preferencesSignal = context.account.postbox.preferencesView(keys: [PreferencesKeys.networkSettings])
|> map(Optional.init)
} else {
preferencesSignal = .single(nil)
}
let signal = combineLatest(sharedContext.presentationData, sharedContext.accountManager.sharedData(keys: Set([SharedDataKeys.loggingSettings, ApplicationSpecificSharedDataKeys.mediaInputSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings])), preferencesSignal)
|> map { presentationData, sharedData, preferences -> (ItemListControllerState, (ItemListNodeState<DebugControllerEntry>, DebugControllerEntry.ItemGenerationArguments)) in
let loggingSettings: LoggingSettings
if let value = sharedData.entries[SharedDataKeys.loggingSettings] as? LoggingSettings {
loggingSettings = value
} else {
loggingSettings = LoggingSettings.defaultSettings
}
let mediaInputSettings: MediaInputSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaInputSettings] as? MediaInputSettings {
mediaInputSettings = value
} else {
mediaInputSettings = MediaInputSettings.defaultSettings
}
let experimentalSettings: ExperimentalUISettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings) ?? ExperimentalUISettings.defaultSettings
let networkSettings: NetworkSettings? = preferences?.values[PreferencesKeys.networkSettings] as? NetworkSettings
var leftNavigationButton: ItemListNavigationButton?
if modal {
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Debug"), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: debugControllerEntries(presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, networkSettings: networkSettings, hasLegacyAppData: hasLegacyAppData), style: .blocks)
return (controllerState, (listState, arguments))
}

View File

@ -437,6 +437,7 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
}
apiEnvironment = apiEnvironment.withUpdatedNetworkSettings((networkSettings ?? NetworkSettings.defaultSettings).mtNetworkSettings)
apiEnvironment.accessHostOverride = networkSettings?.backupHostOverride
var appDataUpdatedImpl: ((Data?) -> Void)?
let syncValue = Atomic<Data?>(value: nil)

View File

@ -16,19 +16,22 @@ import SwiftSignalKit
public struct NetworkSettings: PreferencesEntry, Equatable {
public var reducedBackupDiscoveryTimeout: Bool
public internal(set) var applicationUpdateUrlPrefix: String?
public var backupHostOverride: String?
public static var defaultSettings: NetworkSettings {
return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil)
return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil, backupHostOverride: nil)
}
public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?) {
public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?, backupHostOverride: String?) {
self.reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout
self.applicationUpdateUrlPrefix = applicationUpdateUrlPrefix
self.backupHostOverride = backupHostOverride
}
public init(decoder: PostboxDecoder) {
self.reducedBackupDiscoveryTimeout = decoder.decodeInt32ForKey("reducedBackupDiscoveryTimeout", orElse: 0) != 0
self.applicationUpdateUrlPrefix = decoder.decodeOptionalStringForKey("applicationUpdateUrlPrefix")
self.backupHostOverride = decoder.decodeOptionalStringForKey("backupHostOverride")
}
public func encode(_ encoder: PostboxEncoder) {
@ -38,6 +41,11 @@ public struct NetworkSettings: PreferencesEntry, Equatable {
} else {
encoder.encodeNil(forKey: "applicationUpdateUrlPrefix")
}
if let backupHostOverride = self.backupHostOverride {
encoder.encodeString(backupHostOverride, forKey: "backupHostOverride")
} else {
encoder.encodeNil(forKey: "backupHostOverride")
}
}
public func isEqual(to: PreferencesEntry) -> Bool {
@ -71,6 +79,9 @@ public func updateNetworkSettingsInteractively(transaction: Transaction, network
if updated.reducedBackupDiscoveryTimeout != previous.reducedBackupDiscoveryTimeout {
updateNetwork = true
}
if updated.backupHostOverride != previous.backupHostOverride {
updateNetwork = true
}
return updated
})

View File

@ -596,6 +596,27 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = result
}
}
} else if parsedUrl.host == "hostOverride" {
if let components = URLComponents(string: "/?" + query) {
var host: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "host" {
host = value
}
}
}
}
if let host = host {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings
settings.backupHostOverride = host
return settings
}).start()
return
}
}
}
if let convertedUrl = convertedUrl {

View File

@ -5,59 +5,95 @@ import AsyncDisplayKit
import TelegramPresentationData
import TelegramCore
import AnimationUI
import SwiftSignalKit
final class WalletInfoEmptyNode: ASDisplayNode {
private var presentationData: PresentationData
class WalletInfoEmptyItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let address: String
let selectable: Bool = false
init(theme: PresentationTheme, strings: PresentationStrings, address: String) {
self.theme = theme
self.strings = strings
self.address = address
}
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) {
async {
let node = WalletInfoEmptyItemNode()
node.insets = UIEdgeInsets()
node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem)
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is WalletInfoEmptyItemNode)
if let nodeValue = node() as? WalletInfoEmptyItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply()
})
}
}
}
}
}
}
final class WalletInfoEmptyItemNode: ListViewItemNode {
private let offsetContainer: ASDisplayNode
private let iconNode: ASImageNode
private let animationNode: AnimatedStickerNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let addressNode: ImmediateTextNode
private let titleNode: TextNode
private let textNode: TextNode
private let addressNode: TextNode
init(presentationData: PresentationData, address: String) {
self.presentationData = presentationData
init() {
self.offsetContainer = ASDisplayNode()
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.image = UIImage(bundleImageName: "Wallet/DuckIcon")
self.animationNode = AnimatedStickerNode()
let title = "Wallet Created"
let text = "Your wallet address"
self.iconNode.image = UIImage(bundleImageName: "Wallet/DuckIcon")
self.titleNode = ImmediateTextNode()
self.titleNode = TextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.textNode = TextNode()
self.addressNode = TextNode()
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.textNode.maximumNumberOfLines = 0
self.textNode.lineSpacing = 0.1
self.textNode.textAlignment = .center
super.init(layerBacked: false)
self.addressNode = ImmediateTextNode()
self.addressNode.displaysAsynchronously = false
self.addressNode.attributedText = NSAttributedString(string: address, font: Font.monospace(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.addressNode.maximumNumberOfLines = 0
self.addressNode.lineSpacing = 0.1
self.addressNode.textAlignment = .center
self.wantsTrailingItemSpaceUpdates = true
super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.animationNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.addressNode)
self.offsetContainer.addSubnode(self.iconNode)
self.offsetContainer.addSubnode(self.animationNode)
self.offsetContainer.addSubnode(self.titleNode)
self.offsetContainer.addSubnode(self.textNode)
self.offsetContainer.addSubnode(self.addressNode)
self.addSubnode(self.offsetContainer)
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! WalletInfoEmptyItem, params)
apply()
}
func asyncLayout() -> (_ item: WalletInfoEmptyItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> Void) {
let sideInset: CGFloat = 32.0
let buttonSideInset: CGFloat = 48.0
let iconSpacing: CGFloat = 5.0
@ -66,26 +102,65 @@ final class WalletInfoEmptyNode: ASDisplayNode {
let buttonHeight: CGFloat = 50.0
let iconSize: CGSize
var iconOffset = CGPoint()
iconSize = self.iconNode.image?.size ?? CGSize(width: 140.0, height: 140.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let addressSize = self.addressNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
let contentVerticalOrigin: CGFloat = 0.0
let iconFrame = CGRect(origin: CGPoint(x: floor((width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
self.animationNode.updateLayout(size: iconFrame.size)
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
let textFrame = CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
let addressFrame = CGRect(origin: CGPoint(x: floor((width - addressSize.width) / 2.0), y: textFrame.maxY + titleSpacing), size: addressSize)
transition.updateFrameAdditive(node: self.addressNode, frame: addressFrame)
return addressFrame.maxY
return { [weak self] item, params in
let sideInset: CGFloat = 16.0
var iconOffset = CGPoint()
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 (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 (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.address, 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 contentVerticalOrigin: CGFloat = 32.0
let iconFrame = CGRect(origin: CGPoint(x: floor((params.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.size.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textLayout.size)
let addressFrame = CGRect(origin: CGPoint(x: floor((params.width - addressLayout.size.width) / 2.0), y: textFrame.maxY + titleSpacing), size: addressLayout.size)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: addressFrame.maxY + 32.0), insets: UIEdgeInsets())
return (layout, {
guard let strongSelf = self else {
return
}
strongSelf.offsetContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
let _ = titleApply()
let _ = textApply()
let _ = addressApply()
let transition: ContainedViewLayoutTransition = .immediate
transition.updateFrameAdditive(node: strongSelf.iconNode, frame: iconFrame)
strongSelf.animationNode.updateLayout(size: iconFrame.size)
transition.updateFrameAdditive(node: strongSelf.animationNode, frame: iconFrame)
transition.updateFrameAdditive(node: strongSelf.titleNode, frame: titleFrame)
transition.updateFrameAdditive(node: strongSelf.textNode, frame: textFrame)
transition.updateFrameAdditive(node: strongSelf.addressNode, frame: addressFrame)
strongSelf.contentSize = layout.contentSize
strongSelf.insets = layout.insets
})
}
}
override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
if height.isLessThanOrEqualTo(0.0) {
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
} else {
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels(height / 2.0)), size: self.offsetContainer.bounds.size))
}
}
}

View File

@ -202,7 +202,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let minHeaderHeight: CGFloat = balanceSize.height + balanceSubtitleSize.height + balanceSubtitleSpacing
let minHeaderY = navigationHeight - 44.0 + floor((44.0 - minHeaderHeight) / 2.0)
let maxHeaderY = floor((size.height - balanceSize.height) / 2.0)
let maxHeaderY = floor((size.height - balanceSize.height) / 2.0 - balanceSubtitleSize.height)
let headerScaleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minHeaderOffset) / (maxHeaderOffset - minHeaderOffset)))
let headerPositionTransition: CGFloat = max(0.0, (effectiveOffset - minHeaderOffset) / (maxOffset - minHeaderOffset))
let headerY = headerPositionTransition * maxHeaderY + (1.0 - headerPositionTransition) * minHeaderY
@ -217,9 +217,11 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let headerHeight: CGFloat = 1000.0
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveOffset + 10.0 - headerHeight), size: CGSize(width: size.width, height: headerHeight)))
let leftButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: effectiveOffset - sideInset - buttonHeight), size: CGSize(width: floor((size.width - sideInset * 3.0) / 2.0), height: buttonHeight))
let buttonOffset = effectiveOffset
let leftButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: buttonOffset - sideInset - buttonHeight), size: CGSize(width: floor((size.width - sideInset * 3.0) / 2.0), height: buttonHeight))
let sendButtonFrame = CGRect(origin: CGPoint(x: leftButtonFrame.maxX + sideInset, y: leftButtonFrame.minY), size: CGSize(width: size.width - leftButtonFrame.maxX - sideInset * 2.0, height: buttonHeight))
let fullButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: effectiveOffset - sideInset - buttonHeight), size: CGSize(width: size.width - sideInset * 2.0, height: buttonHeight))
let fullButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: buttonOffset - sideInset - buttonHeight), size: CGSize(width: size.width - sideInset * 2.0, height: buttonHeight))
var receiveButtonFrame: CGRect
if let balance = self.balance, balance > 0 {
@ -275,23 +277,52 @@ private struct WalletInfoListTransaction {
let updates: [ListViewUpdateItem]
}
private struct WalletInfoListEntry: Equatable, Comparable, Identifiable {
let index: Int
let item: WalletTransaction
private enum WalletInfoListEntryId: Hashable {
case empty
case transaction(WalletTransactionId)
}
private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
case empty(String)
case transaction(Int, WalletTransaction)
var stableId: WalletTransactionId {
return self.item.transactionId
var stableId: WalletInfoListEntryId {
switch self {
case .empty:
return .empty
case let .transaction(_, transaction):
return .transaction(transaction.transactionId)
}
}
static func <(lhs: WalletInfoListEntry, rhs: WalletInfoListEntry) -> Bool {
return lhs.index < rhs.index
switch lhs {
case .empty:
switch rhs {
case .empty:
return false
case .transaction:
return true
}
case let .transaction(lhsIndex, _):
switch rhs {
case .empty:
return false
case let .transaction(rhsIndex, _):
return lhsIndex < rhsIndex
}
}
}
func item(theme: PresentationTheme, strings: PresentationStrings, action: @escaping (WalletTransaction) -> Void) -> ListViewItem {
let item = self.item
return WalletInfoTransactionItem(theme: theme, strings: strings, walletTransaction: self.item, action: {
action(item)
})
switch self {
case let .empty(address):
return WalletInfoEmptyItem(theme: theme, strings: strings, address: address)
case let .transaction(_, transaction):
return WalletInfoTransactionItem(theme: theme, strings: strings, walletTransaction: transaction, action: {
action(transaction)
})
}
}
}
@ -315,7 +346,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let headerNode: WalletInfoHeaderNode
private let listNode: ListView
private let emptyNode: WalletInfoEmptyNode
private var enqueuedTransactions: [WalletInfoListTransaction] = []
@ -344,9 +374,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
self.emptyNode = WalletInfoEmptyNode(presentationData: self.presentationData, address: self.address)
self.emptyNode.isHidden = true
super.init()
self.backgroundColor = .white
@ -368,7 +395,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}))
self.addSubnode(self.listNode)
self.addSubnode(self.emptyNode)
self.addSubnode(self.headerNode)
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
@ -462,11 +488,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), headerInsets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: topInset + 3.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let emptyNodeHeight = self.emptyNode.updateLayout(width: layout.size.width, transition: transition)
let maxEmptyNodeHeight: CGFloat = max(100.0, layout.size.height - headerHeight)
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: headerHeight + floor((maxEmptyNodeHeight - emptyNodeHeight) / 2.0)), size: CGSize(width: layout.size.width, height: emptyNodeHeight))
transition.updateFrame(node: self.emptyNode, frame: emptyNodeFrame)
if isFirstLayout {
while !self.enqueuedTransactions.isEmpty {
self.dequeueTransaction()
@ -496,7 +517,16 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return
}
self.loadingMoreTransactions = true
self.transactionListDisposable.set((getWalletTransactions(address: self.address, previousId: self.currentEntries?.last?.item.transactionId, tonInstance: self.tonContext.instance)
var lastTransactionId: WalletTransactionId?
if let last = self.currentEntries?.last {
switch last {
case let .transaction(_, transaction):
lastTransactionId = transaction.transactionId
case .empty:
break
}
}
self.transactionListDisposable.set((getWalletTransactions(address: self.address, previousId: lastTransactionId, tonInstance: self.tonContext.instance)
|> deliverOnMainQueue).start(next: { [weak self] transactions in
guard let strongSelf = self else {
return
@ -518,17 +548,38 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
var updatedEntries: [WalletInfoListEntry] = []
if isReload {
for transaction in transactions {
updatedEntries.append(WalletInfoListEntry(index: updatedEntries.count, item: transaction))
updatedEntries.append(.transaction(updatedEntries.count, transaction))
}
if updatedEntries.isEmpty {
updatedEntries.append(.empty(self.address))
}
} else {
updatedEntries = self.currentEntries ?? []
var existingIds = Set(updatedEntries.map { $0.item.transactionId })
updatedEntries = updatedEntries.filter { entry in
if case .empty = entry {
return false
} else {
return true
}
}
var existingIds = Set<WalletTransactionId>()
for entry in updatedEntries {
switch entry {
case let .transaction(_, transaction):
existingIds.insert(transaction.transactionId)
case .empty:
break
}
}
for transaction in transactions {
if !existingIds.contains(transaction.transactionId) {
existingIds.insert(transaction.transactionId)
updatedEntries.append(WalletInfoListEntry(index: updatedEntries.count, item: transaction))
updatedEntries.append(.transaction(updatedEntries.count, transaction))
}
}
if updatedEntries.isEmpty {
updatedEntries.append(.empty(self.address))
}
}
let transaction = preparedTransition(from: self.currentEntries ?? [], to: updatedEntries, presentationData: self.presentationData, action: { [weak self] transaction in
@ -542,18 +593,8 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.enqueuedTransactions.append(transaction)
self.dequeueTransaction()
if updatedEntries.isEmpty {
self.emptyNode.isHidden = false
} else {
self.emptyNode.isHidden = true
}
if isFirst {
if !updatedEntries.isEmpty {
self.emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} else {
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}

View File

@ -340,11 +340,14 @@ private let timezoneOffset: Int32 = {
return Int32(timeinfoNow.tm_gmtoff)
}()
private let granularity: Int32 = 60 * 60 * 24
private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
private let timestamp: Int32
private let roundedTimestamp: Int32
private let month: Int32
private let year: Int32
private let localTimestamp: Int32
let id: Int64
let theme: PresentationTheme
@ -364,6 +367,12 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
self.year = timeinfo.tm_year
self.id = Int64(self.roundedTimestamp)
if timestamp == Int32.max {
self.localTimestamp = timestamp / (granularity) * (granularity)
} else {
self.localTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
}
}
let stickDirection: ListViewItemHeaderStickDirection = .top
@ -371,12 +380,43 @@ private final class WalletInfoTransactionDateHeader: ListViewItemHeader {
let height: CGFloat = 40.0
func node() -> ListViewItemHeaderNode {
return WalletInfoTransactionDateHeaderNode(theme: self.theme, strings: self.strings, roundedTimestamp: self.roundedTimestamp, month: self.month, year: self.year)
return WalletInfoTransactionDateHeaderNode(theme: self.theme, strings: self.strings, roundedTimestamp: self.localTimestamp, month: self.month, year: self.year)
}
}
private let sectionTitleFont = Font.semibold(17.0)
private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String {
switch index {
case 0:
return strings.Month_GenJanuary
case 1:
return strings.Month_GenFebruary
case 2:
return strings.Month_GenMarch
case 3:
return strings.Month_GenApril
case 4:
return strings.Month_GenMay
case 5:
return strings.Month_GenJune
case 6:
return strings.Month_GenJuly
case 7:
return strings.Month_GenAugust
case 8:
return strings.Month_GenSeptember
case 9:
return strings.Month_GenOctober
case 10:
return strings.Month_GenNovember
case 11:
return strings.Month_GenDecember
default:
return ""
}
}
final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
var theme: PresentationTheme
var strings: PresentationStrings
@ -396,11 +436,30 @@ final class WalletInfoTransactionDateHeaderNode: ListViewItemHeaderNode {
super.init()
let dateText = stringForMonth(strings: strings, month: month, ofYear: year)
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(roundedTimestamp)
var timeinfo: tm = tm()
gmtime_r(&t, &timeinfo)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
var text: String
if timeinfo.tm_year == timeinfoNow.tm_year {
if timeinfo.tm_yday == timeinfoNow.tm_yday {
text = strings.Weekday_Today
} else {
text = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").0
}
} else {
text = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").0
}
self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode)
self.titleNode.attributedText = NSAttributedString(string: dateText, font: sectionTitleFont, textColor: theme.list.itemPrimaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: text, font: sectionTitleFont, textColor: theme.list.itemPrimaryTextColor)
self.titleNode.maximumNumberOfLines = 1
self.titleNode.truncationMode = .byTruncatingTail
}