mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Fix layout
This commit is contained in:
parent
ee32596ebd
commit
053187ec4a
@ -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];
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
||||
|
@ -340,7 +340,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[_apiEnvironment.datacenterAddressOverrides enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, MTDatacenterAddress *address, __unused BOOL *stop) {
|
||||
_datacenterAddressSetById[nDatacenterId] = [[MTDatacenterAddressSet alloc] initWithAddressList:@[address]];
|
||||
}];
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
})
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user