Merge commit '77a592a05c9e599c3dcedb59c1fadc70699bd2e6' into background-task

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

3
.gitmodules vendored
View File

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

3
BUCK
View File

@ -399,8 +399,9 @@ apple_binary(
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
],
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",
],
)

View File

@ -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)

View File

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

Binary file not shown.

View File

@ -4638,12 +4638,12 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
"Conversation.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.";

View File

@ -366,7 +366,7 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
}
}
func pickImage(completion: @escaping (UIImage) -> Void) {
func pickImage(present: @escaping (ViewController) -> Void, completion: @escaping (UIImage) -> Void) {
self.currentImagePickerCompletion = completion
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,

View File

@ -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];

View File

@ -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>

View File

@ -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
}

View File

@ -825,31 +825,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
func foldLineBreaks(_ text: String, allowTwoLines: Bool) -> String {
var lines = text.split { $0.isNewline }
var startedBothLines = false
var result = ""
for line in lines {
if result.isEmpty {
result += line
} else {
if allowTwoLines && !startedBothLines {
result += "\n" + line
startedBothLines = true
} else {
result += " " + line
}
}
}
return result
}
let messageText: String
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
}

View File

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

View File

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

View File

@ -44,7 +44,14 @@ 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 {
continue
if case .tablet = device.type {
if screenSize.height == 1024.0 && screenSize.width == 768.0 {
} else {
continue
}
} else {
continue
}
}
let width = device.screenSize.width

View File

@ -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?()
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)
}
}

View File

@ -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

View File

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

View File

@ -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 {

View File

@ -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()

View File

@ -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

View File

@ -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;

View File

@ -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];
}
}

View File

@ -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,16 +21,58 @@ 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? {
return nil
if !self.delayedFrames.isEmpty {
var minFrameIndex = 0
var minPosition = self.delayedFrames[0].position
for i in 1 ..< self.delayedFrames.count {
if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 {
minFrameIndex = i
minPosition = self.delayedFrames[i].position
}
}
return self.delayedFrames.remove(at: minFrameIndex)
} else {
return nil
}
}
private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? {

View File

@ -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
}

View File

@ -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

View File

@ -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) {

View File

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

View File

@ -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 {
biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0)
if isLandscape {
if bottomInset > 0 && keyboardHidden {
biometricX = cancelX + cancelSize.width + 64.0
}
biometricY = cancelY + floor((cancelSize.height - biometricIcon.size.height) / 2.0)
} else {
biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset
if bottomInset > 0 && keyboardHidden {
biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0)
} else {
biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset
}
}
transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - biometricIcon.size.width) / 2.0), y: biometricY), size: biometricIcon.size))
transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: biometricX, y: biometricY), size: biometricIcon.size))
}
}
}

View File

@ -58,7 +58,7 @@ private func generateButtonImage(background: PasscodeBackground, frame: CGRect,
titleOffset = -11.0
}
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? {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -147,7 +147,7 @@ final class PasscodeSetupControllerNode: ASDisplayNode {
self.wrapperNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
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))

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -2479,18 +2479,23 @@ final class MessageHistoryTable: Table {
}
}
func enumerateMedia(lowerBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
func enumerateMedia(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
var mediaRefs: [MediaId: Media] = [:]
var 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()

View File

@ -763,10 +763,10 @@ public final class Transaction {
}
}
public func enumerateMedia(lowerBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
public func enumerateMedia(lowerBound: MessageIndex?, upperBound: MessageIndex?, limit: Int) -> ([PeerId: Set<MediaId>], [MediaId: Media], MessageIndex?) {
assert(!self.disposed)
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)
}

View File

@ -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:

View File

@ -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)
}
}

View File

@ -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",

View File

@ -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

View File

@ -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()

View File

@ -283,16 +283,11 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - contentSize.width / 2.0), layout.size.width - contentSize.width - 8.0))
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
}
})

View File

@ -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 {
[subscriber putError:nil];
}
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];
}

View File

@ -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))
})

View File

@ -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))
}

View File

@ -266,7 +266,7 @@ func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comm
}
}
public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, present: @escaping (ViewController, Any) -> Void) -> AlertController {
public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, present: @escaping (ViewController, Any) -> Void, push: @escaping (ViewController) -> Void) -> AlertController {
let presentationData = sharedContext.currentPresentationData.with { $0 }
let 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()
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -9,7 +9,7 @@ import TelegramPresentationData
import DeviceAccess
import AccountContext
public final class PermissionController : ViewController {
public final class PermissionController: ViewController {
private let context: AccountContext
private let splitTest: PermissionUISplitTest?
private var state: PermissionControllerContent?
@ -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?()
})
}
}

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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 {
@ -5052,19 +5084,213 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.chatLocationInfoData {
case let .peer(peerView):
self.navigationActionDisposable.set((peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
strongSelf.effectiveNavigationController?.pushViewController(infoController)
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios") == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic) {
strongSelf.effectiveNavigationController?.pushViewController(infoController)
}
}
}))
}
case .search:
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()

View File

@ -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,

View File

@ -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 {

View File

@ -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))
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

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

View File

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

View File

@ -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

View File

@ -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))

View File

@ -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 {

View File

@ -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)
}

View File

@ -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?()
}

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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 {

View File

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

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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() {

View File

@ -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)
}

View File

@ -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))
}
}

View File

@ -94,7 +94,7 @@ public final class WalletInfoScreen: ViewController {
@objc private func settingsPressed() {
if let walletInfo = self.walletInfo {
self.push(walletSettingsController(context: self.context, walletInfo: walletInfo))
self.push(walletSettingsController(context: self.context, walletInfo: walletInfo))
}
}
@ -135,24 +135,24 @@ public final class WalletInfoScreen: ViewController {
}
}
private final class WalletInfoBalanceNode: ASDisplayNode {
final class WalletInfoBalanceNode: ASDisplayNode {
let dateTimeFormat: WalletPresentationDateTimeFormat
let 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 {

View File

@ -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,18 +277,15 @@ 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)
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
case let .pending(transaction):
dateText = stringForMessageTimestamp(timestamp: Int32(clamping: transaction.timestamp), dateTimeFormat: item.dateTimeFormat)
}
@ -307,7 +312,9 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (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

View File

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

View File

@ -49,6 +49,19 @@ public final class WalletInfoTheme {
}
}
public final class WalletTransactionTheme {
public let descriptionBackgroundColor: UIColor
public let descriptionTextColor: UIColor
public init(
descriptionBackgroundColor: UIColor,
descriptionTextColor: UIColor
) {
self.descriptionBackgroundColor = descriptionBackgroundColor
self.descriptionTextColor = descriptionTextColor
}
}
public final class WalletSetupTheme {
public 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

View File

@ -1,167 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import QrCode
class WalletQrCodeItem: ListViewItem, ItemListItem {
let theme: WalletTheme
let address: String
let sectionId: ItemListSectionId
let style: ItemListStyle
let action: (() -> Void)?
let longTapAction: (() -> Void)?
public let isAlwaysPlain: Bool = true
init(theme: WalletTheme, address: String, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: @escaping () -> Void) {
self.theme = theme
self.address = address
self.sectionId = sectionId
self.style = style
self.action = action
self.longTapAction = longTapAction
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = WalletQrCodeItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? WalletQrCodeItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
class WalletQrCodeItemNode: ListViewItemNode {
private let imageNode: TransformImageNode
private var item: WalletQrCodeItem?
var tag: Any? {
return self.item?.tag
}
init() {
self.imageNode = TransformImageNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.imageNode)
}
override func didLoad() {
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
recognizer.highlight = { [weak self] point in
self?.imageNode.alpha = point != nil ? 0.4 : 1.0
}
self.view.addGestureRecognizer(recognizer)
}
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
self.item?.action?()
case .longTap:
self.item?.longTapAction?()
default:
break
}
}
default:
break
}
}
func asyncLayout() -> (_ item: WalletQrCodeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeImageLayout = self.imageNode.asyncLayout()
let currentItem = self.item
return { item, params, neighbors in
var updatedTheme: WalletTheme?
var updatedAddress: String?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
}
if currentItem?.address != item.address || updatedTheme != nil {
updatedAddress = item.address
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let inset: CGFloat = 0.0
var imageSize = CGSize(width: 128.0, height: 128.0)
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
switch item.style {
case .plain:
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
contentSize = CGSize(width: params.width, height: imageSize.height + 30.0)
insets = itemListNeighborsGroupedInsets(neighbors)
}
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
if let updatedAddress = updatedAddress {
strongSelf.imageNode.setSignal(qrCode(string: updatedAddress, color: item.theme.list.itemPrimaryTextColor.withAlphaComponent(0.77), backgroundColor: item.theme.list.blocksBackgroundColor, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem"))), attemptSynchronously: true)
}
let _ = imageApply()
strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: (params.width - imageSize.width) / 2.0, y: 0.0), size: imageSize)
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -118,8 +118,10 @@ public final class WalletQrScanScreen: ViewController {
guard let strongSelf = self else {
return
}
strongSelf.context.pickImage(completion: { image in
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])!
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]
if ciImage.properties.keys.contains((kCGImagePropertyOrientation as String)) {

View File

@ -1,143 +0,0 @@
import Foundation
import UIKit
import SwiftSignalKit
import AppBundle
import AsyncDisplayKit
import Display
import QrCode
import AnimatedStickerNode
public final class WalletQrViewScreen: ViewController {
private let context: WalletContext
private let invoice: String
private var presentationData: WalletPresentationData
private var previousScreenBrightness: CGFloat?
private var displayLinkAnimator: DisplayLinkAnimator?
private let idleTimerExtensionDisposable: Disposable
public init(context: WalletContext, invoice: String) {
self.context = context
self.invoice = invoice
self.presentationData = context.presentationData
let defaultTheme = self.presentationData.theme.navigationBar
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
self.idleTimerExtensionDisposable = context.idleTimerExtension()
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Wallet_Navigation_Back, close: self.presentationData.strings.Wallet_Navigation_Close)))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
self.title = self.presentationData.strings.Wallet_Qr_Title
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.shareButtonPressed))
}
deinit {
self.idleTimerExtensionDisposable.dispose()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = WalletQrViewScreenNode(context: self.context, presentationData: self.presentationData, message: self.invoice)
self.displayNodeDidLoad()
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let screenBrightness = UIScreen.main.brightness
if screenBrightness < 0.85 {
self.previousScreenBrightness = screenBrightness
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.5, from: screenBrightness, to: 0.85, update: { value in
UIScreen.main.brightness = value
}, completion: {
self.displayLinkAnimator = nil
})
}
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
let screenBrightness = UIScreen.main.brightness
if let previousScreenBrightness = self.previousScreenBrightness, screenBrightness > previousScreenBrightness {
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2, from: screenBrightness, to: previousScreenBrightness, update: { value in
UIScreen.main.brightness = value
}, completion: {
self.displayLinkAnimator = nil
})
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! WalletQrViewScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
@objc private func shareButtonPressed() {
//shareInvoiceQrCode(context: self.context, invoice: self.invoice)
}
}
private final class WalletQrViewScreenNode: ViewControllerTracingNode {
private var presentationData: WalletPresentationData
private let invoice: String
private let imageNode: TransformImageNode
private let iconNode: AnimatedStickerNode
init(context: WalletContext, presentationData: WalletPresentationData, message: String) {
self.presentationData = presentationData
self.invoice = message
self.imageNode = TransformImageNode()
self.imageNode.clipsToBounds = true
self.imageNode.cornerRadius = 14.0
self.iconNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 240, height: 240, mode: .direct)
self.iconNode.visibility = true
}
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.imageNode)
self.addSubnode(self.iconNode)
self.imageNode.setSignal(qrCode(string: self.invoice, color: .black, backgroundColor: .white, icon: .cutout), attemptSynchronously: true)
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let makeImageLayout = self.imageNode.asyncLayout()
let imageSide = layout.size.width - 48.0 * 2.0
var imageSize = CGSize(width: imageSide, height: imageSide)
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
let _ = imageApply()
let imageFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: floor((layout.size.height - imageSize.height - layout.intrinsicInsets.bottom) / 2.0)), size: imageSize)
transition.updateFrame(node: self.imageNode, frame: imageFrame)
let iconSide = floor(imageSide * 0.24)
let iconSize = CGSize(width: iconSide, height: iconSide)
self.iconNode.updateLayout(size: iconSize)
transition.updateBounds(node: self.iconNode, bounds: CGRect(origin: CGPoint(), size: iconSize))
transition.updatePosition(node: self.iconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0))
}
}

View File

@ -42,8 +42,6 @@ final class WalletReceiveScreen: ViewController {
private let mode: WalletReceiveScreenMode
private 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

View File

@ -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)
}
}
}))

View File

@ -202,7 +202,12 @@ final class WalletTransactionInfoScreen: ViewController {
}
var string = NSMutableAttributedString(string: "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and for processing your transactions. More info", font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center)
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
}
}

View File

@ -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))

View File

@ -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 \