Merge commit '77a592a05c9e599c3dcedb59c1fadc70699bd2e6' into background-task

This commit is contained in:
Peter 2019-10-22 19:10:24 +04:00
commit cbaf7d1ada
97 changed files with 1504 additions and 1090 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,4 @@
[submodule "submodules/rlottie/rlottie"] [submodule "submodules/rlottie/rlottie"]
path = submodules/rlottie/rlottie path = submodules/rlottie/rlottie
url = https://github.com/peter-iakovlev/rlottie.git url = https://github.com/laktyushin/rlottie.git

3
BUCK
View File

@ -399,8 +399,9 @@ apple_binary(
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/Intents.framework",
"$SDKROOT/System/Library/Frameworks/Contacts.framework",
], ],
) )

View File

@ -343,10 +343,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
self.actionDisposable.set((self.accountPromise.get() self.actionDisposable.set((self.accountPromise.get()
|> castError(IntentHandlingError.self)
|> take(1) |> take(1)
|> mapError { _ -> IntentHandlingError in
return .generic
}
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in |> mapToSignal { account -> Signal<Void, IntentHandlingError> in
guard let account = account else { guard let account = account else {
return .fail(.generic) return .fail(.generic)
@ -456,10 +454,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) { func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
self.actionDisposable.set((self.accountPromise.get() self.actionDisposable.set((self.accountPromise.get()
|> castError(IntentHandlingError.self)
|> take(1) |> take(1)
|> mapError { _ -> IntentHandlingError in
return .generic
}
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in |> mapToSignal { account -> Signal<Void, IntentHandlingError> in
guard let account = account else { guard let account = account else {
return .fail(.generic) return .fail(.generic)
@ -532,10 +528,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) { func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
self.actionDisposable.set((self.accountPromise.get() self.actionDisposable.set((self.accountPromise.get()
|> castError(IntentHandlingError.self)
|> take(1) |> take(1)
|> mapError { _ -> IntentHandlingError in
return .generic
}
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in |> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else { guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
return .fail(.generic) return .fail(.generic)

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_camera.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

View File

@ -4638,12 +4638,12 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.SendMessage.SetReminder" = "Set a Reminder"; "Conversation.SendMessage.SetReminder" = "Set a Reminder";
"Conversation.SelectedMessages_1" = "%@ Message Selected"; "Conversation.SelectedMessages_1" = "%@ Selected";
"Conversation.SelectedMessages_2" = "%@ Messages Selected"; "Conversation.SelectedMessages_2" = "%@ Selected";
"Conversation.SelectedMessages_3_10" = "%@ Messages Selected"; "Conversation.SelectedMessages_3_10" = "%@ Selected";
"Conversation.SelectedMessages_any" = "%@ Messages Selected"; "Conversation.SelectedMessages_any" = "%@ Selected";
"Conversation.SelectedMessages_many" = "%@ Messages Selected"; "Conversation.SelectedMessages_many" = "%@ Selected";
"Conversation.SelectedMessages_0" = "%@ Messages Selected"; "Conversation.SelectedMessages_0" = "%@ Selected";
"AccentColor.Title" = "Accent Color"; "AccentColor.Title" = "Accent Color";
@ -4872,11 +4872,11 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.TransactionInfo.SenderHeader" = "SENDER"; "Wallet.TransactionInfo.SenderHeader" = "SENDER";
"Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address"; "Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address";
"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard."; "Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard.";
"Wallet.TransactionInfo.SendGrams" = "Send Grams"; "Wallet.TransactionInfo.SendGrams" = "Send Grams to This Address";
"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; "Wallet.TransactionInfo.CommentHeader" = "COMMENT";
"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE"; "Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE";
"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE"; "Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE";
"Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet. [More info]()"; "Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions. [More info]()";
"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()"; "Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()";
"Wallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee"; "Wallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee";
"Wallet.WordCheck.Title" = "Test Time!"; "Wallet.WordCheck.Title" = "Test Time!";
@ -4979,3 +4979,15 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; "Wallet.VoiceOver.Editing.ClearText" = "Clear text";
"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them."; "Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them.";
"Conversation.ClearCache" = "Clear Cache";
"ClearCache.Description" = "Media files will be deleted from your phone, but available for re-downloading when necessary.";
"ClearCache.FreeSpaceDescription" = "If you want to save space on your device, you don't need to delete anything.\n\nYou can use cache settings to remove unnecessary media — and re-download files if you need them again.";
"ClearCache.FreeSpace" = "Free Space";
"ClearCache.Success" = "**%@** freed on your %@!";
"Conversation.ScheduleMessage.SendWhenOnline" = "Send When Online";
"ScheduledMessages.ScheduledOnline" = "Scheduled until online";
"Conversation.SwipeToReplyHintTitle" = "Swipe To Reply";
"Conversation.SwipeToReplyHintText" = "Swipe left on any message to reply to it.";

View File

@ -366,7 +366,7 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
} }
} }
func pickImage(completion: @escaping (UIImage) -> Void) { func pickImage(present: @escaping (ViewController) -> Void, completion: @escaping (UIImage) -> Void) {
self.currentImagePickerCompletion = completion self.currentImagePickerCompletion = completion
let pickerController = UIImagePickerController() let pickerController = UIImagePickerController()
@ -465,6 +465,9 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
buttonTextColor: .white, buttonTextColor: .white,
incomingFundsTitleColor: UIColor(rgb: 0x00b12c), incomingFundsTitleColor: UIColor(rgb: 0x00b12c),
outgoingFundsTitleColor: UIColor(rgb: 0xff3b30) outgoingFundsTitleColor: UIColor(rgb: 0xff3b30)
), transaction: WalletTransactionTheme(
descriptionBackgroundColor: UIColor(rgb: 0xf1f1f4),
descriptionTextColor: .black
), setup: WalletSetupTheme( ), setup: WalletSetupTheme(
buttonFillColor: accentColor, buttonFillColor: accentColor,
buttonForegroundColor: .white, buttonForegroundColor: .white,

View File

@ -389,6 +389,11 @@
completionBlock(); completionBlock();
} }
- (NSArray<NSString *> *)suggestionsForResponseToActionWithIdentifier:(NSString *)identifier forNotification:(UNNotification *)notification inputLanguage:(NSString *)inputLanguage
{
return [TGInputController suggestionsForText:nil];
}
- (NSArray<NSString *> *)suggestionsForResponseToActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)localNotification inputLanguage:(NSString *)inputLanguage - (NSArray<NSString *> *)suggestionsForResponseToActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)localNotification inputLanguage:(NSString *)inputLanguage
{ {
return [TGInputController suggestionsForText:nil]; return [TGInputController suggestionsForText:nil];

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>People</string> <string>${APP_NAME}</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -302,7 +302,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.didShowProxyUnavailableTooltipController = true strongSelf.didShowProxyUnavailableTooltipController = true
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), timeout: 60.0, dismissByTapOutside: true) let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), timeout: 60.0, dismissByTapOutside: true)
strongSelf.proxyUnavailableTooltipController = tooltipController strongSelf.proxyUnavailableTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController {
strongSelf.proxyUnavailableTooltipController = nil strongSelf.proxyUnavailableTooltipController = nil
} }

View File

@ -825,31 +825,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
func foldLineBreaks(_ text: String, allowTwoLines: Bool) -> String {
var lines = text.split { $0.isNewline }
var startedBothLines = false
var result = ""
for line in lines {
if result.isEmpty {
result += line
} else {
if allowTwoLines && !startedBothLines {
result += "\n" + line
startedBothLines = true
} else {
result += " " + line
}
}
}
return result
}
let messageText: String let messageText: String
if let currentChatListText = currentChatListText, currentChatListText.0 == text { if let currentChatListText = currentChatListText, currentChatListText.0 == text {
messageText = currentChatListText.1 messageText = currentChatListText.1
chatListText = currentChatListText chatListText = currentChatListText
} else { } else {
messageText = foldLineBreaks(text, allowTwoLines: peerText == nil) messageText = foldLineBreaks(text)
chatListText = (text, messageText) chatListText = (text, messageText)
} }
@ -857,7 +838,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
hasDraft = true hasDraft = true
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
attributedText = NSAttributedString(string: foldLineBreaks(embeddedState.text.string.replacingOccurrences(of: "\n\n", with: " "), allowTwoLines: false), font: textFont, textColor: theme.messageTextColor) attributedText = NSAttributedString(string: foldLineBreaks(embeddedState.text.string.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
} else if let message = message { } else if let message = message {
let composedString: NSMutableAttributedString let composedString: NSMutableAttributedString
if let inlineAuthorPrefix = inlineAuthorPrefix { if let inlineAuthorPrefix = inlineAuthorPrefix {
@ -1839,3 +1820,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
} }
private func foldLineBreaks(_ text: String) -> String {
var lines = text.split { $0.isNewline }
var startedBothLines = false
var result = ""
for line in lines {
if line.isEmpty {
continue
}
if result.isEmpty {
result += line
} else {
result += " " + line
}
}
return result
}

View File

@ -12,6 +12,8 @@ import AccountContext
public enum DeleteChatPeerAction { public enum DeleteChatPeerAction {
case delete case delete
case clearHistory case clearHistory
case clearCache
case clearCacheSuggestion
} }
public final class DeleteChatPeerActionSheetItem: ActionSheetItem { public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
@ -81,8 +83,20 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: overrideImage) self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: overrideImage)
} }
let text: (String, [(Int, NSRange)]) var attributedText: NSAttributedString?
switch action { switch action {
case .clearCache, .clearCacheSuggestion:
switch action {
case .clearCache:
attributedText = NSAttributedString(string: strings.ClearCache_Description, font: Font.regular(14.0), textColor: theme.primaryTextColor)
case .clearCacheSuggestion:
attributedText = NSAttributedString(string: strings.ClearCache_FreeSpaceDescription, font: Font.regular(14.0), textColor: theme.primaryTextColor)
default:
break
}
default:
var text: (String, [(Int, NSRange)])?
switch action {
case .delete: case .delete:
if chatPeer.id == context.account.peerId { if chatPeer.id == context.account.peerId {
text = (strings.ChatList_DeleteSavedMessagesConfirmation, []) text = (strings.ChatList_DeleteSavedMessagesConfirmation, [])
@ -97,16 +111,24 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
} }
case .clearHistory: case .clearHistory:
text = strings.ChatList_ClearChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder)) text = strings.ChatList_ClearChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
} default:
let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: text.0, font: Font.regular(14.0), textColor: theme.primaryTextColor)) break
for (_, range) in text.1 { }
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range) if let text = text {
var formattedAttributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: text.0, font: Font.regular(14.0), textColor: theme.primaryTextColor))
for (_, range) in text.1 {
formattedAttributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
}
attributedText = formattedAttributedText
}
} }
self.textNode.attributedText = attributedText if let attributedText = attributedText {
self.textNode.attributedText = attributedText
self.accessibilityArea.accessibilityLabel = attributedText.string
self.accessibilityArea.accessibilityTraits = .staticText self.accessibilityArea.accessibilityLabel = attributedText.string
self.accessibilityArea.accessibilityTraits = .staticText
}
} }
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {

View File

@ -8,7 +8,6 @@ public enum ActionSheetButtonColor {
case disabled case disabled
} }
public enum ActionSheetButtonFont { public enum ActionSheetButtonFont {
case `default` case `default`
case bold case bold

View File

@ -44,7 +44,14 @@ public enum DeviceMetrics: CaseIterable, Equatable {
let additionalSize = CGSize(width: screenSize.width, height: screenSize.height + 20.0) let additionalSize = CGSize(width: screenSize.width, height: screenSize.height + 20.0)
for device in DeviceMetrics.allCases { for device in DeviceMetrics.allCases {
if let _ = onScreenNavigationHeight, device.onScreenNavigationHeight(inLandscape: false) == nil { if let _ = onScreenNavigationHeight, device.onScreenNavigationHeight(inLandscape: false) == nil {
continue if case .tablet = device.type {
if screenSize.height == 1024.0 && screenSize.width == 768.0 {
} else {
continue
}
} else {
continue
}
} }
let width = device.screenSize.width let width = device.screenSize.width

View File

@ -63,11 +63,12 @@ open class TooltipController: ViewController, StandalonePresentableController {
public private(set) var content: TooltipControllerContent public private(set) var content: TooltipControllerContent
open func updateContent(_ content: TooltipControllerContent, animated: Bool, extendTimer: Bool) { open func updateContent(_ content: TooltipControllerContent, animated: Bool, extendTimer: Bool, arrowOnBottom: Bool = true) {
if self.content != content { if self.content != content {
self.content = content self.content = content
if self.isNodeLoaded { if self.isNodeLoaded {
self.controllerNode.updateText(self.content.text, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate) self.controllerNode.updateText(self.content.text, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
self.controllerNode.arrowOnBottom = arrowOnBottom
if extendTimer, self.timeoutTimer != nil { if extendTimer, self.timeoutTimer != nil {
self.timeoutTimer?.invalidate() self.timeoutTimer?.invalidate()
self.timeoutTimer = nil self.timeoutTimer = nil
@ -84,15 +85,17 @@ open class TooltipController: ViewController, StandalonePresentableController {
private var timeoutTimer: SwiftSignalKit.Timer? private var timeoutTimer: SwiftSignalKit.Timer?
private var layout: ContainerViewLayout? private var layout: ContainerViewLayout?
private var initialArrowOnBottom: Bool
public var dismissed: (() -> Void)? public var dismissed: ((Bool) -> Void)?
public init(content: TooltipControllerContent, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false) { public init(content: TooltipControllerContent, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true) {
self.content = content self.content = content
self.timeout = timeout self.timeout = timeout
self.dismissByTapOutside = dismissByTapOutside self.dismissByTapOutside = dismissByTapOutside
self.dismissByTapOutsideSource = dismissByTapOutsideSource self.dismissByTapOutsideSource = dismissByTapOutsideSource
self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate
self.initialArrowOnBottom = arrowOnBottom
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -108,9 +111,10 @@ open class TooltipController: ViewController, StandalonePresentableController {
} }
override open func loadDisplayNode() { override open func loadDisplayNode() {
self.displayNode = TooltipControllerNode(content: self.content, dismiss: { [weak self] in self.displayNode = TooltipControllerNode(content: self.content, dismiss: { [weak self] tappedInside in
self?.dismiss() self?.dismiss(tappedInside: tappedInside)
}, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource) }, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource)
self.controllerNode.arrowOnBottom = self.initialArrowOnBottom
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }
@ -154,7 +158,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
if self.timeoutTimer == nil { if self.timeoutTimer == nil {
let timeoutTimer = SwiftSignalKit.Timer(timeout: self.timeout, repeat: false, completion: { [weak self] in let timeoutTimer = SwiftSignalKit.Timer(timeout: self.timeout, repeat: false, completion: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.dismissed?() strongSelf.dismissed?(false)
strongSelf.controllerNode.animateOut { strongSelf.controllerNode.animateOut {
self?.presentingViewController?.dismiss(animated: false) self?.presentingViewController?.dismiss(animated: false)
} }
@ -165,16 +169,20 @@ open class TooltipController: ViewController, StandalonePresentableController {
} }
} }
override open func dismiss(completion: (() -> Void)? = nil) { private func dismiss(tappedInside: Bool, completion: (() -> Void)? = nil) {
self.dismissed?() self.dismissed?(tappedInside)
self.controllerNode.animateOut { [weak self] in self.controllerNode.animateOut { [weak self] in
self?.presentingViewController?.dismiss(animated: false) self?.presentingViewController?.dismiss(animated: false)
completion?() completion?()
} }
} }
override open func dismiss(completion: (() -> Void)? = nil) {
self.dismiss(tappedInside: false, completion: completion)
}
open func dismissImmediately() { open func dismissImmediately() {
self.dismissed?() self.dismissed?(false)
self.presentingViewController?.dismiss(animated: false) self.presentingViewController?.dismiss(animated: false)
} }
} }

View File

@ -3,7 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
final class TooltipControllerNode: ASDisplayNode { final class TooltipControllerNode: ASDisplayNode {
private let dismiss: () -> Void private let dismiss: (Bool) -> Void
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
@ -19,7 +19,7 @@ final class TooltipControllerNode: ASDisplayNode {
private var dismissedByTouchOutside = false private var dismissedByTouchOutside = false
private var dismissByTapOutsideSource = false private var dismissByTapOutsideSource = false
init(content: TooltipControllerContent, dismiss: @escaping () -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) { init(content: TooltipControllerContent, dismiss: @escaping (Bool) -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) {
self.dismissByTapOutside = dismissByTapOutside self.dismissByTapOutside = dismissByTapOutside
self.dismissByTapOutsideSource = dismissByTapOutsideSource self.dismissByTapOutsideSource = dismissByTapOutsideSource
@ -129,15 +129,16 @@ final class TooltipControllerNode: ASDisplayNode {
eventIsPresses = event.type == .presses eventIsPresses = event.type == .presses
} }
if event.type == .touches || eventIsPresses { if event.type == .touches || eventIsPresses {
let pointInside = self.containerNode.frame.contains(point)
if self.containerNode.frame.contains(point) || self.dismissByTapOutside { if self.containerNode.frame.contains(point) || self.dismissByTapOutside {
if !self.dismissedByTouchOutside { if !self.dismissedByTouchOutside {
self.dismissedByTouchOutside = true self.dismissedByTouchOutside = true
self.dismiss() self.dismiss(pointInside)
} }
} else if self.dismissByTapOutsideSource, let sourceRect = self.sourceRect, !sourceRect.contains(point) { } else if self.dismissByTapOutsideSource, let sourceRect = self.sourceRect, !sourceRect.contains(point) {
if !self.dismissedByTouchOutside { if !self.dismissedByTouchOutside {
self.dismissedByTouchOutside = true self.dismissedByTouchOutside = true
self.dismiss() self.dismiss(false)
} }
} }
return nil return nil

View File

@ -455,6 +455,7 @@ public enum ViewControllerNavigationPresentation {
} }
navigationController.filterController(self, animated: animated) navigationController.filterController(self, animated: animated)
} else { } else {
self.presentingViewController?.dismiss(animated: false, completion: nil)
assertionFailure() assertionFailure()
} }
} }

View File

@ -479,6 +479,7 @@ public class Window1 {
let screenHeight: CGFloat let screenHeight: CGFloat
var inPopover = false var inPopover = false
if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) {
let screenSize = UIScreen.main.bounds.size
var portraitScreenSize = UIScreen.main.bounds.size var portraitScreenSize = UIScreen.main.bounds.size
if portraitScreenSize.width > portraitScreenSize.height { if portraitScreenSize.width > portraitScreenSize.height {
portraitScreenSize = CGSize(width: portraitScreenSize.height, height: portraitScreenSize.width) portraitScreenSize = CGSize(width: portraitScreenSize.height, height: portraitScreenSize.width)
@ -487,23 +488,35 @@ public class Window1 {
if portraitLayoutSize.width > portraitLayoutSize.height { if portraitLayoutSize.width > portraitLayoutSize.height {
portraitLayoutSize = CGSize(width: portraitLayoutSize.height, height: portraitLayoutSize.width) portraitLayoutSize = CGSize(width: portraitLayoutSize.height, height: portraitLayoutSize.width)
} }
if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 41.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 41.0 { if strongSelf.windowLayout.size.height != screenSize.height {
popoverDelta = 48.0 let heightDelta = screenSize.height - strongSelf.windowLayout.size.height
let heightDeltaValid = heightDelta > 0.0 && heightDelta < 100.0
if heightDeltaValid {
inPopover = true inPopover = true
popoverDelta = heightDelta / 2.0
}
}
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
screenHeight = UIScreen.main.bounds.height
} else {
screenHeight = strongSelf.windowLayout.size.height
}
/*if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 41.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 41.0 {
screenHeight = strongSelf.windowLayout.size.height screenHeight = strongSelf.windowLayout.size.height
} else { } else {
screenHeight = UIScreen.main.bounds.height screenHeight = UIScreen.main.bounds.height
} }
} else if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 39.0 { } else if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 39.0 {
screenHeight = UIScreen.main.bounds.height screenHeight = UIScreen.main.bounds.height
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 39.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 39.0 {
popoverDelta = 40.0
inPopover = true
}
} else { } else {
screenHeight = strongSelf.windowLayout.size.height screenHeight = strongSelf.windowLayout.size.height
} }*/
} else { } else {
screenHeight = UIScreen.main.bounds.width screenHeight = UIScreen.main.bounds.width
} }
@ -513,11 +526,9 @@ public class Window1 {
keyboardHeight = 0.0 keyboardHeight = 0.0
} else { } else {
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY) keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if inPopover { if inPopover && !keyboardHeight.isZero {
if strongSelf.windowLayout.onScreenNavigationHeight != nil { if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
if !keyboardHeight.isZero { keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
keyboardHeight = max(0.0, keyboardHeight + popoverDelta / 2.0)
}
} else { } else {
keyboardHeight = max(0.0, keyboardHeight - popoverDelta) keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
} }
@ -1212,7 +1223,11 @@ public class Window1 {
} }
private func collectScreenEdgeGestures() -> UIRectEdge { private func collectScreenEdgeGestures() -> UIRectEdge {
var edges = self.presentationContext.combinedDeferScreenEdgeGestures() var edges: UIRectEdge = []
if let navigationController = self._rootController as? NavigationController, let overlayController = navigationController.topOverlayController {
edges = edges.union(overlayController.deferScreenEdgeGestures)
}
edges = edges.union(self.presentationContext.combinedDeferScreenEdgeGestures())
for controller in self.topLevelOverlayControllers { for controller in self.topLevelOverlayControllers {
if let controller = controller as? ViewController { if let controller = controller as? ViewController {

View File

@ -414,7 +414,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.requestLayout?(.immediate) self.requestLayout?(.immediate)
} }
self.deleteButton.isHidden = origin == nil if origin == nil {
self.deleteButton.isHidden = true
}
} }
func setMessage(_ message: Message) { func setMessage(_ message: Message) {
@ -831,7 +833,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
if !ask && items.count == 1 { if !ask && items.count == 1 {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start() let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start()
strongSelf.controllerInteraction?.dismissController() strongSelf.controllerInteraction?.dismissController()
} else { } else if !items.isEmpty {
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()

View File

@ -179,7 +179,7 @@ const CGFloat TGLocationMapInset = 100.0f;
return; return;
[self updateMapHeightAnimated:false]; [self updateMapHeightAnimated:false];
_optionsView.frame = CGRectMake(self.view.bounds.size.width - 45.0f - 6.0f - self.controllerSafeAreaInset.right, self.controllerInset.top + 6.0f, 45.0f, 90.0f); _optionsView.frame = CGRectMake(self.view.bounds.size.width - 45.0f - 6.0f - self.controllerSafeAreaInset.right, 56.0f + 6.0f, 45.0f, 90.0f);
_tableView.contentOffset = CGPointMake(0.0f, -_tableViewTopInset - self.controllerInset.top); _tableView.contentOffset = CGPointMake(0.0f, -_tableViewTopInset - self.controllerInset.top);
} }
@ -259,7 +259,7 @@ const CGFloat TGLocationMapInset = 100.0f;
[scrollView setContentOffset:contentOffset animated:false]; [scrollView setContentOffset:contentOffset animated:false];
} }
_optionsView.frame = CGRectMake(self.view.bounds.size.width - 45.0f - 6.0f, self.controllerInset.top + 6.0f, 45.0f, 90.0f); _optionsView.frame = CGRectMake(self.view.bounds.size.width - 45.0f - 6.0f, 56.0f + 6.0f, 45.0f, 90.0f);
} }
- (NSInteger)tableView:(UITableView *)__unused tableView numberOfRowsInSection:(NSInteger)__unused section - (NSInteger)tableView:(UITableView *)__unused tableView numberOfRowsInSection:(NSInteger)__unused section

View File

@ -179,13 +179,13 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
[self.navigationController.view addSubview:_searchBarOverlay]; [self.navigationController.view addSubview:_searchBarOverlay];
CGFloat safeAreaInset = self.controllerSafeAreaInset.top > FLT_EPSILON ? self.controllerSafeAreaInset.top : 0.0f; CGFloat safeAreaInset = self.controllerSafeAreaInset.top > FLT_EPSILON ? self.controllerSafeAreaInset.top : 0.0f;
_searchBarWrapper = [[UIView alloc] initWithFrame:CGRectMake(0, -44 - safeAreaInset, self.navigationController.view.frame.size.width, 44 + safeAreaInset)]; _searchBarWrapper = [[UIView alloc] initWithFrame:CGRectMake(0, -44.0f, self.navigationController.view.frame.size.width, 44.0f)];
_searchBarWrapper.autoresizingMask = UIViewAutoresizingFlexibleWidth; _searchBarWrapper.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_searchBarWrapper.backgroundColor = self.pallete != nil ? self.pallete.backgroundColor : [UIColor whiteColor]; _searchBarWrapper.backgroundColor = self.pallete != nil ? self.pallete.backgroundColor : [UIColor whiteColor];
_searchBarWrapper.hidden = true; _searchBarWrapper.hidden = true;
[self.navigationController.view addSubview:_searchBarWrapper]; [self.navigationController.view addSubview:_searchBarWrapper];
_searchBar = [[TGSearchBar alloc] initWithFrame:CGRectMake(0.0f, safeAreaInset, _searchBarWrapper.frame.size.width, [TGSearchBar searchBarBaseHeight]) style:TGSearchBarStyleHeader]; _searchBar = [[TGSearchBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _searchBarWrapper.frame.size.width, [TGSearchBar searchBarBaseHeight]) style:TGSearchBarStyleHeader];
if (self.pallete != nil) if (self.pallete != nil)
[_searchBar setPallete:self.pallete.searchBarPallete]; [_searchBar setPallete:self.pallete.searchBarPallete];
_searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; _searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
@ -808,10 +808,7 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
} }
else else
{ {
frame.origin.y = 0; frame.origin.y = 0;
if (self.navigationController.modalPresentationStyle == UIModalPresentationFormSheet)
frame.origin.y -= 20;
_searchBarOverlay.alpha = 1.0f; _searchBarOverlay.alpha = 1.0f;
} }
_searchBarWrapper.frame = frame; _searchBarWrapper.frame = frame;

View File

@ -1191,6 +1191,13 @@ static CGFloat transformRotation(CGAffineTransform transform)
else else
[_model _transitionCompleted]; [_model _transitionCompleted];
} }
if (!_previewMode) {
if (self.adjustsStatusBarVisibility && (!_showInterface || [_view.interfaceView prefersStatusBarHidden]))
{
[_context setApplicationStatusBarAlpha:0.0f];
}
}
} }
- (void)viewWillAppear:(BOOL)animated - (void)viewWillAppear:(BOOL)animated
@ -1203,14 +1210,6 @@ static CGFloat transformRotation(CGAffineTransform transform)
if (!_showInterface) if (!_showInterface)
{ {
_view.interfaceView.alpha = 0.0f; _view.interfaceView.alpha = 0.0f;
if (self.adjustsStatusBarVisibility)
[_context setApplicationStatusBarAlpha:0.0f];
}
else if ([_view.interfaceView prefersStatusBarHidden])
{
if (self.adjustsStatusBarVisibility)
[_context setApplicationStatusBarAlpha:0.0f];
} }
} }

View File

@ -9,6 +9,8 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
private let audioFrame: FFMpegAVFrame private let audioFrame: FFMpegAVFrame
private var resetDecoderOnNextFrame = true private var resetDecoderOnNextFrame = true
private var delayedFrames: [MediaTrackFrame] = []
init(codecContext: FFMpegAVCodecContext) { init(codecContext: FFMpegAVCodecContext) {
self.codecContext = codecContext self.codecContext = codecContext
self.audioFrame = FFMpegAVFrame() self.audioFrame = FFMpegAVFrame()
@ -19,16 +21,58 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? {
let status = frame.packet.send(toDecoder: self.codecContext) let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 { if status == 0 {
if self.codecContext.receive(into: self.audioFrame) { while self.codecContext.receive(into: self.audioFrame) {
return convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration) if let convertedFrame = convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration) {
self.delayedFrames.append(convertedFrame)
}
}
if self.delayedFrames.count >= 1 {
var minFrameIndex = 0
var minPosition = self.delayedFrames[0].position
for i in 1 ..< self.delayedFrames.count {
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
minFrameIndex = i
minPosition = self.delayedFrames[i].position
}
}
return self.delayedFrames.remove(at: minFrameIndex)
} }
} }
return nil return nil
} }
func takeQueuedFrame() -> MediaTrackFrame? {
if self.delayedFrames.count >= 1 {
var minFrameIndex = 0
var minPosition = self.delayedFrames[0].position
for i in 1 ..< self.delayedFrames.count {
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
minFrameIndex = i
minPosition = self.delayedFrames[i].position
}
}
return self.delayedFrames.remove(at: minFrameIndex)
} else {
return nil
}
}
func takeRemainingFrame() -> MediaTrackFrame? { func takeRemainingFrame() -> MediaTrackFrame? {
return nil if !self.delayedFrames.isEmpty {
var minFrameIndex = 0
var minPosition = self.delayedFrames[0].position
for i in 1 ..< self.delayedFrames.count {
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
minFrameIndex = i
minPosition = self.delayedFrames[i].position
}
}
return self.delayedFrames.remove(at: minFrameIndex)
} else {
return nil
}
} }
private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? { private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? {

View File

@ -39,6 +39,10 @@ final class FFMpegMediaPassthroughVideoFrameDecoder: MediaTrackFrameDecoder {
return MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: false, rotationAngle: self.rotationAngle) return MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: false, rotationAngle: self.rotationAngle)
} }
func takeQueuedFrame() -> MediaTrackFrame? {
return nil
}
func takeRemainingFrame() -> MediaTrackFrame? { func takeRemainingFrame() -> MediaTrackFrame? {
return nil return nil
} }

View File

@ -87,6 +87,10 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
return nil return nil
} }
public func takeQueuedFrame() -> MediaTrackFrame? {
return nil
}
public func takeRemainingFrame() -> MediaTrackFrame? { public func takeRemainingFrame() -> MediaTrackFrame? {
if !self.delayedFrames.isEmpty { if !self.delayedFrames.isEmpty {
var minFrameIndex = 0 var minFrameIndex = 0

View File

@ -146,6 +146,10 @@ public final class MediaTrackFrameBuffer {
} }
public func takeFrame() -> MediaTrackFrameResult { public func takeFrame() -> MediaTrackFrameResult {
if let decodedFrame = self.decoder.takeQueuedFrame() {
return .frame(decodedFrame)
}
if !self.frames.isEmpty { if !self.frames.isEmpty {
let frame = self.frames.removeFirst() let frame = self.frames.removeFirst()
if let decodedFrame = self.decoder.decode(frame: frame) { if let decodedFrame = self.decoder.decode(frame: frame) {

View File

@ -1,6 +1,7 @@
protocol MediaTrackFrameDecoder { protocol MediaTrackFrameDecoder {
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame?
func takeQueuedFrame() -> MediaTrackFrame?
func takeRemainingFrame() -> MediaTrackFrame? func takeRemainingFrame() -> MediaTrackFrame?
func reset() func reset()
} }

View File

@ -168,10 +168,6 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
} }
var size = validLayout.size var size = validLayout.size
if case .compact = validLayout.metrics.widthClass, size.width > size.height {
size = CGSize(width: size.height, height: size.width)
}
if let background = self.background, background.size == size { if let background = self.background, background.size == size {
return return
} }
@ -334,36 +330,11 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.validLayout = layout self.validLayout = layout
self.updateBackground() self.updateBackground()
if layout.size.width == 320.0 {
self.iconNode.alpha = 0.0
}
let bounds = CGRect(origin: CGPoint(), size: layout.size) let bounds = CGRect(origin: CGPoint(), size: layout.size)
transition.updateFrame(node: self.backgroundNode, frame: bounds) transition.updateFrame(node: self.backgroundNode, frame: bounds)
transition.updateFrame(view: self.effectView, frame: bounds) transition.updateFrame(view: self.effectView, frame: bounds)
let iconSize = CGSize(width: 35.0, height: 37.0)
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0) + 6.0, y: layout.insets(options: .statusBar).top + 15.0), size: iconSize))
let passcodeLayout = PasscodeLayout(layout: layout)
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: passcodeLayout.layout, topOffset: passcodeLayout.inputFieldOffset, transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let titleSize = self.titleNode.updateLayout(layout: layout, transition: transition)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: passcodeLayout.titleOffset), size: titleSize))
var subtitleOffset = passcodeLayout.subtitleOffset
if case .alphanumeric = self.passcodeType {
subtitleOffset = 16.0
}
let subtitleSize = self.subtitleNode.updateLayout(layout: layout, transition: transition)
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: inputFieldFrame.maxY + subtitleOffset), size: subtitleSize))
let (keyboardFrame, keyboardButtonSize) = self.keyboardNode.updateLayout(layout: passcodeLayout, transition: transition)
transition.updateFrame(node: self.keyboardNode, frame: CGRect(origin: CGPoint(), size: layout.size))
switch self.passcodeType { switch self.passcodeType {
case .digits6, .digits4: case .digits6, .digits4:
self.keyboardNode.alpha = 1.0 self.keyboardNode.alpha = 1.0
@ -373,27 +344,104 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
self.deleteButtonNode.alpha = 0.0 self.deleteButtonNode.alpha = 0.0
} }
let isLandscape = layout.orientation == .landscape && layout.deviceMetrics.type != .tablet
let keyboardHidden = self.keyboardNode.alpha == 0.0
let layoutSize: CGSize
if isLandscape {
if keyboardHidden {
layoutSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
} else {
layoutSize = CGSize(width: layout.size.width / 2.0, height: layout.size.height)
}
} else {
layoutSize = layout.size
}
if layout.size.width == 320.0 || (isLandscape && keyboardHidden) {
self.iconNode.alpha = 0.0
}
let passcodeLayout = PasscodeLayout(layout: layout)
let inputFieldOffset: CGFloat
if isLandscape {
let bottomInset = layout.inputHeight ?? 0.0
if !keyboardHidden || bottomInset == 0.0 {
inputFieldOffset = floor(layoutSize.height / 2.0 + 12.0)
} else {
inputFieldOffset = floor(layoutSize.height - bottomInset) / 2.0 - 40.0
}
} else {
inputFieldOffset = passcodeLayout.inputFieldOffset
}
let inputFieldFrame = self.inputFieldNode.updateLayout(size: layoutSize, topOffset: inputFieldOffset, transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: layoutSize))
let titleFrame: CGRect
if isLandscape {
let titleSize = self.titleNode.updateLayout(size: CGSize(width: layoutSize.width, height: layout.size.height), transition: transition)
titleFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.minY - titleSize.height - 16.0), size: titleSize)
} else {
let titleSize = self.titleNode.updateLayout(size: layout.size, transition: transition)
titleFrame = CGRect(origin: CGPoint(x: 0.0, y: passcodeLayout.titleOffset), size: titleSize)
}
transition.updateFrame(node: self.titleNode, frame: titleFrame)
let iconSize = CGSize(width: 35.0, height: 37.0)
let iconFrame: CGRect
if isLandscape {
iconFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: titleFrame.minY - iconSize.height - 14.0), size: iconSize)
} else {
iconFrame = CGRect(origin: CGPoint(x: floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: layout.insets(options: .statusBar).top + 15.0), size: iconSize)
}
transition.updateFrame(node: self.iconNode, frame: iconFrame)
var subtitleOffset = passcodeLayout.subtitleOffset
if case .alphanumeric = self.passcodeType {
subtitleOffset = 16.0
}
let subtitleSize = self.subtitleNode.updateLayout(size: layoutSize, transition: transition)
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.maxY + subtitleOffset), size: subtitleSize))
let (keyboardFrame, keyboardButtonSize) = self.keyboardNode.updateLayout(layout: passcodeLayout, transition: transition)
transition.updateFrame(node: self.keyboardNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size))
let bottomInset = layout.inputHeight ?? 0.0 let bottomInset = layout.inputHeight ?? 0.0
let cancelSize = self.cancelButtonNode.measure(layout.size) let cancelSize = self.cancelButtonNode.measure(layout.size)
var cancelY: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset var bottomButtonY = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset
if bottomInset > 0 && self.keyboardNode.alpha < 1.0 { var cancelX = floor(keyboardFrame.minX + keyboardButtonSize.width / 2.0 - cancelSize.width / 2.0)
cancelY = layout.size.height - bottomInset - cancelSize.height - 20.0 var cancelY = bottomButtonY
if bottomInset > 0 && keyboardHidden {
cancelX = floor((layout.size.width - cancelSize.width) / 2.0)
cancelY = layout.size.height - bottomInset - cancelSize.height - 15.0 - layout.intrinsicInsets.bottom
} else if isLandscape {
bottomButtonY = keyboardFrame.maxY - keyboardButtonSize.height + floor((keyboardButtonSize.height - cancelSize.height) / 2.0)
cancelY = bottomButtonY
} }
transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: floor(keyboardFrame.minX + keyboardButtonSize.width / 2.0 - cancelSize.width / 2.0), y: cancelY), size: cancelSize)) transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: cancelX, y: cancelY), size: cancelSize))
let deleteSize = self.deleteButtonNode.measure(layout.size) let deleteSize = self.deleteButtonNode.measure(layout.size)
transition.updateFrame(node: self.deleteButtonNode, frame: CGRect(origin: CGPoint(x: floor(keyboardFrame.maxX - keyboardButtonSize.width / 2.0 - deleteSize.width / 2.0), y: layout.size.height - layout.intrinsicInsets.bottom - deleteSize.height - passcodeLayout.keyboard.deleteOffset), size: deleteSize)) transition.updateFrame(node: self.deleteButtonNode, frame: CGRect(origin: CGPoint(x: floor(keyboardFrame.maxX - keyboardButtonSize.width / 2.0 - deleteSize.width / 2.0), y: bottomButtonY), size: deleteSize))
if let biometricIcon = self.biometricButtonNode.image(for: .normal) { if let biometricIcon = self.biometricButtonNode.image(for: .normal) {
var biometricX = layout.safeInsets.left + floor((layoutSize.width - biometricIcon.size.width) / 2.0)
var biometricY: CGFloat = 0.0 var biometricY: CGFloat = 0.0
if bottomInset > 0 && self.keyboardNode.alpha < 1.0 { if isLandscape {
biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0) if bottomInset > 0 && keyboardHidden {
biometricX = cancelX + cancelSize.width + 64.0
}
biometricY = cancelY + floor((cancelSize.height - biometricIcon.size.height) / 2.0)
} else { } else {
biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset if bottomInset > 0 && keyboardHidden {
biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0)
} else {
biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset
}
} }
transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - biometricIcon.size.width) / 2.0), y: biometricY), size: biometricIcon.size)) transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: biometricX, y: biometricY), size: biometricIcon.size))
} }
} }
} }

View File

@ -58,7 +58,7 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
titleOffset = -11.0 titleOffset = -11.0
} }
subtitleOffset = -54.0 subtitleOffset = -54.0
} else { } else if size.width > 70.0 {
titleFont = regularTitleFont titleFont = regularTitleFont
subtitleFont = regularSubtitleFont subtitleFont = regularSubtitleFont
if subtitle.isEmpty { if subtitle.isEmpty {
@ -68,6 +68,16 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
} }
subtitleOffset = -48.0 subtitleOffset = -48.0
} }
else {
titleFont = regularTitleFont
subtitleFont = regularSubtitleFont
if subtitle.isEmpty {
titleOffset = -11.0
} else {
titleOffset = -4.0
}
subtitleOffset = -41.0
}
let titlePath = CGMutablePath() let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: titleOffset)) titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: titleOffset))
@ -236,36 +246,66 @@ final class PasscodeEntryKeyboardNode: ASDisplayNode {
} }
func updateLayout(layout: PasscodeLayout, transition: ContainedViewLayoutTransition) -> (CGRect, CGSize) { func updateLayout(layout: PasscodeLayout, transition: ContainedViewLayoutTransition) -> (CGRect, CGSize) {
let origin = CGPoint(x: floor((layout.layout.size.width - layout.keyboard.size.width) / 2.0), y: layout.keyboard.topOffset) let origin: CGPoint
let buttonSize: CGFloat
let horizontalSecond: CGFloat
let horizontalThird: CGFloat
let verticalSecond: CGFloat
let verticalThird: CGFloat
let verticalFourth: CGFloat
let keyboardSize: CGSize
if layout.layout.orientation == .landscape && layout.layout.deviceMetrics.type != .tablet {
let horizontalSpacing: CGFloat = 20.0
let verticalSpacing: CGFloat = 12.0
buttonSize = 65.0
keyboardSize = CGSize(width: buttonSize * 3.0 + horizontalSpacing * 2.0, height: buttonSize * 4.0 + verticalSpacing * 3.0)
horizontalSecond = buttonSize + horizontalSpacing
horizontalThird = buttonSize * 2.0 + horizontalSpacing * 2.0
verticalSecond = buttonSize + verticalSpacing
verticalThird = buttonSize * 2.0 + verticalSpacing * 2.0
verticalFourth = buttonSize * 3.0 + verticalSpacing * 3.0
origin = CGPoint(x: floor(layout.layout.size.width / 2.0 + (layout.layout.size.width / 2.0 - keyboardSize.width) / 2.0) - layout.layout.safeInsets.right, y: floor((layout.layout.size.height - keyboardSize.height) / 2.0))
} else {
origin = CGPoint(x: floor((layout.layout.size.width - layout.keyboard.size.width) / 2.0), y: layout.keyboard.topOffset)
buttonSize = layout.keyboard.buttonSize
horizontalSecond = layout.keyboard.horizontalSecond
horizontalThird = layout.keyboard.horizontalThird
verticalSecond = layout.keyboard.verticalSecond
verticalThird = layout.keyboard.verticalThird
verticalFourth = layout.keyboard.verticalFourth
keyboardSize = layout.keyboard.size
}
if let subnodes = self.subnodes { if let subnodes = self.subnodes {
for i in 0 ..< subnodes.count { for i in 0 ..< subnodes.count {
var origin = origin var origin = origin
if i % 3 == 0 { if i % 3 == 0 {
origin.x += 0.0 origin.x += 0.0
} else if (i % 3 == 1) { } else if (i % 3 == 1) {
origin.x += layout.keyboard.horizontalSecond origin.x += horizontalSecond
} }
else { else {
origin.x += layout.keyboard.horizontalThird origin.x += horizontalThird
} }
if i / 3 == 0 { if i / 3 == 0 {
origin.y += 0.0 origin.y += 0.0
} }
else if i / 3 == 1 { else if i / 3 == 1 {
origin.y += layout.keyboard.verticalSecond origin.y += verticalSecond
} }
else if i / 3 == 2 { else if i / 3 == 2 {
origin.y += layout.keyboard.verticalThird origin.y += verticalThird
} }
else if i / 3 == 3 { else if i / 3 == 3 {
origin.x += layout.keyboard.horizontalSecond origin.x += horizontalSecond
origin.y += layout.keyboard.verticalFourth origin.y += verticalFourth
} }
transition.updateFrame(node: subnodes[i], frame: CGRect(origin: origin, size: CGSize(width: layout.keyboard.buttonSize, height: layout.keyboard.buttonSize))) transition.updateFrame(node: subnodes[i], frame: CGRect(origin: origin, size: CGSize(width: buttonSize, height: buttonSize)))
} }
} }
return (CGRect(origin: origin, size: layout.keyboard.size), CGSize(width: layout.keyboard.buttonSize, height: layout.keyboard.buttonSize)) return (CGRect(origin: origin, size: keyboardSize), CGSize(width: buttonSize, height: buttonSize))
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

View File

@ -13,7 +13,7 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
private let wrapperNode: ASDisplayNode private let wrapperNode: ASDisplayNode
private let textNode: ASTextNode private let textNode: ASTextNode
private var validLayout: ContainerViewLayout? private var validLayout: CGSize?
override init() { override init() {
self.wrapperNode = ASDisplayNode() self.wrapperNode = ASDisplayNode()
@ -34,13 +34,13 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
self.textNode.attributedText = text self.textNode.attributedText = text
completion() completion()
if let validLayout = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(layout: validLayout, transition: .immediate) let _ = self.updateLayout(size: size, transition: .immediate)
} }
case .slideIn: case .slideIn:
self.textNode.attributedText = text self.textNode.attributedText = text
if let validLayout = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(layout: validLayout, transition: .immediate) let _ = self.updateLayout(size: size, transition: .immediate)
} }
let offset = self.wrapperNode.bounds.width / 2.0 let offset = self.wrapperNode.bounds.width / 2.0
@ -61,29 +61,29 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { _ in self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { _ in
completion() completion()
}) })
if let validLayout = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(layout: validLayout, transition: .immediate) let _ = self.updateLayout(size: size, transition: .immediate)
} }
}) })
} else { } else {
self.textNode.attributedText = text self.textNode.attributedText = text
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
completion() completion()
if let validLayout = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(layout: validLayout, transition: .immediate) let _ = self.updateLayout(size: size, transition: .immediate)
} }
} }
} }
} }
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGSize { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = layout self.validLayout = size
let textSize = self.textNode.measure(layout.size) let textSize = self.textNode.measure(size)
let textFrame = CGRect(x: floor((layout.size.width - textSize.width) / 2.0), y: 0.0, width: textSize.width, height: textSize.height) let textFrame = CGRect(x: floor((size.width - textSize.width) / 2.0), y: 0.0, width: textSize.width, height: textSize.height)
transition.updateFrame(node: self.wrapperNode, frame: textFrame) transition.updateFrame(node: self.wrapperNode, frame: textFrame)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(), size: textSize)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(), size: textSize))
return CGSize(width: layout.size.width, height: 25.0) return CGSize(width: size.width, height: 25.0)
} }
} }

View File

@ -139,7 +139,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
private let borderNode: ASImageNode private let borderNode: ASImageNode
private let dotNodes: [PasscodeEntryDotNode] private let dotNodes: [PasscodeEntryDotNode]
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (CGSize, CGFloat)?
public var complete: ((String) -> Void)? public var complete: ((String) -> Void)?
@ -202,15 +202,15 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
if let (layout, topOffset) = self.validLayout { if let (size, topOffset) = self.validLayout {
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate) let _ = self.updateLayout(size: size, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
} }
} }
func updateBackground(_ image: UIImage, size: CGSize) { func updateBackground(_ image: UIImage, size: CGSize) {
self.background = (image, size) self.background = (image, size)
if let (layout, topOffset) = self.validLayout { if let (size, topOffset) = self.validLayout {
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate) let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
} }
} }
@ -302,13 +302,13 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
self.textFieldNode.textField.text = "" self.textFieldNode.textField.text = ""
} }
self.fieldType = fieldType self.fieldType = fieldType
if let (layout, topOffset) = self.validLayout { if let (size, topOffset) = self.validLayout {
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate) let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
} }
} }
public func updateLayout(layout: ContainerViewLayout, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect { public func updateLayout(size: CGSize, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect {
self.validLayout = (layout, topOffset) self.validLayout = (size, topOffset)
let fieldAlpha: CGFloat let fieldAlpha: CGFloat
switch self.fieldType { switch self.fieldType {
@ -321,7 +321,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
transition.updateAlpha(node: self.textFieldNode, alpha: fieldAlpha) transition.updateAlpha(node: self.textFieldNode, alpha: fieldAlpha)
transition.updateAlpha(node: self.borderNode, alpha: fieldAlpha) transition.updateAlpha(node: self.borderNode, alpha: fieldAlpha)
let origin = CGPoint(x: floor((layout.size.width - dotDiameter * 6 - dotSpacing * 5) / 2.0), y: topOffset) let origin = CGPoint(x: floor((size.width - dotDiameter * 6 - dotSpacing * 5) / 2.0), y: topOffset)
for i in 0 ..< self.dotNodes.count { for i in 0 ..< self.dotNodes.count {
let node = self.dotNodes[i] let node = self.dotNodes[i]
let dotAlpha: CGFloat let dotAlpha: CGFloat
@ -343,7 +343,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
if !self.useCustomNumpad { if !self.useCustomNumpad {
inset = 16.0 inset = 16.0
} }
let fieldFrame = CGRect(x: inset, y: origin.y, width: layout.size.width - inset * 2.0, height: fieldHeight) let fieldFrame = CGRect(x: inset, y: origin.y, width: size.width - inset * 2.0, height: fieldHeight)
transition.updateFrame(node: self.borderNode, frame: fieldFrame) transition.updateFrame(node: self.borderNode, frame: fieldFrame)
transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0)) transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
if let (backgroundImage, backgroundSize) = self.background { if let (backgroundImage, backgroundSize) = self.background {

View File

@ -101,7 +101,7 @@ struct PasscodeKeyboardLayout {
self.verticalThird = 176.0 self.verticalThird = 176.0
self.verticalFourth = 264.0 self.verticalFourth = 264.0
self.size = CGSize(width: 265.0, height: 339.0) self.size = CGSize(width: 265.0, height: 339.0)
self.topOffset = 0.0 self.topOffset = 120.0 + (layout.size.height - self.size.height - 120.0) / 2.0
self.biometricsOffset = 30.0 self.biometricsOffset = 30.0
self.deleteOffset = 20.0 self.deleteOffset = 20.0
} }

View File

@ -147,7 +147,7 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
self.wrapperNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.wrapperNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: layout, topOffset: floor(insets.top + navigationBarHeight + (layout.size.height - navigationBarHeight - insets.top - insets.bottom - 24.0) / 2.0), transition: transition) let inputFieldFrame = self.inputFieldNode.updateLayout(size: layout.size, topOffset: floor(insets.top + navigationBarHeight + (layout.size.height - navigationBarHeight - insets.top - insets.bottom - 24.0) / 2.0), transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.inputFieldBackgroundNode, frame: CGRect(x: 0.0, y: inputFieldFrame.minY - 6.0, width: layout.size.width, height: 48.0)) transition.updateFrame(node: self.inputFieldBackgroundNode, frame: CGRect(x: 0.0, y: inputFieldFrame.minY - 6.0, width: layout.size.width, height: 48.0))

View File

@ -893,6 +893,8 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
}, reportChannel: { }, reportChannel: {
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
presentControllerImpl?(c, a) presentControllerImpl?(c, a)
}, push: { c in
pushControllerImpl?(c)
}, completion: { _ in }), nil) }, completion: { _ in }), nil)
}, leaveChannel: { }, leaveChannel: {
let _ = (context.account.postbox.transaction { transaction -> Peer? in let _ = (context.account.postbox.transaction { transaction -> Peer? in

View File

@ -2072,7 +2072,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: { }, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
clearHighlightImpl?() clearHighlightImpl?()
}) })
presentControllerImpl?(controller, nil) pushControllerImpl?(controller)
}) })
}, displayLocationContextMenu: { text in }, displayLocationContextMenu: { text in
displayCopyContextMenuImpl?(text, .location) displayCopyContextMenuImpl?(text, .location)

View File

@ -92,7 +92,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
}) })
} }
} else { } else {
parent?.present(peerReportController(context: context, subject: subject, completion: completion), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) parent?.push(peerReportController(context: context, subject: subject, completion: completion))
} }
f(.dismissWithoutContent) f(.dismissWithoutContent)
}))) })))
@ -103,11 +103,13 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
parent.view.endEditing(true) parent.view.endEditing(true)
parent.present(peerReportOptionsController(context: context, subject: subject, present: { [weak parent] c, a in parent.present(peerReportOptionsController(context: context, subject: subject, present: { [weak parent] c, a in
parent?.present(c, in: .window(.root), with: a) parent?.present(c, in: .window(.root), with: a)
}, push: { [weak parent] c in
parent?.push(c)
}, completion: completion), in: .window(.root)) }, completion: completion), in: .window(.root))
} }
} }
public func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (Bool) -> Void) -> ViewController { public func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping (Bool) -> Void) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme)) let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
@ -170,7 +172,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
}) })
} }
} else { } else {
controller?.present(peerReportController(context: context, subject: subject, completion: completion), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) push(peerReportController(context: context, subject: subject, completion: completion))
} }
controller?.dismissAnimated() controller?.dismissAnimated()
@ -349,6 +351,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }

View File

@ -1181,6 +1181,8 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
}, report: { }, report: {
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
presentControllerImpl?(c, a) presentControllerImpl?(c, a)
}, push: { c in
pushControllerImpl?(c)
}, completion: { _ in }), nil) }, completion: { _ in }), nil)
}) })
@ -1579,8 +1581,6 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
let text: String = presentationData.strings.UserInfo_TapToCall let text: String = presentationData.strings.UserInfo_TapToCall
let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true) let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true)
tooltipController.dismissed = {
}
controller.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in controller.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
if let resultItemNode = resultItemNode { if let resultItemNode = resultItemNode {
return (resultItemNode, callButtonFrame) return (resultItemNode, callButtonFrame)

View File

@ -2479,18 +2479,23 @@ final class MessageHistoryTable: Table {
} }
} }
func enumerateMedia(lowerBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) { func enumerateMedia(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
var mediaRefs: [MediaId: Media] = [:] var mediaRefs: [MediaId: Media] = [:]
var result: [PeerId: Set<MediaId>] = [:] var result: [PeerId: Set<MediaId>] = [:]
var lastIndex: MessageIndex? var lastIndex: MessageIndex?
var count = 0 var count = 0
self.valueBox.range(self.table, start: self.key(lowerBound == nil ? MessageIndex.absoluteLowerBound() : lowerBound!), end: self.key(MessageIndex.absoluteUpperBound()), values: { key, value in self.valueBox.range(self.table, start: self.key(lowerBound == nil ? MessageIndex.absoluteLowerBound() : lowerBound!), end: self.key(upperBound == nil ? MessageIndex.absoluteUpperBound() : upperBound!), values: { key, value in
count += 1 count += 1
let entry = self.readIntermediateEntry(key, value: value) let entry = self.readIntermediateEntry(key, value: value)
lastIndex = entry.message.index lastIndex = entry.message.index
let message = entry.message let message = entry.message
if let upperBound = upperBound, message.id.peerId != upperBound.id.peerId {
return true
}
var parsedMedia: [Media] = [] var parsedMedia: [Media] = []
let embeddedMediaData = message.embeddedMediaData.sharedBufferNoCopy() let embeddedMediaData = message.embeddedMediaData.sharedBufferNoCopy()

View File

@ -763,10 +763,10 @@ public final class Transaction {
} }
} }
public func enumerateMedia(lowerBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) { public func enumerateMedia(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
assert(!self.disposed) assert(!self.disposed)
if let postbox = self.postbox { if let postbox = self.postbox {
return postbox.messageHistoryTable.enumerateMedia(lowerBound: lowerBound, limit: limit) return postbox.messageHistoryTable.enumerateMedia(lowerBound: lowerBound, upperBound: upperBound, limit: limit)
} else { } else {
return ([:], [:], nil) return ([:], [:], nil)
} }

View File

@ -45,29 +45,29 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|> map { data, size, bytesPerRow in |> map { data, size, bytesPerRow in
return { arguments in return { arguments in
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
let side = floorToScreenPixels(arguments.drawingSize.width / CGFloat(size))
let padding: CGFloat = floor((arguments.drawingSize.width - CGFloat(side * CGFloat(size))) / 2.0)
let drawingRect = arguments.drawingRect
let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
let codeScale: CGFloat = 1.10256
let clipSide = fittedRect.width * 0.23124
let clipRect = CGRect(x: fittedRect.midX - clipSide / 2.0, y: fittedRect.midY - clipSide / 2.0, width: clipSide, height: clipSide)
let cutout: (Int, Int)? let cutout: (Int, Int)?
if case .none = icon { if case .none = icon {
cutout = nil cutout = nil
} else { } else {
switch size { var cutoutSize = Int(ceil((clipSide + side * 2.0) / side))
case 39: if size == 39 {
cutout = (14, 24) cutoutSize = 11
case 43: } else if cutoutSize % 2 == 0 {
cutout = (15, 27) cutoutSize += 1
case 47:
cutout = (17, 29)
case 51:
cutout = (19, 31)
case 55:
cutout = (21, 33)
case 59:
cutout = (22, 36)
case 63:
cutout = (23, 39)
default:
cutout = (16, 26)
} }
let start = (size - cutoutSize) / 2
cutout = (start, start + cutoutSize - 1)
} }
func valueAt(x: Int, y: Int) -> Bool { func valueAt(x: Int, y: Int) -> Bool {
if x >= 0 && x < size && y >= 0 && y < size { if x >= 0 && x < size && y >= 0 && y < size {
@ -87,9 +87,6 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
} }
} }
let side = floorToScreenPixels(arguments.drawingSize.width / CGFloat(size))
let padding: CGFloat = floor((arguments.drawingSize.width - CGFloat(side * CGFloat(size))) / 2.0)
let squareSize = CGSize(width: side, height: side) let squareSize = CGSize(width: side, height: side)
let tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale ?? 0.0, clear: true) let tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale ?? 0.0, clear: true)
tmpContext.withContext { c in tmpContext.withContext { c in
@ -233,14 +230,7 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
c.translateBy(x: -padding, y: -padding) c.translateBy(x: -padding, y: -padding)
let drawingRect = arguments.drawingRect
let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
let codeScale: CGFloat = 43.0 / 39.0
let clipSide = 56.0 * fittedRect.width / 267.0 * codeScale
let clipRect = CGRect(x: fittedRect.midX - clipSide / 2.0, y: fittedRect.midY - clipSide / 2.0, width: clipSide, height: clipSide)
switch icon { switch icon {
case .proxy: case .proxy:
let iconScale = fittedRect.width / 420.0 * codeScale let iconScale = fittedRect.width / 420.0 * codeScale

View File

@ -49,7 +49,7 @@ public final class SegmentedControlTheme: Equatable {
public extension SegmentedControlTheme { public extension SegmentedControlTheme {
convenience init(theme: PresentationTheme) { convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.rootController.navigationSearchBar.inputFillColor, foregroundColor: theme.rootController.navigationBar.backgroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.primaryTextColor, dividerColor: theme.list.freeInputField.strokeColor) self.init(backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor, foregroundColor: theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.segmentedTextColor, dividerColor: theme.rootController.navigationBar.segmentedDividerColor)
} }
} }

View File

@ -81,6 +81,8 @@ static_library(
"//submodules/WalletUI:WalletUI", "//submodules/WalletUI:WalletUI",
"//submodules/Markdown:Markdown", "//submodules/Markdown:Markdown",
"//submodules/PhoneNumberFormat:PhoneNumberFormat", "//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/UndoUI:UndoUI",
"//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -185,7 +185,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
} }
case .shareProxyList: case .shareProxyList:
switch rhs { switch rhs {
case .enabled, .serversHeader, .addServer, .server, .useForCalls: case .enabled, .serversHeader, .addServer, .server, .shareProxyList:
return false return false
default: default:
return true return true

View File

@ -12,6 +12,8 @@ import PresentationDataUtils
import OverlayStatusController import OverlayStatusController
import AccountContext import AccountContext
import ItemListPeerItem import ItemListPeerItem
import DeleteChatPeerActionSheetItem
import UndoUI
private final class StorageUsageControllerArguments { private final class StorageUsageControllerArguments {
let account: Account let account: Account
@ -285,7 +287,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
return cacheSettings return cacheSettings
}) })
var presentControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)?
let statsPromise = Promise<CacheUsageStatsResult?>() let statsPromise = Promise<CacheUsageStatsResult?>()
let resetStats: () -> Void = { let resetStats: () -> Void = {
@ -339,7 +341,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
ActionSheetItemGroup(items: timeoutItems), ActionSheetItemGroup(items: timeoutItems),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
presentControllerImpl?(controller) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, openClearAll: { }, openClearAll: {
let _ = (statsPromise.get() let _ = (statsPromise.get()
|> take(1) |> take(1)
@ -506,7 +508,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?() cancelImpl?()
})) }))
presentControllerImpl?(controller) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return ActionDisposable { [weak controller] in return ActionDisposable { [weak controller] in
Queue.mainQueue().async() { Queue.mainQueue().async() {
controller?.dismiss() controller?.dismiss()
@ -530,6 +532,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) statsPromise.set(.single(.result(resultStats)))
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), nil)
})) }))
} }
@ -540,7 +544,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
presentControllerImpl?(controller) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
} }
}) })
@ -548,8 +552,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in
if let result = result, case let .result(stats) = result { if let result = result, case let .result(stats) = result {
var additionalPeerId: PeerId? var additionalPeerId: PeerId?
if var categories = stats.media[peerId] { if var categories = stats.media[peerId], let peer = stats.peers[peerId] {
if let channel = stats.peers[peerId] as? TelegramChannel, case .group = channel.info { if let channel = peer as? TelegramChannel, case .group = channel.info {
for (_, peer) in stats.peers { for (_, peer) in stats.peers {
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId { if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId {
if let additionalCategories = stats.media[group.id] { if let additionalCategories = stats.media[group.id] {
@ -606,6 +610,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
} }
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: context, peer: peer, chatPeer: peer, action: .clearCache, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder))
let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file] let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file]
var totalSize: Int64 = 0 var totalSize: Int64 = 0
@ -681,7 +687,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?() cancelImpl?()
})) }))
presentControllerImpl?(controller) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return ActionDisposable { [weak controller] in return ActionDisposable { [weak controller] in
Queue.mainQueue().async() { Queue.mainQueue().async() {
controller?.dismiss() controller?.dismiss()
@ -705,6 +711,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
clearDisposable.set((signal clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
statsPromise.set(.single(.result(resultStats))) statsPromise.set(.single(.result(resultStats)))
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: false, action: { _ in }), nil)
})) }))
} }
@ -715,7 +723,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
]) ])
presentControllerImpl?(controller) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
} }
} }
@ -739,8 +747,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)
presentControllerImpl = { [weak controller] c in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) controller?.present(c, in: .window(.root), with: a)
} }
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
controller?.dismiss() controller?.dismiss()

View File

@ -283,16 +283,11 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - contentSize.width / 2.0), layout.size.width - contentSize.width - 8.0)) let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - contentSize.width / 2.0), layout.size.width - contentSize.width - 8.0))
strongSelf.tooltipContainerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: contentSize) strongSelf.tooltipContainerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: contentSize)
//transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: contentSize))
strongSelf.tooltipContainerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) strongSelf.tooltipContainerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom)
strongSelf.tooltipContainerNode.updateLayout(transition: .immediate) strongSelf.tooltipContainerNode.updateLayout(transition: .immediate)
let textFrame = CGRect(origin: CGPoint(x: 6.0, y: 17.0), size: textSize) let textFrame = CGRect(origin: CGPoint(x: 6.0, y: 17.0), size: textSize)
// if transition.isAnimated, textFrame.size != self.textNode.frame.size {
// transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0))
// }
strongSelf.textNode.frame = textFrame strongSelf.textNode.frame = textFrame
} }
}) })

View File

@ -198,16 +198,43 @@ static CGSize TGFitSize(CGSize size, CGSize maxSize) {
} else { } else {
if ([(NSObject *)item respondsToSelector:@selector(absoluteString)]) { if ([(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
NSURL *url = (NSURL *)item; NSURL *url = (NSURL *)item;
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[url path]];
if (image != nil) { CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) url, NULL);
UIImage *result = TGScaleImageToPixelSize(image, TGFitSize(image.size, maxSize));
NSData *resultData = UIImageJPEGRepresentation(result, 0.52f); CFDictionaryRef options = (__bridge CFDictionaryRef) @{
if (resultData != nil) { (id) kCGImageSourceCreateThumbnailWithTransform : @YES,
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:result.size]}]; (id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
[subscriber putCompletion]; (id) kCGImageSourceThumbnailMaxPixelSize : @(maxSize.width)
} else { };
[subscriber putError:nil];
} CGImageRef image = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
if (image == nil) {
[subscriber putError:nil];
return;
}
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"img%d", (int)arc4random()]];
CFURLRef tempUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:tempPath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(tempUrl, kUTTypeJPEG, 1, NULL);
NSDictionary *properties = @{ (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.52)};
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(destination, image, nil);
if (!CGImageDestinationFinalize(destination)) {
CFRelease(destination);
[subscriber putError:nil];
return;
}
CFRelease(destination);
NSData *resultData = [[NSData alloc] initWithContentsOfFile:tempPath options:NSDataReadingMappedIfSafe error:nil];
if (resultData != nil) {
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image))]}];
[subscriber putCompletion];
} else { } else {
[subscriber putError:nil]; [subscriber putError:nil];
} }

View File

@ -190,6 +190,10 @@ public final class CallController: ViewController {
c.presentationArguments = a c.presentationArguments = a
window.present(c, on: .root, blockInteraction: false, completion: {}) window.present(c, on: .root, blockInteraction: false, completion: {})
} }
}, push: { [weak self] c in
if let strongSelf = self {
strongSelf.push(c)
}
}) })
strongSelf.present(controller, in: .window(.root)) strongSelf.present(controller, in: .window(.root))
}) })

View File

@ -275,6 +275,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
let controller = ItemListController(sharedContext: sharedContext, state: signal) let controller = ItemListController(sharedContext: sharedContext, state: signal)
controller.navigationPresentation = .modal
presentControllerImpl = { [weak controller] c in presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root)) controller?.present(c, in: .window(.root))
} }

View File

@ -266,7 +266,7 @@ func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comm
} }
} }
public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, present: @escaping (ViewController, Any) -> Void) -> AlertController { public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, present: @escaping (ViewController, Any) -> Void, push: @escaping (ViewController) -> Void) -> AlertController {
let presentationData = sharedContext.currentPresentationData.with { $0 } let presentationData = sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme let theme = presentationData.theme
let strings = presentationData.strings let strings = presentationData.strings
@ -282,8 +282,7 @@ public func callRatingController(sharedContext: SharedAccountContext, account: A
}, apply: { rating in }, apply: { rating in
dismissImpl?(true) dismissImpl?(true)
if rating < 4 { if rating < 4 {
let controller = callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated) push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated))
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} else { } else {
let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start() let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start()
} }

View File

@ -55,10 +55,17 @@ private final class CacheUsageStatsState {
var mediaResourceIds: [MediaId: [MediaResourceId]] = [:] var mediaResourceIds: [MediaId: [MediaResourceId]] = [:]
var allResourceIds = Set<WrappedMediaResourceId>() var allResourceIds = Set<WrappedMediaResourceId>()
var lowerBound: MessageIndex? var lowerBound: MessageIndex?
var upperBound: MessageIndex?
} }
public func collectCacheUsageStats(account: Account, additionalCachePaths: [String], logFilesPath: String) -> Signal<CacheUsageStatsResult, NoError> { public func collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal<CacheUsageStatsResult, NoError> {
let state = Atomic<CacheUsageStatsState>(value: CacheUsageStatsState()) var initialState = CacheUsageStatsState()
if let peerId = peerId {
initialState.lowerBound = MessageIndex.lowerBound(peerId: peerId)
initialState.upperBound = MessageIndex.upperBound(peerId: peerId)
}
let state = Atomic<CacheUsageStatsState>(value: initialState)
let excludeResourceIds = account.postbox.transaction { transaction -> Set<WrappedMediaResourceId> in let excludeResourceIds = account.postbox.transaction { transaction -> Set<WrappedMediaResourceId> in
var result = Set<WrappedMediaResourceId>() var result = Set<WrappedMediaResourceId>()
@ -72,7 +79,7 @@ public func collectCacheUsageStats(account: Account, additionalCachePaths: [Stri
return excludeResourceIds return excludeResourceIds
|> mapToSignal { excludeResourceIds -> Signal<CacheUsageStatsResult, NoError> in |> mapToSignal { excludeResourceIds -> Signal<CacheUsageStatsResult, NoError> in
let fetch = account.postbox.transaction { transaction -> ([PeerId : Set<MediaId>], [MediaId : Media], MessageIndex?) in let fetch = account.postbox.transaction { transaction -> ([PeerId : Set<MediaId>], [MediaId : Media], MessageIndex?) in
return transaction.enumerateMedia(lowerBound: state.with { $0.lowerBound }, limit: 1000) return transaction.enumerateMedia(lowerBound: state.with { $0.lowerBound }, upperBound: state.with { $0.upperBound }, limit: 1000)
} }
|> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() } |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() }
@ -169,6 +176,27 @@ public func collectCacheUsageStats(account: Account, additionalCachePaths: [Stri
} }
} }
if updatedLowerBound == nil { if updatedLowerBound == nil {
if peerId != nil {
let (finalMedia, finalMediaResourceIds, allResourceIds) = state.with { state -> ([PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], [MediaId: [MediaResourceId]], Set<WrappedMediaResourceId>) in
return (state.media, state.mediaResourceIds, state.allResourceIds)
}
return account.postbox.transaction { transaction -> CacheUsageStats in
var peers: [PeerId: Peer] = [:]
for peerId in finalMedia.keys {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId, let associatedPeer = transaction.getPeer(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
}
}
return CacheUsageStats(media: finalMedia, mediaResourceIds: finalMediaResourceIds, peers: peers, otherSize: 0, otherPaths: [], cacheSize: 0, tempPaths: [], tempSize: 0, immutableSize: 0)
} |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() }
|> mapToSignal { stats -> Signal<CacheUsageStatsResult, CollectCacheUsageStatsError> in
return .fail(.done(stats))
}
}
let (finalMedia, finalMediaResourceIds, allResourceIds) = state.with { state -> ([PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], [MediaId: [MediaResourceId]], Set<WrappedMediaResourceId>) in let (finalMedia, finalMediaResourceIds, allResourceIds) = state.with { state -> ([PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], [MediaId: [MediaResourceId]], Set<WrappedMediaResourceId>) in
return (state.media, state.mediaResourceIds, state.allResourceIds) return (state.media, state.mediaResourceIds, state.allResourceIds)
} }
@ -205,7 +233,7 @@ public func collectCacheUsageStats(account: Account, additionalCachePaths: [Stri
} }
} }
} }
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logFilesPath), includingPropertiesForKeys: [URLResourceKey.fileSizeKey], options: []) { if let logFilesPath = logFilesPath, let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logFilesPath), includingPropertiesForKeys: [URLResourceKey.fileSizeKey], options: []) {
for url in files { for url in files {
if let fileSize = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize { if let fileSize = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize {
immutableSize += Int64(fileSize) immutableSize += Int64(fileSize)

View File

@ -195,7 +195,7 @@ public extension Message {
func effectivelyFailed(timestamp: Int32) -> Bool { func effectivelyFailed(timestamp: Int32) -> Bool {
if self.flags.contains(.Failed) { if self.flags.contains(.Failed) {
return true return true
} else if self.id.namespace == Namespaces.Message.ScheduledCloud { } else if self.id.namespace == Namespaces.Message.ScheduledCloud && self.timestamp != 0x7FFFFFFE {
return timestamp > self.timestamp + 60 return timestamp > self.timestamp + 60
} else { } else {
return false return false

View File

@ -490,7 +490,7 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
context.keychain = keychain context.keychain = keychain
var wrappedAdditionalSource: MTSignal? var wrappedAdditionalSource: MTSignal?
#if os(iOS) #if os(iOS)
if #available(iOS 10.0, *) { if #available(iOS 10.0, *), !supplementary {
var cloudDataContextValue: CloudDataContext? var cloudDataContextValue: CloudDataContext?
if let value = cloudDataContext.with({ $0 }) { if let value = cloudDataContext.with({ $0 }) {
cloudDataContextValue = value cloudDataContextValue = value

View File

@ -9,7 +9,7 @@ import TelegramPresentationData
import DeviceAccess import DeviceAccess
import AccountContext import AccountContext
public final class PermissionController : ViewController { public final class PermissionController: ViewController {
private let context: AccountContext private let context: AccountContext
private let splitTest: PermissionUISplitTest? private let splitTest: PermissionUISplitTest?
private var state: PermissionControllerContent? private var state: PermissionControllerContent?
@ -238,11 +238,4 @@ public final class PermissionController : ViewController {
@objc private func nextPressed() { @objc private func nextPressed() {
self.skip?() self.skip?()
} }
override public func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
}
} }

View File

@ -85,7 +85,11 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
separatorColor: UIColor(rgb: 0x3d3d40), separatorColor: UIColor(rgb: 0x3d3d40),
badgeBackgroundColor: badgeFillColor, badgeBackgroundColor: badgeFillColor,
badgeStrokeColor: UIColor(rgb: 0x1c1c1d), badgeStrokeColor: UIColor(rgb: 0x1c1c1d),
badgeTextColor: badgeTextColor badgeTextColor: badgeTextColor,
segmentedBackgroundColor: UIColor(rgb: 0x3a3b3d),
segmentedForegroundColor: UIColor(rgb: 0x6f7075),
segmentedTextColor: UIColor(rgb: 0xffffff),
segmentedDividerColor: UIColor(rgb: 0x505155)
) )
let navigationSearchBar = PresentationThemeNavigationSearchBar( let navigationSearchBar = PresentationThemeNavigationSearchBar(

View File

@ -61,7 +61,11 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
separatorColor: mainSeparatorColor, separatorColor: mainSeparatorColor,
badgeBackgroundColor: UIColor(rgb: 0xef5b5b), badgeBackgroundColor: UIColor(rgb: 0xef5b5b),
badgeStrokeColor: UIColor(rgb: 0xef5b5b), badgeStrokeColor: UIColor(rgb: 0xef5b5b),
badgeTextColor: UIColor(rgb: 0xffffff) badgeTextColor: UIColor(rgb: 0xffffff),
segmentedBackgroundColor: mainInputColor,
segmentedForegroundColor: mainBackgroundColor,
segmentedTextColor: UIColor(rgb: 0xffffff),
segmentedDividerColor: mainSecondaryTextColor.withAlphaComponent(0.5)
) )
let navigationSearchBar = PresentationThemeNavigationSearchBar( let navigationSearchBar = PresentationThemeNavigationSearchBar(

View File

@ -69,7 +69,11 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr
separatorColor: UIColor(rgb: 0xb1b1b1), separatorColor: UIColor(rgb: 0xb1b1b1),
badgeBackgroundColor: UIColor(rgb: 0xff3b30), badgeBackgroundColor: UIColor(rgb: 0xff3b30),
badgeStrokeColor: UIColor(rgb: 0xff3b30), badgeStrokeColor: UIColor(rgb: 0xff3b30),
badgeTextColor: .white badgeTextColor: .white,
segmentedBackgroundColor: UIColor(rgb: 0xe9e9e9),
segmentedForegroundColor: UIColor(rgb: 0xf7f7f7),
segmentedTextColor: UIColor(rgb: 0x000000),
segmentedDividerColor: UIColor(rgb: 0xd6d6dc)
) )
let navigationSearchBar = PresentationThemeNavigationSearchBar( let navigationSearchBar = PresentationThemeNavigationSearchBar(

View File

@ -106,8 +106,12 @@ public final class PresentationThemeRootNavigationBar {
public let badgeBackgroundColor: UIColor public let badgeBackgroundColor: UIColor
public let badgeStrokeColor: UIColor public let badgeStrokeColor: UIColor
public let badgeTextColor: UIColor public let badgeTextColor: UIColor
public let segmentedBackgroundColor: UIColor
public let segmentedForegroundColor: UIColor
public let segmentedTextColor: UIColor
public let segmentedDividerColor: UIColor
public init(buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlColor: UIColor, accentTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) { public init(buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlColor: UIColor, accentTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, segmentedBackgroundColor: UIColor, segmentedForegroundColor: UIColor, segmentedTextColor: UIColor, segmentedDividerColor: UIColor) {
self.buttonColor = buttonColor self.buttonColor = buttonColor
self.disabledButtonColor = disabledButtonColor self.disabledButtonColor = disabledButtonColor
self.primaryTextColor = primaryTextColor self.primaryTextColor = primaryTextColor
@ -119,6 +123,10 @@ public final class PresentationThemeRootNavigationBar {
self.badgeBackgroundColor = badgeBackgroundColor self.badgeBackgroundColor = badgeBackgroundColor
self.badgeStrokeColor = badgeStrokeColor self.badgeStrokeColor = badgeStrokeColor
self.badgeTextColor = badgeTextColor self.badgeTextColor = badgeTextColor
self.segmentedBackgroundColor = segmentedBackgroundColor
self.segmentedForegroundColor = segmentedForegroundColor
self.segmentedTextColor = segmentedTextColor
self.segmentedDividerColor = segmentedDividerColor
} }
} }

View File

@ -339,6 +339,10 @@ extension PresentationThemeRootNavigationBar: Codable {
case badgeFill case badgeFill
case badgeStroke case badgeStroke
case badgeText case badgeText
case segmentedBg
case segmentedFg
case segmentedText
case segmentedDivider
} }
public convenience init(from decoder: Decoder) throws { public convenience init(from decoder: Decoder) throws {
@ -353,7 +357,11 @@ extension PresentationThemeRootNavigationBar: Codable {
separatorColor: try decodeColor(values, .separator), separatorColor: try decodeColor(values, .separator),
badgeBackgroundColor: try decodeColor(values, .badgeFill), badgeBackgroundColor: try decodeColor(values, .badgeFill),
badgeStrokeColor: try decodeColor(values, .badgeStroke), badgeStrokeColor: try decodeColor(values, .badgeStroke),
badgeTextColor: try decodeColor(values, .badgeText)) badgeTextColor: try decodeColor(values, .badgeText),
segmentedBackgroundColor: try decodeColor(values, .segmentedBg),
segmentedForegroundColor: try decodeColor(values, .segmentedFg),
segmentedTextColor: try decodeColor(values, .segmentedText),
segmentedDividerColor: try decodeColor(values, .segmentedDivider))
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -369,6 +377,10 @@ extension PresentationThemeRootNavigationBar: Codable {
try encodeColor(&values, self.badgeBackgroundColor, .badgeFill) try encodeColor(&values, self.badgeBackgroundColor, .badgeFill)
try encodeColor(&values, self.badgeStrokeColor, .badgeStroke) try encodeColor(&values, self.badgeStrokeColor, .badgeStroke)
try encodeColor(&values, self.badgeTextColor, .badgeText) try encodeColor(&values, self.badgeTextColor, .badgeText)
try encodeColor(&values, self.segmentedBackgroundColor, .segmentedBg)
try encodeColor(&values, self.segmentedForegroundColor, .segmentedFg)
try encodeColor(&values, self.segmentedTextColor, .segmentedText)
try encodeColor(&values, self.segmentedDividerColor, .segmentedDivider)
} }
} }

View File

@ -24,7 +24,7 @@ extension ApplicationShortcutItem {
case .compose: case .compose:
icon = UIApplicationShortcutIcon(type: .compose) icon = UIApplicationShortcutIcon(type: .compose)
case .camera: case .camera:
icon = UIApplicationShortcutIcon(type: .capturePhoto) icon = UIApplicationShortcutIcon(templateImageName: "Shortcuts/Camera")
case .savedMessages: case .savedMessages:
icon = UIApplicationShortcutIcon(templateImageName: "Shortcuts/SavedMessages") icon = UIApplicationShortcutIcon(templateImageName: "Shortcuts/SavedMessages")
} }

View File

@ -52,6 +52,7 @@ import WalletUI
import WalletUrl import WalletUrl
import LocalizedPeerData import LocalizedPeerData
import PhoneNumberFormat import PhoneNumberFormat
import SettingsUI
public enum ChatControllerPeekActions { public enum ChatControllerPeekActions {
case standard case standard
@ -266,6 +267,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var raiseToListen: RaiseToListenManager? private var raiseToListen: RaiseToListenManager?
private var voicePlaylistDidEndTimestamp: Double = 0.0 private var voicePlaylistDidEndTimestamp: Double = 0.0
private weak var searchResultsTooltipController: TooltipController?
private weak var messageTooltipController: TooltipController? private weak var messageTooltipController: TooltipController?
private weak var videoUnmuteTooltipController: TooltipController? private weak var videoUnmuteTooltipController: TooltipController?
private weak var silentPostTooltipController: TooltipController? private weak var silentPostTooltipController: TooltipController?
@ -1149,7 +1151,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .url(url): case let .url(url):
var cleanUrl = url var cleanUrl = url
var canAddToReadingList = true var canAddToReadingList = true
let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: .url(url: url)).count > 1 var canOpenIn = availableOpenInOptions(context: strongSelf.context, item: .url(url: url)).count > 1
let mailtoString = "mailto:" let mailtoString = "mailto:"
let telString = "tel:" let telString = "tel:"
var openText = strongSelf.presentationData.strings.Conversation_LinkDialogOpen var openText = strongSelf.presentationData.strings.Conversation_LinkDialogOpen
@ -1162,6 +1164,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
phoneNumber = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...]) phoneNumber = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...])
cleanUrl = phoneNumber! cleanUrl = phoneNumber!
openText = strongSelf.presentationData.strings.UserInfo_PhoneCall openText = strongSelf.presentationData.strings.UserInfo_PhoneCall
canOpenIn = false
} else if canOpenIn { } else if canOpenIn {
openText = strongSelf.presentationData.strings.Conversation_FileOpenIn openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
} }
@ -1470,6 +1473,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a) strongSelf.present(c, in: .window(.root), with: a)
} }
}, push: { [weak self] c in
if let strongSelf = self {
strongSelf.push(c)
}
}) })
strongSelf.chatDisplayNode.dismissInput() strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(controller, in: .window(.root)) strongSelf.present(controller, in: .window(.root))
@ -1518,22 +1525,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
strongSelf.context.sharedContext.applicationBindings.openAppStorePage() strongSelf.context.sharedContext.applicationBindings.openAppStorePage()
} }
}, displayMessageTooltip: { [weak self] messageId, text, sourceNode, sourceFrame in }, displayMessageTooltip: { [weak self] messageId, text, node, nodeRect in
if let strongSelf = self { if let strongSelf = self {
if let sourceNode = sourceNode { if let node = node {
strongSelf.messageTooltipController?.dismiss() strongSelf.messageTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true) let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.messageTooltipController = tooltipController strongSelf.messageTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController {
strongSelf.messageTooltipController = nil strongSelf.messageTooltipController = nil
} }
} }
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self { if let strongSelf = self {
var rect = sourceNode.view.convert(sourceNode.view.bounds, to: strongSelf.chatDisplayNode.view) var rect = node.view.convert(node.view.bounds, to: strongSelf.chatDisplayNode.view)
if let sourceFrame = sourceFrame { if let nodeRect = nodeRect {
rect = CGRect(origin: rect.origin.offsetBy(dx: sourceFrame.minX, dy: sourceFrame.minY - sourceNode.bounds.minY), size: sourceFrame.size) rect = CGRect(origin: rect.origin.offsetBy(dx: nodeRect.minX, dy: nodeRect.minY - node.bounds.minY), size: nodeRect.size)
} }
return (strongSelf.chatDisplayNode, rect) return (strongSelf.chatDisplayNode, rect)
} }
@ -1685,6 +1692,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root)) strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root))
} }
}) })
}, displaySwipeToReplyHint: { [weak self] in
if let strongSelf = self {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: true, action: { _ in }), in: .window(.root))
}
}, requestMessageUpdate: { [weak self] id in }, requestMessageUpdate: { [weak self] id in
if let strongSelf = self { if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
@ -2997,6 +3008,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
}, push: { c in
self?.push(c)
}, completion: { _ in }), in: .window(.root)) }, completion: { _ in }), in: .window(.root))
} }
}, reportMessages: { [weak self] messages, contextController in }, reportMessages: { [weak self] messages, contextController in
@ -3562,7 +3575,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let tooltipController = TooltipController(content: .text(banDescription)) let tooltipController = TooltipController(content: .text(banDescription))
strongSelf.mediaRestrictedTooltipController = tooltipController strongSelf.mediaRestrictedTooltipController = tooltipController
strongSelf.mediaRestrictedTooltipControllerMode = isStickers strongSelf.mediaRestrictedTooltipControllerMode = isStickers
tooltipController.dismissed = { [weak tooltipController] in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
strongSelf.mediaRestrictedTooltipController = nil strongSelf.mediaRestrictedTooltipController = nil
} }
@ -3597,7 +3610,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.videoUnmuteTooltipController?.dismiss() strongSelf.videoUnmuteTooltipController?.dismiss()
let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true) let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.videoUnmuteTooltipController = tooltipController strongSelf.videoUnmuteTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.videoUnmuteTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.videoUnmuteTooltipController === tooltipController {
strongSelf.videoUnmuteTooltipController = nil strongSelf.videoUnmuteTooltipController = nil
ApplicationSpecificNotice.setVolumeButtonToUnmute(accountManager: strongSelf.context.sharedContext.accountManager) ApplicationSpecificNotice.setVolumeButtonToUnmute(accountManager: strongSelf.context.sharedContext.accountManager)
@ -3852,7 +3865,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if let rect = rect { } else if let rect = rect {
let tooltipController = TooltipController(content: .text(text)) let tooltipController = TooltipController(content: .text(text))
strongSelf.silentPostTooltipController = tooltipController strongSelf.silentPostTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.silentPostTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.silentPostTooltipController === tooltipController {
strongSelf.silentPostTooltipController = nil strongSelf.silentPostTooltipController = nil
} }
@ -4078,6 +4091,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
strongSelf.openScheduledMessages() strongSelf.openScheduledMessages()
} }
}, displaySearchResultsTooltip: { [weak self] node, nodeRect in
if let strongSelf = self {
strongSelf.searchResultsTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.ChatSearch_ResultsTooltip), dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.searchResultsTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.searchResultsTooltipController === tooltipController {
strongSelf.searchResultsTooltipController = nil
}
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
var rect = node.view.convert(node.view.bounds, to: strongSelf.chatDisplayNode.view)
rect = CGRect(origin: rect.origin.offsetBy(dx: nodeRect.minX, dy: nodeRect.minY - node.bounds.minY), size: nodeRect.size)
return (strongSelf.chatDisplayNode, rect)
}
return nil
}))
}
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get())) }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get()))
switch self.chatLocation { switch self.chatLocation {
@ -5052,19 +5084,213 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.chatLocationInfoData { switch self.chatLocationInfoData {
case let .peer(peerView): case let .peer(peerView):
self.navigationActionDisposable.set((peerView.get() self.navigationActionDisposable.set((peerView.get()
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peerView in |> deliverOnMainQueue).start(next: { [weak self] peerView in
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible { if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) { if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
strongSelf.effectiveNavigationController?.pushViewController(infoController) strongSelf.effectiveNavigationController?.pushViewController(infoController)
}
} }
}
})) }))
} }
case .search: case .search:
self.interfaceInteraction?.beginMessageSearch(.everything, "") self.interfaceInteraction?.beginMessageSearch(.everything, "")
case .dismiss: case .dismiss:
self.dismiss() self.dismiss()
case .clearCache:
let clearDisposable = MetaDisposable()
switch self.chatLocationInfoData {
case let .peer(peerView):
self.navigationActionDisposable.set((peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
guard let strongSelf = self, let peer = peerView.peers[peerView.peerId] else {
return
}
let peerId = peer.id
let cacheUsageStats = (collectCacheUsageStats(account: strongSelf.context.account, peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self, case let .result(stats) = result, var categories = stats.media[peer.id] else {
return
}
let presentationData = strongSelf.presentationData
let controller = ActionSheetController(presentationTheme: presentationData.theme)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
var sizeIndex: [PeerCacheUsageCategory: (Bool, Int64)] = [:]
var itemIndex = 0
let updateTotalSize: () -> Void = { [weak controller] in
controller?.updateItem(groupIndex: 0, itemIndex: itemIndex, { item in
let title: String
let filteredSize = sizeIndex.values.reduce(0, { $0 + ($1.0 ? $1.1 : 0) })
if filteredSize == 0 {
title = presentationData.strings.Cache_ClearNone
} else {
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0
}
if let item = item as? ActionSheetButtonItem {
return ActionSheetButtonItem(title: title, color: filteredSize != 0 ? .accent : .disabled, enabled: filteredSize != 0, action: item.action)
}
return item
})
}
let toggleCheck: (PeerCacheUsageCategory, Int) -> Void = { [weak controller] category, itemIndex in
if let (value, size) = sizeIndex[category] {
sizeIndex[category] = (!value, size)
}
controller?.updateItem(groupIndex: 0, itemIndex: itemIndex, { item in
if let item = item as? ActionSheetCheckboxItem {
return ActionSheetCheckboxItem(title: item.title, label: item.label, value: !item.value, action: item.action)
}
return item
})
updateTotalSize()
}
var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: strongSelf.context, peer: peer, chatPeer: peer, action: .clearCache, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder))
let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file]
var totalSize: Int64 = 0
func stringForCategory(strings: PresentationStrings, category: PeerCacheUsageCategory) -> String {
switch category {
case .image:
return strings.Cache_Photos
case .video:
return strings.Cache_Videos
case .audio:
return strings.Cache_Music
case .file:
return strings.Cache_Files
}
}
for categoryId in validCategories {
if let media = categories[categoryId] {
var categorySize: Int64 = 0
for (_, size) in media {
categorySize += size
}
sizeIndex[categoryId] = (true, categorySize)
totalSize += categorySize
if categorySize > 1024 {
let index = itemIndex
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), value: true, action: { value in
toggleCheck(categoryId, index)
}))
itemIndex += 1
}
}
}
if items.isEmpty {
strongSelf.presentClearCacheSuggestion()
} else {
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0, action: {
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
var clearMediaIds = Set<MediaId>()
var media = stats.media
if var categories = media[peerId] {
for category in clearCategories {
if let contents = categories[category] {
for (mediaId, _) in contents {
clearMediaIds.insert(mediaId)
}
}
categories.removeValue(forKey: category)
}
media[peerId] = categories
}
// if let additionalPeerId = additionalPeerId {
// if var categories = media[additionalPeerId] {
// for category in clearCategories {
// if let contents = categories[category] {
// for (mediaId, _) in contents {
// clearMediaIds.insert(mediaId)
// }
// }
// categories.removeValue(forKey: category)
// }
//
// media[additionalPeerId] = categories
// }
// }
var clearResourceIds = Set<WrappedMediaResourceId>()
for id in clearMediaIds {
if let ids = stats.mediaResourceIds[id] {
for resourceId in ids {
clearResourceIds.insert(WrappedMediaResourceId(resourceId))
}
}
}
var signal = clearCachedMediaResources(account: strongSelf.context.account, mediaResourceIds: clearResourceIds)
let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize)
var cancelImpl: (() -> Void)?
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
signal = signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = {
clearDisposable.set(nil)
}
clearDisposable.set((signal
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout {
let deviceName = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", deviceName).0), elevatedLayout: true, action: { _ in }), in: .window(.root))
}
}))
dismissAction()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
}))
controller.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(controller, in: .window(.root))
}
})
}))
}
} }
} }
@ -7506,6 +7732,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
private func presentClearCacheSuggestion() {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return
}
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: peer, chatPeer: peer, action: .clearCacheSuggestion, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ClearCache_FreeSpace, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
let controller = storageUsageController(context: strongSelf.context, isModal: true)
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
self.chatDisplayNode.dismissInput()
self.present(actionSheet, in: .window(.root))
}
@available(iOSApplicationExtension 11.0, iOS 11.0, *) @available(iOSApplicationExtension 11.0, iOS 11.0, *)
public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String]) return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String])
@ -7569,7 +7824,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if let rect = rect { } else if let rect = rect {
let tooltipController = TooltipController(content: .text(text)) let tooltipController = TooltipController(content: .text(text))
self.mediaRecordingModeTooltipController = tooltipController self.mediaRecordingModeTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] in tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController {
strongSelf.mediaRecordingModeTooltipController = nil strongSelf.mediaRecordingModeTooltipController = nil
} }
@ -7584,6 +7839,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
private func dismissAllTooltips() { private func dismissAllTooltips() {
self.searchResultsTooltipController?.dismiss()
self.messageTooltipController?.dismiss() self.messageTooltipController?.dismiss()
self.videoUnmuteTooltipController?.dismiss() self.videoUnmuteTooltipController?.dismiss()
self.silentPostTooltipController?.dismiss() self.silentPostTooltipController?.dismiss()

View File

@ -100,6 +100,7 @@ public final class ChatControllerInteraction {
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
let updateMessageReaction: (MessageId, String) -> Void let updateMessageReaction: (MessageId, String) -> Void
let openMessageReactions: (MessageId) -> Void let openMessageReactions: (MessageId) -> Void
let displaySwipeToReplyHint: () -> Void
let requestMessageUpdate: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void let cancelInteractiveKeyboardGestures: () -> Void
@ -114,7 +115,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])? var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>() var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String) -> Void, openMessageReactions: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
@ -166,7 +167,8 @@ public final class ChatControllerInteraction {
self.performTextSelectionAction = performTextSelectionAction self.performTextSelectionAction = performTextSelectionAction
self.updateMessageReaction = updateMessageReaction self.updateMessageReaction = updateMessageReaction
self.openMessageReactions = openMessageReactions self.openMessageReactions = openMessageReactions
self.displaySwipeToReplyHint = displaySwipeToReplyHint
self.requestMessageUpdate = requestMessageUpdate self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -202,6 +204,7 @@ public final class ChatControllerInteraction {
}, performTextSelectionAction: { _, _, _ in }, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in }, updateMessageReaction: { _, _ in
}, openMessageReactions: { _ in }, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -1533,6 +1533,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}) })
} }
self.searchNavigationNode?.deactivate() self.searchNavigationNode?.deactivate()
self.view.window?.endEditing(true)
} }
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
@ -2122,6 +2124,19 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
var forwardingToSameChat = false
if case let .peer(id) = self.chatPresentationInterfaceState.chatLocation, id.namespace == Namespaces.Peer.CloudUser, id != self.context.account.peerId, let forwardMessageIds = self.chatPresentationInterfaceState.interfaceState.forwardMessageIds {
for messageId in forwardMessageIds {
if messageId.peerId == id {
forwardingToSameChat = true
}
}
}
if !messages.isEmpty && forwardingToSameChat {
//self.controllerInteraction.displaySwipeToReplyHint()
}
if !messages.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil { if !messages.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil {
self.setupSendActionOnViewUpdate({ [weak self] in self.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode {

View File

@ -9,6 +9,7 @@ import AccountContext
enum ChatNavigationButtonAction { enum ChatNavigationButtonAction {
case openChatInfo case openChatInfo
case clearHistory case clearHistory
case clearCache
case cancelMessageSelection case cancelMessageSelection
case search case search
case dismiss case dismiss
@ -45,6 +46,9 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
if canClear { if canClear {
return ChatNavigationButton(action: .clearHistory, buttonItem: UIBarButtonItem(title: title, style: .plain, target: target, action: selector)) return ChatNavigationButton(action: .clearHistory, buttonItem: UIBarButtonItem(title: title, style: .plain, target: target, action: selector))
} else {
title = strings.Conversation_ClearCache
return ChatNavigationButton(action: .clearCache, buttonItem: UIBarButtonItem(title: title, style: .plain, target: target, action: selector))
} }
} }
} }

View File

@ -32,7 +32,9 @@ final class ChatMessageDateHeader: ListViewItemHeader {
self.presentationData = presentationData self.presentationData = presentationData
self.context = context self.context = context
self.action = action self.action = action
if timestamp == Int32.max { if timestamp == 0x7FFFFFFE {
self.roundedTimestamp = 0x7FFFFFFE
} else if timestamp == Int32.max {
self.roundedTimestamp = timestamp / (granularity) * (granularity) self.roundedTimestamp = timestamp / (granularity) * (granularity)
} else { } else {
self.roundedTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity) self.roundedTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
@ -151,7 +153,9 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
} }
if scheduled { if scheduled {
if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday { if localTimestamp == 0x7FFFFFFE {
text = presentationData.strings.ScheduledMessages_ScheduledOnline
} else if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday {
text = presentationData.strings.ScheduledMessages_ScheduledToday text = presentationData.strings.ScheduledMessages_ScheduledToday
} else { } else {
text = presentationData.strings.ScheduledMessages_ScheduledDate(text).0 text = presentationData.strings.ScheduledMessages_ScheduledDate(text).0

View File

@ -312,7 +312,7 @@ private struct ChatMessagePollOptionResult: Equatable {
} }
private final class ChatMessagePollOptionNode: ASDisplayNode { private final class ChatMessagePollOptionNode: ASDisplayNode {
private let highlightedBackgroundNode: ASImageNode private let highlightedBackgroundNode: ASDisplayNode
private var radioNode: ChatMessagePollOptionRadioNode? private var radioNode: ChatMessagePollOptionRadioNode?
private let percentageNode: ASDisplayNode private let percentageNode: ASDisplayNode
private var percentageImage: UIImage? private var percentageImage: UIImage?
@ -326,10 +326,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
var pressed: (() -> Void)? var pressed: (() -> Void)?
override init() { override init() {
self.highlightedBackgroundNode = ASImageNode() self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.displayWithoutProcessing = true
self.highlightedBackgroundNode.displaysAsynchronously = false
self.highlightedBackgroundNode.isLayerBacked = true
self.highlightedBackgroundNode.alpha = 0.0 self.highlightedBackgroundNode.alpha = 0.0
self.highlightedBackgroundNode.isUserInteractionEnabled = false self.highlightedBackgroundNode.isUserInteractionEnabled = false
@ -344,8 +341,6 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
self.percentageNode = ASDisplayNode() self.percentageNode = ASDisplayNode()
self.percentageNode.alpha = 0.0 self.percentageNode.alpha = 0.0
self.percentageNode.isLayerBacked = true self.percentageNode.isLayerBacked = true
//self.percentageNode.displaysAsynchronously = false
//self.percentageNode.displayWithoutProcessing = true
super.init() super.init()
@ -407,7 +402,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
let previousResult = node.currentResult let previousResult = node.currentResult
node.currentResult = optionResult node.currentResult = optionResult
node.highlightedBackgroundNode.backgroundColor = (incoming ? presentationData.theme.theme.chat.message.incoming.accentTextColor : presentationData.theme.theme.chat.message.outgoing.accentTextColor).withAlphaComponent(0.15) node.highlightedBackgroundNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.highlight : presentationData.theme.theme.chat.message.outgoing.polls.highlight
node.buttonNode.accessibilityLabel = option.text node.buttonNode.accessibilityLabel = option.text

View File

@ -113,9 +113,10 @@ final class ChatPanelInterfaceInteraction {
let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void
let displaySendMessageOptions: () -> Void let displaySendMessageOptions: () -> Void
let openScheduledMessages: () -> Void let openScheduledMessages: () -> Void
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
let statuses: ChatPanelInterfaceInteractionStatuses? let statuses: ChatPanelInterfaceInteractionStatuses?
init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, openSearchResults: @escaping () -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping () -> Void, openScheduledMessages: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, openSearchResults: @escaping () -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping () -> Void, openScheduledMessages: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
self.setupReplyMessage = setupReplyMessage self.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection self.beginMessageSelection = beginMessageSelection
@ -182,6 +183,7 @@ final class ChatPanelInterfaceInteraction {
self.displaySlowmodeTooltip = displaySlowmodeTooltip self.displaySlowmodeTooltip = displaySlowmodeTooltip
self.displaySendMessageOptions = displaySendMessageOptions self.displaySendMessageOptions = displaySendMessageOptions
self.openScheduledMessages = openScheduledMessages self.openScheduledMessages = openScheduledMessages
self.displaySearchResultsTooltip = displaySearchResultsTooltip
self.statuses = statuses self.statuses = statuses
} }
} }

View File

@ -114,6 +114,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, displaySlowmodeTooltip: { _, _ in }, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { }, displaySendMessageOptions: {
}, openScheduledMessages: { }, openScheduledMessages: {
}, displaySearchResultsTooltip: { _, _ in
}, statuses: nil) }, statuses: nil)
self.navigationItem.titleView = self.titleView self.navigationItem.titleView = self.titleView

View File

@ -416,6 +416,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, performTextSelectionAction: { _, _, _ in }, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in }, updateMessageReaction: { _, _ in
}, openMessageReactions: { _ in }, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -64,7 +64,7 @@ final class ChatScheduleTimeController: ViewController {
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = ChatScheduleTimeControllerNode(context: self.context, mode: self.mode, currentTime: self.currentTime, minimalTime: self.minimalTime, dismissByTapOutside: self.dismissByTapOutside) self.displayNode = ChatScheduleTimeControllerNode(context: self.context, mode: self.mode, currentTime: self.currentTime, minimalTime: self.minimalTime, dismissByTapOutside: self.dismissByTapOutside)
self.controllerNode.completion = { [weak self] time in self.controllerNode.completion = { [weak self] time in
self?.completion(time + 5) self?.completion(time != 0x7FFFFFFE ? time + 5 : time)
self?.dismiss() self?.dismiss()
} }
self.controllerNode.dismiss = { [weak self] in self.controllerNode.dismiss = { [weak self] in

View File

@ -26,6 +26,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
private let titleNode: ASTextNode private let titleNode: ASTextNode
private let cancelButton: HighlightableButtonNode private let cancelButton: HighlightableButtonNode
private let doneButton: SolidRoundedButtonNode private let doneButton: SolidRoundedButtonNode
private let onlineButton: HighlightableButtonNode
private var pickerView: UIDatePicker? private var pickerView: UIDatePicker?
private let dateFormatter: DateFormatter private let dateFormatter: DateFormatter
@ -76,6 +77,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
self.onlineButton = HighlightableButtonNode()
self.onlineButton.setTitle(self.presentationData.strings.Conversation_ScheduleMessage_SendWhenOnline, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
self.dateFormatter = DateFormatter() self.dateFormatter = DateFormatter()
self.dateFormatter.timeStyle = .none self.dateFormatter.timeStyle = .none
self.dateFormatter.dateStyle = .short self.dateFormatter.dateStyle = .short
@ -98,6 +102,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.contentContainerNode.addSubnode(self.titleNode) self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.cancelButton) self.contentContainerNode.addSubnode(self.cancelButton)
self.contentContainerNode.addSubnode(self.doneButton) self.contentContainerNode.addSubnode(self.doneButton)
//self.contentContainerNode.addSubnode(self.onlineButton)
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
self.doneButton.pressed = { [weak self] in self.doneButton.pressed = { [weak self] in
@ -112,6 +117,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
} }
} }
} }
self.onlineButton.addTarget(self, action: #selector(self.onlineButtonPressed), forControlEvents: .touchUpInside)
self.setupPickerView(currentTime: currentTime) self.setupPickerView(currentTime: currentTime)
self.updateButtonTitle() self.updateButtonTitle()
@ -131,7 +137,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
pickerView.timeZone = TimeZone.current pickerView.timeZone = TimeZone.current
pickerView.minuteInterval = 1 pickerView.minuteInterval = 1
pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor") pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
contentContainerNode.view.addSubview(pickerView) self.contentContainerNode.view.addSubview(pickerView)
pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
self.pickerView = pickerView self.pickerView = pickerView
@ -155,6 +161,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme))
self.onlineButton.setTitle(self.presentationData.strings.Conversation_ScheduleMessage_SendWhenOnline, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
} }
private func updateMinimumDate(currentTime: Int32? = nil) { private func updateMinimumDate(currentTime: Int32? = nil) {
@ -234,6 +241,10 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.cancel?() self.cancel?()
} }
@objc func onlineButtonPressed() {
self.completion?(0x7FFFFFFE)
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if self.dismissByTapOutside, case .ended = recognizer.state { if self.dismissByTapOutside, case .ended = recognizer.state {
self.cancelButtonPressed() self.cancelButtonPressed()
@ -300,10 +311,12 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
let cleanInsets = layout.insets(options: [.statusBar]) let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top) insets.top = max(10.0, insets.top)
var buttonOffset: CGFloat = 0.0 //44.0
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleHeight: CGFloat = 54.0 let titleHeight: CGFloat = 54.0
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + buttonOffset
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight) let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight - buttonOffset)
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
@ -330,7 +343,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
let buttonInset: CGFloat = 16.0 let buttonInset: CGFloat = 16.0
let buttonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) let buttonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - buttonHeight - insets.bottom - 10.0, width: contentFrame.width, height: buttonHeight)) transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - buttonHeight - insets.bottom - 10.0 - buttonOffset, width: contentFrame.width, height: buttonHeight))
let onlineSize = self.onlineButton.measure(CGSize(width: width, height: titleHeight))
let onlineFrame = CGRect(origin: CGPoint(x: ceil((layout.size.width - onlineSize.width) / 2.0), y: contentHeight - 36.0 - insets.bottom), size: onlineSize)
transition.updateFrame(node: self.onlineButton, frame: onlineFrame)
self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight)) self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))

View File

@ -6,6 +6,7 @@ import TelegramCore
import SyncCore import SyncCore
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
import TelegramNotices
import TelegramPresentationData import TelegramPresentationData
import ActivityIndicator import ActivityIndicator
@ -25,6 +26,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
private let activityDisposable = MetaDisposable() private let activityDisposable = MetaDisposable()
private var displayActivity = false private var displayActivity = false
private var needsSearchResultsTooltip = true
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, LayoutMetrics)? private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, LayoutMetrics)?
override var interfaceInteraction: ChatPanelInterfaceInteraction? { override var interfaceInteraction: ChatPanelInterfaceInteraction? {
@ -80,6 +83,26 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
@objc func upPressed() { @objc func upPressed() {
self.interfaceInteraction?.navigateMessageSearch(.earlier) self.interfaceInteraction?.navigateMessageSearch(.earlier)
guard self.needsSearchResultsTooltip, let context = self.context else {
return
}
let _ = (ApplicationSpecificNotice.getChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] counter in
guard let strongSelf = self else {
return
}
if counter >= 3 {
strongSelf.needsSearchResultsTooltip = false
} else if arc4random_uniform(4) == 1 {
strongSelf.needsSearchResultsTooltip = false
let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager).start()
strongSelf.interfaceInteraction?.displaySearchResultsTooltip(strongSelf.resultsButton, strongSelf.resultsButton.bounds)
}
})
} }
@objc func downPressed() { @objc func downPressed() {
@ -96,6 +119,10 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
@objc func resultsPressed() { @objc func resultsPressed() {
self.interfaceInteraction?.openSearchResults() self.interfaceInteraction?.openSearchResults()
if let context = self.context {
let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager, count: 4).start()
}
} }
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {

View File

@ -1576,7 +1576,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
if let inputText = current.inputText.mutableCopy() as? NSMutableAttributedString { if let inputText = current.inputText.mutableCopy() as? NSMutableAttributedString {
inputText.replaceCharacters(in: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count), with: attributedString) inputText.replaceCharacters(in: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count), with: attributedString)
return (ChatTextInputState(inputText: inputText), inputMode) let updatedRange = current.selectionRange.lowerBound + attributedString.length
return (ChatTextInputState(inputText: inputText, selectionRange: updatedRange ..< updatedRange), inputMode)
} else { } else {
return (ChatTextInputState(inputText: attributedString), inputMode) return (ChatTextInputState(inputText: attributedString), inputMode)
} }

View File

@ -315,6 +315,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
var replaceControllerImpl: ((ViewController) -> Void)? var replaceControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushImpl: ((ViewController) -> Void)?
var endEditingImpl: (() -> Void)? var endEditingImpl: (() -> Void)?
var clearHighlightImpl: (() -> Void)? var clearHighlightImpl: (() -> Void)?
@ -584,7 +585,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: { }, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
clearHighlightImpl?() clearHighlightImpl?()
}) })
presentControllerImpl?(controller, nil) pushImpl?(controller)
}) })
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get())) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()))
@ -620,6 +621,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
pushImpl = { [weak controller] c in
controller?.push(c)
}
controller.willDisappear = { _ in controller.willDisappear = { _ in
endEditingImpl?() endEditingImpl?()
} }

View File

@ -19,6 +19,7 @@ import PeerInfoUI
import SettingsUI import SettingsUI
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import ShareController
private enum ChatMessageGalleryControllerData { private enum ChatMessageGalleryControllerData {
case url(String) case url(String)
@ -27,7 +28,7 @@ private enum ChatMessageGalleryControllerData {
case map(TelegramMediaMap) case map(TelegramMediaMap)
case stickerPack(StickerPackReference) case stickerPack(StickerPackReference)
case audio(TelegramMediaFile) case audio(TelegramMediaFile)
case document(TelegramMediaFile) case document(TelegramMediaFile, Bool)
case gallery(GalleryController) case gallery(GalleryController)
case secretGallery(SecretMediaPreviewController) case secretGallery(SecretMediaPreviewController)
case chatAvatars(AvatarGalleryController, Media) case chatAvatars(AvatarGalleryController, Media)
@ -153,14 +154,10 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
return .gallery(gallery) return .gallery(gallery)
} }
} }
#if DEBUG
if ext == "mkv" { if ext == "mkv" {
let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in return .document(file, true)
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(gallery)
} }
#endif
} }
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
@ -171,7 +168,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
} }
if !file.isVideo { if !file.isVideo {
return .document(file) return .document(file, false)
} }
} }
@ -272,9 +269,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
params.dismissInput() params.dismissInput()
params.present(controller, nil) params.present(controller, nil)
return true return true
case let .document(file): case let .document(file, immediateShare):
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
if let rootController = params.navigationController?.view.window?.rootViewController { if immediateShare {
let controller = ShareController(context: params.context, subject: .media(.standalone(media: file)), immediateExternalShare: true)
params.present(controller, nil)
} else if let rootController = params.navigationController?.view.window?.rootViewController {
presentDocumentPreviewController(rootController: rootController, theme: presentationData.theme, strings: presentationData.strings, postbox: params.context.account.postbox, file: file) presentDocumentPreviewController(rootController: rootController, theme: presentationData.theme, strings: presentationData.strings, postbox: params.context.account.postbox, file: file)
} }
return true return true

View File

@ -117,6 +117,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, performTextSelectionAction: { _, _, _ in }, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in }, updateMessageReaction: { _, _ in
}, openMessageReactions: { _ in }, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))

View File

@ -405,6 +405,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, performTextSelectionAction: { _, _, _ in }, performTextSelectionAction: { _, _, _ in
}, updateMessageReaction: { _, _ in }, updateMessageReaction: { _, _ in
}, openMessageReactions: { _ in }, openMessageReactions: { _ in
}, displaySwipeToReplyHint: {
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
@ -423,6 +424,8 @@ public class PeerMediaCollectionController: TelegramBaseController {
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
}, push: { c in
self?.push(c)
}, completion: { _ in }), in: .window(.root)) }, completion: { _ in }), in: .window(.root))
} }
}, reportMessages: { _, _ in }, reportMessages: { _, _ in
@ -517,6 +520,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, displaySlowmodeTooltip: { _, _ in }, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { }, displaySendMessageOptions: {
}, openScheduledMessages: { }, openScheduledMessages: {
}, displaySearchResultsTooltip: { _, _ in
}, statuses: nil) }, statuses: nil)
self.updateInterfaceState(animated: false, { return $0 }) self.updateInterfaceState(animated: false, { return $0 })

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,9 @@ func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, da
timestamp = message.timestamp timestamp = message.timestamp
} }
var dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat) var dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
if timestamp == 0x7FFFFFFE {
dateText = " "
}
var authorTitle: String? var authorTitle: String?
if let author = message.author as? TelegramUser { if let author = message.author as? TelegramUser {

View File

@ -141,60 +141,63 @@ final class WalletContextImpl: WalletContext {
buttonTextColor: .white, buttonTextColor: .white,
incomingFundsTitleColor: theme.chatList.secretTitleColor, incomingFundsTitleColor: theme.chatList.secretTitleColor,
outgoingFundsTitleColor: theme.list.itemDestructiveColor outgoingFundsTitleColor: theme.list.itemDestructiveColor
), setup: WalletSetupTheme( ), transaction: WalletTransactionTheme(
buttonFillColor: theme.list.itemCheckColors.fillColor, descriptionBackgroundColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill,
buttonForegroundColor: theme.list.itemCheckColors.foregroundColor, descriptionTextColor: theme.chat.message.incoming.primaryTextColor
inputBackgroundColor: theme.actionSheet.inputBackgroundColor, ), setup: WalletSetupTheme(
inputPlaceholderColor: theme.actionSheet.inputPlaceholderColor, buttonFillColor: theme.list.itemCheckColors.fillColor,
inputTextColor: theme.actionSheet.inputTextColor, buttonForegroundColor: theme.list.itemCheckColors.foregroundColor,
inputClearButtonColor: theme.actionSheet.inputClearButtonColor.withAlphaComponent(0.8) inputBackgroundColor: theme.actionSheet.inputBackgroundColor,
), inputPlaceholderColor: theme.actionSheet.inputPlaceholderColor,
list: WalletListTheme( inputTextColor: theme.actionSheet.inputTextColor,
itemPrimaryTextColor: theme.list.itemPrimaryTextColor, inputClearButtonColor: theme.actionSheet.inputClearButtonColor.withAlphaComponent(0.8)
itemSecondaryTextColor: theme.list.itemSecondaryTextColor, ),
itemPlaceholderTextColor: theme.list.itemPlaceholderTextColor, list: WalletListTheme(
itemDestructiveColor: theme.list.itemDestructiveColor, itemPrimaryTextColor: theme.list.itemPrimaryTextColor,
itemAccentColor: theme.list.itemAccentColor, itemSecondaryTextColor: theme.list.itemSecondaryTextColor,
itemDisabledTextColor: theme.list.itemDisabledTextColor, itemPlaceholderTextColor: theme.list.itemPlaceholderTextColor,
plainBackgroundColor: theme.list.plainBackgroundColor, itemDestructiveColor: theme.list.itemDestructiveColor,
blocksBackgroundColor: theme.list.blocksBackgroundColor, itemAccentColor: theme.list.itemAccentColor,
itemPlainSeparatorColor: theme.list.itemPlainSeparatorColor, itemDisabledTextColor: theme.list.itemDisabledTextColor,
itemBlocksBackgroundColor: theme.list.itemBlocksBackgroundColor, plainBackgroundColor: theme.list.plainBackgroundColor,
itemBlocksSeparatorColor: theme.list.itemBlocksSeparatorColor, blocksBackgroundColor: theme.list.blocksBackgroundColor,
itemHighlightedBackgroundColor: theme.list.itemHighlightedBackgroundColor, itemPlainSeparatorColor: theme.list.itemPlainSeparatorColor,
sectionHeaderTextColor: theme.list.sectionHeaderTextColor, itemBlocksBackgroundColor: theme.list.itemBlocksBackgroundColor,
freeTextColor: theme.list.freeTextColor, itemBlocksSeparatorColor: theme.list.itemBlocksSeparatorColor,
freeTextErrorColor: theme.list.freeTextErrorColor, itemHighlightedBackgroundColor: theme.list.itemHighlightedBackgroundColor,
inputClearButtonColor: theme.list.inputClearButtonColor sectionHeaderTextColor: theme.list.sectionHeaderTextColor,
), freeTextColor: theme.list.freeTextColor,
statusBarStyle: theme.rootController.statusBarStyle.style, freeTextErrorColor: theme.list.freeTextErrorColor,
navigationBar: navigationBarData.theme, inputClearButtonColor: theme.list.inputClearButtonColor
keyboardAppearance: theme.rootController.keyboardColor.keyboardAppearance, ),
alert: AlertControllerTheme(presentationTheme: theme), statusBarStyle: theme.rootController.statusBarStyle.style,
actionSheet: ActionSheetControllerTheme(presentationTheme: theme) navigationBar: navigationBarData.theme,
), strings: WalletStrings( keyboardAppearance: theme.rootController.keyboardColor.keyboardAppearance,
primaryComponent: WalletStringsComponent( alert: AlertControllerTheme(presentationTheme: theme),
languageCode: strings.primaryComponent.languageCode, actionSheet: ActionSheetControllerTheme(presentationTheme: theme)
localizedName: strings.primaryComponent.localizedName, ), strings: WalletStrings(
pluralizationRulesCode: strings.primaryComponent.pluralizationRulesCode, primaryComponent: WalletStringsComponent(
dict: strings.primaryComponent.dict languageCode: strings.primaryComponent.languageCode,
), localizedName: strings.primaryComponent.localizedName,
secondaryComponent: strings.secondaryComponent.flatMap { component in pluralizationRulesCode: strings.primaryComponent.pluralizationRulesCode,
return WalletStringsComponent( dict: strings.primaryComponent.dict
languageCode: component.languageCode, ),
localizedName: component.localizedName, secondaryComponent: strings.secondaryComponent.flatMap { component in
pluralizationRulesCode: component.pluralizationRulesCode, return WalletStringsComponent(
dict: component.dict languageCode: component.languageCode,
) localizedName: component.localizedName,
}, pluralizationRulesCode: component.pluralizationRulesCode,
groupingSeparator: strings.groupingSeparator dict: component.dict
), dateTimeFormat: WalletPresentationDateTimeFormat( )
timeFormat: timeFormat, },
dateFormat: dateFormat, groupingSeparator: strings.groupingSeparator
dateSeparator: presentationData.dateTimeFormat.dateSeparator, ), dateTimeFormat: WalletPresentationDateTimeFormat(
decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, timeFormat: timeFormat,
groupingSeparator: presentationData.dateTimeFormat.groupingSeparator dateFormat: dateFormat,
) dateSeparator: presentationData.dateTimeFormat.dateSeparator,
decimalSeparator: presentationData.dateTimeFormat.decimalSeparator,
groupingSeparator: presentationData.dateTimeFormat.groupingSeparator
)
) )
} }
@ -241,11 +244,11 @@ final class WalletContextImpl: WalletContext {
}) })
} }
func pickImage(completion: @escaping (UIImage) -> Void) { func pickImage(present: @escaping (ViewController) -> Void, completion: @escaping (UIImage) -> Void) {
self.context.sharedContext.openImagePicker(context: self.context, completion: { image in self.context.sharedContext.openImagePicker(context: self.context, completion: { image in
completion(image) completion(image)
}, present: { [weak self] controller in }, present: { [weak self] controller in
self?.context.sharedContext.mainWindow?.present(controller, on: .root) present(controller)
}) })
} }
} }

View File

@ -10,6 +10,7 @@ public enum UndoOverlayContent {
case revealedArchive(title: String, text: String, undo: Bool) case revealedArchive(title: String, text: String, undo: Bool)
case succeed(text: String) case succeed(text: String)
case emoji(path: String, text: String) case emoji(path: String, text: String)
case swipeToReply(title: String, text: String)
} }
public final class UndoOverlayController: ViewController { public final class UndoOverlayController: ViewController {

View File

@ -137,6 +137,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.maximumNumberOfLines = 2 self.textNode.maximumNumberOfLines = 2
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .swipeToReply(title, text):
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_swipereply", colors: [:], scale: 1.0)
self.animatedStickerNode = nil
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
} }
self.remainingSeconds = self.originalRemainingSeconds self.remainingSeconds = self.originalRemainingSeconds
@ -168,7 +178,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
case .removedChat: case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode) self.panelWrapperNode.addSubnode(self.timerTextNode)
self.panelWrapperNode.addSubnode(self.statusNode) self.panelWrapperNode.addSubnode(self.statusNode)
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji: case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply:
break break
} }
self.iconNode.flatMap(self.panelWrapperNode.addSubnode) self.iconNode.flatMap(self.panelWrapperNode.addSubnode)

View File

@ -51,7 +51,7 @@ public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": tr
public func explicitUrl(_ url: String) -> String { public func explicitUrl(_ url: String) -> String {
var url = url var url = url
if !url.hasPrefix("http") && !url.hasPrefix("https") { if !url.hasPrefix("http") && !url.hasPrefix("https") && url.range(of: "://") == nil {
url = "https://\(url)" url = "https://\(url)"
} }
return url return url

View File

@ -65,15 +65,20 @@ class WalletAmountItem: ListViewItem, ItemListItem {
private let integralFont = Font.medium(48.0) private let integralFont = Font.medium(48.0)
private let fractionalFont = Font.medium(24.0) private let fractionalFont = Font.medium(24.0)
private let iconSize = CGSize(width: 50.0, height: 50.0)
private let verticalOffset: CGFloat = -10.0
class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemNode, ItemListItemFocusableNode { class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemNode, ItemListItemFocusableNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let containerNode: ASDisplayNode
private let textNode: TextFieldNode private let textNode: TextFieldNode
private let iconNode: AnimatedStickerNode private let iconNode: AnimatedStickerNode
private let measureNode: TextNode private let measureNode: TextNode
private var item: WalletAmountItem? private var item: WalletAmountItem?
private var validLayout: (CGFloat, CGFloat, CGFloat)?
var tag: ItemListItemTag? { var tag: ItemListItemTag? {
return self.item?.tag return self.item?.tag
@ -86,6 +91,8 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.containerNode = ASDisplayNode()
self.textNode = TextFieldNode() self.textNode = TextFieldNode()
self.iconNode = AnimatedStickerNode() self.iconNode = AnimatedStickerNode()
@ -100,8 +107,9 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
self.clipsToBounds = false self.clipsToBounds = false
self.addSubnode(self.textNode) self.addSubnode(self.containerNode)
self.addSubnode(self.iconNode) self.containerNode.addSubnode(self.textNode)
self.containerNode.addSubnode(self.iconNode)
} }
override func didLoad() { override func didLoad() {
@ -122,9 +130,49 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
} }
private func inputFieldAsyncLayout() -> (_ item: WalletAmountItem, _ params: ListViewItemLayoutParams) -> (NSAttributedString, NSAttributedString, () -> Void) {
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode)
return { item, params in
let contentSize = CGSize(width: params.width, height: 100.0)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
let attributedPlaceholderText = NSAttributedString(string: "0", font: integralFont, textColor: item.theme.list.itemPlaceholderTextColor)
let attributedAmountText = amountAttributedString(item.amount, integralFont: integralFont, fractionalFont: fractionalFont, color: item.theme.list.itemPrimaryTextColor)
let (measureLayout, _) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: item.amount.isEmpty ? attributedPlaceholderText : attributedAmountText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
return (attributedPlaceholderText, attributedAmountText, { [weak self] in
if let strongSelf = self {
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - max(31.0, measureLayout.size.width)) / 2.0 - 28.0), y: floor((layout.contentSize.height - iconSize.height) / 2.0) - 3.0 + verticalOffset), size: iconSize)
strongSelf.iconNode.updateLayout(size: iconFrame.size)
strongSelf.iconNode.frame = iconFrame
let totalWidth = measureLayout.size.width + iconSize.width + 6.0
let paddedWidth = layout.size.width - 32.0
if totalWidth > paddedWidth {
let scale = paddedWidth / totalWidth
strongSelf.containerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
} else {
strongSelf.containerNode.transform = CATransform3DIdentity
}
}
})
}
}
private func updateInputField() {
guard let item = self.item, let validLayout = self.validLayout else {
return
}
let makeInputFieldLayout = self.inputFieldAsyncLayout()
let (_, _, inputFieldApply) = makeInputFieldLayout(item, ListViewItemLayoutParams(width: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2))
inputFieldApply()
}
func asyncLayout() -> (_ item: WalletAmountItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: WalletAmountItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item let currentItem = self.item
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode) let makeInputFieldLayout = self.inputFieldAsyncLayout()
return { item, params, neighbors in return { item, params, neighbors in
var updatedTheme: WalletTheme? var updatedTheme: WalletTheme?
@ -143,16 +191,12 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
insets.top = 0.0 insets.top = 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size let (attributedPlaceholderText, attributedAmountText, inputFieldApply) = makeInputFieldLayout(item, params)
let attributedPlaceholderText = NSAttributedString(string: "0", font: integralFont, textColor: item.theme.list.itemPlaceholderTextColor)
let attributedAmountText = amountAttributedString(item.amount, integralFont: integralFont, fractionalFont: fractionalFont, color: item.theme.list.itemPrimaryTextColor)
let (measureLayout, _) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: item.amount.isEmpty ? attributedPlaceholderText : attributedAmountText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
return (layout, { [weak self] in return (layout, { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.validLayout = (params.width, params.leftInset, params.rightInset)
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
@ -185,10 +229,7 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
strongSelf.textNode.textField.attributedText = attributedAmountText strongSelf.textNode.textField.attributedText = attributedAmountText
} }
let iconSize = CGSize(width: 50.0, height: 50.0) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset - 67.0, y: floor((layout.contentSize.height - 48.0) / 2.0) + verticalOffset), size: CGSize(width: max(1.0, params.width + iconSize.width - 5.0 + 100.0), height: 48.0))
let verticalOffset: CGFloat = -10.0
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - 48.0) / 2.0) + verticalOffset), size: CGSize(width: max(1.0, params.width - (leftInset + rightInset) + iconSize.width - 5.0), height: 48.0))
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
@ -207,16 +248,15 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
} }
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.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.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.size.width - bottomStripeInset, height: separatorHeight))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) { if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) {
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText
strongSelf.textNode.textField.accessibilityHint = attributedPlaceholderText.string strongSelf.textNode.textField.accessibilityHint = attributedPlaceholderText.string
} }
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - max(31.0, measureLayout.size.width)) / 2.0 - 28.0), y: floor((layout.contentSize.height - iconSize.height) / 2.0) - 3.0 + verticalOffset), size: iconSize) inputFieldApply()
strongSelf.iconNode.updateLayout(size: iconFrame.size)
strongSelf.iconNode.frame = iconFrame
} }
}) })
} }
@ -232,6 +272,7 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
@objc private func textFieldTextChanged(_ textField: UITextField) { @objc private func textFieldTextChanged(_ textField: UITextField) {
self.textUpdated(self.textNode.textField.text ?? "") self.textUpdated(self.textNode.textField.text ?? "")
self.updateInputField()
} }
@objc private func clearButtonPressed() { @objc private func clearButtonPressed() {

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import UIKit import UIKit
import SwiftSignalKit import SwiftSignalKit
import Display
import WalletCore import WalletCore
public enum WalletContextGetServerSaltError { public enum WalletContextGetServerSaltError {
@ -26,5 +27,5 @@ public protocol WalletContext {
func shareUrl(_ url: String) func shareUrl(_ url: String)
func openPlatformSettings() func openPlatformSettings()
func authorizeAccessToCamera(completion: @escaping () -> Void) func authorizeAccessToCamera(completion: @escaping () -> Void)
func pickImage(completion: @escaping (UIImage) -> Void) func pickImage(present: @escaping (ViewController) -> Void, completion: @escaping (UIImage) -> Void)
} }

View File

@ -184,7 +184,7 @@ protocol WalletCreateInvoiceScreen {
private final class WalletCreateInvoiceScreenImpl: ItemListController, WalletCreateInvoiceScreen { private final class WalletCreateInvoiceScreenImpl: ItemListController, WalletCreateInvoiceScreen {
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
return CGSize(width: layout.size.width, height: layout.size.height - 174.0) return CGSize(width: layout.size.width, height: min(640.0, layout.size.height))
} }
} }

View File

@ -94,7 +94,7 @@ public final class WalletInfoScreen: ViewController {
@objc private func settingsPressed() { @objc private func settingsPressed() {
if let walletInfo = self.walletInfo { if let walletInfo = self.walletInfo {
self.push(walletSettingsController(context: self.context, walletInfo: walletInfo)) self.push(walletSettingsController(context: self.context, walletInfo: walletInfo))
} }
} }
@ -135,24 +135,24 @@ public final class WalletInfoScreen: ViewController {
} }
} }
private final class WalletInfoBalanceNode: ASDisplayNode { final class WalletInfoBalanceNode: ASDisplayNode {
let dateTimeFormat: WalletPresentationDateTimeFormat let dateTimeFormat: WalletPresentationDateTimeFormat
let balanceIntegralTextNode: ImmediateTextNode let balanceIntegralTextNode: ImmediateTextNode
let balanceFractionalTextNode: ImmediateTextNode let balanceFractionalTextNode: ImmediateTextNode
let balanceIconNode: AnimatedStickerNode let balanceIconNode: AnimatedStickerNode
var balance: String = " " { var balance: (String, UIColor) = (" ", .white) {
didSet { didSet {
let integralString = NSMutableAttributedString() let integralString = NSMutableAttributedString()
let fractionalString = NSMutableAttributedString() let fractionalString = NSMutableAttributedString()
if let range = self.balance.range(of: self.dateTimeFormat.decimalSeparator) { if let range = self.balance.0.range(of: self.dateTimeFormat.decimalSeparator) {
let integralPart = String(self.balance[..<range.lowerBound]) let integralPart = String(self.balance.0[..<range.lowerBound])
let fractionalPart = String(self.balance[range.lowerBound...]) let fractionalPart = String(self.balance.0[range.lowerBound...])
integralString.append(NSAttributedString(string: integralPart, font: Font.medium(48.0), textColor: .white)) integralString.append(NSAttributedString(string: integralPart, font: Font.medium(48.0), textColor: self.balance.1))
fractionalString.append(NSAttributedString(string: fractionalPart, font: Font.medium(48.0), textColor: .white)) fractionalString.append(NSAttributedString(string: fractionalPart, font: Font.medium(48.0), textColor: self.balance.1))
} else { } else {
integralString.append(NSAttributedString(string: self.balance, font: Font.medium(48.0), textColor: .white)) integralString.append(NSAttributedString(string: self.balance.0, font: Font.medium(48.0), textColor: self.balance.1))
} }
self.balanceIntegralTextNode.attributedText = integralString self.balanceIntegralTextNode.attributedText = integralString
self.balanceFractionalTextNode.attributedText = fractionalString self.balanceFractionalTextNode.attributedText = fractionalString
@ -161,7 +161,7 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
var isLoading: Bool = true var isLoading: Bool = true
init(theme: WalletTheme, dateTimeFormat: WalletPresentationDateTimeFormat) { init(dateTimeFormat: WalletPresentationDateTimeFormat) {
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.balanceIntegralTextNode = ImmediateTextNode() self.balanceIntegralTextNode = ImmediateTextNode()
@ -244,7 +244,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
init(presentationData: WalletPresentationData, hasActions: Bool, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) { init(presentationData: WalletPresentationData, hasActions: Bool, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
self.hasActions = hasActions self.hasActions = hasActions
self.balanceNode = WalletInfoBalanceNode(theme: presentationData.theme, dateTimeFormat: presentationData.dateTimeFormat) self.balanceNode = WalletInfoBalanceNode(dateTimeFormat: presentationData.dateTimeFormat)
self.balanceSubtitleNode = ImmediateTextNode() self.balanceSubtitleNode = ImmediateTextNode()
self.balanceSubtitleNode.displaysAsynchronously = false self.balanceSubtitleNode.displaysAsynchronously = false
@ -856,7 +856,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private func updateCombinedState(combinedState: CombinedWalletState?, isUpdated: Bool) { private func updateCombinedState(combinedState: CombinedWalletState?, isUpdated: Bool) {
self.combinedState = combinedState self.combinedState = combinedState
if let combinedState = combinedState { if let combinedState = combinedState {
self.headerNode.balanceNode.balance = formatBalanceText(max(0, combinedState.walletState.balance), decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator) self.headerNode.balanceNode.balance = (formatBalanceText(max(0, combinedState.walletState.balance), decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator), .white)
self.headerNode.balance = max(0, combinedState.walletState.balance) self.headerNode.balance = max(0, combinedState.walletState.balance)
if self.isReady, let (layout, navigationHeight) = self.validLayout { if self.isReady, let (layout, navigationHeight) = self.validLayout {

View File

@ -113,6 +113,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let textNode: TextNode private let textNode: TextNode
private let descriptionNode: TextNode private let descriptionNode: TextNode
private let feesNode: TextNode
private let dateNode: TextNode private let dateNode: TextNode
private var statusNode: StatusClockNode? private var statusNode: StatusClockNode?
@ -157,6 +158,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
self.descriptionNode.contentMode = .left self.descriptionNode.contentMode = .left
self.descriptionNode.contentsScale = UIScreen.main.scale self.descriptionNode.contentsScale = UIScreen.main.scale
self.feesNode = TextNode()
self.feesNode.isUserInteractionEnabled = false
self.feesNode.contentMode = .left
self.feesNode.contentsScale = UIScreen.main.scale
self.dateNode = TextNode() self.dateNode = TextNode()
self.dateNode.isUserInteractionEnabled = false self.dateNode.isUserInteractionEnabled = false
self.dateNode.contentMode = .left self.dateNode.contentMode = .left
@ -175,6 +181,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
self.addSubnode(self.directionNode) self.addSubnode(self.directionNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.descriptionNode) self.addSubnode(self.descriptionNode)
self.addSubnode(self.feesNode)
self.addSubnode(self.dateNode) self.addSubnode(self.dateNode)
self.addSubnode(self.activateArea) self.addSubnode(self.activateArea)
@ -186,6 +193,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode) let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
let makeFeesLayout = TextNode.asyncLayout(self.feesNode)
let makeDateLayout = TextNode.asyncLayout(self.dateNode) let makeDateLayout = TextNode.asyncLayout(self.dateNode)
let currentItem = self.item let currentItem = self.item
@ -269,18 +277,15 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
} }
} }
var feeText: String = ""
let dateText: String let dateText: String
switch item.walletTransaction { switch item.walletTransaction {
case let .completed(transaction): case let .completed(transaction):
let fee = transaction.storageFee + transaction.otherFee let fee = transaction.storageFee + transaction.otherFee
if fee != 0 { if fee != 0 {
let feeText = item.strings.Wallet_Info_TransactionBlockchainFee(formatBalanceText(-fee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0 feeText = item.strings.Wallet_Info_TransactionBlockchainFee(formatBalanceText(-fee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
if !description.isEmpty {
description.append("\n")
}
description += "\(feeText)"
} }
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat) dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
case let .pending(transaction): case let .pending(transaction):
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat) dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
} }
@ -307,7 +312,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: description, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: description, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (feesLayout, feesApply) = makeFeesLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: feeText, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var contentSize: CGSize var contentSize: CGSize
var insets: UIEdgeInsets var insets: UIEdgeInsets
@ -328,6 +335,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
if !descriptionLayout.size.width.isZero { if !descriptionLayout.size.width.isZero {
contentSize.height += descriptionLayout.size.height + textSpacing contentSize.height += descriptionLayout.size.height + textSpacing
} }
if !feesLayout.size.width.isZero {
contentSize.height += feesLayout.size.height + textSpacing
}
insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
var topHighlightInset: CGFloat = 0.0 var topHighlightInset: CGFloat = 0.0
if dateHeaderAtBottom, let header = item.header { if dateHeaderAtBottom, let header = item.header {
@ -356,6 +366,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let _ = titleApply() let _ = titleApply()
let _ = textApply() let _ = textApply()
let _ = descriptionApply() let _ = descriptionApply()
let _ = feesApply()
let _ = dateApply() let _ = dateApply()
let _ = directionApply() let _ = directionApply()
@ -385,7 +396,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size) let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size)
strongSelf.textNode.frame = textFrame strongSelf.textNode.frame = textFrame
strongSelf.descriptionNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textFrame.maxY + textSpacing), size: descriptionLayout.size) let descriptionFrame = CGRect(origin: CGPoint(x: leftInset, y: textFrame.maxY + textSpacing), size: descriptionLayout.size)
strongSelf.descriptionNode.frame = descriptionFrame
strongSelf.feesNode.frame = CGRect(origin: CGPoint(x: leftInset, y: descriptionFrame.maxY + textSpacing), size: feesLayout.size)
let dateFrame = CGRect(origin: CGPoint(x: params.width - leftInset - dateLayout.size.width, y: topInset), size: dateLayout.size) let dateFrame = CGRect(origin: CGPoint(x: params.width - leftInset - dateLayout.size.width, y: topInset), size: dateLayout.size)
strongSelf.dateNode.frame = dateFrame strongSelf.dateNode.frame = dateFrame

View File

@ -1,248 +0,0 @@
/*import Foundation
import UIKit
import AppBundle
import AsyncDisplayKit
import Display
import AnimationUI
import SwiftSignalKit
import OverlayStatusController
import PasscodeInputFieldNode
public enum WalletPasscodeMode {
case setup
case authorizeTransfer(WalletInfo, String, Int64, Data)
}
public final class WalletPasscodeScreen: ViewController {
private let context: WalletContext
private var presentationData: WalletPresentationData
private let mode: WalletPasscodeMode
private let randomId: Int64
public init(context: WalletContext, mode: WalletPasscodeMode) {
self.context = context
self.mode = mode
self.randomId = arc4random64()
self.presentationData = context.presentationData
let defaultNavigationPresentationData = NavigationBarPresentationData(WalletTheme: self.presentationData.theme, WalletStrings: 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)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.backPressed)), animated: false)
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func backPressed() {
self.view.endEditing(true)
self.dismiss()
}
override public func loadDisplayNode() {
self.displayNode = WalletPasscodeScreenNode(account: self.context.account, WalletPresentationData: self.presentationData, mode: self.mode, proceed: { [weak self] in
guard let strongSelf = self else {
return
}
switch strongSelf.mode {
case .setup:
break
case let .authorizeTransfer(walletInfo, address, amount, comment):
if let navigationController = strongSelf.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
controllers = controllers.filter { controller in
if controller is WalletSplashScreen {
return false
}
if controller is WalletSendScreen {
return false
}
if controller is WalletPasscodeScreen {
return false
}
return true
}
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sending(walletInfo, address, amount, comment, strongSelf.randomId, Data()), walletCreatedPreloadState: nil))
strongSelf.view.endEditing(true)
navigationController.setViewControllers(controllers, animated: true)
}
}
}, requestBiometrics: {
})
self.displayNodeDidLoad()
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
(self.displayNode as! WalletPasscodeScreenNode).activateInput()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.disablesInteractiveTransitionGestureRecognizer = true
(self.displayNode as! WalletPasscodeScreenNode).activateInput()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! WalletPasscodeScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class WalletPasscodeScreenNode: ViewControllerTracingNode {
private var presentationData: WalletPresentationData
private let mode: WalletPasscodeMode
private let requestBiometrics: () -> Void
private let iconNode: ASImageNode
private let animationNode: AnimatedStickerNode
private let titleNode: ImmediateTextNode
private let biometricsActionTitleNode: ImmediateTextNode
private let biometricsActionButtonNode: HighlightTrackingButtonNode
private let inputFieldNode: PasscodeInputFieldNode
private let hapticFeedback = HapticFeedback()
init(account: Account, presentationData: WalletPresentationData, mode: WalletPasscodeMode, proceed: @escaping () -> Void, requestBiometrics: @escaping () -> Void) {
self.presentationData = WalletPresentationData
self.mode = mode
self.requestBiometrics = requestBiometrics
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.animationNode = AnimatedStickerNode()
let title: String
let biometricsActionText: String
title = "Enter Passcode"
biometricsActionText = "Use Face ID"
self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/PasscodeIcon")
self.titleNode = ImmediateTextNode()
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.biometricsActionTitleNode = ImmediateTextNode()
self.biometricsActionTitleNode.displaysAsynchronously = false
self.biometricsActionTitleNode.attributedText = NSAttributedString(string: biometricsActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor, paragraphAlignment: .center)
self.biometricsActionTitleNode.textAlignment = .center
self.biometricsActionButtonNode = HighlightTrackingButtonNode()
self.inputFieldNode = PasscodeInputFieldNode(color: self.presentationData.theme.list.itemPrimaryTextColor, accentColor: self.presentationData.theme.list.itemAccentColor, fieldType: .digits4, keyboardAppearance: self.presentationData.theme.rootController.keyboardColor.keyboardAppearance)
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.iconNode)
self.addSubnode(self.animationNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.biometricsActionTitleNode)
self.addSubnode(self.biometricsActionButtonNode)
self.addSubnode(self.inputFieldNode)
self.biometricsActionButtonNode.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
return
}
if highlighted {
strongSelf.biometricsActionTitleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.biometricsActionTitleNode.alpha = 0.4
} else {
strongSelf.biometricsActionTitleNode.alpha = 1.0
strongSelf.biometricsActionTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
self.biometricsActionButtonNode.addTarget(self, action: #selector(self.biometricsActionPressed), forControlEvents: .touchUpInside)
self.inputFieldNode.complete = { [weak self] passcode in
if passcode == "1111" {
proceed()
} else {
self?.animateError()
}
}
}
@objc private func biometricsActionPressed() {
self.requestBiometrics()
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 32.0
let buttonSideInset: CGFloat = 48.0
let iconSpacing: CGFloat = 21.0
let titleSpacing: CGFloat = 60.0
let biometricsSpacing: CGFloat = 44.0
let buttonHeight: CGFloat = 50.0
let inputFieldHeight: CGFloat = 34.0
let iconSize = self.iconNode.image?.size ?? CGSize(width: 140.0, height: 140.0)
var iconOffset = CGPoint()
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let biometricsActionSize = self.biometricsActionTitleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let insets = layout.insets(options: [.input])
let contentHeight = iconSize.height + iconSpacing + titleSize.height + titleSpacing + inputFieldHeight
let contentVerticalOrigin = floor((layout.size.height - contentHeight - iconSize.height / 2.0 - insets.bottom) / 2.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.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((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: layout, topOffset: titleFrame.maxY + titleSpacing, transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let minimalBottomInset: CGFloat = 60.0
let bottomInset = layout.intrinsicInsets.bottom + max(minimalBottomInset, biometricsActionSize.height + biometricsSpacing * 2.0)
if !biometricsActionSize.width.isZero {
let biometricsActionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - biometricsActionSize.width) / 2.0), y: inputFieldFrame.maxY + floor((layout.size.height - insets.bottom - inputFieldFrame.maxY - biometricsActionSize.height) / 2.0)), size: biometricsActionSize)
transition.updateFrameAdditive(node: self.biometricsActionTitleNode, frame: biometricsActionFrame)
transition.updateFrame(node: self.biometricsActionButtonNode, frame: biometricsActionFrame.insetBy(dx: -10.0, dy: -10.0))
}
}
func activateInput() {
self.inputFieldNode.activateInput()
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.titleNode.attributedText?.string)
}
func animateError() {
self.inputFieldNode.reset()
self.inputFieldNode.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
self.hapticFeedback.error()
}
}
*/

View File

@ -49,6 +49,19 @@ public final class WalletInfoTheme {
} }
} }
public final class WalletTransactionTheme {
public let descriptionBackgroundColor: UIColor
public let descriptionTextColor: UIColor
public init(
descriptionBackgroundColor: UIColor,
descriptionTextColor: UIColor
) {
self.descriptionBackgroundColor = descriptionBackgroundColor
self.descriptionTextColor = descriptionTextColor
}
}
public final class WalletSetupTheme { public final class WalletSetupTheme {
public let buttonFillColor: UIColor public let buttonFillColor: UIColor
public let buttonForegroundColor: UIColor public let buttonForegroundColor: UIColor
@ -131,6 +144,7 @@ public final class WalletListTheme {
public final class WalletTheme: Equatable { public final class WalletTheme: Equatable {
public let info: WalletInfoTheme public let info: WalletInfoTheme
public let transaction: WalletTransactionTheme
public let setup: WalletSetupTheme public let setup: WalletSetupTheme
public let list: WalletListTheme public let list: WalletListTheme
public let statusBarStyle: StatusBarStyle public let statusBarStyle: StatusBarStyle
@ -141,8 +155,9 @@ public final class WalletTheme: Equatable {
private let resourceCache = WalletThemeResourceCache() private let resourceCache = WalletThemeResourceCache()
public init(info: WalletInfoTheme, setup: WalletSetupTheme, list: WalletListTheme, statusBarStyle: StatusBarStyle, navigationBar: NavigationBarTheme, keyboardAppearance: UIKeyboardAppearance, alert: AlertControllerTheme, actionSheet: ActionSheetControllerTheme) { public init(info: WalletInfoTheme, transaction: WalletTransactionTheme, setup: WalletSetupTheme, list: WalletListTheme, statusBarStyle: StatusBarStyle, navigationBar: NavigationBarTheme, keyboardAppearance: UIKeyboardAppearance, alert: AlertControllerTheme, actionSheet: ActionSheetControllerTheme) {
self.info = info self.info = info
self.transaction = transaction
self.setup = setup self.setup = setup
self.list = list self.list = list
self.statusBarStyle = statusBarStyle self.statusBarStyle = statusBarStyle

View File

@ -1,167 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import QrCode
class WalletQrCodeItem: ListViewItem, ItemListItem {
let theme: WalletTheme
let address: String
let sectionId: ItemListSectionId
let style: ItemListStyle
let action: (() -> Void)?
let longTapAction: (() -> Void)?
public let isAlwaysPlain: Bool = true
init(theme: WalletTheme, 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) {
async {
let node = WalletQrCodeItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
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 {
if let nodeValue = node() as? WalletQrCodeItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
class WalletQrCodeItemNode: ListViewItemNode {
private let imageNode: TransformImageNode
private var item: WalletQrCodeItem?
var tag: Any? {
return self.item?.tag
}
init() {
self.imageNode = TransformImageNode()
super.init(layerBacked: false, dynamicBounce: false)
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()
let currentItem = self.item
return { item, params, neighbors in
var updatedTheme: WalletTheme?
var updatedAddress: String?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
}
if currentItem?.address != item.address || updatedTheme != nil {
updatedAddress = item.address
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
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))
switch item.style {
case .plain:
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
insets = itemListNeighborsGroupedInsets(neighbors)
}
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
if let updatedAddress = updatedAddress {
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"))), attemptSynchronously: true)
}
let _ = imageApply()
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: (params.width - imageSize.width) / 2.0, y: 0.0), size: imageSize)
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -118,8 +118,10 @@ public final class WalletQrScanScreen: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.context.pickImage(completion: { image in strongSelf.context.pickImage(present: { c in
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])! strongSelf.push(c)
}, completion: { image in
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
if let ciImage = CIImage(image: image) { if let ciImage = CIImage(image: image) {
var options: [String: Any] var options: [String: Any]
if ciImage.properties.keys.contains((kCGImagePropertyOrientation as String)) { if ciImage.properties.keys.contains((kCGImagePropertyOrientation as String)) {

View File

@ -1,143 +0,0 @@
import Foundation
import UIKit
import SwiftSignalKit
import AppBundle
import AsyncDisplayKit
import Display
import QrCode
import AnimatedStickerNode
public final class WalletQrViewScreen: ViewController {
private let context: WalletContext
private let invoice: String
private var presentationData: WalletPresentationData
private var previousScreenBrightness: CGFloat?
private var displayLinkAnimator: DisplayLinkAnimator?
private let idleTimerExtensionDisposable: Disposable
public init(context: WalletContext, invoice: String) {
self.context = context
self.invoice = invoice
self.presentationData = context.presentationData
let defaultTheme = self.presentationData.theme.navigationBar
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
self.idleTimerExtensionDisposable = context.idleTimerExtension()
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Wallet_Navigation_Back, close: self.presentationData.strings.Wallet_Navigation_Close)))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
self.title = self.presentationData.strings.Wallet_Qr_Title
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: 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: WalletPresentationData
private let invoice: String
private let imageNode: TransformImageNode
private let iconNode: AnimatedStickerNode
init(context: WalletContext, presentationData: WalletPresentationData, message: String) {
self.presentationData = presentationData
self.invoice = message
self.imageNode = TransformImageNode()
self.imageNode.clipsToBounds = true
self.imageNode.cornerRadius = 14.0
self.iconNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 240, height: 240, mode: .direct)
self.iconNode.visibility = true
}
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.imageNode)
self.addSubnode(self.iconNode)
self.imageNode.setSignal(qrCode(string: self.invoice, color: .black, backgroundColor: .white, icon: .cutout), 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()
let imageFrame = 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)
transition.updateFrame(node: self.imageNode, frame: imageFrame)
let iconSide = floor(imageSide * 0.24)
let iconSize = CGSize(width: iconSide, height: iconSide)
self.iconNode.updateLayout(size: iconSize)
transition.updateBounds(node: self.iconNode, bounds: CGRect(origin: CGPoint(), size: iconSize))
transition.updatePosition(node: self.iconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0))
}
}

View File

@ -42,8 +42,6 @@ final class WalletReceiveScreen: ViewController {
private let mode: WalletReceiveScreenMode private let mode: WalletReceiveScreenMode
private var presentationData: WalletPresentationData private var presentationData: WalletPresentationData
private var previousScreenBrightness: CGFloat?
private var displayLinkAnimator: DisplayLinkAnimator?
private let idleTimerExtensionDisposable: Disposable private let idleTimerExtensionDisposable: Disposable
public init(context: WalletContext, mode: WalletReceiveScreenMode) { public init(context: WalletContext, mode: WalletReceiveScreenMode) {
@ -89,38 +87,26 @@ final class WalletReceiveScreen: ViewController {
} }
self?.push(walletCreateInvoiceScreen(context: strongSelf.context, address: strongSelf.mode.address)) self?.push(walletCreateInvoiceScreen(context: strongSelf.context, address: strongSelf.mode.address))
} }
(self.displayNode as! WalletReceiveScreenNode).displayCopyContextMenu = { [weak self] node, frame, text in
guard let strongSelf = self else {
return
}
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Wallet_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Wallet_ContextMenuCopy), action: {
UIPasteboard.general.string = text
})])
strongSelf.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (node, frame.insetBy(dx: 0.0, dy: -2.0), strongSelf.displayNode, strongSelf.displayNode.view.bounds)
} else {
return nil
}
}))
}
self.displayNodeDidLoad() 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 func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
return CGSize(width: layout.size.width, height: layout.size.height - 174.0) return CGSize(width: layout.size.width, height: min(640.0, layout.size.height))
} }
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -172,6 +158,7 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
private let secondaryButtonNode: HighlightableButtonNode private let secondaryButtonNode: HighlightableButtonNode
var openCreateInvoice: (() -> Void)? var openCreateInvoice: (() -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, CGRect, String) -> Void)?
init(context: WalletContext, presentationData: WalletPresentationData, mode: WalletReceiveScreenMode) { init(context: WalletContext, presentationData: WalletPresentationData, mode: WalletReceiveScreenMode) {
self.context = context self.context = context
@ -234,21 +221,16 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
} }
let textFont = Font.regular(16.0) let textFont = Font.regular(16.0)
let addressFont = Font.monospace(17.0)
let textColor = self.presentationData.theme.list.itemPrimaryTextColor let textColor = self.presentationData.theme.list.itemPrimaryTextColor
let secondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor let secondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
let url = urlForMode(self.mode) let url = urlForMode(self.mode)
switch self.mode { switch self.mode {
case let .receive(address): case let .receive(address):
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor) self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor)
self.urlTextNode.attributedText = NSAttributedString(string: formatAddress(url + " "), font: addressFont, textColor: textColor, paragraphAlignment: .justified)
self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareAddress self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareAddress
self.secondaryButtonNode.setTitle(self.presentationData.strings.Wallet_Receive_CreateInvoice, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal) self.secondaryButtonNode.setTitle(self.presentationData.strings.Wallet_Receive_CreateInvoice, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
case let .invoice(address, amount, comment): case let .invoice(address, amount, comment):
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor, paragraphAlignment: .center)
let sliced = String(url.enumerated().map { $0 > 0 && $0 % 32 == 0 ? ["\n", $1] : [$1]}.joined())
self.urlTextNode.attributedText = NSAttributedString(string: sliced, font: addressFont, textColor: textColor, paragraphAlignment: .justified)
self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareInvoiceUrl self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareInvoiceUrl
} }
@ -258,6 +240,32 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
self.secondaryButtonNode.addTarget(self, action: #selector(createInvoicePressed), forControlEvents: .touchUpInside) self.secondaryButtonNode.addTarget(self, action: #selector(createInvoicePressed), forControlEvents: .touchUpInside)
} }
override func didLoad() {
super.didLoad()
let addressGestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapAddressGesture(_:)))
addressGestureRecognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
self.urlTextNode.view.addGestureRecognizer(addressGestureRecognizer)
}
@objc func tapLongTapOrDoubleTapAddressGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .longTap:
self.displayCopyContextMenu?(self, self.urlTextNode.frame, urlForMode(self.mode))
default:
break
}
}
default:
break
}
}
@objc private func qrPressed() { @objc private func qrPressed() {
shareInvoiceQrCode(context: self.context, invoice: urlForMode(self.mode)) shareInvoiceQrCode(context: self.context, invoice: urlForMode(self.mode))
} }
@ -293,8 +301,30 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
transition.updateBounds(node: self.qrIconNode, bounds: CGRect(origin: CGPoint(), size: iconSize)) transition.updateBounds(node: self.qrIconNode, bounds: CGRect(origin: CGPoint(), size: iconSize))
transition.updatePosition(node: self.qrIconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0)) transition.updatePosition(node: self.qrIconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0))
let urlTextSize = self.urlTextNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude)) if self.urlTextNode.attributedText?.string.isEmpty ?? true {
transition.updateFrame(node: self.urlTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - urlTextSize.width) / 2.0), y: imageFrame.maxY + 25.0), size: urlTextSize)) var url = urlForMode(self.mode)
if case .receive = self.mode {
url = url + "?"
}
let addressFont: UIFont
let countRatio: CGFloat
if layout.size.width == 320.0 {
addressFont = Font.monospace(16.0)
countRatio = 0.0999
} else {
addressFont = Font.monospace(17.0)
countRatio = 0.0853
}
let count = min(url.count / 2, Int(ceil(min(layout.size.width, layout.size.height) * countRatio)))
let sliced = String(url.enumerated().map { $0 > 0 && $0 % count == 0 ? ["\n", $1] : [$1]}.joined())
self.urlTextNode.attributedText = NSAttributedString(string: sliced, font: addressFont, textColor: self.presentationData.theme.list.itemPrimaryTextColor, paragraphAlignment: .justified)
}
let addressInset: CGFloat = 12.0
let urlTextSize = self.urlTextNode.updateLayout(CGSize(width: layout.size.width - addressInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.urlTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - urlTextSize.width) / 2.0), y: imageFrame.maxY + 23.0), size: urlTextSize))
let buttonSideInset: CGFloat = 16.0 let buttonSideInset: CGFloat = 16.0
let bottomInset = insets.bottom + 10.0 let bottomInset = insets.bottom + 10.0

View File

@ -336,9 +336,9 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo
popImpl?() popImpl?()
if let updatedState = updatedState { if let updatedState = updatedState {
if updatedState.amount.isEmpty { if updatedState.amount.isEmpty {
selectNextInputItemImpl?(WalletSendScreenEntryTag.address)
} else if updatedState.comment.isEmpty {
selectNextInputItemImpl?(WalletSendScreenEntryTag.amount) selectNextInputItemImpl?(WalletSendScreenEntryTag.amount)
} else if updatedState.comment.isEmpty {
selectNextInputItemImpl?(WalletSendScreenEntryTag.comment)
} }
} }
})) }))

View File

@ -202,7 +202,12 @@ final class WalletTransactionInfoScreen: ViewController {
} }
var string = NSMutableAttributedString(string: "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and for processing your transactions. More info", font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) var string = NSMutableAttributedString(string: "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and for processing your transactions. More info", font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center)
string.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor(rgb: 0x6bb2ff), range: NSMakeRange(string.string.count - 10, 10)) string.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor(rgb: 0x6bb2ff), range: NSMakeRange(string.string.count - 10, 10))
let controller = TooltipController(content: .attributedText(string), timeout: 3.0, dismissByTapOutside: true, dismissByTapOutsideSource: false, dismissImmediatelyOnLayoutUpdate: false) let controller = TooltipController(content: .attributedText(string), timeout: 3.0, dismissByTapOutside: true, dismissByTapOutsideSource: false, dismissImmediatelyOnLayoutUpdate: false, arrowOnBottom: false)
controller.dismissed = { [weak self] tappedInside in
if let strongSelf = self, tappedInside {
strongSelf.context.openUrl(strongSelf.presentationData.strings.Wallet_TransactionInfo_FeeInfoURL)
}
}
strongSelf.present(controller, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { strongSelf.present(controller, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: {
if let strongSelf = self { if let strongSelf = self {
return (node.view, rect.insetBy(dx: 0.0, dy: -4.0)) return (node.view, rect.insetBy(dx: 0.0, dy: -4.0))
@ -210,20 +215,55 @@ final class WalletTransactionInfoScreen: ViewController {
return nil return nil
})) }))
} }
(self.displayNode as! WalletTransactionInfoScreenNode).displayCopyContextMenu = { [weak self] node, frame, text in
guard let strongSelf = self else {
return
}
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Wallet_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Wallet_ContextMenuCopy), action: {
UIPasteboard.general.string = text
})])
strongSelf.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (node, frame.insetBy(dx: 0.0, dy: -2.0), strongSelf.displayNode, strongSelf.displayNode.view.bounds)
} else {
return nil
}
}))
}
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }
private let measureTextNode = TextNode() private let measureTextNode = TextNode()
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
let insets = layout.insets(options: [])
let minHeight: CGFloat = 424.0
let maxHeight: CGFloat = min(596.0, layout.size.height)
let text = NSAttributedString(string: extractDescription(self.walletTransaction), font: Font.regular(17.0), textColor: .black) let text = NSAttributedString(string: extractDescription(self.walletTransaction), font: Font.regular(17.0), textColor: .black)
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode) let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
let (textLayout, _) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, _) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var textHeight = textLayout.size.height
if textHeight > 0.0 { var resultHeight = minHeight
textHeight += 24.0 if textLayout.size.height > 0.0 {
let textHeight = textLayout.size.height + 24.0
let minOverscroll: CGFloat = 42.0
let maxOverscroll: CGFloat = 148.0
let contentHeight = minHeight + textHeight
let difference = contentHeight - maxHeight
if difference < 0.0 {
resultHeight = contentHeight
} else if difference > maxOverscroll {
resultHeight = maxHeight
} else if difference > minOverscroll {
resultHeight = maxHeight - (maxOverscroll - difference)
} else {
resultHeight = maxHeight - (minOverscroll - difference)
}
} }
let insets = layout.insets(options: [])
return CGSize(width: layout.size.width, height: 428.0 + insets.bottom + textHeight) return CGSize(width: layout.size.width, height: resultHeight + insets.bottom)
} }
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -237,33 +277,36 @@ final class WalletTransactionInfoScreen: ViewController {
} }
} }
private let integralFont = Font.medium(48.0) private let amountFont = Font.medium(48.0)
private let fractionalFont = Font.medium(24.0) private let fractionalFont = Font.medium(24.0)
private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode { private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: WalletContext private let context: WalletContext
private var presentationData: WalletPresentationData private var presentationData: WalletPresentationData
private let walletTransaction: WalletInfoTransaction private let walletTransaction: WalletInfoTransaction
private let incoming: Bool private let incoming: Bool
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let timeNode: ImmediateTextNode private let timeNode: ImmediateTextNode
private let navigationBackgroundNode: ASDisplayNode
private let amountNode: ImmediateTextNode private let navigationSeparatorNode: ASDisplayNode
private let iconNode: AnimatedStickerNode private let scrollNode: ASScrollNode
private let amountNode: WalletInfoBalanceNode
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
private let feesNode: ImmediateTextNode private let feesNode: ImmediateTextNode
private let feesInfoIconNode: ASImageNode
private let feesButtonNode: ASButtonNode private let feesButtonNode: ASButtonNode
private let commentBackgroundNode: ASImageNode private let commentBackgroundNode: ASImageNode
private let commentTextNode: ImmediateTextNode private let commentTextNode: ImmediateTextNode
private let commentSeparatorNode: ASDisplayNode
private let addressTextNode: ImmediateTextNode private let addressTextNode: ImmediateTextNode
private let buttonNode: SolidRoundedButtonNode private let buttonNode: SolidRoundedButtonNode
private var validLayout: (ContainerViewLayout, CGFloat)?
var send: ((String) -> Void)? var send: ((String) -> Void)?
var displayFeesTooltip: ((ASDisplayNode, CGRect) -> Void)? var displayFeesTooltip: ((ASDisplayNode, CGRect) -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, CGRect, String) -> Void)?
init(context: WalletContext, presentationData: WalletPresentationData, walletTransaction: WalletInfoTransaction) { init(context: WalletContext, presentationData: WalletPresentationData, walletTransaction: WalletInfoTransaction) {
self.context = context self.context = context
@ -278,29 +321,39 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
self.timeNode.textAlignment = .center self.timeNode.textAlignment = .center
self.timeNode.maximumNumberOfLines = 1 self.timeNode.maximumNumberOfLines = 1
self.amountNode = ImmediateTextNode() self.navigationBackgroundNode = ASDisplayNode()
self.amountNode.textAlignment = .center self.navigationBackgroundNode.backgroundColor = self.presentationData.theme.navigationBar.backgroundColor
self.amountNode.maximumNumberOfLines = 1 self.navigationBackgroundNode.alpha = 0.0
self.navigationSeparatorNode = ASDisplayNode()
self.navigationSeparatorNode.backgroundColor = self.presentationData.theme.navigationBar.separatorColor
self.iconNode = AnimatedStickerNode() self.scrollNode = ASScrollNode()
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct) self.amountNode = WalletInfoBalanceNode(dateTimeFormat: presentationData.dateTimeFormat)
self.iconNode.visibility = true
}
self.feesNode = ImmediateTextNode() self.feesNode = ImmediateTextNode()
self.feesNode.textAlignment = .center self.feesNode.textAlignment = .center
self.feesNode.maximumNumberOfLines = 2 self.feesNode.maximumNumberOfLines = 2
self.feesNode.lineSpacing = 0.35 self.feesNode.lineSpacing = 0.35
self.feesInfoIconNode = ASImageNode()
self.feesInfoIconNode.displaysAsynchronously = false
self.feesInfoIconNode.displayWithoutProcessing = true
self.feesInfoIconNode.image = UIImage(bundleImageName: "Wallet/InfoIcon")
self.feesButtonNode = ASButtonNode() self.feesButtonNode = ASButtonNode()
self.commentBackgroundNode = ASImageNode() self.commentBackgroundNode = ASImageNode()
self.commentBackgroundNode.contentMode = .scaleToFill self.commentBackgroundNode.contentMode = .scaleToFill
self.commentBackgroundNode.isUserInteractionEnabled = true
self.commentTextNode = ImmediateTextNode() self.commentTextNode = ImmediateTextNode()
self.commentTextNode.textAlignment = .natural self.commentTextNode.textAlignment = .natural
self.commentTextNode.maximumNumberOfLines = 0 self.commentTextNode.maximumNumberOfLines = 0
self.commentTextNode.isUserInteractionEnabled = false
self.commentSeparatorNode = ASDisplayNode()
self.commentSeparatorNode.backgroundColor = self.presentationData.theme.list.itemPlainSeparatorColor
self.addressTextNode = ImmediateTextNode() self.addressTextNode = ImmediateTextNode()
self.addressTextNode.maximumNumberOfLines = 4 self.addressTextNode.maximumNumberOfLines = 4
@ -327,14 +380,18 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.scrollNode)
self.addSubnode(self.feesNode)
self.addSubnode(self.feesInfoIconNode)
self.addSubnode(self.feesButtonNode)
self.scrollNode.addSubnode(self.commentBackgroundNode)
self.scrollNode.addSubnode(self.commentTextNode)
self.addSubnode(self.navigationBackgroundNode)
self.navigationBackgroundNode.addSubnode(self.navigationSeparatorNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.timeNode) self.addSubnode(self.timeNode)
self.addSubnode(self.amountNode) self.addSubnode(self.amountNode)
self.addSubnode(self.iconNode) self.addSubnode(self.commentSeparatorNode)
self.addSubnode(self.feesNode)
self.addSubnode(self.feesButtonNode)
self.addSubnode(self.commentBackgroundNode)
self.addSubnode(self.commentTextNode)
self.addSubnode(self.addressTextNode) self.addSubnode(self.addressTextNode)
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
@ -346,9 +403,8 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_TransactionInfo_Title, font: titleFont, textColor: textColor) self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_TransactionInfo_Title, font: titleFont, textColor: textColor)
self.timeNode.attributedText = NSAttributedString(string: stringForFullDate(timestamp: Int32(clamping: timestamp), strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat), font: subtitleFont, textColor: seccondaryTextColor) self.timeNode.attributedText = NSAttributedString(string: stringForFullDate(timestamp: Int32(clamping: timestamp), strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat), font: subtitleFont, textColor: seccondaryTextColor)
let amountString: String let amountString: String
let amountColor: UIColor let amountColor: UIColor
if transferredValue <= 0 { if transferredValue <= 0 {
@ -358,8 +414,8 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
amountString = "\(formatBalanceText(transferredValue, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator))" amountString = "\(formatBalanceText(transferredValue, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator))"
amountColor = self.presentationData.theme.info.incomingFundsTitleColor amountColor = self.presentationData.theme.info.incomingFundsTitleColor
} }
self.amountNode.attributedText = amountAttributedString(amountString, integralFont: integralFont, fractionalFont: fractionalFont, color: amountColor) self.amountNode.balance = (amountString, amountColor)
var feesString: String = "" var feesString: String = ""
if case let .completed(transaction) = walletTransaction { if case let .completed(transaction) = walletTransaction {
if transaction.storageFee != 0 { if transaction.storageFee != 0 {
@ -372,16 +428,18 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
feesString.append(formatBalanceText(transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " transaction fee") feesString.append(formatBalanceText(transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " transaction fee")
} }
if !feesString.isEmpty { self.feesInfoIconNode.isHidden = feesString.isEmpty
feesString.append("(?)")
}
} }
self.feesNode.attributedText = NSAttributedString(string: feesString, font: subtitleFont, textColor: seccondaryTextColor) self.feesNode.attributedText = NSAttributedString(string: feesString, font: subtitleFont, textColor: seccondaryTextColor)
self.feesButtonNode.addTarget(self, action: #selector(feesPressed), forControlEvents: .touchUpInside) self.feesButtonNode.addTarget(self, action: #selector(feesPressed), forControlEvents: .touchUpInside)
self.commentBackgroundNode.image = messageBubbleImage(incoming: transferredValue > 0, fillColor: UIColor(rgb: 0xf1f1f5), strokeColor: UIColor(rgb: 0xf1f1f5)) var commentBackgroundColor = presentationData.theme.transaction.descriptionBackgroundColor
self.commentTextNode.attributedText = NSAttributedString(string: extractDescription(walletTransaction), font: Font.regular(17.0), textColor: .black) if commentBackgroundColor.distance(to: presentationData.theme.list.plainBackgroundColor) < 100 {
commentBackgroundColor = UIColor(rgb: 0xf1f1f4)
}
self.commentBackgroundNode.image = messageBubbleImage(incoming: transferredValue > 0, fillColor: commentBackgroundColor, strokeColor: presentationData.theme.transaction.descriptionBackgroundColor)
self.commentTextNode.attributedText = NSAttributedString(string: extractDescription(walletTransaction), font: Font.regular(17.0), textColor: presentationData.theme.transaction.descriptionTextColor)
let address = extractAddress(walletTransaction) let address = extractAddress(walletTransaction)
var singleAddress: String? var singleAddress: String?
@ -391,7 +449,7 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
if let address = singleAddress { if let address = singleAddress {
self.addressTextNode.attributedText = NSAttributedString(string: formatAddress(address), font: addressFont, textColor: textColor, paragraphAlignment: .justified) self.addressTextNode.attributedText = NSAttributedString(string: formatAddress(address), font: addressFont, textColor: textColor, paragraphAlignment: .justified)
self.buttonNode.title = "Send Grams to This Address" self.buttonNode.title = presentationData.strings.Wallet_TransactionInfo_SendGrams
self.buttonNode.pressed = { [weak self] in self.buttonNode.pressed = { [weak self] in
self?.send?(address) self?.send?(address)
@ -399,41 +457,191 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
} }
} }
override func didLoad() {
super.didLoad()
self.scrollNode.view.delegate = self
self.scrollNode.view.alwaysBounceVertical = true
self.scrollNode.view.showsVerticalScrollIndicator = false
let commentGestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapCommentGesture(_:)))
commentGestureRecognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
self.commentBackgroundNode.view.addGestureRecognizer(commentGestureRecognizer)
let addressGestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapAddressGesture(_:)))
addressGestureRecognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
self.addressTextNode.view.addGestureRecognizer(addressGestureRecognizer)
}
@objc func tapLongTapOrDoubleTapCommentGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .longTap:
let description = extractDescription(self.walletTransaction)
if !description.isEmpty {
self.displayCopyContextMenu?(self, self.commentBackgroundNode.convert(self.commentBackgroundNode.bounds, to: self), description)
}
default:
break
}
}
default:
break
}
}
@objc func tapLongTapOrDoubleTapAddressGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .longTap:
let address = extractAddress(self.walletTransaction)
var singleAddress: String?
if case let .list(list) = address, list.count == 1 {
singleAddress = list.first
}
if let address = singleAddress {
self.displayCopyContextMenu?(self, self.addressTextNode.convert(self.addressTextNode.bounds, to: self), address)
}
default:
break
}
}
default:
break
}
}
@objc private func feesPressed() { @objc private func feesPressed() {
self.displayFeesTooltip?(self.feesNode, self.feesNode.bounds) self.displayFeesTooltip?(self.feesNode, self.feesNode.bounds)
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateTitle(transition: .immediate)
}
private func updateTitle(transition: ContainedViewLayoutTransition) {
guard let (layout, navigationHeight) = self.validLayout else {
return
}
let width = layout.size.width
let sideInset: CGFloat = 16.0
let minOffset = navigationHeight
let maxOffset: CGFloat = 200.0
let nominalFeesHeight: CGFloat = 42.0
let minHeaderOffset = minOffset
let maxHeaderOffset = (minOffset + maxOffset) / 2.0
let maxHeaderPositionOffset = maxOffset - nominalFeesHeight
let offset: CGFloat = max(0.0, maxOffset - self.scrollNode.view.contentOffset.y)
let effectiveOffset = max(offset, navigationHeight)
let minFeesOffset = maxOffset - nominalFeesHeight
let maxFeesOffset = maxOffset
let feesTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minFeesOffset) / (maxFeesOffset - minFeesOffset)))
let feesAlpha: CGFloat = feesTransition
transition.updateAlpha(node: self.feesNode, alpha: feesAlpha)
let headerScaleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minHeaderOffset) / (maxHeaderOffset - minHeaderOffset)))
let balanceHeight = self.amountNode.update(width: width, scaleTransition: headerScaleTransition, transition: transition)
let balanceSize = CGSize(width: width, height: balanceHeight)
let maxHeaderScale: CGFloat = min(1.0, (width - 40.0) / balanceSize.width)
let minHeaderScale: CGFloat = min(0.435, (width - 80.0 * 2.0) / balanceSize.width)
let minHeaderHeight: CGFloat = balanceSize.height
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let minHeaderY = floor((navigationHeight - minHeaderHeight) / 2.0)
var insets = layout.insets(options: []) let maxHeaderY: CGFloat = 90.0
insets.top += navigationHeight let headerPositionTransition: CGFloat = min(1.0, max(0.0, (effectiveOffset - minHeaderOffset) / (maxHeaderPositionOffset - minHeaderOffset)))
let inset: CGFloat = 22.0 let headerY = headerPositionTransition * maxHeaderY + (1.0 - headerPositionTransition) * minHeaderY
let headerScale = headerScaleTransition * maxHeaderScale + (1.0 - headerScaleTransition) * minHeaderScale
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude)) let balanceFrame = CGRect(origin: CGPoint(x: 0.0, y: headerY), size: balanceSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize) transition.updateFrame(node: self.amountNode, frame: balanceFrame)
transition.updateFrame(node: self.titleNode, frame: titleFrame) transition.updateSublayerTransformScale(node: self.amountNode, scale: headerScale)
let subtitleSize = self.timeNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude)) let feesSize = self.feesNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let subtitleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) let feesFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - feesSize.width) / 2.0), y: headerY + 64.0), size: feesSize)
transition.updateFrame(node: self.timeNode, frame: subtitleFrame)
let amountSize = self.amountNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let amountFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - amountSize.width) / 2.0) + 18.0, y: 90.0), size: amountSize)
transition.updateFrame(node: self.amountNode, frame: amountFrame)
let iconSize = CGSize(width: 50.0, height: 50.0)
let iconFrame = CGRect(origin: CGPoint(x: amountFrame.minX - iconSize.width, y: amountFrame.minY), size: iconSize)
self.iconNode.updateLayout(size: iconFrame.size)
self.iconNode.frame = iconFrame
let feesSize = self.feesNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let feesFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - feesSize.width) / 2.0), y: amountFrame.maxY + 8.0), size: feesSize)
transition.updateFrame(node: self.feesNode, frame: feesFrame) transition.updateFrame(node: self.feesNode, frame: feesFrame)
transition.updateFrame(node: self.feesButtonNode, frame: feesFrame) transition.updateFrame(node: self.feesButtonNode, frame: feesFrame)
self.feesButtonNode.isUserInteractionEnabled = feesAlpha > 1.0 - CGFloat.ulpOfOne
let minTitleOffset = minOffset
let maxTitleOffset = (minOffset + maxOffset) / 2.0
let titleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minTitleOffset) / (maxTitleOffset - minTitleOffset)))
let titleAlpha: CGFloat = titleTransition * titleTransition
transition.updateAlpha(node: self.titleNode, alpha: titleAlpha)
transition.updateAlpha(node: self.timeNode, alpha: titleAlpha)
let minTitleY: CGFloat = -44.0
let maxTitleY: CGFloat = 10.0
let titleY: CGFloat = titleTransition * maxTitleY + (1.0 - titleTransition) * minTitleY
let titleSize = self.titleNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: titleY), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
let subtitleSize = self.timeNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let subtitleFrame = CGRect(origin: CGPoint(x: floor((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
transition.updateFrame(node: self.timeNode, frame: subtitleFrame)
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
let navigationAlpha: CGFloat = (headerScaleTransition <= 0.0 + CGFloat.ulpOfOne) ? 1.0 : 0.0
if self.navigationBackgroundNode.alpha != navigationAlpha {
alphaTransition.updateAlpha(node: self.navigationBackgroundNode, alpha: navigationAlpha, beginWithCurrentState: true)
}
let separatorAlpha: CGFloat = self.scrollNode.view.contentOffset.y + self.scrollNode.frame.height >= self.scrollNode.view.contentSize.height ? 0.0 : 1.0
if self.commentSeparatorNode.alpha != separatorAlpha {
alphaTransition.updateAlpha(node: self.commentSeparatorNode, alpha: separatorAlpha, beginWithCurrentState: true)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationHeight)
var insets = layout.insets(options: [])
insets.top += navigationHeight
let sideInset: CGFloat = 22.0
self.updateTitle(transition: transition)
let buttonSideInset: CGFloat = 16.0
let bottomInset = insets.bottom + 10.0
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let buttonHeight: CGFloat = 50.0
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
let addressSize = self.addressTextNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let addressFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - addressSize.width) / 2.0), y: buttonFrame.minY - addressSize.height - 34.0), size: addressSize)
transition.updateFrame(node: self.addressTextNode, frame: addressFrame)
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: navigationHeight))
transition.updateFrame(node: self.navigationSeparatorNode, frame: CGRect(x: 0.0, y: navigationHeight, width: layout.size.width, height: UIScreenPixel))
let commentSeparatorFrame = CGRect(x: 0.0, y: addressFrame.minY - 36.0, width: layout.size.width, height: UIScreenPixel)
transition.updateFrame(node: self.commentSeparatorNode, frame: commentSeparatorFrame)
let scrollFrame = CGRect(x: 0.0, y: navigationHeight, width: layout.size.width, height: commentSeparatorFrame.minY - navigationHeight)
transition.updateFrame(node: self.scrollNode, frame: scrollFrame)
let commentSize = self.commentTextNode.updateLayout(CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) let commentSize = self.commentTextNode.updateLayout(CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
let commentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - commentSize.width) / 2.0), y: amountFrame.maxY + 84.0), size: CGSize(width: commentSize.width, height: commentSize.height)) let commentOrigin = CGPoint(x: floor((layout.size.width - commentSize.width) / 2.0), y: 175.0)
let commentFrame = CGRect(origin: commentOrigin, size: commentSize)
transition.updateFrame(node: self.commentTextNode, frame: commentFrame) transition.updateFrame(node: self.commentTextNode, frame: commentFrame)
var commentBackgroundFrame = commentSize.width > 0.0 ? commentFrame.insetBy(dx: -11.0, dy: -7.0) : CGRect() var commentBackgroundFrame = commentSize.width > 0.0 ? commentFrame.insetBy(dx: -11.0, dy: -7.0) : CGRect()
commentBackgroundFrame.size.width += 7.0 commentBackgroundFrame.size.width += 7.0
if self.incoming { if self.incoming {
@ -441,16 +649,12 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
} }
transition.updateFrame(node: self.commentBackgroundNode, frame: commentBackgroundFrame) transition.updateFrame(node: self.commentBackgroundNode, frame: commentBackgroundFrame)
let buttonSideInset: CGFloat = 16.0 let contentHeight = commentOrigin.y + commentBackgroundFrame.height
let bottomInset = insets.bottom + 10.0 self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: contentHeight)
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let buttonHeight: CGFloat = 50.0
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight)) let isScrollEnabled = contentHeight - scrollFrame.height > 20.0
transition.updateFrame(node: self.buttonNode, frame: buttonFrame) self.scrollNode.view.isScrollEnabled = isScrollEnabled
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition) self.scrollNode.clipsToBounds = isScrollEnabled
self.commentSeparatorNode.isHidden = !isScrollEnabled
let addressSize = self.addressTextNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.addressTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - addressSize.width) / 2.0), y: buttonFrame.minY - addressSize.height - 44.0), size: addressSize))
} }
} }

View File

@ -254,14 +254,16 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
} }
private func updateTitle() { private func updateTitle() {
guard let listTitleFrame = self.listTitleFrame else { guard let layout = self.validLayout, let listTitleFrame = self.listTitleFrame else {
return return
} }
let scrollView = self.scrollNode.view let scrollView = self.scrollNode.view
let navigationHeight = self.navigationHeight ?? 0.0 let navigationHeight = self.navigationHeight ?? 0.0
let minY = navigationHeight - 44.0 + floor(44.0 / 2.0) let nominalNavigationHeight = navigationHeight - (layout.0.statusBarHeight ?? 0.0)
let maxY = minY + 44.0
let minY = navigationHeight - nominalNavigationHeight + floor(nominalNavigationHeight / 2.0)
let maxY = minY + nominalNavigationHeight
let y = max(minY, -scrollView.contentOffset.y + listTitleFrame.midY) let y = max(minY, -scrollView.contentOffset.y + listTitleFrame.midY)
var t = (y - minY) / (maxY - minY) var t = (y - minY) / (maxY - minY)
t = max(0.0, min(1.0, t)) t = max(0.0, min(1.0, t))

View File

@ -55,7 +55,7 @@ CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \
--enable-libopus \ --enable-libopus \
--enable-audiotoolbox \ --enable-audiotoolbox \
--enable-bsf=aac_adtstoasc \ --enable-bsf=aac_adtstoasc \
--enable-decoder=h264,hevc,libopus,mp3_at,aac_at,flac,alac_at,pcm_s16le,pcm_s24le,gsm_ms_at \ --enable-decoder=h264,hevc,libopus,mp3_at,aac,flac,alac_at,pcm_s16le,pcm_s24le,gsm_ms_at \
--enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska \ --enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska \
--enable-parser=aac,h264,mp3,libopus \ --enable-parser=aac,h264,mp3,libopus \
--enable-protocol=file \ --enable-protocol=file \