Low disk space alert

Contacts sort selector
Bot button icons
tg://msg scheme support
Calculate service message color using current wallpaper
Various fixes
This commit is contained in:
Ilya Laktyushin 2018-12-28 21:23:00 +04:00
parent b1214087ba
commit e8a63d4fc8
65 changed files with 3208 additions and 2699 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "BotLink@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "BotLink@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "BotLocation@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "BotLocation@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "BotMessage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "BotMessage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "BotPhone@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "BotPhone@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "BotShare@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "BotShare@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -72,6 +72,7 @@
099529AE21D045C400805E13 /* ThemeGridActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AD21D045C400805E13 /* ThemeGridActionNode.swift */; };
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
099529B221D24F5800805E13 /* RadialDownloadContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */; };
099529B421D3E5D800805E13 /* CheckDiskSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B321D3E5D800805E13 /* CheckDiskSpace.swift */; };
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4C21A7B73800847FA6 /* PermissionController.swift */; };
@ -1176,6 +1177,7 @@
099529AD21D045C400805E13 /* ThemeGridActionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeGridActionNode.swift; sourceTree = "<group>"; };
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageUnsupportedBubbleContentNode.swift; sourceTree = "<group>"; };
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialDownloadContentNode.swift; sourceTree = "<group>"; };
099529B321D3E5D800805E13 /* CheckDiskSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDiskSpace.swift; sourceTree = "<group>"; };
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSessionsEmptyStateItem.swift; sourceTree = "<group>"; };
09B4EE4C21A7B73800847FA6 /* PermissionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionController.swift; sourceTree = "<group>"; };
@ -4665,6 +4667,7 @@
0902838C2194AEB90067EFBD /* ImageTransparency.swift */,
09C9EA32219F79F600E90146 /* ID3Artwork.h */,
09C9EA31219F79F500E90146 /* ID3Artwork.m */,
099529B321D3E5D800805E13 /* CheckDiskSpace.swift */,
);
name = Utils;
sourceTree = "<group>";
@ -5285,6 +5288,7 @@
D0E9BA671F055B5500F079A4 /* BotCheckoutNativeCardEntryControllerNode.swift in Sources */,
D0EC6D291EB9F58800EBF1C3 /* FetchVideoMediaResource.swift in Sources */,
D0AFCC791F4C8D2C000720C6 /* InstantPageSlideshowItem.swift in Sources */,
099529B421D3E5D800805E13 /* CheckDiskSpace.swift in Sources */,
D04281EF200E3D88009DDE36 /* GroupInfoSearchItem.swift in Sources */,
D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */,
D081E104217F57D2003CD921 /* LanguageLinkPreviewController.swift in Sources */,

View File

@ -273,7 +273,7 @@ class AvatarGalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -293,7 +293,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let editingOffset: CGFloat
var editableControlSizeAndApply: (CGSize, () -> ItemListEditableControlNode)?
if item.editing {
let sizeAndApply = editableControlLayout(56.0, item.theme, false)
let sizeAndApply = editableControlLayout(50.0, item.theme, false)
editableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0.width
} else {
@ -420,7 +420,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 56.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 50.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let outgoingIcon = PresentationResourcesCallList.outgoingIcon(item.theme)
let infoIcon = PresentationResourcesCallList.infoButton(item.theme)
@ -522,13 +522,13 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
})
}
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 52.0, y: 8.0), size: CGSize(width: 40.0, height: 40.0)))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 52.0, y: 5.0), size: CGSize(width: 40.0, height: 40.0)))
let _ = titleApply()
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 8.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 6.0), size: titleLayout.size))
let _ = statusApply()
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 30.0), size: statusLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 27.0), size: statusLayout.size))
let _ = dateApply()
transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + params.width - dateRightInset - dateLayout.size.width, y: floor((nodeLayout.contentSize.height - dateLayout.size.height) / 2.0) + 2.0), size: dateLayout.size))

View File

@ -803,9 +803,8 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
return nil
} |> deliverOnMainQueue).start(next: { link in
if let link = link {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = ShareProxyServerActionSheetController(theme: presentationData.theme, strings: presentationData.strings, updatedPresentationData: .complete(), link: link)
presentControllerImpl?(controller, nil)
let shareController = ShareController(account: account, subject: .url(link))
presentControllerImpl?(shareController, nil)
}
})
})

View File

@ -2476,6 +2476,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
return
}
guard checkAvailableDiskSpace(account: strongSelf.account, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}) else {
return
}
let hasOngoingCall: Signal<Bool, NoError>
if let signal = strongSelf.account.telegramApplicationContext.hasOngoingCall {
hasOngoingCall = signal
@ -4886,7 +4891,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
}
self.chatDisplayNode.dismissInput()
self.present(controller, in: .window(.root))
self.present(controller, in: .window(.root), blockInteraction: true)
}
private func openPeer(peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: Message?) {
@ -5453,7 +5458,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
switch strongSelf.peekActions {
case .standard:
if let peer = data.peer {
if let peer = data.peer, peer.id != strongSelf.account.peerId {
if let _ = data.peer as? TelegramUser {
items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in
if let strongSelf = self {

View File

@ -1,9 +1,11 @@
import Foundation
import TelegramCore
import Display
import SwiftSignalKit
import Postbox
private var backgroundImageForWallpaper: (TelegramWallpaper, UIImage)?
private var serviceBackgroundColorForWallpaper: (TelegramWallpaper, UIColor)?
func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbox) -> UIImage? {
var backgroundImage: UIImage?
@ -33,3 +35,59 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbo
}
return backgroundImage
}
func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal<UIColor, NoError> {
if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
return .single(color)
} else {
switch wallpaper {
case .builtin, .color:
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
case let .image(representations):
if let largest = largestImageRepresentation(representations) {
return Signal<UIColor, NoError> { subscriber in
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
let data = (postbox.mediaBox.resourceData(largest.resource)
|> mapToSignal { data -> Signal<UIColor, NoError> in
if data.complete {
let image = UIImage(contentsOfFile: data.path)
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
context.withFlippedContext({ context in
if let cgImage = image?.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
}
})
var color = context.colorAt(CGPoint())
var hue: CGFloat = 0.0
var saturation: CGFloat = 0.0
var brightness: CGFloat = 0.0
var alpha: CGFloat = 0.0
if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
brightness = max(0.0, brightness * 0.65)
alpha = 0.4
color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
}
return .single(color)
}
return .complete()
}).start(next: { next in
subscriber.putNext(next)
}, completed: {
subscriber.putCompletion()
})
return ActionDisposable {
fetch.dispose()
data.dispose()
}
}
|> afterNext { color in
serviceBackgroundColorForWallpaper = (wallpaper, color)
}
} else {
return .single(UIColor(rgb: 0x000000, alpha: 0.3))
}
}
}
}

View File

@ -9,10 +9,14 @@ private let titleFont = Font.medium(16.0)
private final class ChatMessageActionButtonNode: ASDisplayNode {
private let backgroundNode: ASImageNode
private var titleNode: TextNode?
private var iconNode: ASImageNode?
private var buttonView: HighlightTrackingButton?
private var button: ReplyMarkupButton?
var pressed: ((ReplyMarkupButton) -> Void)?
var longTapped: ((ReplyMarkupButton) -> Void)?
var longTapRecognizer: UILongPressGestureRecognizer?
override init() {
self.backgroundNode = ASImageNode()
@ -45,6 +49,11 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
}
}
}
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longTapGesture(_:)))
longTapRecognizer.minimumPressDuration = 0.3
buttonView.addGestureRecognizer(longTapRecognizer)
self.longTapRecognizer = longTapRecognizer
}
@objc func buttonPressed() {
@ -53,6 +62,12 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
}
}
@objc func longTapGesture(_ recognizer: UILongPressGestureRecognizer) {
if let button = self.button, let longTapped = self.longTapped, recognizer.state == .began {
longTapped(button)
}
}
class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ account: Account, _ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ message: Message, _ button: ReplyMarkupButton, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, () -> ChatMessageActionButtonNode))) {
let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode)
@ -88,6 +103,22 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
backgroundImage = incoming ? graphics.chatBubbleActionButtonIncomingBottomSingleImage : graphics.chatBubbleActionButtonOutgoingBottomSingleImage
}
let iconImage: UIImage?
switch button.action {
case .text:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage
case .url:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLinkIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
case .requestPhone:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPhoneIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
case .requestMap:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLocationIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
case .switchInline:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingShareIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
default:
iconImage = nil
}
return (titleSize.size.width + sideInset + sideInset, { width in
return (CGSize(width: width, height: 42.0), {
let node: ChatMessageActionButtonNode
@ -99,9 +130,29 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
node.button = button
switch button.action {
case .url:
node.longTapRecognizer?.isEnabled = true
default:
node.longTapRecognizer?.isEnabled = false
}
node.backgroundNode.image = backgroundImage
node.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0))
if iconImage != nil {
if node.iconNode == nil {
let iconNode = ASImageNode()
iconNode.contentMode = .center
node.iconNode = iconNode
node.addSubnode(iconNode)
}
node.iconNode?.image = iconImage
} else if node.iconNode != nil {
node.iconNode?.removeFromSupernode()
node.iconNode = nil
}
let titleNode = titleApply()
if node.titleNode !== titleNode {
node.titleNode = titleNode
@ -110,7 +161,9 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
}
titleNode.frame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size)
node.buttonView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0))
node.iconNode?.frame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0)
return node
})
@ -123,7 +176,9 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
private var buttonNodes: [ChatMessageActionButtonNode] = []
private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)?
private var buttonLongTappedWrapper: ((ReplyMarkupButton) -> Void)?
var buttonPressed: ((ReplyMarkupButton) -> Void)?
var buttonLongTapped: ((ReplyMarkupButton) -> Void)?
override init() {
super.init()
@ -133,6 +188,12 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
buttonPressed(button)
}
}
self.buttonLongTappedWrapper = { [weak self] button in
if let buttonLongTapped = self?.buttonLongTapped {
buttonLongTapped(button)
}
}
}
class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ account: Account, _ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animated: Bool) -> ChatMessageActionButtonsNode)) {
@ -230,6 +291,7 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
if buttonNode.supernode == nil {
node.addSubnode(buttonNode)
buttonNode.pressed = node.buttonPressedWrapper
buttonNode.longTapped = node.buttonLongTappedWrapper
}
index += 1
}

View File

@ -1424,6 +1424,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
strongSelf.performMessageButtonAction(button: button)
}
}
actionButtonsNode.buttonLongTapped = { button in
if let strongSelf = self {
strongSelf.presentMessageButtonContextMenu(button: button)
}
}
strongSelf.addSubnode(actionButtonsNode)
} else {
if case let .System(duration) = animation {

View File

@ -452,6 +452,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
strongSelf.performMessageButtonAction(button: button)
}
}
actionButtonsNode.buttonLongTapped = { button in
if let strongSelf = self {
strongSelf.presentMessageButtonContextMenu(button: button)
}
}
strongSelf.addSubnode(actionButtonsNode)
} else {
if case let .System(duration) = animation {

View File

@ -236,4 +236,15 @@ public class ChatMessageItemView: ListViewItemNode {
}
}
}
func presentMessageButtonContextMenu(button: ReplyMarkupButton) {
if let item = self.item {
switch button.action {
case let .url(url):
item.controllerInteraction.longTap(.url(url))
default:
break
}
}
}
}

View File

@ -61,6 +61,14 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var hasReplyMarkup: Bool = false
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty {
hasReplyMarkup = true
break
}
}
let bubbleInsets: UIEdgeInsets
let sizeCalculation: InteractiveMediaNodeSizeCalculation
@ -91,6 +99,8 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
var updatedPosition: ChatMessageBubbleContentPosition = position
if forceFullCorners, case .linear = updatedPosition {
updatedPosition = .linear(top: .None(.None(.None)), bottom: .None(.None(.None)))
} else if hasReplyMarkup, case let .linear(top, _) = updatedPosition {
updatedPosition = .linear(top: top, bottom: .Neighbour)
}
let imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: updatedPosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius)

View File

@ -431,6 +431,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.performMessageButtonAction(button: button)
}
}
actionButtonsNode.buttonLongTapped = { button in
if let strongSelf = self {
strongSelf.presentMessageButtonContextMenu(button: button)
}
}
strongSelf.addSubnode(actionButtonsNode)
} else {
if case let .System(duration) = animation {

View File

@ -0,0 +1,37 @@
import Foundation
import Display
import TelegramCore
func totalDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? 0
} catch {
return 0
}
}
func freeDiskSpace() -> Int64 {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
return (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
} catch {
return 0
}
}
func checkAvailableDiskSpace(account: Account, threshold: Int64 = 100 * 1024 * 1024, present: @escaping (ViewController, Any?) -> Void) -> Bool {
guard freeDiskSpace() < threshold else {
return true
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = textAlertController(account: account, title: nil, text: presentationData.strings.Cache_LowDiskSpaceText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
let controller = storageUsageController(account: account, isModal: true)
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
present(controller, nil)
return false
}

View File

@ -34,17 +34,17 @@ final class ComposeControllerNode: ASDisplayNode {
var openCreateNewSecretChatImpl: (() -> Void)?
var openCreateNewChannelImpl: (() -> Void)?
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: [
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewGroup, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
self.contactListNode = ContactListNode(account: account, presentation: .single(.natural(displaySearch: true, options: [
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewGroup, icon: .generic(UIImage(bundleImageName: "Contact List/CreateGroupActionIcon")!), action: {
openCreateNewGroupImpl?()
}),
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewEncryptedChat, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateSecretChatActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewEncryptedChat, icon: .generic(UIImage(bundleImageName: "Contact List/CreateSecretChatActionIcon")!), action: {
openCreateNewSecretChatImpl?()
}),
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateChannelActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: {
openCreateNewChannelImpl?()
})
]), displayPermissionPlaceholder: false)
])), displayPermissionPlaceholder: false)
super.init()

View File

@ -189,9 +189,9 @@ class ContactsAddItemNode: ListViewItemNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 48.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 50.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 14.0), size: titleLayout.size)
return (nodeLayout, { [weak self] animated in
if let strongSelf = self {
@ -216,7 +216,7 @@ class ContactsAddItemNode: ListViewItemNode {
if let updatedIcon = updatedIcon {
strongSelf.iconNode.image = updatedIcon
}
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(x: 14.0, y: 4.0, width: 40.0, height: 40.0))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(x: 14.0, y: 5.0, width: 40.0, height: 40.0))
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))

View File

@ -3,14 +3,59 @@ import Display
import AsyncDisplayKit
import SwiftSignalKit
public enum ContactListActionItemInlineIconPosition {
case left
case right
}
public enum ContactListActionItemIcon : Equatable {
case none
case generic(UIImage)
case inline(UIImage, ContactListActionItemInlineIconPosition)
var image: UIImage? {
switch self {
case .none:
return nil
case let .generic(image):
return image
case let .inline(image, _):
return image
}
}
public static func ==(lhs: ContactListActionItemIcon, rhs: ContactListActionItemIcon) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case let .generic(image):
if case .generic(image) = rhs {
return true
} else {
return false
}
case let .inline(image, position):
if case .inline(image, position) = rhs {
return true
} else {
return false
}
}
}
}
class ContactListActionItem: ListViewItem {
let theme: PresentationTheme
let title: String
let icon: UIImage?
let icon: ContactListActionItemIcon
let action: () -> Void
let header: ListViewItemHeader?
init(theme: PresentationTheme, title: String, icon: UIImage?, header: ListViewItemHeader?, action: @escaping () -> Void) {
init(theme: PresentationTheme, title: String, icon: ContactListActionItemIcon, header: ListViewItemHeader?, action: @escaping () -> Void) {
self.theme = theme
self.title = title
self.icon = icon
@ -152,13 +197,13 @@ class ContactListActionItemNode: ListViewItemNode {
}
var leftInset: CGFloat = 16.0 + params.leftInset
if item.icon != nil {
if case .generic = item.icon {
leftInset += 49.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - 10.0 - leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize = CGSize(width: params.width, height: 48.0)
let contentSize = CGSize(width: params.width, height: 50.0)
let insets = UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)
let separatorHeight = UIScreenPixel
@ -174,13 +219,13 @@ class ContactListActionItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
strongSelf.iconNode.image = generateTintedImage(image: item.icon.image, color: item.theme.list.itemAccentColor)
}
let _ = titleApply()
strongSelf.iconNode.image = item.icon
if let image = item.icon {
if let image = item.icon.image {
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
}
@ -200,7 +245,7 @@ class ContactListActionItemNode: ListViewItemNode {
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 48.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -172,7 +172,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
interaction.suppressWarning()
})
case let .permissionEnable(theme, text):
return ContactListActionItem(theme: theme, title: text, icon: nil, header: nil, action: {
return ContactListActionItem(theme: theme, title: text, icon: .none, header: nil, action: {
interaction.authorize()
})
case let .option(_, option, header, theme, _):
@ -457,25 +457,30 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
var indexHeader: unichar = 35
switch peer.indexName {
case let .title(title, _):
if let c = title.uppercased().utf16.first {
if let c = title.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
indexHeader = c
}
case let .personName(first, last, _, _):
switch sortOrder {
case .firstLast:
if let c = first.uppercased().utf16.first {
if let c = first.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
indexHeader = c
} else if let c = last.uppercased().utf16.first {
} else if let c = last.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
indexHeader = c
}
case .lastFirst:
if let c = last.uppercased().utf16.first {
if let c = last.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
indexHeader = c
} else if let c = first.uppercased().utf16.first {
} else if let c = first.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
indexHeader = c
}
}
}
if let scalar = UnicodeScalar(indexHeader), !NSCharacterSet.uppercaseLetters.contains(scalar) {
if let c = "#".utf16.first {
indexHeader = c
}
}
let header: ContactListNameIndexHeader
if let cached = headerCache[indexHeader] {
header = cached
@ -590,11 +595,11 @@ private struct ContactsListNodeTransition {
public struct ContactListAdditionalOption: Equatable {
public let title: String
public let icon: UIImage?
public let icon: ContactListActionItemIcon
public let action: () -> Void
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {
return lhs.title == rhs.title && lhs.icon === rhs.icon
return lhs.title == rhs.title && lhs.icon == rhs.icon
}
}
@ -638,11 +643,11 @@ enum ContactListFilter {
final class ContactListNode: ASDisplayNode {
private let account: Account
private let presentation: ContactListPresentation
private var presentation: ContactListPresentation?
private let filters: [ContactListFilter]
let listNode: ListView
private var indexNode: CollectionIndexNode?
private var indexNode: CollectionIndexNode
private var indexSections: [String]?
private var queuedTransitions: [ContactsListNodeTransition] = []
@ -696,9 +701,8 @@ final class ContactListNode: ASDisplayNode {
private var authorizationNode: PermissionContentNode
private let displayPermissionPlaceholder: Bool
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true) {
init(account: Account, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true) {
self.account = account
self.presentation = presentation
self.filters = filters
self.displayPermissionPlaceholder = displayPermissionPlaceholder
@ -707,13 +711,7 @@ final class ContactListNode: ASDisplayNode {
self.listNode = ListView()
self.listNode.dynamicBounceEnabled = !self.presentationData.disableAnimations
var generateSections = false
if case .natural = presentation {
generateSections = true
self.indexNode = CollectionIndexNode()
} else {
self.indexNode = nil
}
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations))
@ -751,15 +749,13 @@ final class ContactListNode: ASDisplayNode {
super.init()
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
if self.indexNode == nil {
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
}
//self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
self.selectionStateValue = selectionState
self.selectionStatePromise.set(.single(selectionState))
self.addSubnode(self.listNode)
self.indexNode.flatMap(self.addSubnode)
self.addSubnode(self.indexNode)
self.addSubnode(self.authorizationNode)
let processingQueue = Queue()
@ -775,7 +771,7 @@ final class ContactListNode: ASDisplayNode {
self?.openPeer?(peer)
})
self.indexNode?.indexSelected = { [weak self] section in
self.indexNode.indexSelected = { [weak self] section in
guard let strongSelf = self, let entries = previousEntries.with({ $0 }) else {
return
}
@ -811,8 +807,16 @@ final class ContactListNode: ASDisplayNode {
let selectionStateSignal = self.selectionStatePromise.get()
let transition: Signal<ContactsListNodeTransition, NoError>
let themeAndStringsPromise = self.themeAndStringsPromise
transition = presentation
|> mapToSignal { presentation in
var generateSections = false
if case .natural = presentation {
generateSections = true
}
if case let .search(query, searchChatList, searchDeviceContacts) = presentation {
transition = query
return query
|> mapToSignal { query in
let foundLocalContacts: Signal<([Peer], [PeerId : PeerPresence]), NoError>
if searchChatList {
@ -928,7 +932,7 @@ final class ContactListNode: ASDisplayNode {
}
}
} else {
transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get())
return (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get())
|> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus, warningSuppressed -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
@ -999,6 +1003,7 @@ final class ContactListNode: ASDisplayNode {
})
|> deliverOnMainQueue
}
}
self.disposable.set(transition.start(next: { [weak self] transition in
self?.enqueueTransition(transition)
}))
@ -1127,7 +1132,7 @@ final class ContactListNode: ASDisplayNode {
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if let indexNode = self.indexNode, let indexSections = self.indexSections {
if let indexSections = self.indexSections {
var insets = layout.insets(options: [.input])
if let inputHeight = layout.inputHeight {
insets.bottom -= inputHeight
@ -1137,7 +1142,7 @@ final class ContactListNode: ASDisplayNode {
let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom))
transition.updateFrame(node: indexNode, frame: indexNodeFrame)
indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition)
self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition)
}
self.authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition)
@ -1168,11 +1173,11 @@ final class ContactListNode: ASDisplayNode {
} else if transition.animation != .none {
if transition.animation == .insertion {
options.insert(.AnimateInsertion)
} else if case .orderedByPresence = self.presentation {
} else if let presentation = self.presentation, case .orderedByPresence = presentation {
options.insert(.AnimateCrossfade)
}
}
if let indexNode = self.indexNode, let layout = self.validLayout {
if let layout = self.validLayout {
self.indexSections = transition.indexSections
var insets = layout.insets(options: [.input])
@ -1184,9 +1189,9 @@ final class ContactListNode: ASDisplayNode {
}
let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom))
indexNode.frame = indexNodeFrame
self.indexNode.frame = indexNodeFrame
indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .immediate)
self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut))
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
if let strongSelf = self {

View File

@ -56,7 +56,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
}
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, options: options), filters: filters, selectionState: ContactListNodeGroupSelectionState())
self.contactListNode = ContactListNode(account: account, presentation: .single(.natural(displaySearch: false, options: options)), filters: filters, selectionState: ContactListNodeGroupSelectionState())
self.tokenListNode = EditableTokenListNode(theme: EditableTokenListNodeTheme(backgroundColor: self.presentationData.theme.rootController.navigationBar.backgroundColor, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, selectedTextColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.chatList.searchBarKeyboardColor), placeholder: placeholder)
super.init()
@ -99,7 +99,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
if case let .peerSelection(value) = mode {
searchChatList = value
}
let searchResultsNode = ContactListNode(account: account, presentation: .search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false), filters: filters, selectionState: selectionState)
let searchResultsNode = ContactListNode(account: account, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false)), filters: filters, selectionState: selectionState)
searchResultsNode.openPeer = { peer in
self?.tokenListNode.setText("")
self?.openPeer?(peer)

View File

@ -39,7 +39,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.displayDeviceContacts = displayDeviceContacts
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: options))
self.contactListNode = ContactListNode(account: account, presentation: .single(.natural(displaySearch: true, options: options)))
self.dimNode = ASDisplayNode()

View File

@ -2,27 +2,36 @@ import Foundation
import Postbox
import SwiftSignalKit
public enum ContactsSortOrder: Int32 {
case presence
case natural
}
public struct ContactSynchronizationSettings: Equatable, PreferencesEntry {
public var synchronizeDeviceContacts: Bool
public var nameDisplayOrder: PresentationPersonNameOrder
public var sortOrder: ContactsSortOrder
public static var defaultSettings: ContactSynchronizationSettings {
return ContactSynchronizationSettings(synchronizeDeviceContacts: true, nameDisplayOrder: .firstLast)
return ContactSynchronizationSettings(synchronizeDeviceContacts: true, nameDisplayOrder: .firstLast, sortOrder: .presence)
}
public init(synchronizeDeviceContacts: Bool, nameDisplayOrder: PresentationPersonNameOrder) {
public init(synchronizeDeviceContacts: Bool, nameDisplayOrder: PresentationPersonNameOrder, sortOrder: ContactsSortOrder) {
self.synchronizeDeviceContacts = synchronizeDeviceContacts
self.nameDisplayOrder = nameDisplayOrder
self.sortOrder = sortOrder
}
public init(decoder: PostboxDecoder) {
self.synchronizeDeviceContacts = decoder.decodeInt32ForKey("synchronizeDeviceContacts", orElse: 0) != 0
self.nameDisplayOrder = PresentationPersonNameOrder(rawValue: decoder.decodeInt32ForKey("nameDisplayOrder", orElse: 0)) ?? .firstLast
self.sortOrder = ContactsSortOrder(rawValue: decoder.decodeInt32ForKey("sortOrder", orElse: 0)) ?? .presence
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.synchronizeDeviceContacts ? 1 : 0, forKey: "synchronizeDeviceContacts")
encoder.encodeInt32(self.nameDisplayOrder.rawValue, forKey: "synchronizeDeviceContacts")
encoder.encodeInt32(self.nameDisplayOrder.rawValue, forKey: "nameDisplayOrder")
encoder.encodeInt32(self.sortOrder.rawValue, forKey: "sortOrder")
}
public func isEqual(to: PreferencesEntry) -> Bool {

View File

@ -22,6 +22,7 @@ public class ContactsController: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var authorizationDisposable: Disposable?
private let sortOrderPromise = Promise<ContactsSortOrder>()
public init(account: Account) {
self.account = account
@ -46,6 +47,7 @@ public class ContactsController: ViewController {
self.tabBarItem.selectedImage = icon
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(self.sortPressed))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed))
self.scrollToTop = { [weak self] in
@ -68,27 +70,37 @@ public class ContactsController: ViewController {
}
})
let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings]))
if #available(iOSApplicationExtension 10.0, *) {
let warningKey = PostboxViewKey.noticeEntry(ApplicationSpecificNotice.contactsPermissionWarningKey())
let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings]))
self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(account: account, subject: .contacts), account.postbox.combinedView(keys: [warningKey, preferencesKey])
|> map { combined -> Bool in
let synchronizeDeviceContacts: Bool = ((combined.views[preferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings)?.synchronizeDeviceContacts ?? true
|> map { combined -> (Bool, ContactsSortOrder) in
let settings = ((combined.views[preferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings)
let synchronizeDeviceContacts: Bool = settings?.synchronizeDeviceContacts ?? true
let sortOrder: ContactsSortOrder = settings?.sortOrder ?? .presence
if !synchronizeDeviceContacts {
return true
return (true, sortOrder)
}
let timestamp = (combined.views[warningKey] as? NoticeEntryView)?.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
if let timestamp = timestamp, timestamp > 0 {
return true
return (true, sortOrder)
} else {
return false
return (false, sortOrder)
}
})
|> deliverOnMainQueue).start(next: { [weak self] status, suppressed in
|> deliverOnMainQueue).start(next: { [weak self] status, suppressedAndSortOrder in
if let strongSelf = self {
let (suppressed, sortOrder) = suppressedAndSortOrder
strongSelf.tabBarItem.badgeValue = status != .allowed && !suppressed ? "!" : nil
strongSelf.sortOrderPromise.set(.single(sortOrder))
}
})
} else {
self.sortOrderPromise.set(account.postbox.combinedView(keys: [preferencesKey])
|> map { combined -> ContactsSortOrder in
let settings = ((combined.views[preferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings)
return settings?.sortOrder ?? .presence
})
}
}
@ -113,7 +125,7 @@ public class ContactsController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = ContactsControllerNode(account: self.account, present: { [weak self] c, a in
self.displayNode = ContactsControllerNode(account: self.account, sortOrder: sortOrderPromise.get() |> distinctUntilChanged, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
})
self._ready.set(self.contactsNode.contactListNode.ready)
@ -226,6 +238,40 @@ public class ContactsController: ViewController {
}
}
func updateSortOrder(_ sortOrder: ContactsSortOrder) {
self.sortOrderPromise.set(.single(sortOrder))
let _ = updateContactSettingsInteractively(postbox: self.account.postbox) { current -> ContactSynchronizationSettings in
var updated = current
updated.sortOrder = sortOrder
return updated
}.start()
}
@objc func sortPressed() {
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: self.presentationData.strings.Contacts_SortBy))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Contacts_SortByName, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.updateSortOrder(.natural)
}
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Contacts_SortByPresence, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.updateSortOrder(.presence)
}
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
self.present(actionSheet, in: .window(.root))
}
@objc func addPressed() {
let _ = (DeviceAccess.authorizationStatus(account: self.account, subject: .contacts)
|> take(1)

View File

@ -22,15 +22,27 @@ final class ContactsControllerNode: ASDisplayNode {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
init(account: Account, present: @escaping (ViewController, Any?) -> Void) {
init(account: Account, sortOrder: Signal<ContactsSortOrder, NoError>, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
var inviteImpl: (() -> Void)?
self.contactListNode = ContactListNode(account: account, presentation: .orderedByPresence(options: [ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/AddMemberIcon"), color: self.presentationData.theme.list.itemAccentColor), action: {
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
inviteImpl?()
})]))
})]
let presentation = sortOrder
|> map { sortOrder -> ContactListPresentation in
switch sortOrder {
case .presence:
return .orderedByPresence(options: options)
case .natural:
return .natural(displaySearch: true, options: options)
}
}
self.contactListNode = ContactListNode(account: account, presentation: presentation)
super.init()

View File

@ -562,13 +562,13 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 48.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 50.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let titleFrame: CGRect
if statusAttributedString != nil {
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 4.0), size: titleLayout.size)
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 6.0), size: titleLayout.size)
} else {
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size)
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 14.0), size: titleLayout.size)
}
let peerRevealOptions: [ItemListRevealOption]
@ -641,7 +641,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
}
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 51.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0)))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: 5.0), size: CGSize(width: 40.0, height: 40.0)))
let _ = titleApply()
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame.offsetBy(dx: revealOffset, dy: 0.0))
@ -650,7 +650,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0
let _ = statusApply()
let statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 25.0), size: statusLayout.size)
let statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: 27.0), size: statusLayout.size)
let previousStatusFrame = strongSelf.statusNode.frame
strongSelf.statusNode.frame = statusFrame
@ -776,7 +776,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
var avatarFrame = self.avatarNode.frame
avatarFrame.origin.x = offset + leftInset - 51.0
avatarFrame.origin.x = offset + leftInset - 50.0
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
var titleFrame = self.titleNode.frame

View File

@ -1,7 +1,7 @@
import Foundation
import UIKit
private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> PresentationTheme {
private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroundColor: UIColor, day: Bool) -> PresentationTheme {
let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
let constructiveColor: UIColor = UIColor(rgb: 0x4cd964)
let secretColor: UIColor = UIColor(rgb: 0x00B12C)
@ -201,15 +201,15 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
outgoingFileDescriptionColor: UIColor(rgb: 0x6fb26a),
incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6),
outgoingFileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.3), withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45)),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x748391, alpha: 0.45)),
shareButtonStrokeColor: .clear,
shareButtonForegroundColor: .white,
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6),
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0),
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.25), withoutWallpaper: UIColor(rgb: 0x596E89, alpha: 0.35)),
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596E89, alpha: 0.35)),
actionButtonsIncomingStrokeColor: .clear,
actionButtonsIncomingTextColor: .white,
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.25), withoutWallpaper: UIColor(rgb: 0x596E89, alpha: 0.35)),
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596E89, alpha: 0.35)),
actionButtonsOutgoingStrokeColor: .clear,
actionButtonsOutgoingTextColor: .white,
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
@ -263,11 +263,11 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
mediaOverlayControlBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.6),
mediaOverlayControlForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0),
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x3996ee),
actionButtonsIncomingTextColor: UIColor(rgb: 0x3996ee),
actionButtonsIncomingStrokeColor: accentColor.withMultipliedBrightnessBy(1.2),
actionButtonsIncomingTextColor: accentColor.withMultipliedBrightnessBy(1.2),
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x3996ee),
actionButtonsOutgoingTextColor: UIColor(rgb: 0x3996ee),
actionButtonsOutgoingStrokeColor: accentColor.withMultipliedBrightnessBy(1.2),
actionButtonsOutgoingTextColor: accentColor.withMultipliedBrightnessBy(1.2),
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
selectionControlFillColor: accentColor,
selectionControlForegroundColor: .white,
@ -281,7 +281,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
)
let serviceMessage = PresentationThemeServiceMessage(
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x748391, alpha: 0.45), primaryText: .white, linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: UIColor(rgb: 0x748391, alpha: 0.45), dateFillFloating: UIColor(rgb: 0x939fab, alpha: 0.5)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x000000, alpha: 0.25), primaryText: .white, linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.25), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2))),
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x748391, alpha: 0.45), primaryText: .white, linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: UIColor(rgb: 0x748391, alpha: 0.45), dateFillFloating: UIColor(rgb: 0x939fab, alpha: 0.5)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: serviceBackgroundColor, primaryText: .white, linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: serviceBackgroundColor, dateFillFloating: serviceBackgroundColor.withAlphaComponent(serviceBackgroundColor.alpha * 0.6667))),
unreadBarFillColor: UIColor(white: 1.0, alpha: 0.9),
unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2),
unreadBarTextColor: UIColor(rgb: 0x86868d),
@ -425,10 +425,14 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
)
}
public let defaultPresentationTheme = makeDefaultPresentationTheme(accentColor: UIColor(rgb: 0x007ee5), day: false)
public let defaultPresentationTheme = makeDefaultPresentationTheme(accentColor: UIColor(rgb: 0x007ee5), serviceBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.3), day: false)
let defaultDayAccentColor: Int32 = 0x007ee5
func makeDefaultPresentationTheme(serviceBackgroundColor: UIColor?) -> PresentationTheme {
return makeDefaultPresentationTheme(accentColor: UIColor(rgb: 0x007ee5), serviceBackgroundColor: serviceBackgroundColor ?? .black, day: false)
}
func makeDefaultDayPresentationTheme(accentColor: Int32?) -> PresentationTheme {
let color: UIColor
if let accentColor = accentColor {
@ -436,5 +440,5 @@ func makeDefaultDayPresentationTheme(accentColor: Int32?) -> PresentationTheme {
} else {
color = UIColor(rgb: UInt32(bitPattern: defaultDayAccentColor))
}
return makeDefaultPresentationTheme(accentColor: color, day: true)
return makeDefaultPresentationTheme(accentColor: color, serviceBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.3), day: true)
}

View File

@ -732,7 +732,7 @@ class GalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -1390,7 +1390,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
}
if canCreateInviteLink {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
inviteByLinkImpl?()
}))
}

View File

@ -1199,7 +1199,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
if let map = media.media as? TelegramMediaMap {
let controller = legacyLocationController(message: nil, mapMedia: map, account: self.account, modal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in })
let controller = legacyLocationController(message: nil, mapMedia: map, account: self.account, isModal: false, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: { }, openUrl: { _ in })
self.pushController(controller)
return
}

View File

@ -276,7 +276,7 @@ class InstantPageGalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -225,7 +225,7 @@ private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceC
entries.append(.search(theme, strings))
entries.append(.option(0, ContactListAdditionalOption(title: strings.Contacts_ShareTelegram, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/InviteActionIcon"), color: theme.list.itemAccentColor), action: {
entries.append(.option(0, ContactListAdditionalOption(title: strings.Contacts_ShareTelegram, icon: .generic(UIImage(bundleImageName: "Contact List/InviteActionIcon")!), action: {
interaction.shareTelegram()
}), theme, strings))

View File

@ -381,7 +381,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
let editingOffset: CGFloat
if item.editing.editing {
let sizeAndApply = editableControlLayout(48.0, item.theme, false)
let sizeAndApply = editableControlLayout(50.0, item.theme, false)
editableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0.width
} else {
@ -425,7 +425,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
break
}
}
let contentSize = CGSize(width: params.width, height: 48.0)
let contentSize = CGSize(width: params.width, height: 50.0)
let separatorHeight = UIScreenPixel
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -544,8 +544,8 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: statusAttributedString == nil ? 13.0 : 5.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 25.0), size: statusLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: statusAttributedString == nil ? 14.0 : 6.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 27.0), size: statusLayout.size))
if let currentSwitchNode = currentSwitchNode {
if currentSwitchNode !== strongSelf.switchNode {
@ -608,7 +608,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset - rightInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 12.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0)))
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0, y: 5.0), size: CGSize(width: 40.0, height: 40.0)))
if item.peer.id == item.account.peerId, case .threatSelfAsSaved = item.aliasHandling {
strongSelf.avatarNode.setPeer(account: item.account, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.theme.list.mediaPlaceholderColor)
@ -616,7 +616,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.avatarNode.setPeer(account: item.account, peer: item.peer, emptyColor: item.theme.list.mediaPlaceholderColor)
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 48.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
if let presence = item.presence as? TelegramUserPresence {
strongSelf.peerPresenceManager?.reset(presence: presence)
@ -711,7 +711,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - self.labelNode.bounds.size.width - rightLabelInset, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size))
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 12.0, y: self.avatarNode.frame.minY), size: CGSize(width: 40.0, height: 40.0)))
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: CGSize(width: 40.0, height: 40.0)))
}
override func revealOptionsInteractivelyOpened() {

View File

@ -120,7 +120,7 @@ func legacyLocationPalette(from theme: PresentationTheme) -> TGLocationPallete {
return TGLocationPallete(backgroundColor: listTheme.plainBackgroundColor, selectionColor: listTheme.itemHighlightedBackgroundColor, separatorColor: listTheme.itemPlainSeparatorColor, textColor: listTheme.itemPrimaryTextColor, secondaryTextColor: listTheme.itemSecondaryTextColor, accentColor: listTheme.itemAccentColor, destructiveColor: listTheme.itemDestructiveColor, locationColor: UIColor(rgb: 0x008df2), liveLocationColor: UIColor(rgb: 0xff6464), iconColor: searchTheme.backgroundColor, sectionHeaderBackgroundColor: theme.chatList.sectionHeaderFillColor, sectionHeaderTextColor: theme.chatList.sectionHeaderTextColor, searchBarPallete: TGSearchBarPallete(dark: theme.overallDarkAppearance, backgroundColor: searchTheme.backgroundColor, highContrastBackgroundColor: searchTheme.backgroundColor, textColor: searchTheme.inputTextColor, placeholderColor: searchTheme.inputPlaceholderTextColor, clearIcon: generateClearIcon(color: theme.rootController.activeNavigationSearchBar.inputClearButtonColor), barBackgroundColor: searchTheme.backgroundColor, barSeparatorColor: searchTheme.separatorColor, plainBackgroundColor: searchTheme.backgroundColor, accentColor: searchTheme.accentColor, accentContrastColor: searchTheme.backgroundColor, menuBackgroundColor: searchTheme.backgroundColor, segmentedControlBackgroundImage: nil, segmentedControlSelectedImage: nil, segmentedControlHighlightedImage: nil, segmentedControlDividerImage: nil), avatarPlaceholder: nil)
}
func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, account: Account, modal: Bool, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController {
func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, account: Account, isModal: Bool, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController {
let legacyLocation = TGLocationMediaAttachment()
legacyLocation.latitude = mapMedia.latitude
legacyLocation.longitude = mapMedia.longitude
@ -130,7 +130,7 @@ func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, acc
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: modal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, strings: presentationData.strings)
let legacyController = LegacyController(presentation: isModal ? .modal(animateIn: true) : .navigation, theme: presentationData.theme, strings: presentationData.strings)
let controller: TGLocationViewController
if let message = message {
@ -227,7 +227,7 @@ func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, acc
let theme = (account.telegramApplicationContext.currentPresentationData.with { $0 }).theme
controller.pallete = legacyLocationPalette(from: theme)
controller.modalMode = modal
controller.modalMode = isModal
controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in
if let strongLegacyController = legacyController, let location = legacyLocation {
let map = telegramMap(for: location)
@ -249,7 +249,7 @@ func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, acc
}
}
if modal {
if isModal {
let navigationController = TGNavigationController(controllers: [controller])!
legacyController.bind(controller: navigationController)
controller.navigation_setDismiss({ [weak legacyController] in

View File

@ -199,7 +199,7 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
case let .map(mapMedia):
dismissInput()
let controller = legacyLocationController(message: message, mapMedia: mapMedia, account: account, modal: modal, openPeer: { peer in
let controller = legacyLocationController(message: message, mapMedia: mapMedia, account: account, isModal: modal, openPeer: { peer in
openPeer(peer, .info)
}, sendLiveLocation: { coordinate, period in
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)

View File

@ -80,7 +80,6 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
dismissInput()
present(LanguageLinkPreviewController(account: account, identifier: identifier), nil)
case let .proxy(host, port, username, password, secret):
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let server: ProxyServerSettings
if let secret = secret {
server = ProxyServerSettings(host: host, port: abs(port), connection: .mtp(secret: secret))
@ -128,23 +127,26 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
})
dismissInput()
case let .share(url, text):
let controller = PeerSelectionController(account: account)
controller.peerSelected = { [weak controller] peerId in
if let strongController = controller {
strongController.dismiss()
let textInputState: ChatTextInputState
case let .share(url, text, to):
let continueWithPeer: (PeerId) -> Void = { peerId in
let textInputState: ChatTextInputState?
if let text = text, !text.isEmpty {
if let url = url, !url.isEmpty {
let urlString = NSMutableAttributedString(string: "\(url)\n")
let textString = NSAttributedString(string: "\(text)")
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
urlString.append(textString)
textInputState = ChatTextInputState(inputText: urlString, selectionRange: selectionRange)
} else {
textInputState = ChatTextInputState(inputText: NSAttributedString(string: "\(text)"))
}
} else if let url = url, !url.isEmpty {
textInputState = ChatTextInputState(inputText: NSAttributedString(string: "\(url)"))
} else {
textInputState = nil
}
if let textInputState = textInputState {
let _ = (account.postbox.transaction({ transaction -> Void in
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
if let currentState = currentState as? ChatInterfaceState {
@ -159,9 +161,21 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
})
}
}
if let to = to {
} else {
let controller = PeerSelectionController(account: account)
controller.peerSelected = { [weak controller] peerId in
if let strongController = controller {
strongController.dismiss()
continueWithPeer(peerId)
}
}
if let navigationController = navigationController {
account.telegramApplicationContext.applicationBindings.dismissNativeController()
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
}
}
}
}

View File

@ -218,9 +218,7 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
}
let continueHandling: () -> Void = {
let handleInternalUrl: (String) -> Void = { url in
let _ = (resolveUrl(account: account, url: url)
|> deliverOnMainQueue).start(next: { resolved in
let handleRevolvedUrl: (ResolvedUrl) -> Void = { resolved in
if case let .externalUrl(value) = resolved {
applicationContext.applicationBindings.openUrl(value)
} else {
@ -259,7 +257,11 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
dismissInput()
})
}
})
}
let handleInternalUrl: (String) -> Void = { url in
let _ = (resolveUrl(account: account, url: url)
|> deliverOnMainQueue).start(next: handleRevolvedUrl)
}
if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == applicationContext.applicationBindings.appSpecificScheme), let query = parsedUrl.query {
@ -329,6 +331,26 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
convertedUrl = "https://t.me/setlanguage/\(lang)"
}
}
} else if parsedUrl.host == "msg" {
if let components = URLComponents(string: "/?" + query) {
var sharePhoneNumber: String?
var shareText: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "to" {
sharePhoneNumber = value
} else if queryItem.name == "text" {
shareText = value
}
}
}
}
if sharePhoneNumber != nil || shareText != nil {
handleRevolvedUrl(.share(url: nil, text: shareText, to: sharePhoneNumber))
return
}
}
} else if parsedUrl.host == "msg_url" {
if let components = URLComponents(string: "/?" + query) {
var shareUrl: String?

View File

@ -336,7 +336,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.recursivelyEnsureDisplaySynchronously(true)
contactListNode.enableUpdates = true
} else {
let contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: []))
let contactListNode = ContactListNode(account: account, presentation: .single(.natural(displaySearch: true, options: [])))
self.contactListNode = contactListNode
contactListNode.enableUpdates = true
contactListNode.activateSearch = { [weak self] in

View File

@ -358,6 +358,9 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
let contactSettings: ContactSynchronizationSettings = (view.views[preferencesKey] as! PreferencesView).values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings ?? ContactSynchronizationSettings.defaultSettings
return (.single(UIColor(rgb: 0x000000, alpha: 0.3))
|> then(chatServiceBackgroundColor(wallpaper: themeSettings.chatWallpaper, postbox: postbox)))
|> mapToSignal { serviceBackgroundColor in
return applicationBindings.applicationInForeground
|> mapToSignal({ inForeground -> Signal<PresentationData, NoError> in
if inForeground {
@ -389,7 +392,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
case let .builtin(reference):
switch reference {
case .dayClassic:
themeValue = defaultPresentationTheme
themeValue = makeDefaultPresentationTheme(serviceBackgroundColor: serviceBackgroundColor)
case .nightGrayscale:
themeValue = defaultDarkPresentationTheme
case .nightAccent:
@ -425,6 +428,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
})
}
}
}
public func defaultPresentationData() -> PresentationData {
let dateTimeFormat = currentDateTimeFormat()

File diff suppressed because it is too large Load Diff

View File

@ -222,6 +222,18 @@ public final class PrincipalThemeAdditionalGraphics {
public let chatBubbleActionButtonOutgoingBottomRightImage: UIImage
public let chatBubbleActionButtonOutgoingBottomSingleImage: UIImage
public let chatBubbleActionButtonIncomingMessageIconImage: UIImage
public let chatBubbleActionButtonIncomingLinkIconImage: UIImage
public let chatBubbleActionButtonIncomingShareIconImage: UIImage
public let chatBubbleActionButtonIncomingPhoneIconImage: UIImage
public let chatBubbleActionButtonIncomingLocationIconImage: UIImage
public let chatBubbleActionButtonOutgoingMessageIconImage: UIImage
public let chatBubbleActionButtonOutgoingLinkIconImage: UIImage
public let chatBubbleActionButtonOutgoingShareIconImage: UIImage
public let chatBubbleActionButtonOutgoingPhoneIconImage: UIImage
public let chatBubbleActionButtonOutgoingLocationIconImage: UIImage
init(_ theme: PresentationThemeChat, wallpaper: TelegramWallpaper) {
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
self.chatServiceBubbleFillImage = generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context -> Void in
@ -243,5 +255,15 @@ public final class PrincipalThemeAdditionalGraphics {
self.chatBubbleActionButtonOutgoingBottomLeftImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.bubble.actionButtonsOutgoingFillColor, wallpaper: wallpaper), strokeColor: theme.bubble.actionButtonsOutgoingStrokeColor, position: .bottomLeft)
self.chatBubbleActionButtonOutgoingBottomRightImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.bubble.actionButtonsOutgoingFillColor, wallpaper: wallpaper), strokeColor: theme.bubble.actionButtonsOutgoingStrokeColor, position: .bottomRight)
self.chatBubbleActionButtonOutgoingBottomSingleImage = messageBubbleActionButtonImage(color: bubbleVariableColor(variableColor: theme.bubble.actionButtonsOutgoingFillColor, wallpaper: wallpaper), strokeColor: theme.bubble.actionButtonsOutgoingStrokeColor, position: .bottomSingle)
self.chatBubbleActionButtonIncomingMessageIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: theme.bubble.actionButtonsIncomingTextColor)!
self.chatBubbleActionButtonIncomingLinkIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: theme.bubble.actionButtonsIncomingTextColor)!
self.chatBubbleActionButtonIncomingShareIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotShare"), color: theme.bubble.actionButtonsIncomingTextColor)!
self.chatBubbleActionButtonIncomingPhoneIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPhone"), color: theme.bubble.actionButtonsIncomingTextColor)!
self.chatBubbleActionButtonIncomingLocationIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLocation"), color: theme.bubble.actionButtonsIncomingTextColor)!
self.chatBubbleActionButtonOutgoingMessageIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: theme.bubble.actionButtonsOutgoingTextColor)!
self.chatBubbleActionButtonOutgoingLinkIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: theme.bubble.actionButtonsOutgoingTextColor)!
self.chatBubbleActionButtonOutgoingShareIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotShare"), color: theme.bubble.actionButtonsOutgoingTextColor)!
self.chatBubbleActionButtonOutgoingPhoneIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPhone"), color: theme.bubble.actionButtonsOutgoingTextColor)!
self.chatBubbleActionButtonOutgoingLocationIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLocation"), color: theme.bubble.actionButtonsOutgoingTextColor)!
}
}

View File

@ -4,14 +4,21 @@ import AsyncDisplayKit
import LegacyComponents
import SwiftSignalKit
private extension CAShapeLayer {
func animateStrokeStart(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeStart", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
}
func animateStrokeEnd(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeEnd", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
}
}
final class RadialDownloadContentNode: RadialStatusContentNode {
var color: UIColor {
didSet {
self.leftLine.fillColor = UIColor.clear.cgColor
self.leftLine.strokeColor = self.color.cgColor
self.rightLine.fillColor = UIColor.clear.cgColor
self.rightLine.strokeColor = self.color.cgColor
self.arrowBody.fillColor = UIColor.clear.cgColor
self.arrowBody.strokeColor = self.color.cgColor
self.setNeedsDisplay()
}
@ -69,7 +76,7 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
}
}
private func svgPath(_ path: StaticString, scale: CGFloat = 1.0, offset: CGPoint = CGPoint()) throws -> UIBezierPath {
private func svgPath(_ path: StaticString, scale: CGPoint = CGPoint(x: 1.0, y: 1.0), offset: CGPoint = CGPoint()) throws -> UIBezierPath {
var index: UnsafePointer<UInt8> = path.utf8Start
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
let path = UIBezierPath()
@ -78,22 +85,22 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
index = index.successor()
if c == 77 { // M
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
path.move(to: CGPoint(x: x, y: y))
} else if c == 76 { // L
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
path.addLine(to: CGPoint(x: x, y: y))
} else if c == 67 { // C
let x1 = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
let y1 = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
let x2 = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
let y2 = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
let x1 = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
let y1 = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
let x2 = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
let y2 = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
let y = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
path.addCurve(to: CGPoint(x: x, y: y), controlPoint1: CGPoint(x: x1, y: y1), controlPoint2: CGPoint(x: x2, y: y2))
} else if c == 32 { // space
continue
@ -107,6 +114,7 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
let bounds = self.bounds
let diameter = min(bounds.size.width, bounds.size.height)
let factor = diameter / 50.0
var lineWidth: CGFloat = 2.0
if diameter < 24.0 {
@ -117,20 +125,10 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
self.rightLine.lineWidth = lineWidth
self.arrowBody.lineWidth = lineWidth
let factor = diameter / 50.0
let arrowHeadSize: CGFloat = 15.0 * factor
let arrowLength: CGFloat = 18.0 * factor
let arrowHeadOffset: CGFloat = 1.0 * factor
var bodyPath = UIBezierPath()
if let path = try? svgPath("M1.20125335,62.2095675 C1.78718228,62.9863141 2.3877868,63.7395876 3.00158591,64.4690754 C22.1087455,87.1775489 54.0019347,86.8368674 54.0066002,54.0178571 L54.0066002,0.625 ", scale: 0.333333 * factor, offset: CGPoint(x: 7.0 * factor, y: (17.0 - UIScreenPixel) * factor)) {
bodyPath = path
}
self.arrowBody.path = bodyPath.cgPath
self.arrowBody.strokeStart = 0.62
let leftPath = UIBezierPath()
leftPath.move(to: CGPoint(x: diameter / 2.0, y: diameter / 2.0 + arrowLength / 2.0 + arrowHeadOffset))
leftPath.addLine(to: CGPoint(x: diameter / 2.0 - arrowHeadSize / 2.0, y: diameter / 2.0 + arrowLength / 2.0 - arrowHeadSize / 2.0 + arrowHeadOffset))
@ -145,6 +143,18 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
private let duration: Double = 0.2
override func prepareAnimateOut(completion: @escaping () -> Void) {
let bounds = self.bounds
let diameter = min(bounds.size.width, bounds.size.height)
let factor = diameter / 50.0
var bodyPath = UIBezierPath()
if let path = try? svgPath("M1.20125335,62.2095675 C1.78718228,62.9863141 2.3877868,63.7395876 3.00158591,64.4690754 C22.1087455,87.1775489 54.0019347,86.8368674 54.0066002,54.0178571 L54.0066002,0.625 ", scale: CGPoint(x: 0.333333 * factor, y: 0.333333 * factor), offset: CGPoint(x: 7.0 * factor, y: (17.0 - UIScreenPixel) * factor)) {
bodyPath = path
}
self.arrowBody.path = bodyPath.cgPath
self.arrowBody.strokeStart = 0.62
self.leftLine.animateStrokeEnd(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.rightLine.animateStrokeEnd(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
@ -162,16 +172,23 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
self.arrowBody.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.4, removeOnCompletion: false)
}
override func prepareAnimateIn(from: RadialStatusNodeState?) {
let bounds = self.bounds
let diameter = min(bounds.size.width, bounds.size.height)
let factor = diameter / 50.0
var bodyPath = UIBezierPath()
if let path = try? svgPath("M1.20125335,62.2095675 C1.78718228,62.9863141 2.3877868,63.7395876 3.00158591,64.4690754 C22.1087455,87.1775489 54.0019347,86.8368674 54.0066002,54.0178571 L54.0066002,0.625 ", scale: CGPoint(x: -0.333333 * factor, y: 0.333333 * factor), offset: CGPoint(x: 43.0 * factor, y: (17.0 - UIScreenPixel) * factor)) {
bodyPath = path
}
self.arrowBody.path = bodyPath.cgPath
self.arrowBody.strokeStart = 0.62
}
override func animateIn(from: RadialStatusNodeState) {
if case .progress = from {
var transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
transform = CATransform3DTranslate(transform, -50.0, 0.0, 0.0)
self.arrowBody.transform = transform
self.arrowBody.animateStrokeStart(from: 0.0, to: 0.62, duration: 0.5, removeOnCompletion: false, completion: { [weak self] _ in
UIView.performWithoutAnimation {
self?.arrowBody.transform = CATransform3DIdentity
}
})
self.arrowBody.animateStrokeStart(from: 0.0, to: 0.62, duration: 0.5, removeOnCompletion: false, completion: nil)
self.arrowBody.animateStrokeEnd(from: 0.0, to: 1.0, duration: 0.5, removeOnCompletion: false, completion: nil)
self.leftLine.animateStrokeEnd(from: 0.0, to: 1.0, duration: 0.2, delay: 0.3, removeOnCompletion: false)
@ -185,13 +202,3 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
}
}
}
private extension CAShapeLayer {
func animateStrokeStart(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeStart", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
}
func animateStrokeEnd(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeEnd", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
}
}

View File

@ -301,7 +301,7 @@ final class RadialProgressContentNode: RadialStatusContentNode {
self.cancelNode.layer.animateRotation(from: 0.0, to: CGFloat.pi / 3.0, duration: duration)
}
override func prepareAnimateIn() {
override func prepareAnimateIn(from: RadialStatusNodeState?) {
self.ready = true
self.spinnerNode.progress = self.progress
}

View File

@ -20,7 +20,7 @@ class RadialStatusContentNode: ASDisplayNode {
self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
}
func prepareAnimateIn() {
func prepareAnimateIn(from: RadialStatusNodeState?) {
}
func animateIn(from: RadialStatusNodeState) {

View File

@ -127,7 +127,7 @@ public enum RadialStatusNodeState: Equatable {
public final class RadialStatusNode: ASControlNode {
var backgroundNodeColor: UIColor {
didSet {
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), animated: false, completion: {})
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, completion: {})
}
}
@ -152,7 +152,7 @@ public final class RadialStatusNode: ASControlNode {
if contentNode !== self.contentNode {
self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
} else {
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: animated, completion: completion)
}
} else {
completion()
@ -177,13 +177,13 @@ public final class RadialStatusNode: ASControlNode {
if let contentNode = strongSelf.contentNode {
strongSelf.addSubnode(contentNode)
contentNode.frame = strongSelf.bounds
contentNode.prepareAnimateIn()
contentNode.prepareAnimateIn(from: fromState)
if strongSelf.isNodeLoaded {
contentNode.layout()
contentNode.animateIn(from: fromState)
}
}
strongSelf.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: previousContentNode, animated: animated, completion: completion)
})
} else {
previousContentNode.removeFromSupernode()
@ -195,7 +195,7 @@ public final class RadialStatusNode: ASControlNode {
contentNode.layout()
}
}
strongSelf.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
}
}
}
@ -203,14 +203,14 @@ public final class RadialStatusNode: ASControlNode {
self.contentNode = node
if let contentNode = self.contentNode {
contentNode.frame = self.bounds
contentNode.prepareAnimateIn()
contentNode.prepareAnimateIn(from: nil)
self.addSubnode(contentNode)
}
self.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion)
}
}
private func transitionToBackgroundColor(_ color: UIColor?, animated: Bool, completion: @escaping () -> Void) {
private func transitionToBackgroundColor(_ color: UIColor?, previousContentNode: RadialStatusContentNode?, animated: Bool, completion: @escaping () -> Void) {
let currentColor = self.backgroundNode?.color
var updated = false
@ -235,7 +235,9 @@ public final class RadialStatusNode: ASControlNode {
} else if let backgroundNode = self.backgroundNode {
self.backgroundNode = nil
if animated {
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak backgroundNode] _ in
backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
previousContentNode?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak backgroundNode] _ in
backgroundNode?.removeFromSupernode()
completion()
})

View File

@ -208,7 +208,7 @@ public final class SecretMediaPreviewController: ViewController {
public override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -170,7 +170,7 @@ class SecureIdDocumentGalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -251,7 +251,7 @@ private func stringForCategory(strings: PresentationStrings, category: PeerCache
}
}
func storageUsageController(account: Account) -> ViewController {
func storageUsageController(account: Account, isModal: Bool = false) -> ViewController {
let cacheSettingsPromise = Promise<CacheStorageSettings>()
cacheSettingsPromise.set(account.postbox.preferencesView(keys: [PreferencesKeys.cacheStorageSettings])
|> map { view -> CacheStorageSettings in
@ -672,10 +672,15 @@ func storageUsageController(account: Account) -> ViewController {
})
})
var dismissImpl: (() -> Void)?
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, cacheSettingsPromise.get(), statsPromise.get()) |> deliverOnMainQueue
|> map { presentationData, cacheSettings, cacheStats -> (ItemListControllerState, (ItemListNodeState<StorageUsageEntry>, StorageUsageEntry.ItemGenerationArguments)) in
let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
}) : nil
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Cache_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Cache_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats), style: .blocks, emptyStateItem: nil, animateChanges: false)
return (controllerState, (listState, arguments))
@ -687,6 +692,8 @@ func storageUsageController(account: Account) -> ViewController {
presentControllerImpl = { [weak controller] c in
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
dismissImpl = { [weak controller] in
controller?.dismiss()
}
return controller
}

View File

@ -71,7 +71,7 @@ class ThemeGalleryController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData))
self.title = "Chat Preview"
self.title = self.presentationData.strings.Wallpaper_Title
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
let initialEntries: [ThemeGalleryEntry] = wallpapers.map { ThemeGalleryEntry.wallpaper($0) }
@ -152,7 +152,7 @@ class ThemeGalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)

View File

@ -33,12 +33,8 @@ final class ThemeGridController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
switch mode {
case .wallpapers:
self.title = self.presentationData.strings.Wallpaper_Title
case .solidColors:
self.title = "Solid Colors"
}
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.scrollToTop = { [weak self] in
@ -69,12 +65,7 @@ final class ThemeGridController: ViewController {
}
private func updateThemeAndStrings() {
switch mode {
case .wallpapers:
self.title = self.presentationData.strings.Wallpaper_Title
case .solidColors:
self.title = "Solid Colors"
}
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))

View File

@ -19,7 +19,7 @@ enum ParsedInternalUrl {
case internalInstantView(url: String)
case confirmationCode(Int)
case cancelAccountReset(phone: String, hash: String)
case share(url: String, text: String?)
case share(url: String?, text: String?, to: String?)
}
private enum ParsedUrl {
@ -40,7 +40,7 @@ enum ResolvedUrl {
case localization(String)
case confirmationCode(Int)
case cancelAccountReset(phone: String, hash: String)
case share(url: String, text: String?)
case share(url: String?, text: String?, to: String?)
}
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
@ -163,7 +163,7 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
}
if let url = url {
return .share(url: url, text: text)
return .share(url: url, text: text, to: nil)
}
}
return nil
@ -231,8 +231,8 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
return .single(.confirmationCode(code))
case let .cancelAccountReset(phone, hash):
return .single(.cancelAccountReset(phone: phone, hash: hash))
case let .share(url, text):
return .single(.share(url: url, text: text))
case let .share(url, text, to):
return .single(.share(url: url, text: text, to: to))
}
}

View File

@ -213,7 +213,7 @@ class WebSearchGalleryController: ViewController {
override func loadDisplayNode() {
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
if let strongSelf = self {
strongSelf.present(controller, in: .window(.root), with: arguments)
strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true)
}
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)