mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '77a592a05c9e599c3dcedb59c1fadc70699bd2e6' into background-task
This commit is contained in:
commit
cbaf7d1ada
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,4 @@
|
||||
|
||||
[submodule "submodules/rlottie/rlottie"]
|
||||
path = submodules/rlottie/rlottie
|
||||
url = https://github.com/peter-iakovlev/rlottie.git
|
||||
url = https://github.com/laktyushin/rlottie.git
|
||||
|
3
BUCK
3
BUCK
@ -399,8 +399,9 @@ apple_binary(
|
||||
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/Intents.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/Contacts.framework",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -343,10 +343,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> castError(IntentHandlingError.self)
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
@ -456,10 +454,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> castError(IntentHandlingError.self)
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||
guard let account = account else {
|
||||
return .fail(.generic)
|
||||
@ -532,10 +528,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
|
||||
self.actionDisposable.set((self.accountPromise.get()
|
||||
|> castError(IntentHandlingError.self)
|
||||
|> take(1)
|
||||
|> mapError { _ -> IntentHandlingError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
||||
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||
return .fail(.generic)
|
||||
|
12
Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json
vendored
Normal file
12
Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_camera.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf
vendored
Normal file
BIN
Telegram-iOS/Icons.xcassets/Shortcuts/Camera.imageset/ic_camera.pdf
vendored
Normal file
Binary file not shown.
@ -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.SelectedMessages_1" = "%@ Message Selected";
|
||||
"Conversation.SelectedMessages_2" = "%@ Messages Selected";
|
||||
"Conversation.SelectedMessages_3_10" = "%@ Messages Selected";
|
||||
"Conversation.SelectedMessages_any" = "%@ Messages Selected";
|
||||
"Conversation.SelectedMessages_many" = "%@ Messages Selected";
|
||||
"Conversation.SelectedMessages_0" = "%@ Messages Selected";
|
||||
"Conversation.SelectedMessages_1" = "%@ Selected";
|
||||
"Conversation.SelectedMessages_2" = "%@ Selected";
|
||||
"Conversation.SelectedMessages_3_10" = "%@ Selected";
|
||||
"Conversation.SelectedMessages_any" = "%@ Selected";
|
||||
"Conversation.SelectedMessages_many" = "%@ Selected";
|
||||
"Conversation.SelectedMessages_0" = "%@ Selected";
|
||||
|
||||
"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.CopyAddress" = "Copy Wallet Address";
|
||||
"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.StorageFeeHeader" = "STORAGE 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.FeeInfoURL" = "https://telegram.org/wallet/fee";
|
||||
"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.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.";
|
||||
|
@ -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
|
||||
|
||||
let pickerController = UIImagePickerController()
|
||||
@ -465,6 +465,9 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
||||
buttonTextColor: .white,
|
||||
incomingFundsTitleColor: UIColor(rgb: 0x00b12c),
|
||||
outgoingFundsTitleColor: UIColor(rgb: 0xff3b30)
|
||||
), transaction: WalletTransactionTheme(
|
||||
descriptionBackgroundColor: UIColor(rgb: 0xf1f1f4),
|
||||
descriptionTextColor: .black
|
||||
), setup: WalletSetupTheme(
|
||||
buttonFillColor: accentColor,
|
||||
buttonForegroundColor: .white,
|
||||
|
@ -389,6 +389,11 @@
|
||||
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
|
||||
{
|
||||
return [TGInputController suggestionsForText:nil];
|
||||
|
@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>People</string>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
@ -302,7 +302,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.didShowProxyUnavailableTooltipController = true
|
||||
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), timeout: 60.0, dismissByTapOutside: true)
|
||||
strongSelf.proxyUnavailableTooltipController = tooltipController
|
||||
tooltipController.dismissed = { [weak tooltipController] in
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController {
|
||||
strongSelf.proxyUnavailableTooltipController = nil
|
||||
}
|
||||
|
@ -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
|
||||
if let currentChatListText = currentChatListText, currentChatListText.0 == text {
|
||||
messageText = currentChatListText.1
|
||||
chatListText = currentChatListText
|
||||
} else {
|
||||
messageText = foldLineBreaks(text, allowTwoLines: peerText == nil)
|
||||
messageText = foldLineBreaks(text)
|
||||
chatListText = (text, messageText)
|
||||
}
|
||||
|
||||
@ -857,7 +838,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
hasDraft = true
|
||||
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 {
|
||||
let composedString: NSMutableAttributedString
|
||||
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
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import AccountContext
|
||||
public enum DeleteChatPeerAction {
|
||||
case delete
|
||||
case clearHistory
|
||||
case clearCache
|
||||
case clearCacheSuggestion
|
||||
}
|
||||
|
||||
public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
|
||||
@ -81,7 +83,19 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
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 {
|
||||
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:
|
||||
if chatPeer.id == context.account.peerId {
|
||||
@ -97,17 +111,25 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
}
|
||||
case .clearHistory:
|
||||
text = strings.ChatList_ClearChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
|
||||
default:
|
||||
break
|
||||
}
|
||||
let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: text.0, font: Font.regular(14.0), textColor: theme.primaryTextColor))
|
||||
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 {
|
||||
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||
formattedAttributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||
}
|
||||
attributedText = formattedAttributedText
|
||||
}
|
||||
}
|
||||
|
||||
if let attributedText = attributedText {
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.accessibilityArea.accessibilityLabel = attributedText.string
|
||||
self.accessibilityArea.accessibilityTraits = .staticText
|
||||
}
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedSize.width - 20.0, height: .greatestFiniteMagnitude))
|
||||
|
@ -8,7 +8,6 @@ public enum ActionSheetButtonColor {
|
||||
case disabled
|
||||
}
|
||||
|
||||
|
||||
public enum ActionSheetButtonFont {
|
||||
case `default`
|
||||
case bold
|
||||
|
@ -44,8 +44,15 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
let additionalSize = CGSize(width: screenSize.width, height: screenSize.height + 20.0)
|
||||
for device in DeviceMetrics.allCases {
|
||||
if let _ = onScreenNavigationHeight, device.onScreenNavigationHeight(inLandscape: false) == nil {
|
||||
if case .tablet = device.type {
|
||||
if screenSize.height == 1024.0 && screenSize.width == 768.0 {
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let width = device.screenSize.width
|
||||
let height = device.screenSize.height
|
||||
|
@ -63,11 +63,12 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
|
||||
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 {
|
||||
self.content = content
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateText(self.content.text, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
|
||||
self.controllerNode.arrowOnBottom = arrowOnBottom
|
||||
if extendTimer, self.timeoutTimer != nil {
|
||||
self.timeoutTimer?.invalidate()
|
||||
self.timeoutTimer = nil
|
||||
@ -84,15 +85,17 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
private var timeoutTimer: SwiftSignalKit.Timer?
|
||||
|
||||
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.timeout = timeout
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
self.dismissByTapOutsideSource = dismissByTapOutsideSource
|
||||
self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate
|
||||
self.initialArrowOnBottom = arrowOnBottom
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -108,9 +111,10 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = TooltipControllerNode(content: self.content, dismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
self.displayNode = TooltipControllerNode(content: self.content, dismiss: { [weak self] tappedInside in
|
||||
self?.dismiss(tappedInside: tappedInside)
|
||||
}, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource)
|
||||
self.controllerNode.arrowOnBottom = self.initialArrowOnBottom
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
@ -154,7 +158,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
if self.timeoutTimer == nil {
|
||||
let timeoutTimer = SwiftSignalKit.Timer(timeout: self.timeout, repeat: false, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismissed?()
|
||||
strongSelf.dismissed?(false)
|
||||
strongSelf.controllerNode.animateOut {
|
||||
self?.presentingViewController?.dismiss(animated: false)
|
||||
}
|
||||
@ -165,16 +169,20 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
}
|
||||
}
|
||||
|
||||
override open func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.dismissed?()
|
||||
private func dismiss(tappedInside: Bool, completion: (() -> Void)? = nil) {
|
||||
self.dismissed?(tappedInside)
|
||||
self.controllerNode.animateOut { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false)
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
override open func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.dismiss(tappedInside: false, completion: completion)
|
||||
}
|
||||
|
||||
open func dismissImmediately() {
|
||||
self.dismissed?()
|
||||
self.dismissed?(false)
|
||||
self.presentingViewController?.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class TooltipControllerNode: ASDisplayNode {
|
||||
private let dismiss: () -> Void
|
||||
private let dismiss: (Bool) -> Void
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
@ -19,7 +19,7 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
private var dismissedByTouchOutside = 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.dismissByTapOutsideSource = dismissByTapOutsideSource
|
||||
|
||||
@ -129,15 +129,16 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
eventIsPresses = event.type == .presses
|
||||
}
|
||||
if event.type == .touches || eventIsPresses {
|
||||
let pointInside = self.containerNode.frame.contains(point)
|
||||
if self.containerNode.frame.contains(point) || self.dismissByTapOutside {
|
||||
if !self.dismissedByTouchOutside {
|
||||
self.dismissedByTouchOutside = true
|
||||
self.dismiss()
|
||||
self.dismiss(pointInside)
|
||||
}
|
||||
} else if self.dismissByTapOutsideSource, let sourceRect = self.sourceRect, !sourceRect.contains(point) {
|
||||
if !self.dismissedByTouchOutside {
|
||||
self.dismissedByTouchOutside = true
|
||||
self.dismiss()
|
||||
self.dismiss(false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -455,6 +455,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
}
|
||||
navigationController.filterController(self, animated: animated)
|
||||
} else {
|
||||
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
@ -479,6 +479,7 @@ public class Window1 {
|
||||
let screenHeight: CGFloat
|
||||
var inPopover = false
|
||||
if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) {
|
||||
let screenSize = UIScreen.main.bounds.size
|
||||
var portraitScreenSize = UIScreen.main.bounds.size
|
||||
if portraitScreenSize.width > portraitScreenSize.height {
|
||||
portraitScreenSize = CGSize(width: portraitScreenSize.height, height: portraitScreenSize.width)
|
||||
@ -487,23 +488,35 @@ public class Window1 {
|
||||
if portraitLayoutSize.width > portraitLayoutSize.height {
|
||||
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 {
|
||||
popoverDelta = 48.0
|
||||
|
||||
if strongSelf.windowLayout.size.height != screenSize.height {
|
||||
let heightDelta = screenSize.height - strongSelf.windowLayout.size.height
|
||||
|
||||
let heightDeltaValid = heightDelta > 0.0 && heightDelta < 100.0
|
||||
|
||||
if heightDeltaValid {
|
||||
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
|
||||
} else {
|
||||
screenHeight = UIScreen.main.bounds.height
|
||||
}
|
||||
} else if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 39.0 {
|
||||
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 {
|
||||
screenHeight = strongSelf.windowLayout.size.height
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
screenHeight = UIScreen.main.bounds.width
|
||||
}
|
||||
@ -513,11 +526,9 @@ public class Window1 {
|
||||
keyboardHeight = 0.0
|
||||
} else {
|
||||
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
|
||||
if inPopover {
|
||||
if strongSelf.windowLayout.onScreenNavigationHeight != nil {
|
||||
if !keyboardHeight.isZero {
|
||||
keyboardHeight = max(0.0, keyboardHeight + popoverDelta / 2.0)
|
||||
}
|
||||
if inPopover && !keyboardHeight.isZero {
|
||||
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
||||
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
|
||||
} else {
|
||||
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
|
||||
}
|
||||
@ -1212,7 +1223,11 @@ public class Window1 {
|
||||
}
|
||||
|
||||
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 {
|
||||
if let controller = controller as? ViewController {
|
||||
|
@ -414,7 +414,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
|
||||
self.deleteButton.isHidden = origin == nil
|
||||
if origin == nil {
|
||||
self.deleteButton.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
func setMessage(_ message: Message) {
|
||||
@ -831,7 +833,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if !ask && items.count == 1 {
|
||||
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start()
|
||||
strongSelf.controllerInteraction?.dismissController()
|
||||
} else {
|
||||
} else if !items.isEmpty {
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
@ -179,7 +179,7 @@ const CGFloat TGLocationMapInset = 100.0f;
|
||||
return;
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
@ -259,7 +259,7 @@ const CGFloat TGLocationMapInset = 100.0f;
|
||||
[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
|
||||
|
@ -179,13 +179,13 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
||||
[self.navigationController.view addSubview:_searchBarOverlay];
|
||||
|
||||
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.backgroundColor = self.pallete != nil ? self.pallete.backgroundColor : [UIColor whiteColor];
|
||||
_searchBarWrapper.hidden = true;
|
||||
[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)
|
||||
[_searchBar setPallete:self.pallete.searchBarPallete];
|
||||
_searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
@ -809,9 +809,6 @@ const CGPoint TGLocationPickerPinOffset = { 0.0f, 33.0f };
|
||||
else
|
||||
{
|
||||
frame.origin.y = 0;
|
||||
if (self.navigationController.modalPresentationStyle == UIModalPresentationFormSheet)
|
||||
frame.origin.y -= 20;
|
||||
|
||||
_searchBarOverlay.alpha = 1.0f;
|
||||
}
|
||||
_searchBarWrapper.frame = frame;
|
||||
|
@ -1191,6 +1191,13 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
||||
else
|
||||
[_model _transitionCompleted];
|
||||
}
|
||||
|
||||
if (!_previewMode) {
|
||||
if (self.adjustsStatusBarVisibility && (!_showInterface || [_view.interfaceView prefersStatusBarHidden]))
|
||||
{
|
||||
[_context setApplicationStatusBarAlpha:0.0f];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
@ -1203,14 +1210,6 @@ static CGFloat transformRotation(CGAffineTransform transform)
|
||||
if (!_showInterface)
|
||||
{
|
||||
_view.interfaceView.alpha = 0.0f;
|
||||
|
||||
if (self.adjustsStatusBarVisibility)
|
||||
[_context setApplicationStatusBarAlpha:0.0f];
|
||||
}
|
||||
else if ([_view.interfaceView prefersStatusBarHidden])
|
||||
{
|
||||
if (self.adjustsStatusBarVisibility)
|
||||
[_context setApplicationStatusBarAlpha:0.0f];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
||||
private let audioFrame: FFMpegAVFrame
|
||||
private var resetDecoderOnNextFrame = true
|
||||
|
||||
private var delayedFrames: [MediaTrackFrame] = []
|
||||
|
||||
init(codecContext: FFMpegAVCodecContext) {
|
||||
self.codecContext = codecContext
|
||||
self.audioFrame = FFMpegAVFrame()
|
||||
@ -19,17 +21,59 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
||||
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? {
|
||||
let status = frame.packet.send(toDecoder: self.codecContext)
|
||||
if status == 0 {
|
||||
if self.codecContext.receive(into: self.audioFrame) {
|
||||
return convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration)
|
||||
while self.codecContext.receive(into: self.audioFrame) {
|
||||
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
|
||||
}
|
||||
|
||||
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? {
|
||||
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? {
|
||||
guard let data = self.swrContext.resample(frame) else {
|
||||
|
@ -39,6 +39,10 @@ final class FFMpegMediaPassthroughVideoFrameDecoder: MediaTrackFrameDecoder {
|
||||
return MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: false, rotationAngle: self.rotationAngle)
|
||||
}
|
||||
|
||||
func takeQueuedFrame() -> MediaTrackFrame? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func takeRemainingFrame() -> MediaTrackFrame? {
|
||||
return nil
|
||||
}
|
||||
|
@ -87,6 +87,10 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func takeQueuedFrame() -> MediaTrackFrame? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func takeRemainingFrame() -> MediaTrackFrame? {
|
||||
if !self.delayedFrames.isEmpty {
|
||||
var minFrameIndex = 0
|
||||
|
@ -146,6 +146,10 @@ public final class MediaTrackFrameBuffer {
|
||||
}
|
||||
|
||||
public func takeFrame() -> MediaTrackFrameResult {
|
||||
if let decodedFrame = self.decoder.takeQueuedFrame() {
|
||||
return .frame(decodedFrame)
|
||||
}
|
||||
|
||||
if !self.frames.isEmpty {
|
||||
let frame = self.frames.removeFirst()
|
||||
if let decodedFrame = self.decoder.decode(frame: frame) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
protocol MediaTrackFrameDecoder {
|
||||
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame?
|
||||
func takeQueuedFrame() -> MediaTrackFrame?
|
||||
func takeRemainingFrame() -> MediaTrackFrame?
|
||||
func reset()
|
||||
}
|
||||
|
@ -168,10 +168,6 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -335,35 +331,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
||||
|
||||
self.updateBackground()
|
||||
|
||||
if layout.size.width == 320.0 {
|
||||
self.iconNode.alpha = 0.0
|
||||
}
|
||||
|
||||
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
||||
transition.updateFrame(node: self.backgroundNode, 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 {
|
||||
case .digits6, .digits4:
|
||||
self.keyboardNode.alpha = 1.0
|
||||
@ -373,27 +344,104 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
|
||||
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 cancelSize = self.cancelButtonNode.measure(layout.size)
|
||||
var cancelY: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset
|
||||
if bottomInset > 0 && self.keyboardNode.alpha < 1.0 {
|
||||
cancelY = layout.size.height - bottomInset - cancelSize.height - 20.0
|
||||
var bottomButtonY = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset
|
||||
var cancelX = floor(keyboardFrame.minX + keyboardButtonSize.width / 2.0 - cancelSize.width / 2.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)
|
||||
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) {
|
||||
var biometricX = layout.safeInsets.left + floor((layoutSize.width - biometricIcon.size.width) / 2.0)
|
||||
var biometricY: CGFloat = 0.0
|
||||
if bottomInset > 0 && self.keyboardNode.alpha < 1.0 {
|
||||
if isLandscape {
|
||||
if bottomInset > 0 && keyboardHidden {
|
||||
biometricX = cancelX + cancelSize.width + 64.0
|
||||
}
|
||||
biometricY = cancelY + floor((cancelSize.height - biometricIcon.size.height) / 2.0)
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
|
||||
titleOffset = -11.0
|
||||
}
|
||||
subtitleOffset = -54.0
|
||||
} else {
|
||||
} else if size.width > 70.0 {
|
||||
titleFont = regularTitleFont
|
||||
subtitleFont = regularSubtitleFont
|
||||
if subtitle.isEmpty {
|
||||
@ -68,6 +68,16 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
|
||||
}
|
||||
subtitleOffset = -48.0
|
||||
}
|
||||
else {
|
||||
titleFont = regularTitleFont
|
||||
subtitleFont = regularSubtitleFont
|
||||
if subtitle.isEmpty {
|
||||
titleOffset = -11.0
|
||||
} else {
|
||||
titleOffset = -4.0
|
||||
}
|
||||
subtitleOffset = -41.0
|
||||
}
|
||||
|
||||
let titlePath = CGMutablePath()
|
||||
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) {
|
||||
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 {
|
||||
for i in 0 ..< subnodes.count {
|
||||
var origin = origin
|
||||
if i % 3 == 0 {
|
||||
origin.x += 0.0
|
||||
} else if (i % 3 == 1) {
|
||||
origin.x += layout.keyboard.horizontalSecond
|
||||
origin.x += horizontalSecond
|
||||
}
|
||||
else {
|
||||
origin.x += layout.keyboard.horizontalThird
|
||||
origin.x += horizontalThird
|
||||
}
|
||||
|
||||
if i / 3 == 0 {
|
||||
origin.y += 0.0
|
||||
}
|
||||
else if i / 3 == 1 {
|
||||
origin.y += layout.keyboard.verticalSecond
|
||||
origin.y += verticalSecond
|
||||
}
|
||||
else if i / 3 == 2 {
|
||||
origin.y += layout.keyboard.verticalThird
|
||||
origin.y += verticalThird
|
||||
}
|
||||
else if i / 3 == 3 {
|
||||
origin.x += layout.keyboard.horizontalSecond
|
||||
origin.y += layout.keyboard.verticalFourth
|
||||
origin.x += horizontalSecond
|
||||
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? {
|
||||
|
@ -13,7 +13,7 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
|
||||
private let wrapperNode: ASDisplayNode
|
||||
private let textNode: ASTextNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var validLayout: CGSize?
|
||||
|
||||
override init() {
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
@ -34,13 +34,13 @@ final class PasscodeEntryLabelNode: ASDisplayNode {
|
||||
self.textNode.attributedText = text
|
||||
completion()
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: validLayout, transition: .immediate)
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
case .slideIn:
|
||||
self.textNode.attributedText = text
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: validLayout, transition: .immediate)
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
|
||||
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
|
||||
completion()
|
||||
})
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: validLayout, transition: .immediate)
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.textNode.attributedText = text
|
||||
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
completion()
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: validLayout, transition: .immediate)
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
self.validLayout = layout
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
self.validLayout = size
|
||||
|
||||
let textSize = self.textNode.measure(layout.size)
|
||||
let textFrame = CGRect(x: floor((layout.size.width - textSize.width) / 2.0), y: 0.0, width: textSize.width, height: textSize.height)
|
||||
let textSize = self.textNode.measure(size)
|
||||
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.textNode, frame: CGRect(origin: CGPoint(), size: textSize))
|
||||
|
||||
return CGSize(width: layout.size.width, height: 25.0)
|
||||
return CGSize(width: size.width, height: 25.0)
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let borderNode: ASImageNode
|
||||
private let dotNodes: [PasscodeEntryDotNode]
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var validLayout: (CGSize, CGFloat)?
|
||||
|
||||
public var complete: ((String) -> Void)?
|
||||
|
||||
@ -202,15 +202,15 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
|
||||
self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
|
||||
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
|
||||
if let (size, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateBackground(_ image: UIImage, size: CGSize) {
|
||||
self.background = (image, size)
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate)
|
||||
if let (size, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,13 +302,13 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
self.textFieldNode.textField.text = ""
|
||||
}
|
||||
self.fieldType = fieldType
|
||||
if let (layout, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, topOffset: topOffset, transition: .immediate)
|
||||
if let (size, topOffset) = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(layout: ContainerViewLayout, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect {
|
||||
self.validLayout = (layout, topOffset)
|
||||
public func updateLayout(size: CGSize, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect {
|
||||
self.validLayout = (size, topOffset)
|
||||
|
||||
let fieldAlpha: CGFloat
|
||||
switch self.fieldType {
|
||||
@ -321,7 +321,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
transition.updateAlpha(node: self.textFieldNode, 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 {
|
||||
let node = self.dotNodes[i]
|
||||
let dotAlpha: CGFloat
|
||||
@ -343,7 +343,7 @@ public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
if !self.useCustomNumpad {
|
||||
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.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
|
||||
if let (backgroundImage, backgroundSize) = self.background {
|
||||
|
@ -101,7 +101,7 @@ struct PasscodeKeyboardLayout {
|
||||
self.verticalThird = 176.0
|
||||
self.verticalFourth = 264.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.deleteOffset = 20.0
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
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.inputFieldBackgroundNode, frame: CGRect(x: 0.0, y: inputFieldFrame.minY - 6.0, width: layout.size.width, height: 48.0))
|
||||
|
@ -893,6 +893,8 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
||||
}, reportChannel: {
|
||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, push: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, completion: { _ in }), nil)
|
||||
}, leaveChannel: {
|
||||
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
||||
|
@ -2072,7 +2072,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
||||
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
|
||||
clearHighlightImpl?()
|
||||
})
|
||||
presentControllerImpl?(controller, nil)
|
||||
pushControllerImpl?(controller)
|
||||
})
|
||||
}, displayLocationContextMenu: { text in
|
||||
displayCopyContextMenuImpl?(text, .location)
|
||||
|
@ -92,7 +92,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
})
|
||||
}
|
||||
} 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)
|
||||
})))
|
||||
@ -103,11 +103,13 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
parent.view.endEditing(true)
|
||||
parent.present(peerReportOptionsController(context: context, subject: subject, present: { [weak parent] c, a in
|
||||
parent?.present(c, in: .window(.root), with: a)
|
||||
}, push: { [weak parent] c in
|
||||
parent?.push(c)
|
||||
}, 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 controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
|
||||
|
||||
@ -170,7 +172,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
})
|
||||
}
|
||||
} 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()
|
||||
@ -349,6 +351,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
|
@ -1181,6 +1181,8 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
}, report: {
|
||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, push: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, completion: { _ in }), nil)
|
||||
})
|
||||
|
||||
@ -1579,8 +1581,6 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
let text: String = presentationData.strings.UserInfo_TapToCall
|
||||
|
||||
let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true)
|
||||
tooltipController.dismissed = {
|
||||
}
|
||||
controller.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
||||
if let resultItemNode = resultItemNode {
|
||||
return (resultItemNode, callButtonFrame)
|
||||
|
@ -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 result: [PeerId: Set<MediaId>] = [:]
|
||||
var lastIndex: MessageIndex?
|
||||
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
|
||||
|
||||
let entry = self.readIntermediateEntry(key, value: value)
|
||||
lastIndex = entry.message.index
|
||||
|
||||
let message = entry.message
|
||||
|
||||
if let upperBound = upperBound, message.id.peerId != upperBound.id.peerId {
|
||||
return true
|
||||
}
|
||||
|
||||
var parsedMedia: [Media] = []
|
||||
|
||||
let embeddedMediaData = message.embeddedMediaData.sharedBufferNoCopy()
|
||||
|
@ -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)
|
||||
if let postbox = self.postbox {
|
||||
return postbox.messageHistoryTable.enumerateMedia(lowerBound: lowerBound, limit: limit)
|
||||
return postbox.messageHistoryTable.enumerateMedia(lowerBound: lowerBound, upperBound: upperBound, limit: limit)
|
||||
} else {
|
||||
return ([:], [:], nil)
|
||||
}
|
||||
|
@ -45,29 +45,29 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|
||||
|> map { data, size, bytesPerRow in
|
||||
return { arguments in
|
||||
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)?
|
||||
if case .none = icon {
|
||||
cutout = nil
|
||||
} else {
|
||||
switch size {
|
||||
case 39:
|
||||
cutout = (14, 24)
|
||||
case 43:
|
||||
cutout = (15, 27)
|
||||
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)
|
||||
var cutoutSize = Int(ceil((clipSide + side * 2.0) / side))
|
||||
if size == 39 {
|
||||
cutoutSize = 11
|
||||
} else if cutoutSize % 2 == 0 {
|
||||
cutoutSize += 1
|
||||
}
|
||||
let start = (size - cutoutSize) / 2
|
||||
cutout = (start, start + cutoutSize - 1)
|
||||
}
|
||||
func valueAt(x: Int, y: Int) -> Bool {
|
||||
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 tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale ?? 0.0, clear: true)
|
||||
tmpContext.withContext { c in
|
||||
@ -233,13 +230,6 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|
||||
|
||||
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 {
|
||||
case .proxy:
|
||||
|
@ -49,7 +49,7 @@ public final class SegmentedControlTheme: Equatable {
|
||||
|
||||
public extension SegmentedControlTheme {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,8 @@ static_library(
|
||||
"//submodules/WalletUI:WalletUI",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -185,7 +185,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry {
|
||||
}
|
||||
case .shareProxyList:
|
||||
switch rhs {
|
||||
case .enabled, .serversHeader, .addServer, .server, .useForCalls:
|
||||
case .enabled, .serversHeader, .addServer, .server, .shareProxyList:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
@ -12,6 +12,8 @@ import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import ItemListPeerItem
|
||||
import DeleteChatPeerActionSheetItem
|
||||
import UndoUI
|
||||
|
||||
private final class StorageUsageControllerArguments {
|
||||
let account: Account
|
||||
@ -285,7 +287,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
return cacheSettings
|
||||
})
|
||||
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
|
||||
let statsPromise = Promise<CacheUsageStatsResult?>()
|
||||
let resetStats: () -> Void = {
|
||||
@ -339,7 +341,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
ActionSheetItemGroup(items: timeoutItems),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller)
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, openClearAll: {
|
||||
let _ = (statsPromise.get()
|
||||
|> take(1)
|
||||
@ -506,7 +508,7 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
presentControllerImpl?(controller)
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
@ -530,6 +532,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
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: [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
|
||||
if let result = result, case let .result(stats) = result {
|
||||
var additionalPeerId: PeerId?
|
||||
if var categories = stats.media[peerId] {
|
||||
if let channel = stats.peers[peerId] as? TelegramChannel, case .group = channel.info {
|
||||
if var categories = stats.media[peerId], let peer = stats.peers[peerId] {
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
for (_, peer) in stats.peers {
|
||||
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId {
|
||||
if let additionalCategories = stats.media[group.id] {
|
||||
@ -606,6 +610,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
}
|
||||
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]
|
||||
|
||||
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: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
presentControllerImpl?(controller)
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
@ -705,6 +711,8 @@ public func storageUsageController(context: AccountContext, isModal: Bool = fals
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
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: [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)
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
|
@ -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))
|
||||
|
||||
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.updateLayout(transition: .immediate)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
|
@ -198,16 +198,43 @@ static CGSize TGFitSize(CGSize size, CGSize maxSize) {
|
||||
} else {
|
||||
if ([(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
|
||||
NSURL *url = (NSURL *)item;
|
||||
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[url path]];
|
||||
if (image != nil) {
|
||||
UIImage *result = TGScaleImageToPixelSize(image, TGFitSize(image.size, maxSize));
|
||||
NSData *resultData = UIImageJPEGRepresentation(result, 0.52f);
|
||||
if (resultData != nil) {
|
||||
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:result.size]}];
|
||||
[subscriber putCompletion];
|
||||
} else {
|
||||
|
||||
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) url, NULL);
|
||||
|
||||
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
|
||||
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
|
||||
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
|
||||
(id) kCGImageSourceThumbnailMaxPixelSize : @(maxSize.width)
|
||||
};
|
||||
|
||||
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 {
|
||||
[subscriber putError:nil];
|
||||
}
|
||||
|
@ -190,6 +190,10 @@ public final class CallController: ViewController {
|
||||
c.presentationArguments = a
|
||||
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))
|
||||
})
|
||||
|
@ -275,6 +275,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
|
||||
|
||||
|
||||
let controller = ItemListController(sharedContext: sharedContext, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
|
@ -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 theme = presentationData.theme
|
||||
let strings = presentationData.strings
|
||||
@ -282,8 +282,7 @@ public func callRatingController(sharedContext: SharedAccountContext, account: A
|
||||
}, apply: { rating in
|
||||
dismissImpl?(true)
|
||||
if rating < 4 {
|
||||
let controller = callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated)
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated))
|
||||
} else {
|
||||
let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start()
|
||||
}
|
||||
|
@ -55,10 +55,17 @@ private final class CacheUsageStatsState {
|
||||
var mediaResourceIds: [MediaId: [MediaResourceId]] = [:]
|
||||
var allResourceIds = Set<WrappedMediaResourceId>()
|
||||
var lowerBound: MessageIndex?
|
||||
var upperBound: MessageIndex?
|
||||
}
|
||||
|
||||
public func collectCacheUsageStats(account: Account, additionalCachePaths: [String], logFilesPath: String) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
let state = Atomic<CacheUsageStatsState>(value: CacheUsageStatsState())
|
||||
public func collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
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
|
||||
var result = Set<WrappedMediaResourceId>()
|
||||
@ -72,7 +79,7 @@ public func collectCacheUsageStats(account: Account, additionalCachePaths: [Stri
|
||||
return excludeResourceIds
|
||||
|> mapToSignal { excludeResourceIds -> Signal<CacheUsageStatsResult, NoError> 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() }
|
||||
|
||||
@ -169,6 +176,27 @@ public func collectCacheUsageStats(account: Account, additionalCachePaths: [Stri
|
||||
}
|
||||
}
|
||||
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
|
||||
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 {
|
||||
if let fileSize = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize {
|
||||
immutableSize += Int64(fileSize)
|
||||
|
@ -195,7 +195,7 @@ public extension Message {
|
||||
func effectivelyFailed(timestamp: Int32) -> Bool {
|
||||
if self.flags.contains(.Failed) {
|
||||
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
|
||||
} else {
|
||||
return false
|
||||
|
@ -490,7 +490,7 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary
|
||||
context.keychain = keychain
|
||||
var wrappedAdditionalSource: MTSignal?
|
||||
#if os(iOS)
|
||||
if #available(iOS 10.0, *) {
|
||||
if #available(iOS 10.0, *), !supplementary {
|
||||
var cloudDataContextValue: CloudDataContext?
|
||||
if let value = cloudDataContext.with({ $0 }) {
|
||||
cloudDataContextValue = value
|
||||
|
@ -238,11 +238,4 @@ public final class PermissionController : ViewController {
|
||||
@objc private func nextPressed() {
|
||||
self.skip?()
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,11 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
separatorColor: UIColor(rgb: 0x3d3d40),
|
||||
badgeBackgroundColor: badgeFillColor,
|
||||
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(
|
||||
|
@ -61,7 +61,11 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
||||
separatorColor: mainSeparatorColor,
|
||||
badgeBackgroundColor: 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(
|
||||
|
@ -69,7 +69,11 @@ private func makeDefaultDayPresentationTheme(accentColor: UIColor, serviceBackgr
|
||||
separatorColor: UIColor(rgb: 0xb1b1b1),
|
||||
badgeBackgroundColor: 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(
|
||||
|
@ -106,8 +106,12 @@ public final class PresentationThemeRootNavigationBar {
|
||||
public let badgeBackgroundColor: UIColor
|
||||
public let badgeStrokeColor: 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.disabledButtonColor = disabledButtonColor
|
||||
self.primaryTextColor = primaryTextColor
|
||||
@ -119,6 +123,10 @@ public final class PresentationThemeRootNavigationBar {
|
||||
self.badgeBackgroundColor = badgeBackgroundColor
|
||||
self.badgeStrokeColor = badgeStrokeColor
|
||||
self.badgeTextColor = badgeTextColor
|
||||
self.segmentedBackgroundColor = segmentedBackgroundColor
|
||||
self.segmentedForegroundColor = segmentedForegroundColor
|
||||
self.segmentedTextColor = segmentedTextColor
|
||||
self.segmentedDividerColor = segmentedDividerColor
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,6 +339,10 @@ extension PresentationThemeRootNavigationBar: Codable {
|
||||
case badgeFill
|
||||
case badgeStroke
|
||||
case badgeText
|
||||
case segmentedBg
|
||||
case segmentedFg
|
||||
case segmentedText
|
||||
case segmentedDivider
|
||||
}
|
||||
|
||||
public convenience init(from decoder: Decoder) throws {
|
||||
@ -353,7 +357,11 @@ extension PresentationThemeRootNavigationBar: Codable {
|
||||
separatorColor: try decodeColor(values, .separator),
|
||||
badgeBackgroundColor: try decodeColor(values, .badgeFill),
|
||||
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 {
|
||||
@ -369,6 +377,10 @@ extension PresentationThemeRootNavigationBar: Codable {
|
||||
try encodeColor(&values, self.badgeBackgroundColor, .badgeFill)
|
||||
try encodeColor(&values, self.badgeStrokeColor, .badgeStroke)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ extension ApplicationShortcutItem {
|
||||
case .compose:
|
||||
icon = UIApplicationShortcutIcon(type: .compose)
|
||||
case .camera:
|
||||
icon = UIApplicationShortcutIcon(type: .capturePhoto)
|
||||
icon = UIApplicationShortcutIcon(templateImageName: "Shortcuts/Camera")
|
||||
case .savedMessages:
|
||||
icon = UIApplicationShortcutIcon(templateImageName: "Shortcuts/SavedMessages")
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import WalletUI
|
||||
import WalletUrl
|
||||
import LocalizedPeerData
|
||||
import PhoneNumberFormat
|
||||
import SettingsUI
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -266,6 +267,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var raiseToListen: RaiseToListenManager?
|
||||
private var voicePlaylistDidEndTimestamp: Double = 0.0
|
||||
|
||||
private weak var searchResultsTooltipController: TooltipController?
|
||||
private weak var messageTooltipController: TooltipController?
|
||||
private weak var videoUnmuteTooltipController: TooltipController?
|
||||
private weak var silentPostTooltipController: TooltipController?
|
||||
@ -1149,7 +1151,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case let .url(url):
|
||||
var cleanUrl = url
|
||||
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 telString = "tel:"
|
||||
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))...])
|
||||
cleanUrl = phoneNumber!
|
||||
openText = strongSelf.presentationData.strings.UserInfo_PhoneCall
|
||||
canOpenIn = false
|
||||
} else if canOpenIn {
|
||||
openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
|
||||
}
|
||||
@ -1470,6 +1473,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
}, push: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.push(c)
|
||||
}
|
||||
})
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
@ -1518,22 +1525,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
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 sourceNode = sourceNode {
|
||||
if let node = node {
|
||||
strongSelf.messageTooltipController?.dismiss()
|
||||
let tooltipController = TooltipController(content: .text(text), dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
|
||||
strongSelf.messageTooltipController = tooltipController
|
||||
tooltipController.dismissed = { [weak tooltipController] in
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController {
|
||||
strongSelf.messageTooltipController = nil
|
||||
}
|
||||
}
|
||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||
if let strongSelf = self {
|
||||
var rect = sourceNode.view.convert(sourceNode.view.bounds, to: strongSelf.chatDisplayNode.view)
|
||||
if let sourceFrame = sourceFrame {
|
||||
rect = CGRect(origin: rect.origin.offsetBy(dx: sourceFrame.minX, dy: sourceFrame.minY - sourceNode.bounds.minY), size: sourceFrame.size)
|
||||
var rect = node.view.convert(node.view.bounds, to: strongSelf.chatDisplayNode.view)
|
||||
if let nodeRect = nodeRect {
|
||||
rect = CGRect(origin: rect.origin.offsetBy(dx: nodeRect.minX, dy: nodeRect.minY - node.bounds.minY), size: nodeRect.size)
|
||||
}
|
||||
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))
|
||||
}
|
||||
})
|
||||
}, 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
|
||||
if let strongSelf = self {
|
||||
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 {
|
||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, push: { c in
|
||||
self?.push(c)
|
||||
}, completion: { _ in }), in: .window(.root))
|
||||
}
|
||||
}, reportMessages: { [weak self] messages, contextController in
|
||||
@ -3562,7 +3575,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let tooltipController = TooltipController(content: .text(banDescription))
|
||||
strongSelf.mediaRestrictedTooltipController = tooltipController
|
||||
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
|
||||
tooltipController.dismissed = { [weak tooltipController] in
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
||||
strongSelf.mediaRestrictedTooltipController = nil
|
||||
}
|
||||
@ -3597,7 +3610,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.videoUnmuteTooltipController?.dismiss()
|
||||
let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
|
||||
strongSelf.videoUnmuteTooltipController = tooltipController
|
||||
tooltipController.dismissed = { [weak tooltipController] in
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.videoUnmuteTooltipController === tooltipController {
|
||||
strongSelf.videoUnmuteTooltipController = nil
|
||||
ApplicationSpecificNotice.setVolumeButtonToUnmute(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||
@ -3852,7 +3865,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else if let rect = rect {
|
||||
let tooltipController = TooltipController(content: .text(text))
|
||||
strongSelf.silentPostTooltipController = tooltipController
|
||||
tooltipController.dismissed = { [weak tooltipController] in
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.silentPostTooltipController === tooltipController {
|
||||
strongSelf.silentPostTooltipController = nil
|
||||
}
|
||||
@ -4078,6 +4091,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
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()))
|
||||
|
||||
switch self.chatLocation {
|
||||
@ -5065,6 +5097,200 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
case .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, *)
|
||||
public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
|
||||
return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String])
|
||||
@ -7569,7 +7824,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else if let rect = rect {
|
||||
let tooltipController = TooltipController(content: .text(text))
|
||||
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 {
|
||||
strongSelf.mediaRecordingModeTooltipController = nil
|
||||
}
|
||||
@ -7584,6 +7839,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func dismissAllTooltips() {
|
||||
self.searchResultsTooltipController?.dismiss()
|
||||
self.messageTooltipController?.dismiss()
|
||||
self.videoUnmuteTooltipController?.dismiss()
|
||||
self.silentPostTooltipController?.dismiss()
|
||||
|
@ -100,6 +100,7 @@ public final class ChatControllerInteraction {
|
||||
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
||||
let updateMessageReaction: (MessageId, String) -> Void
|
||||
let openMessageReactions: (MessageId) -> Void
|
||||
let displaySwipeToReplyHint: () -> Void
|
||||
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -114,7 +115,7 @@ public final class ChatControllerInteraction {
|
||||
var searchTextHighightState: (String, [MessageIndex])?
|
||||
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.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -166,6 +167,7 @@ public final class ChatControllerInteraction {
|
||||
self.performTextSelectionAction = performTextSelectionAction
|
||||
self.updateMessageReaction = updateMessageReaction
|
||||
self.openMessageReactions = openMessageReactions
|
||||
self.displaySwipeToReplyHint = displaySwipeToReplyHint
|
||||
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
@ -202,6 +204,7 @@ public final class ChatControllerInteraction {
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -1533,6 +1533,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
})
|
||||
}
|
||||
self.searchNavigationNode?.deactivate()
|
||||
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
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 {
|
||||
self.setupSendActionOnViewUpdate({ [weak self] in
|
||||
if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode {
|
||||
|
@ -9,6 +9,7 @@ import AccountContext
|
||||
enum ChatNavigationButtonAction {
|
||||
case openChatInfo
|
||||
case clearHistory
|
||||
case clearCache
|
||||
case cancelMessageSelection
|
||||
case search
|
||||
case dismiss
|
||||
@ -45,6 +46,9 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
|
||||
|
||||
if canClear {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ final class ChatMessageDateHeader: ListViewItemHeader {
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
self.action = action
|
||||
if timestamp == Int32.max {
|
||||
if timestamp == 0x7FFFFFFE {
|
||||
self.roundedTimestamp = 0x7FFFFFFE
|
||||
} else if timestamp == Int32.max {
|
||||
self.roundedTimestamp = timestamp / (granularity) * (granularity)
|
||||
} else {
|
||||
self.roundedTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity)
|
||||
@ -151,7 +153,9 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
text = presentationData.strings.ScheduledMessages_ScheduledDate(text).0
|
||||
|
@ -312,7 +312,7 @@ private struct ChatMessagePollOptionResult: Equatable {
|
||||
}
|
||||
|
||||
private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
private let highlightedBackgroundNode: ASImageNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private var radioNode: ChatMessagePollOptionRadioNode?
|
||||
private let percentageNode: ASDisplayNode
|
||||
private var percentageImage: UIImage?
|
||||
@ -326,10 +326,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
var pressed: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
self.highlightedBackgroundNode = ASImageNode()
|
||||
self.highlightedBackgroundNode.displayWithoutProcessing = true
|
||||
self.highlightedBackgroundNode.displaysAsynchronously = false
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
self.highlightedBackgroundNode.isUserInteractionEnabled = false
|
||||
|
||||
@ -344,8 +341,6 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
self.percentageNode = ASDisplayNode()
|
||||
self.percentageNode.alpha = 0.0
|
||||
self.percentageNode.isLayerBacked = true
|
||||
//self.percentageNode.displaysAsynchronously = false
|
||||
//self.percentageNode.displayWithoutProcessing = true
|
||||
|
||||
super.init()
|
||||
|
||||
@ -407,7 +402,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
let previousResult = node.currentResult
|
||||
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
|
||||
|
||||
|
@ -113,9 +113,10 @@ final class ChatPanelInterfaceInteraction {
|
||||
let displaySlowmodeTooltip: (ASDisplayNode, CGRect) -> Void
|
||||
let displaySendMessageOptions: () -> Void
|
||||
let openScheduledMessages: () -> Void
|
||||
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
|
||||
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.setupEditMessage = setupEditMessage
|
||||
self.beginMessageSelection = beginMessageSelection
|
||||
@ -182,6 +183,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
self.displaySlowmodeTooltip = displaySlowmodeTooltip
|
||||
self.displaySendMessageOptions = displaySendMessageOptions
|
||||
self.openScheduledMessages = openScheduledMessages
|
||||
self.displaySearchResultsTooltip = displaySearchResultsTooltip
|
||||
self.statuses = statuses
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: {
|
||||
}, openScheduledMessages: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, statuses: nil)
|
||||
|
||||
self.navigationItem.titleView = self.titleView
|
||||
|
@ -416,6 +416,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
@ -64,7 +64,7 @@ final class ChatScheduleTimeController: ViewController {
|
||||
override public func loadDisplayNode() {
|
||||
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?.completion(time + 5)
|
||||
self?.completion(time != 0x7FFFFFFE ? time + 5 : time)
|
||||
self?.dismiss()
|
||||
}
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
|
@ -26,6 +26,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
private let titleNode: ASTextNode
|
||||
private let cancelButton: HighlightableButtonNode
|
||||
private let doneButton: SolidRoundedButtonNode
|
||||
private let onlineButton: HighlightableButtonNode
|
||||
|
||||
private var pickerView: UIDatePicker?
|
||||
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.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.timeStyle = .none
|
||||
self.dateFormatter.dateStyle = .short
|
||||
@ -98,6 +102,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
self.contentContainerNode.addSubnode(self.titleNode)
|
||||
self.contentContainerNode.addSubnode(self.cancelButton)
|
||||
self.contentContainerNode.addSubnode(self.doneButton)
|
||||
//self.contentContainerNode.addSubnode(self.onlineButton)
|
||||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
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.updateButtonTitle()
|
||||
@ -131,7 +137,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
pickerView.timeZone = TimeZone.current
|
||||
pickerView.minuteInterval = 1
|
||||
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)
|
||||
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.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) {
|
||||
@ -234,6 +241,10 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
self.cancel?()
|
||||
}
|
||||
|
||||
@objc func onlineButtonPressed() {
|
||||
self.completion?(0x7FFFFFFE)
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if self.dismissByTapOutside, case .ended = recognizer.state {
|
||||
self.cancelButtonPressed()
|
||||
@ -300,10 +311,12 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
|
||||
var buttonOffset: CGFloat = 0.0 //44.0
|
||||
|
||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
let titleHeight: CGFloat = 54.0
|
||||
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight)
|
||||
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + buttonOffset
|
||||
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight - buttonOffset)
|
||||
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||
@ -330,7 +343,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
||||
|
||||
let buttonInset: CGFloat = 16.0
|
||||
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))
|
||||
|
||||
|
@ -6,6 +6,7 @@ import TelegramCore
|
||||
import SyncCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramNotices
|
||||
import TelegramPresentationData
|
||||
import ActivityIndicator
|
||||
|
||||
@ -25,6 +26,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
private let activityDisposable = MetaDisposable()
|
||||
private var displayActivity = false
|
||||
|
||||
private var needsSearchResultsTooltip = true
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, LayoutMetrics)?
|
||||
|
||||
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
||||
@ -80,6 +83,26 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
@objc func upPressed() {
|
||||
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() {
|
||||
@ -96,6 +119,10 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
@objc func resultsPressed() {
|
||||
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 {
|
||||
|
@ -1576,7 +1576,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
if let inputText = current.inputText.mutableCopy() as? NSMutableAttributedString {
|
||||
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 {
|
||||
return (ChatTextInputState(inputText: attributedString), inputMode)
|
||||
}
|
||||
|
@ -315,6 +315,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
var replaceControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var endEditingImpl: (() -> Void)?
|
||||
var clearHighlightImpl: (() -> Void)?
|
||||
|
||||
@ -584,7 +585,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
|
||||
clearHighlightImpl?()
|
||||
})
|
||||
presentControllerImpl?(controller, nil)
|
||||
pushImpl?(controller)
|
||||
})
|
||||
|
||||
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
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
controller.willDisappear = { _ in
|
||||
endEditingImpl?()
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import PeerInfoUI
|
||||
import SettingsUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import ShareController
|
||||
|
||||
private enum ChatMessageGalleryControllerData {
|
||||
case url(String)
|
||||
@ -27,7 +28,7 @@ private enum ChatMessageGalleryControllerData {
|
||||
case map(TelegramMediaMap)
|
||||
case stickerPack(StickerPackReference)
|
||||
case audio(TelegramMediaFile)
|
||||
case document(TelegramMediaFile)
|
||||
case document(TelegramMediaFile, Bool)
|
||||
case gallery(GalleryController)
|
||||
case secretGallery(SecretMediaPreviewController)
|
||||
case chatAvatars(AvatarGalleryController, Media)
|
||||
@ -153,14 +154,10 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
|
||||
return .gallery(gallery)
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
|
||||
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
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
return .gallery(gallery)
|
||||
return .document(file, true)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
|
||||
@ -171,7 +168,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
|
||||
}
|
||||
|
||||
if !file.isVideo {
|
||||
return .document(file)
|
||||
return .document(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,9 +269,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
params.dismissInput()
|
||||
params.present(controller, nil)
|
||||
return true
|
||||
case let .document(file):
|
||||
case let .document(file, immediateShare):
|
||||
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)
|
||||
}
|
||||
return true
|
||||
|
@ -117,6 +117,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
|
@ -405,6 +405,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
@ -423,6 +424,8 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
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
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, push: { c in
|
||||
self?.push(c)
|
||||
}, completion: { _ in }), in: .window(.root))
|
||||
}
|
||||
}, reportMessages: { _, _ in
|
||||
@ -517,6 +520,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: {
|
||||
}, openScheduledMessages: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, statuses: nil)
|
||||
|
||||
self.updateInterfaceState(animated: false, { return $0 })
|
||||
|
File diff suppressed because one or more lines are too long
@ -20,6 +20,9 @@ func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, da
|
||||
timestamp = message.timestamp
|
||||
}
|
||||
var dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
|
||||
if timestamp == 0x7FFFFFFE {
|
||||
dateText = " "
|
||||
}
|
||||
|
||||
var authorTitle: String?
|
||||
if let author = message.author as? TelegramUser {
|
||||
|
@ -141,6 +141,9 @@ final class WalletContextImpl: WalletContext {
|
||||
buttonTextColor: .white,
|
||||
incomingFundsTitleColor: theme.chatList.secretTitleColor,
|
||||
outgoingFundsTitleColor: theme.list.itemDestructiveColor
|
||||
), transaction: WalletTransactionTheme(
|
||||
descriptionBackgroundColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill,
|
||||
descriptionTextColor: theme.chat.message.incoming.primaryTextColor
|
||||
), setup: WalletSetupTheme(
|
||||
buttonFillColor: theme.list.itemCheckColors.fillColor,
|
||||
buttonForegroundColor: theme.list.itemCheckColors.foregroundColor,
|
||||
@ -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
|
||||
completion(image)
|
||||
}, present: { [weak self] controller in
|
||||
self?.context.sharedContext.mainWindow?.present(controller, on: .root)
|
||||
present(controller)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public enum UndoOverlayContent {
|
||||
case revealedArchive(title: String, text: String, undo: Bool)
|
||||
case succeed(text: String)
|
||||
case emoji(path: String, text: String)
|
||||
case swipeToReply(title: String, text: String)
|
||||
}
|
||||
|
||||
public final class UndoOverlayController: ViewController {
|
||||
|
@ -137,6 +137,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
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
|
||||
@ -168,7 +178,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
self.panelWrapperNode.addSubnode(self.statusNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply:
|
||||
break
|
||||
}
|
||||
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||
|
@ -51,7 +51,7 @@ public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": tr
|
||||
|
||||
public func explicitUrl(_ url: String) -> String {
|
||||
var url = url
|
||||
if !url.hasPrefix("http") && !url.hasPrefix("https") {
|
||||
if !url.hasPrefix("http") && !url.hasPrefix("https") && url.range(of: "://") == nil {
|
||||
url = "https://\(url)"
|
||||
}
|
||||
return url
|
||||
|
@ -65,15 +65,20 @@ class WalletAmountItem: ListViewItem, ItemListItem {
|
||||
private let integralFont = Font.medium(48.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 {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private let textNode: TextFieldNode
|
||||
private let iconNode: AnimatedStickerNode
|
||||
private let measureNode: TextNode
|
||||
|
||||
private var item: WalletAmountItem?
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
@ -86,6 +91,8 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
self.textNode = TextFieldNode()
|
||||
|
||||
self.iconNode = AnimatedStickerNode()
|
||||
@ -100,8 +107,9 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
|
||||
|
||||
self.clipsToBounds = false
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
self.containerNode.addSubnode(self.iconNode)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
let currentItem = self.item
|
||||
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode)
|
||||
let makeInputFieldLayout = self.inputFieldAsyncLayout()
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: WalletTheme?
|
||||
@ -143,16 +191,12 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
|
||||
insets.top = 0.0
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
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()))
|
||||
let (attributedPlaceholderText, attributedAmountText, inputFieldApply) = makeInputFieldLayout(item, params)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.validLayout = (params.width, params.leftInset, params.rightInset)
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
@ -185,10 +229,7 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
|
||||
strongSelf.textNode.textField.attributedText = attributedAmountText
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 50.0, height: 50.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))
|
||||
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))
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
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.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) {
|
||||
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText
|
||||
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)
|
||||
strongSelf.iconNode.updateLayout(size: iconFrame.size)
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
inputFieldApply()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -232,6 +272,7 @@ class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemN
|
||||
|
||||
@objc private func textFieldTextChanged(_ textField: UITextField) {
|
||||
self.textUpdated(self.textNode.textField.text ?? "")
|
||||
self.updateInputField()
|
||||
}
|
||||
|
||||
@objc private func clearButtonPressed() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import WalletCore
|
||||
|
||||
public enum WalletContextGetServerSaltError {
|
||||
@ -26,5 +27,5 @@ public protocol WalletContext {
|
||||
func shareUrl(_ url: String)
|
||||
func openPlatformSettings()
|
||||
func authorizeAccessToCamera(completion: @escaping () -> Void)
|
||||
func pickImage(completion: @escaping (UIImage) -> Void)
|
||||
func pickImage(present: @escaping (ViewController) -> Void, completion: @escaping (UIImage) -> Void)
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ protocol WalletCreateInvoiceScreen {
|
||||
|
||||
private final class WalletCreateInvoiceScreenImpl: ItemListController, WalletCreateInvoiceScreen {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,24 +135,24 @@ public final class WalletInfoScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
let dateTimeFormat: WalletPresentationDateTimeFormat
|
||||
|
||||
let balanceIntegralTextNode: ImmediateTextNode
|
||||
let balanceFractionalTextNode: ImmediateTextNode
|
||||
let balanceIconNode: AnimatedStickerNode
|
||||
|
||||
var balance: String = " " {
|
||||
var balance: (String, UIColor) = (" ", .white) {
|
||||
didSet {
|
||||
let integralString = NSMutableAttributedString()
|
||||
let fractionalString = NSMutableAttributedString()
|
||||
if let range = self.balance.range(of: self.dateTimeFormat.decimalSeparator) {
|
||||
let integralPart = String(self.balance[..<range.lowerBound])
|
||||
let fractionalPart = String(self.balance[range.lowerBound...])
|
||||
integralString.append(NSAttributedString(string: integralPart, font: Font.medium(48.0), textColor: .white))
|
||||
fractionalString.append(NSAttributedString(string: fractionalPart, font: Font.medium(48.0), textColor: .white))
|
||||
if let range = self.balance.0.range(of: self.dateTimeFormat.decimalSeparator) {
|
||||
let integralPart = String(self.balance.0[..<range.lowerBound])
|
||||
let fractionalPart = String(self.balance.0[range.lowerBound...])
|
||||
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: self.balance.1))
|
||||
} 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.balanceFractionalTextNode.attributedText = fractionalString
|
||||
@ -161,7 +161,7 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
|
||||
var isLoading: Bool = true
|
||||
|
||||
init(theme: WalletTheme, dateTimeFormat: WalletPresentationDateTimeFormat) {
|
||||
init(dateTimeFormat: WalletPresentationDateTimeFormat) {
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
|
||||
self.balanceIntegralTextNode = ImmediateTextNode()
|
||||
@ -244,7 +244,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
init(presentationData: WalletPresentationData, hasActions: Bool, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
||||
self.hasActions = hasActions
|
||||
|
||||
self.balanceNode = WalletInfoBalanceNode(theme: presentationData.theme, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
self.balanceNode = WalletInfoBalanceNode(dateTimeFormat: presentationData.dateTimeFormat)
|
||||
|
||||
self.balanceSubtitleNode = ImmediateTextNode()
|
||||
self.balanceSubtitleNode.displaysAsynchronously = false
|
||||
@ -856,7 +856,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
private func updateCombinedState(combinedState: CombinedWalletState?, isUpdated: Bool) {
|
||||
self.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)
|
||||
|
||||
if self.isReady, let (layout, navigationHeight) = self.validLayout {
|
||||
|
@ -113,6 +113,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
private let iconNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
private let descriptionNode: TextNode
|
||||
private let feesNode: TextNode
|
||||
private let dateNode: TextNode
|
||||
private var statusNode: StatusClockNode?
|
||||
|
||||
@ -157,6 +158,11 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
self.descriptionNode.contentMode = .left
|
||||
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.isUserInteractionEnabled = false
|
||||
self.dateNode.contentMode = .left
|
||||
@ -175,6 +181,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.directionNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.descriptionNode)
|
||||
self.addSubnode(self.feesNode)
|
||||
self.addSubnode(self.dateNode)
|
||||
|
||||
self.addSubnode(self.activateArea)
|
||||
@ -186,6 +193,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
let makeDirectionLayout = TextNode.asyncLayout(self.directionNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
let makeFeesLayout = TextNode.asyncLayout(self.feesNode)
|
||||
let makeDateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
|
||||
let currentItem = self.item
|
||||
@ -269,16 +277,13 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
var feeText: String = ""
|
||||
let dateText: String
|
||||
switch item.walletTransaction {
|
||||
case let .completed(transaction):
|
||||
let fee = transaction.storageFee + transaction.otherFee
|
||||
if fee != 0 {
|
||||
let feeText = item.strings.Wallet_Info_TransactionBlockchainFee(formatBalanceText(-fee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
|
||||
if !description.isEmpty {
|
||||
description.append("\n")
|
||||
}
|
||||
description += "\(feeText)"
|
||||
feeText = item.strings.Wallet_Info_TransactionBlockchainFee(formatBalanceText(-fee, decimalSeparator: item.dateTimeFormat.decimalSeparator)).0
|
||||
}
|
||||
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
|
||||
case let .pending(transaction):
|
||||
@ -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 (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 insets: UIEdgeInsets
|
||||
@ -328,6 +335,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
if !descriptionLayout.size.width.isZero {
|
||||
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)
|
||||
var topHighlightInset: CGFloat = 0.0
|
||||
if dateHeaderAtBottom, let header = item.header {
|
||||
@ -356,6 +366,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
let _ = titleApply()
|
||||
let _ = textApply()
|
||||
let _ = descriptionApply()
|
||||
let _ = feesApply()
|
||||
let _ = dateApply()
|
||||
let _ = directionApply()
|
||||
|
||||
@ -385,7 +396,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textLayout.size)
|
||||
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)
|
||||
strongSelf.dateNode.frame = dateFrame
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
*/
|
@ -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 let buttonFillColor: UIColor
|
||||
public let buttonForegroundColor: UIColor
|
||||
@ -131,6 +144,7 @@ public final class WalletListTheme {
|
||||
|
||||
public final class WalletTheme: Equatable {
|
||||
public let info: WalletInfoTheme
|
||||
public let transaction: WalletTransactionTheme
|
||||
public let setup: WalletSetupTheme
|
||||
public let list: WalletListTheme
|
||||
public let statusBarStyle: StatusBarStyle
|
||||
@ -141,8 +155,9 @@ public final class WalletTheme: Equatable {
|
||||
|
||||
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.transaction = transaction
|
||||
self.setup = setup
|
||||
self.list = list
|
||||
self.statusBarStyle = statusBarStyle
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -118,7 +118,9 @@ public final class WalletQrScanScreen: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.context.pickImage(completion: { image in
|
||||
strongSelf.context.pickImage(present: { c in
|
||||
strongSelf.push(c)
|
||||
}, completion: { image in
|
||||
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
|
||||
if let ciImage = CIImage(image: image) {
|
||||
var options: [String: Any]
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -42,8 +42,6 @@ final class WalletReceiveScreen: ViewController {
|
||||
private let mode: WalletReceiveScreenMode
|
||||
private var presentationData: WalletPresentationData
|
||||
|
||||
private var previousScreenBrightness: CGFloat?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
private let idleTimerExtensionDisposable: Disposable
|
||||
|
||||
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.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()
|
||||
}
|
||||
|
||||
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? {
|
||||
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) {
|
||||
@ -172,6 +158,7 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
|
||||
private let secondaryButtonNode: HighlightableButtonNode
|
||||
|
||||
var openCreateInvoice: (() -> Void)?
|
||||
var displayCopyContextMenu: ((ASDisplayNode, CGRect, String) -> Void)?
|
||||
|
||||
init(context: WalletContext, presentationData: WalletPresentationData, mode: WalletReceiveScreenMode) {
|
||||
self.context = context
|
||||
@ -234,21 +221,16 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let textFont = Font.regular(16.0)
|
||||
let addressFont = Font.monospace(17.0)
|
||||
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
|
||||
let secondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
|
||||
let url = urlForMode(self.mode)
|
||||
switch self.mode {
|
||||
case let .receive(address):
|
||||
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.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):
|
||||
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
|
||||
}
|
||||
|
||||
@ -258,6 +240,32 @@ private final class WalletReceiveScreenNode: ViewControllerTracingNode {
|
||||
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() {
|
||||
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.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))
|
||||
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))
|
||||
if self.urlTextNode.attributedText?.string.isEmpty ?? true {
|
||||
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 bottomInset = insets.bottom + 10.0
|
||||
|
@ -336,9 +336,9 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo
|
||||
popImpl?()
|
||||
if let updatedState = updatedState {
|
||||
if updatedState.amount.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.address)
|
||||
} else if updatedState.comment.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.amount)
|
||||
} else if updatedState.comment.isEmpty {
|
||||
selectNextInputItemImpl?(WalletSendScreenEntryTag.comment)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -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)
|
||||
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: {
|
||||
if let strongSelf = self {
|
||||
return (node.view, rect.insetBy(dx: 0.0, dy: -4.0))
|
||||
@ -210,20 +215,55 @@ final class WalletTransactionInfoScreen: ViewController {
|
||||
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()
|
||||
}
|
||||
|
||||
private let measureTextNode = TextNode()
|
||||
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 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()))
|
||||
var textHeight = textLayout.size.height
|
||||
if textHeight > 0.0 {
|
||||
textHeight += 24.0
|
||||
|
||||
var resultHeight = minHeight
|
||||
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) {
|
||||
@ -237,10 +277,10 @@ 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 final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let context: WalletContext
|
||||
private var presentationData: WalletPresentationData
|
||||
private let walletTransaction: WalletInfoTransaction
|
||||
@ -248,22 +288,25 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let timeNode: ImmediateTextNode
|
||||
|
||||
private let amountNode: ImmediateTextNode
|
||||
private let iconNode: AnimatedStickerNode
|
||||
private let navigationBackgroundNode: ASDisplayNode
|
||||
private let navigationSeparatorNode: ASDisplayNode
|
||||
private let scrollNode: ASScrollNode
|
||||
private let amountNode: WalletInfoBalanceNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
private let feesNode: ImmediateTextNode
|
||||
private let feesInfoIconNode: ASImageNode
|
||||
private let feesButtonNode: ASButtonNode
|
||||
|
||||
private let commentBackgroundNode: ASImageNode
|
||||
private let commentTextNode: ImmediateTextNode
|
||||
|
||||
private let commentSeparatorNode: ASDisplayNode
|
||||
private let addressTextNode: ImmediateTextNode
|
||||
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var send: ((String) -> Void)?
|
||||
var displayFeesTooltip: ((ASDisplayNode, CGRect) -> Void)?
|
||||
var displayCopyContextMenu: ((ASDisplayNode, CGRect, String) -> Void)?
|
||||
|
||||
init(context: WalletContext, presentationData: WalletPresentationData, walletTransaction: WalletInfoTransaction) {
|
||||
self.context = context
|
||||
@ -278,29 +321,39 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
self.timeNode.textAlignment = .center
|
||||
self.timeNode.maximumNumberOfLines = 1
|
||||
|
||||
self.amountNode = ImmediateTextNode()
|
||||
self.amountNode.textAlignment = .center
|
||||
self.amountNode.maximumNumberOfLines = 1
|
||||
self.navigationBackgroundNode = ASDisplayNode()
|
||||
self.navigationBackgroundNode.backgroundColor = self.presentationData.theme.navigationBar.backgroundColor
|
||||
self.navigationBackgroundNode.alpha = 0.0
|
||||
self.navigationSeparatorNode = ASDisplayNode()
|
||||
self.navigationSeparatorNode.backgroundColor = self.presentationData.theme.navigationBar.separatorColor
|
||||
|
||||
self.iconNode = AnimatedStickerNode()
|
||||
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
|
||||
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct)
|
||||
self.iconNode.visibility = true
|
||||
}
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
self.amountNode = WalletInfoBalanceNode(dateTimeFormat: presentationData.dateTimeFormat)
|
||||
|
||||
self.feesNode = ImmediateTextNode()
|
||||
self.feesNode.textAlignment = .center
|
||||
self.feesNode.maximumNumberOfLines = 2
|
||||
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.commentBackgroundNode = ASImageNode()
|
||||
self.commentBackgroundNode.contentMode = .scaleToFill
|
||||
self.commentBackgroundNode.isUserInteractionEnabled = true
|
||||
|
||||
self.commentTextNode = ImmediateTextNode()
|
||||
self.commentTextNode.textAlignment = .natural
|
||||
self.commentTextNode.maximumNumberOfLines = 0
|
||||
self.commentTextNode.isUserInteractionEnabled = false
|
||||
|
||||
self.commentSeparatorNode = ASDisplayNode()
|
||||
self.commentSeparatorNode.backgroundColor = self.presentationData.theme.list.itemPlainSeparatorColor
|
||||
|
||||
self.addressTextNode = ImmediateTextNode()
|
||||
self.addressTextNode.maximumNumberOfLines = 4
|
||||
@ -327,14 +380,18 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
|
||||
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.timeNode)
|
||||
self.addSubnode(self.amountNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.feesNode)
|
||||
self.addSubnode(self.feesButtonNode)
|
||||
self.addSubnode(self.commentBackgroundNode)
|
||||
self.addSubnode(self.commentTextNode)
|
||||
self.addSubnode(self.commentSeparatorNode)
|
||||
self.addSubnode(self.addressTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
@ -346,7 +403,6 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
|
||||
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)
|
||||
|
||||
let amountString: String
|
||||
@ -358,7 +414,7 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
amountString = "\(formatBalanceText(transferredValue, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator))"
|
||||
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 = ""
|
||||
if case let .completed(transaction) = walletTransaction {
|
||||
@ -372,16 +428,18 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
feesString.append(formatBalanceText(transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " transaction fee")
|
||||
}
|
||||
|
||||
if !feesString.isEmpty {
|
||||
feesString.append("(?)")
|
||||
}
|
||||
self.feesInfoIconNode.isHidden = feesString.isEmpty
|
||||
}
|
||||
self.feesNode.attributedText = NSAttributedString(string: feesString, font: subtitleFont, textColor: seccondaryTextColor)
|
||||
|
||||
self.feesButtonNode.addTarget(self, action: #selector(feesPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.commentBackgroundNode.image = messageBubbleImage(incoming: transferredValue > 0, fillColor: UIColor(rgb: 0xf1f1f5), strokeColor: UIColor(rgb: 0xf1f1f5))
|
||||
self.commentTextNode.attributedText = NSAttributedString(string: extractDescription(walletTransaction), font: Font.regular(17.0), textColor: .black)
|
||||
var commentBackgroundColor = presentationData.theme.transaction.descriptionBackgroundColor
|
||||
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)
|
||||
var singleAddress: String?
|
||||
@ -391,7 +449,7 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
|
||||
if let address = singleAddress {
|
||||
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?.send?(address)
|
||||
@ -399,39 +457,189 @@ 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() {
|
||||
self.displayFeesTooltip?(self.feesNode, self.feesNode.bounds)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top += navigationHeight
|
||||
let inset: CGFloat = 22.0
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.updateTitle(transition: .immediate)
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
private func updateTitle(transition: ContainedViewLayoutTransition) {
|
||||
guard let (layout, navigationHeight) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let subtitleSize = self.timeNode.updateLayout(CGSize(width: layout.size.width - inset * 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)
|
||||
transition.updateFrame(node: self.timeNode, frame: subtitleFrame)
|
||||
let width = layout.size.width
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
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 minOffset = navigationHeight
|
||||
let maxOffset: CGFloat = 200.0
|
||||
|
||||
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 nominalFeesHeight: CGFloat = 42.0
|
||||
let minHeaderOffset = minOffset
|
||||
let maxHeaderOffset = (minOffset + maxOffset) / 2.0
|
||||
let maxHeaderPositionOffset = maxOffset - nominalFeesHeight
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
let minHeaderY = floor((navigationHeight - minHeaderHeight) / 2.0)
|
||||
let maxHeaderY: CGFloat = 90.0
|
||||
let headerPositionTransition: CGFloat = min(1.0, max(0.0, (effectiveOffset - minHeaderOffset) / (maxHeaderPositionOffset - minHeaderOffset)))
|
||||
let headerY = headerPositionTransition * maxHeaderY + (1.0 - headerPositionTransition) * minHeaderY
|
||||
let headerScale = headerScaleTransition * maxHeaderScale + (1.0 - headerScaleTransition) * minHeaderScale
|
||||
|
||||
let balanceFrame = CGRect(origin: CGPoint(x: 0.0, y: headerY), size: balanceSize)
|
||||
transition.updateFrame(node: self.amountNode, frame: balanceFrame)
|
||||
transition.updateSublayerTransformScale(node: self.amountNode, scale: headerScale)
|
||||
|
||||
let feesSize = self.feesNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let feesFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - feesSize.width) / 2.0), y: headerY + 64.0), size: feesSize)
|
||||
transition.updateFrame(node: self.feesNode, 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 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)
|
||||
|
||||
var commentBackgroundFrame = commentSize.width > 0.0 ? commentFrame.insetBy(dx: -11.0, dy: -7.0) : CGRect()
|
||||
@ -441,16 +649,12 @@ private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
transition.updateFrame(node: self.commentBackgroundNode, frame: commentBackgroundFrame)
|
||||
|
||||
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 contentHeight = commentOrigin.y + commentBackgroundFrame.height
|
||||
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
|
||||
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 - 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))
|
||||
let isScrollEnabled = contentHeight - scrollFrame.height > 20.0
|
||||
self.scrollNode.view.isScrollEnabled = isScrollEnabled
|
||||
self.scrollNode.clipsToBounds = isScrollEnabled
|
||||
self.commentSeparatorNode.isHidden = !isScrollEnabled
|
||||
}
|
||||
}
|
||||
|
@ -254,14 +254,16 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
|
||||
}
|
||||
|
||||
private func updateTitle() {
|
||||
guard let listTitleFrame = self.listTitleFrame else {
|
||||
guard let layout = self.validLayout, let listTitleFrame = self.listTitleFrame else {
|
||||
return
|
||||
}
|
||||
let scrollView = self.scrollNode.view
|
||||
|
||||
let navigationHeight = self.navigationHeight ?? 0.0
|
||||
let minY = navigationHeight - 44.0 + floor(44.0 / 2.0)
|
||||
let maxY = minY + 44.0
|
||||
let nominalNavigationHeight = navigationHeight - (layout.0.statusBarHeight ?? 0.0)
|
||||
|
||||
let minY = navigationHeight - nominalNavigationHeight + floor(nominalNavigationHeight / 2.0)
|
||||
let maxY = minY + nominalNavigationHeight
|
||||
let y = max(minY, -scrollView.contentOffset.y + listTitleFrame.midY)
|
||||
var t = (y - minY) / (maxY - minY)
|
||||
t = max(0.0, min(1.0, t))
|
||||
|
@ -55,7 +55,7 @@ CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \
|
||||
--enable-libopus \
|
||||
--enable-audiotoolbox \
|
||||
--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-parser=aac,h264,mp3,libopus \
|
||||
--enable-protocol=file \
|
||||
|
Loading…
x
Reference in New Issue
Block a user