Low disk space alert
Contacts sort selector Bot button icons tg://msg scheme support Calculate service message color using current wallpaper Various fixes
BIN
Images.xcassets/Chat/Message/BotLink.imageset/BotLink@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Images.xcassets/Chat/Message/BotLink.imageset/BotLink@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 322 B |
22
Images.xcassets/Chat/Message/BotLink.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Images.xcassets/Chat/Message/BotLocation.imageset/BotLocation@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Images.xcassets/Chat/Message/BotLocation.imageset/BotLocation@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 555 B |
22
Images.xcassets/Chat/Message/BotLocation.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Images.xcassets/Chat/Message/BotMessage.imageset/BotMessage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Images.xcassets/Chat/Message/BotMessage.imageset/BotMessage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
22
Images.xcassets/Chat/Message/BotMessage.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Images.xcassets/Chat/Message/BotPhone.imageset/BotPhone@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Images.xcassets/Chat/Message/BotPhone.imageset/BotPhone@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
22
Images.xcassets/Chat/Message/BotPhone.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Images.xcassets/Chat/Message/BotShare.imageset/BotShare@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Images.xcassets/Chat/Message/BotShare.imageset/BotShare@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
22
Images.xcassets/Chat/Message/BotShare.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -72,6 +72,7 @@
|
|||||||
099529AE21D045C400805E13 /* ThemeGridActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AD21D045C400805E13 /* ThemeGridActionNode.swift */; };
|
099529AE21D045C400805E13 /* ThemeGridActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AD21D045C400805E13 /* ThemeGridActionNode.swift */; };
|
||||||
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
||||||
099529B221D24F5800805E13 /* RadialDownloadContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B121D24F5800805E13 /* RadialDownloadContentNode.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 */; };
|
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
||||||
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
|
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
|
||||||
09B4EE4D21A7B73800847FA6 /* PermissionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4C21A7B73800847FA6 /* PermissionController.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
09B4EE4C21A7B73800847FA6 /* PermissionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionController.swift; sourceTree = "<group>"; };
|
||||||
@ -4665,6 +4667,7 @@
|
|||||||
0902838C2194AEB90067EFBD /* ImageTransparency.swift */,
|
0902838C2194AEB90067EFBD /* ImageTransparency.swift */,
|
||||||
09C9EA32219F79F600E90146 /* ID3Artwork.h */,
|
09C9EA32219F79F600E90146 /* ID3Artwork.h */,
|
||||||
09C9EA31219F79F500E90146 /* ID3Artwork.m */,
|
09C9EA31219F79F500E90146 /* ID3Artwork.m */,
|
||||||
|
099529B321D3E5D800805E13 /* CheckDiskSpace.swift */,
|
||||||
);
|
);
|
||||||
name = Utils;
|
name = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -5285,6 +5288,7 @@
|
|||||||
D0E9BA671F055B5500F079A4 /* BotCheckoutNativeCardEntryControllerNode.swift in Sources */,
|
D0E9BA671F055B5500F079A4 /* BotCheckoutNativeCardEntryControllerNode.swift in Sources */,
|
||||||
D0EC6D291EB9F58800EBF1C3 /* FetchVideoMediaResource.swift in Sources */,
|
D0EC6D291EB9F58800EBF1C3 /* FetchVideoMediaResource.swift in Sources */,
|
||||||
D0AFCC791F4C8D2C000720C6 /* InstantPageSlideshowItem.swift in Sources */,
|
D0AFCC791F4C8D2C000720C6 /* InstantPageSlideshowItem.swift in Sources */,
|
||||||
|
099529B421D3E5D800805E13 /* CheckDiskSpace.swift in Sources */,
|
||||||
D04281EF200E3D88009DDE36 /* GroupInfoSearchItem.swift in Sources */,
|
D04281EF200E3D88009DDE36 /* GroupInfoSearchItem.swift in Sources */,
|
||||||
D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */,
|
D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */,
|
||||||
D081E104217F57D2003CD921 /* LanguageLinkPreviewController.swift in Sources */,
|
D081E104217F57D2003CD921 /* LanguageLinkPreviewController.swift in Sources */,
|
||||||
|
|||||||
@ -273,7 +273,7 @@ class AvatarGalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -293,7 +293,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let editingOffset: CGFloat
|
let editingOffset: CGFloat
|
||||||
var editableControlSizeAndApply: (CGSize, () -> ItemListEditableControlNode)?
|
var editableControlSizeAndApply: (CGSize, () -> ItemListEditableControlNode)?
|
||||||
if item.editing {
|
if item.editing {
|
||||||
let sizeAndApply = editableControlLayout(56.0, item.theme, false)
|
let sizeAndApply = editableControlLayout(50.0, item.theme, false)
|
||||||
editableControlSizeAndApply = sizeAndApply
|
editableControlSizeAndApply = sizeAndApply
|
||||||
editingOffset = sizeAndApply.0.width
|
editingOffset = sizeAndApply.0.width
|
||||||
} else {
|
} 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 (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 outgoingIcon = PresentationResourcesCallList.outgoingIcon(item.theme)
|
||||||
let infoIcon = PresentationResourcesCallList.infoButton(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()
|
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()
|
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()
|
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))
|
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))
|
||||||
|
|||||||
@ -803,9 +803,8 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
|
|||||||
return nil
|
return nil
|
||||||
} |> deliverOnMainQueue).start(next: { link in
|
} |> deliverOnMainQueue).start(next: { link in
|
||||||
if let link = link {
|
if let link = link {
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
let shareController = ShareController(account: account, subject: .url(link))
|
||||||
let controller = ShareProxyServerActionSheetController(theme: presentationData.theme, strings: presentationData.strings, updatedPresentationData: .complete(), link: link)
|
presentControllerImpl?(shareController, nil)
|
||||||
presentControllerImpl?(controller, nil)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2476,6 +2476,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
|
guard let strongSelf = self, strongSelf.beginMediaRecordingRequestId == requestId else {
|
||||||
return
|
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>
|
let hasOngoingCall: Signal<Bool, NoError>
|
||||||
if let signal = strongSelf.account.telegramApplicationContext.hasOngoingCall {
|
if let signal = strongSelf.account.telegramApplicationContext.hasOngoingCall {
|
||||||
hasOngoingCall = signal
|
hasOngoingCall = signal
|
||||||
@ -4886,7 +4891,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.chatDisplayNode.dismissInput()
|
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?) {
|
private func openPeer(peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: Message?) {
|
||||||
@ -5453,7 +5458,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
|
|
||||||
switch strongSelf.peekActions {
|
switch strongSelf.peekActions {
|
||||||
case .standard:
|
case .standard:
|
||||||
if let peer = data.peer {
|
if let peer = data.peer, peer.id != strongSelf.account.peerId {
|
||||||
if let _ = data.peer as? TelegramUser {
|
if let _ = data.peer as? TelegramUser {
|
||||||
items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in
|
items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Display
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
private var backgroundImageForWallpaper: (TelegramWallpaper, UIImage)?
|
private var backgroundImageForWallpaper: (TelegramWallpaper, UIImage)?
|
||||||
|
private var serviceBackgroundColorForWallpaper: (TelegramWallpaper, UIColor)?
|
||||||
|
|
||||||
func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbox) -> UIImage? {
|
func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbox) -> UIImage? {
|
||||||
var backgroundImage: UIImage?
|
var backgroundImage: UIImage?
|
||||||
@ -33,3 +35,59 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbo
|
|||||||
}
|
}
|
||||||
return backgroundImage
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,10 +9,14 @@ private let titleFont = Font.medium(16.0)
|
|||||||
private final class ChatMessageActionButtonNode: ASDisplayNode {
|
private final class ChatMessageActionButtonNode: ASDisplayNode {
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
private var titleNode: TextNode?
|
private var titleNode: TextNode?
|
||||||
|
private var iconNode: ASImageNode?
|
||||||
private var buttonView: HighlightTrackingButton?
|
private var buttonView: HighlightTrackingButton?
|
||||||
|
|
||||||
private var button: ReplyMarkupButton?
|
private var button: ReplyMarkupButton?
|
||||||
var pressed: ((ReplyMarkupButton) -> Void)?
|
var pressed: ((ReplyMarkupButton) -> Void)?
|
||||||
|
var longTapped: ((ReplyMarkupButton) -> Void)?
|
||||||
|
|
||||||
|
var longTapRecognizer: UILongPressGestureRecognizer?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.backgroundNode = ASImageNode()
|
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() {
|
@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))) {
|
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)
|
let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
||||||
|
|
||||||
@ -88,6 +103,22 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
backgroundImage = incoming ? graphics.chatBubbleActionButtonIncomingBottomSingleImage : graphics.chatBubbleActionButtonOutgoingBottomSingleImage
|
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 (titleSize.size.width + sideInset + sideInset, { width in
|
||||||
return (CGSize(width: width, height: 42.0), {
|
return (CGSize(width: width, height: 42.0), {
|
||||||
let node: ChatMessageActionButtonNode
|
let node: ChatMessageActionButtonNode
|
||||||
@ -99,9 +130,29 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
|
|
||||||
node.button = button
|
node.button = button
|
||||||
|
|
||||||
|
switch button.action {
|
||||||
|
case .url:
|
||||||
|
node.longTapRecognizer?.isEnabled = true
|
||||||
|
default:
|
||||||
|
node.longTapRecognizer?.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
node.backgroundNode.image = backgroundImage
|
node.backgroundNode.image = backgroundImage
|
||||||
node.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0))
|
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()
|
let titleNode = titleApply()
|
||||||
if node.titleNode !== titleNode {
|
if node.titleNode !== titleNode {
|
||||||
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)
|
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.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
|
return node
|
||||||
})
|
})
|
||||||
@ -123,7 +176,9 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
private var buttonNodes: [ChatMessageActionButtonNode] = []
|
private var buttonNodes: [ChatMessageActionButtonNode] = []
|
||||||
|
|
||||||
private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)?
|
private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)?
|
||||||
|
private var buttonLongTappedWrapper: ((ReplyMarkupButton) -> Void)?
|
||||||
var buttonPressed: ((ReplyMarkupButton) -> Void)?
|
var buttonPressed: ((ReplyMarkupButton) -> Void)?
|
||||||
|
var buttonLongTapped: ((ReplyMarkupButton) -> Void)?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
@ -133,6 +188,12 @@ final class ChatMessageActionButtonsNode: ASDisplayNode {
|
|||||||
buttonPressed(button)
|
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)) {
|
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 {
|
if buttonNode.supernode == nil {
|
||||||
node.addSubnode(buttonNode)
|
node.addSubnode(buttonNode)
|
||||||
buttonNode.pressed = node.buttonPressedWrapper
|
buttonNode.pressed = node.buttonPressedWrapper
|
||||||
|
buttonNode.longTapped = node.buttonLongTappedWrapper
|
||||||
}
|
}
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1424,6 +1424,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
strongSelf.performMessageButtonAction(button: button)
|
strongSelf.performMessageButtonAction(button: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actionButtonsNode.buttonLongTapped = { button in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||||
|
}
|
||||||
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration) = animation {
|
||||||
|
|||||||
@ -452,6 +452,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
|||||||
strongSelf.performMessageButtonAction(button: button)
|
strongSelf.performMessageButtonAction(button: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actionButtonsNode.buttonLongTapped = { button in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||||
|
}
|
||||||
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration) = animation {
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 bubbleInsets: UIEdgeInsets
|
||||||
let sizeCalculation: InteractiveMediaNodeSizeCalculation
|
let sizeCalculation: InteractiveMediaNodeSizeCalculation
|
||||||
|
|
||||||
@ -91,6 +99,8 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var updatedPosition: ChatMessageBubbleContentPosition = position
|
var updatedPosition: ChatMessageBubbleContentPosition = position
|
||||||
if forceFullCorners, case .linear = updatedPosition {
|
if forceFullCorners, case .linear = updatedPosition {
|
||||||
updatedPosition = .linear(top: .None(.None(.None)), bottom: .None(.None(.None)))
|
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)
|
let imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: updatedPosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius)
|
||||||
|
|||||||
@ -431,6 +431,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.performMessageButtonAction(button: button)
|
strongSelf.performMessageButtonAction(button: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actionButtonsNode.buttonLongTapped = { button in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.presentMessageButtonContextMenu(button: button)
|
||||||
|
}
|
||||||
|
}
|
||||||
strongSelf.addSubnode(actionButtonsNode)
|
strongSelf.addSubnode(actionButtonsNode)
|
||||||
} else {
|
} else {
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration) = animation {
|
||||||
|
|||||||
37
TelegramUI/CheckDiskSpace.swift
Normal 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
|
||||||
|
}
|
||||||
@ -34,17 +34,17 @@ final class ComposeControllerNode: ASDisplayNode {
|
|||||||
var openCreateNewSecretChatImpl: (() -> Void)?
|
var openCreateNewSecretChatImpl: (() -> Void)?
|
||||||
var openCreateNewChannelImpl: (() -> Void)?
|
var openCreateNewChannelImpl: (() -> Void)?
|
||||||
|
|
||||||
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: [
|
self.contactListNode = ContactListNode(account: account, presentation: .single(.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: {
|
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewGroup, icon: .generic(UIImage(bundleImageName: "Contact List/CreateGroupActionIcon")!), action: {
|
||||||
openCreateNewGroupImpl?()
|
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?()
|
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?()
|
openCreateNewChannelImpl?()
|
||||||
})
|
})
|
||||||
]), displayPermissionPlaceholder: false)
|
])), displayPermissionPlaceholder: false)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -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 (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
|
return (nodeLayout, { [weak self] animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -216,7 +216,7 @@ class ContactsAddItemNode: ListViewItemNode {
|
|||||||
if let updatedIcon = updatedIcon {
|
if let updatedIcon = updatedIcon {
|
||||||
strongSelf.iconNode.image = 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
|
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))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||||
|
|||||||
@ -3,14 +3,59 @@ import Display
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
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 {
|
class ContactListActionItem: ListViewItem {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let title: String
|
let title: String
|
||||||
let icon: UIImage?
|
let icon: ContactListActionItemIcon
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let header: ListViewItemHeader?
|
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.theme = theme
|
||||||
self.title = title
|
self.title = title
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
@ -152,13 +197,13 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var leftInset: CGFloat = 16.0 + params.leftInset
|
var leftInset: CGFloat = 16.0 + params.leftInset
|
||||||
if item.icon != nil {
|
if case .generic = item.icon {
|
||||||
leftInset += 49.0
|
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 (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 insets = UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
@ -174,13 +219,13 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
strongSelf.iconNode.image = generateTintedImage(image: item.icon.image, color: item.theme.list.itemAccentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|
||||||
strongSelf.iconNode.image = item.icon
|
if let image = item.icon.image {
|
||||||
|
|
||||||
if let image = item.icon {
|
|
||||||
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)
|
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.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))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,7 +172,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
interaction.suppressWarning()
|
interaction.suppressWarning()
|
||||||
})
|
})
|
||||||
case let .permissionEnable(theme, text):
|
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()
|
interaction.authorize()
|
||||||
})
|
})
|
||||||
case let .option(_, option, header, theme, _):
|
case let .option(_, option, header, theme, _):
|
||||||
@ -457,25 +457,30 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
|||||||
var indexHeader: unichar = 35
|
var indexHeader: unichar = 35
|
||||||
switch peer.indexName {
|
switch peer.indexName {
|
||||||
case let .title(title, _):
|
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
|
indexHeader = c
|
||||||
}
|
}
|
||||||
case let .personName(first, last, _, _):
|
case let .personName(first, last, _, _):
|
||||||
switch sortOrder {
|
switch sortOrder {
|
||||||
case .firstLast:
|
case .firstLast:
|
||||||
if let c = first.uppercased().utf16.first {
|
if let c = first.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
|
||||||
indexHeader = c
|
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
|
indexHeader = c
|
||||||
}
|
}
|
||||||
case .lastFirst:
|
case .lastFirst:
|
||||||
if let c = last.uppercased().utf16.first {
|
if let c = last.folding(options: .diacriticInsensitive, locale: .current).uppercased().utf16.first {
|
||||||
indexHeader = c
|
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
|
indexHeader = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let scalar = UnicodeScalar(indexHeader), !NSCharacterSet.uppercaseLetters.contains(scalar) {
|
||||||
|
if let c = "#".utf16.first {
|
||||||
|
indexHeader = c
|
||||||
|
}
|
||||||
|
}
|
||||||
let header: ContactListNameIndexHeader
|
let header: ContactListNameIndexHeader
|
||||||
if let cached = headerCache[indexHeader] {
|
if let cached = headerCache[indexHeader] {
|
||||||
header = cached
|
header = cached
|
||||||
@ -590,11 +595,11 @@ private struct ContactsListNodeTransition {
|
|||||||
|
|
||||||
public struct ContactListAdditionalOption: Equatable {
|
public struct ContactListAdditionalOption: Equatable {
|
||||||
public let title: String
|
public let title: String
|
||||||
public let icon: UIImage?
|
public let icon: ContactListActionItemIcon
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
|
||||||
public static func ==(lhs: ContactListAdditionalOption, rhs: ContactListAdditionalOption) -> Bool {
|
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 {
|
final class ContactListNode: ASDisplayNode {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let presentation: ContactListPresentation
|
private var presentation: ContactListPresentation?
|
||||||
private let filters: [ContactListFilter]
|
private let filters: [ContactListFilter]
|
||||||
|
|
||||||
let listNode: ListView
|
let listNode: ListView
|
||||||
private var indexNode: CollectionIndexNode?
|
private var indexNode: CollectionIndexNode
|
||||||
private var indexSections: [String]?
|
private var indexSections: [String]?
|
||||||
|
|
||||||
private var queuedTransitions: [ContactsListNodeTransition] = []
|
private var queuedTransitions: [ContactsListNodeTransition] = []
|
||||||
@ -696,9 +701,8 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
private var authorizationNode: PermissionContentNode
|
private var authorizationNode: PermissionContentNode
|
||||||
private let displayPermissionPlaceholder: Bool
|
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.account = account
|
||||||
self.presentation = presentation
|
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
||||||
|
|
||||||
@ -707,13 +711,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.dynamicBounceEnabled = !self.presentationData.disableAnimations
|
self.listNode.dynamicBounceEnabled = !self.presentationData.disableAnimations
|
||||||
|
|
||||||
var generateSections = false
|
|
||||||
if case .natural = presentation {
|
|
||||||
generateSections = true
|
|
||||||
self.indexNode = CollectionIndexNode()
|
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))
|
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()
|
super.init()
|
||||||
|
|
||||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
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.selectionStateValue = selectionState
|
||||||
self.selectionStatePromise.set(.single(selectionState))
|
self.selectionStatePromise.set(.single(selectionState))
|
||||||
|
|
||||||
self.addSubnode(self.listNode)
|
self.addSubnode(self.listNode)
|
||||||
self.indexNode.flatMap(self.addSubnode)
|
self.addSubnode(self.indexNode)
|
||||||
self.addSubnode(self.authorizationNode)
|
self.addSubnode(self.authorizationNode)
|
||||||
|
|
||||||
let processingQueue = Queue()
|
let processingQueue = Queue()
|
||||||
@ -775,7 +771,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
self?.openPeer?(peer)
|
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 {
|
guard let strongSelf = self, let entries = previousEntries.with({ $0 }) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -811,8 +807,16 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
let selectionStateSignal = self.selectionStatePromise.get()
|
let selectionStateSignal = self.selectionStatePromise.get()
|
||||||
let transition: Signal<ContactsListNodeTransition, NoError>
|
let transition: Signal<ContactsListNodeTransition, NoError>
|
||||||
let themeAndStringsPromise = self.themeAndStringsPromise
|
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 {
|
if case let .search(query, searchChatList, searchDeviceContacts) = presentation {
|
||||||
transition = query
|
return query
|
||||||
|> mapToSignal { query in
|
|> mapToSignal { query in
|
||||||
let foundLocalContacts: Signal<([Peer], [PeerId : PeerPresence]), NoError>
|
let foundLocalContacts: Signal<([Peer], [PeerId : PeerPresence]), NoError>
|
||||||
if searchChatList {
|
if searchChatList {
|
||||||
@ -928,7 +932,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
|> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus, warningSuppressed -> Signal<ContactsListNodeTransition, NoError> in
|
||||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||||
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
|
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
|
||||||
@ -999,6 +1003,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
})
|
})
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.disposable.set(transition.start(next: { [weak self] transition in
|
self.disposable.set(transition.start(next: { [weak self] transition in
|
||||||
self?.enqueueTransition(transition)
|
self?.enqueueTransition(transition)
|
||||||
}))
|
}))
|
||||||
@ -1127,7 +1132,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve)
|
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 })
|
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])
|
var insets = layout.insets(options: [.input])
|
||||||
if let inputHeight = layout.inputHeight {
|
if let inputHeight = layout.inputHeight {
|
||||||
insets.bottom -= 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))
|
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)
|
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)
|
self.authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition)
|
||||||
@ -1168,11 +1173,11 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
} else if transition.animation != .none {
|
} else if transition.animation != .none {
|
||||||
if transition.animation == .insertion {
|
if transition.animation == .insertion {
|
||||||
options.insert(.AnimateInsertion)
|
options.insert(.AnimateInsertion)
|
||||||
} else if case .orderedByPresence = self.presentation {
|
} else if let presentation = self.presentation, case .orderedByPresence = presentation {
|
||||||
options.insert(.AnimateCrossfade)
|
options.insert(.AnimateCrossfade)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let indexNode = self.indexNode, let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
self.indexSections = transition.indexSections
|
self.indexSections = transition.indexSections
|
||||||
|
|
||||||
var insets = layout.insets(options: [.input])
|
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))
|
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
|
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
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)
|
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()
|
super.init()
|
||||||
@ -99,7 +99,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
if case let .peerSelection(value) = mode {
|
if case let .peerSelection(value) = mode {
|
||||||
searchChatList = value
|
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
|
searchResultsNode.openPeer = { peer in
|
||||||
self?.tokenListNode.setText("")
|
self?.tokenListNode.setText("")
|
||||||
self?.openPeer?(peer)
|
self?.openPeer?(peer)
|
||||||
|
|||||||
@ -39,7 +39,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
|||||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
self.displayDeviceContacts = displayDeviceContacts
|
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()
|
self.dimNode = ASDisplayNode()
|
||||||
|
|
||||||
|
|||||||
@ -2,27 +2,36 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
public enum ContactsSortOrder: Int32 {
|
||||||
|
case presence
|
||||||
|
case natural
|
||||||
|
}
|
||||||
|
|
||||||
public struct ContactSynchronizationSettings: Equatable, PreferencesEntry {
|
public struct ContactSynchronizationSettings: Equatable, PreferencesEntry {
|
||||||
public var synchronizeDeviceContacts: Bool
|
public var synchronizeDeviceContacts: Bool
|
||||||
public var nameDisplayOrder: PresentationPersonNameOrder
|
public var nameDisplayOrder: PresentationPersonNameOrder
|
||||||
|
public var sortOrder: ContactsSortOrder
|
||||||
|
|
||||||
public static var defaultSettings: ContactSynchronizationSettings {
|
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.synchronizeDeviceContacts = synchronizeDeviceContacts
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
|
self.sortOrder = sortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.synchronizeDeviceContacts = decoder.decodeInt32ForKey("synchronizeDeviceContacts", orElse: 0) != 0
|
self.synchronizeDeviceContacts = decoder.decodeInt32ForKey("synchronizeDeviceContacts", orElse: 0) != 0
|
||||||
self.nameDisplayOrder = PresentationPersonNameOrder(rawValue: decoder.decodeInt32ForKey("nameDisplayOrder", orElse: 0)) ?? .firstLast
|
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) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeInt32(self.synchronizeDeviceContacts ? 1 : 0, forKey: "synchronizeDeviceContacts")
|
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 {
|
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public class ContactsController: ViewController {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
private var authorizationDisposable: Disposable?
|
private var authorizationDisposable: Disposable?
|
||||||
|
private let sortOrderPromise = Promise<ContactsSortOrder>()
|
||||||
|
|
||||||
public init(account: Account) {
|
public init(account: Account) {
|
||||||
self.account = account
|
self.account = account
|
||||||
@ -46,6 +47,7 @@ public class ContactsController: ViewController {
|
|||||||
self.tabBarItem.selectedImage = icon
|
self.tabBarItem.selectedImage = icon
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
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.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationAddIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.addPressed))
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
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, *) {
|
if #available(iOSApplicationExtension 10.0, *) {
|
||||||
let warningKey = PostboxViewKey.noticeEntry(ApplicationSpecificNotice.contactsPermissionWarningKey())
|
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])
|
self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(account: account, subject: .contacts), account.postbox.combinedView(keys: [warningKey, preferencesKey])
|
||||||
|> map { combined -> Bool in
|
|> map { combined -> (Bool, ContactsSortOrder) in
|
||||||
let synchronizeDeviceContacts: Bool = ((combined.views[preferencesKey] as? PreferencesView)?.values[ApplicationSpecificPreferencesKeys.contactSynchronizationSettings] as? ContactSynchronizationSettings)?.synchronizeDeviceContacts ?? true
|
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 {
|
if !synchronizeDeviceContacts {
|
||||||
return true
|
return (true, sortOrder)
|
||||||
}
|
}
|
||||||
let timestamp = (combined.views[warningKey] as? NoticeEntryView)?.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
|
let timestamp = (combined.views[warningKey] as? NoticeEntryView)?.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
|
||||||
if let timestamp = timestamp, timestamp > 0 {
|
if let timestamp = timestamp, timestamp > 0 {
|
||||||
return true
|
return (true, sortOrder)
|
||||||
} else {
|
} 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 {
|
if let strongSelf = self {
|
||||||
|
let (suppressed, sortOrder) = suppressedAndSortOrder
|
||||||
strongSelf.tabBarItem.badgeValue = status != .allowed && !suppressed ? "!" : nil
|
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() {
|
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?.present(c, in: .window(.root), with: a)
|
||||||
})
|
})
|
||||||
self._ready.set(self.contactsNode.contactListNode.ready)
|
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() {
|
@objc func addPressed() {
|
||||||
let _ = (DeviceAccess.authorizationStatus(account: self.account, subject: .contacts)
|
let _ = (DeviceAccess.authorizationStatus(account: self.account, subject: .contacts)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|
|||||||
@ -22,15 +22,27 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
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.account = account
|
||||||
|
|
||||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var inviteImpl: (() -> Void)?
|
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?()
|
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()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -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 (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
|
let titleFrame: CGRect
|
||||||
if statusAttributedString != nil {
|
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 {
|
} 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]
|
let peerRevealOptions: [ItemListRevealOption]
|
||||||
@ -641,7 +641,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
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()
|
let _ = titleApply()
|
||||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame.offsetBy(dx: revealOffset, dy: 0.0))
|
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
|
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0
|
||||||
|
|
||||||
let _ = statusApply()
|
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
|
let previousStatusFrame = strongSelf.statusNode.frame
|
||||||
|
|
||||||
strongSelf.statusNode.frame = statusFrame
|
strongSelf.statusNode.frame = statusFrame
|
||||||
@ -776,7 +776,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var avatarFrame = self.avatarNode.frame
|
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)
|
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||||
|
|
||||||
var titleFrame = self.titleNode.frame
|
var titleFrame = self.titleNode.frame
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
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 destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
let constructiveColor: UIColor = UIColor(rgb: 0x4cd964)
|
let constructiveColor: UIColor = UIColor(rgb: 0x4cd964)
|
||||||
let secretColor: UIColor = UIColor(rgb: 0x00B12C)
|
let secretColor: UIColor = UIColor(rgb: 0x00B12C)
|
||||||
@ -201,15 +201,15 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
|||||||
outgoingFileDescriptionColor: UIColor(rgb: 0x6fb26a),
|
outgoingFileDescriptionColor: UIColor(rgb: 0x6fb26a),
|
||||||
incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6),
|
incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6),
|
||||||
outgoingFileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8),
|
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,
|
shareButtonStrokeColor: .clear,
|
||||||
shareButtonForegroundColor: .white,
|
shareButtonForegroundColor: .white,
|
||||||
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6),
|
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6),
|
||||||
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0),
|
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,
|
actionButtonsIncomingStrokeColor: .clear,
|
||||||
actionButtonsIncomingTextColor: .white,
|
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,
|
actionButtonsOutgoingStrokeColor: .clear,
|
||||||
actionButtonsOutgoingTextColor: .white,
|
actionButtonsOutgoingTextColor: .white,
|
||||||
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
|
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
|
||||||
@ -263,11 +263,11 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
|||||||
mediaOverlayControlBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.6),
|
mediaOverlayControlBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.6),
|
||||||
mediaOverlayControlForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
mediaOverlayControlForegroundColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
|
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
|
||||||
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x3996ee),
|
actionButtonsIncomingStrokeColor: accentColor.withMultipliedBrightnessBy(1.2),
|
||||||
actionButtonsIncomingTextColor: UIColor(rgb: 0x3996ee),
|
actionButtonsIncomingTextColor: accentColor.withMultipliedBrightnessBy(1.2),
|
||||||
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
|
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8), withoutWallpaper: UIColor(rgb: 0xffffff, alpha: 0.8)),
|
||||||
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x3996ee),
|
actionButtonsOutgoingStrokeColor: accentColor.withMultipliedBrightnessBy(1.2),
|
||||||
actionButtonsOutgoingTextColor: UIColor(rgb: 0x3996ee),
|
actionButtonsOutgoingTextColor: accentColor.withMultipliedBrightnessBy(1.2),
|
||||||
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
|
selectionControlBorderColor: UIColor(rgb: 0xC7C7CC),
|
||||||
selectionControlFillColor: accentColor,
|
selectionControlFillColor: accentColor,
|
||||||
selectionControlForegroundColor: .white,
|
selectionControlForegroundColor: .white,
|
||||||
@ -281,7 +281,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
|||||||
)
|
)
|
||||||
|
|
||||||
let serviceMessage = PresentationThemeServiceMessage(
|
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),
|
unreadBarFillColor: UIColor(white: 1.0, alpha: 0.9),
|
||||||
unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2),
|
unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2),
|
||||||
unreadBarTextColor: UIColor(rgb: 0x86868d),
|
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
|
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 {
|
func makeDefaultDayPresentationTheme(accentColor: Int32?) -> PresentationTheme {
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
if let accentColor = accentColor {
|
if let accentColor = accentColor {
|
||||||
@ -436,5 +440,5 @@ func makeDefaultDayPresentationTheme(accentColor: Int32?) -> PresentationTheme {
|
|||||||
} else {
|
} else {
|
||||||
color = UIColor(rgb: UInt32(bitPattern: defaultDayAccentColor))
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -732,7 +732,7 @@ class GalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -1390,7 +1390,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canCreateInviteLink {
|
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?()
|
inviteByLinkImpl?()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1199,7 +1199,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let map = media.media as? TelegramMediaMap {
|
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)
|
self.pushController(controller)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -276,7 +276,7 @@ class InstantPageGalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -225,7 +225,7 @@ private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceC
|
|||||||
|
|
||||||
entries.append(.search(theme, strings))
|
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()
|
interaction.shareTelegram()
|
||||||
}), theme, strings))
|
}), theme, strings))
|
||||||
|
|
||||||
|
|||||||
@ -381,7 +381,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let editingOffset: CGFloat
|
let editingOffset: CGFloat
|
||||||
if item.editing.editing {
|
if item.editing.editing {
|
||||||
let sizeAndApply = editableControlLayout(48.0, item.theme, false)
|
let sizeAndApply = editableControlLayout(50.0, item.theme, false)
|
||||||
editableControlSizeAndApply = sizeAndApply
|
editableControlSizeAndApply = sizeAndApply
|
||||||
editingOffset = sizeAndApply.0.width
|
editingOffset = sizeAndApply.0.width
|
||||||
} else {
|
} else {
|
||||||
@ -425,7 +425,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let contentSize = CGSize(width: params.width, height: 48.0)
|
let contentSize = CGSize(width: params.width, height: 50.0)
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
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.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.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.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: 25.0), size: statusLayout.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 let currentSwitchNode = currentSwitchNode {
|
||||||
if currentSwitchNode !== strongSelf.switchNode {
|
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.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 {
|
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)
|
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.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 {
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
strongSelf.peerPresenceManager?.reset(presence: presence)
|
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.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() {
|
override func revealOptionsInteractivelyOpened() {
|
||||||
|
|||||||
@ -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)
|
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()
|
let legacyLocation = TGLocationMediaAttachment()
|
||||||
legacyLocation.latitude = mapMedia.latitude
|
legacyLocation.latitude = mapMedia.latitude
|
||||||
legacyLocation.longitude = mapMedia.longitude
|
legacyLocation.longitude = mapMedia.longitude
|
||||||
@ -130,7 +130,7 @@ func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, acc
|
|||||||
|
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
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
|
let controller: TGLocationViewController
|
||||||
|
|
||||||
if let message = message {
|
if let message = message {
|
||||||
@ -227,7 +227,7 @@ func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, acc
|
|||||||
let theme = (account.telegramApplicationContext.currentPresentationData.with { $0 }).theme
|
let theme = (account.telegramApplicationContext.currentPresentationData.with { $0 }).theme
|
||||||
controller.pallete = legacyLocationPalette(from: theme)
|
controller.pallete = legacyLocationPalette(from: theme)
|
||||||
|
|
||||||
controller.modalMode = modal
|
controller.modalMode = isModal
|
||||||
controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in
|
controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in
|
||||||
if let strongLegacyController = legacyController, let location = legacyLocation {
|
if let strongLegacyController = legacyController, let location = legacyLocation {
|
||||||
let map = telegramMap(for: location)
|
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])!
|
let navigationController = TGNavigationController(controllers: [controller])!
|
||||||
legacyController.bind(controller: navigationController)
|
legacyController.bind(controller: navigationController)
|
||||||
controller.navigation_setDismiss({ [weak legacyController] in
|
controller.navigation_setDismiss({ [weak legacyController] in
|
||||||
|
|||||||
@ -199,7 +199,7 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
|
|||||||
case let .map(mapMedia):
|
case let .map(mapMedia):
|
||||||
dismissInput()
|
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)
|
openPeer(peer, .info)
|
||||||
}, sendLiveLocation: { coordinate, period in
|
}, 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)
|
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)
|
||||||
|
|||||||
@ -80,7 +80,6 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
dismissInput()
|
dismissInput()
|
||||||
present(LanguageLinkPreviewController(account: account, identifier: identifier), nil)
|
present(LanguageLinkPreviewController(account: account, identifier: identifier), nil)
|
||||||
case let .proxy(host, port, username, password, secret):
|
case let .proxy(host, port, username, password, secret):
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
||||||
let server: ProxyServerSettings
|
let server: ProxyServerSettings
|
||||||
if let secret = secret {
|
if let secret = secret {
|
||||||
server = ProxyServerSettings(host: host, port: abs(port), connection: .mtp(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)
|
present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
})
|
})
|
||||||
dismissInput()
|
dismissInput()
|
||||||
case let .share(url, text):
|
case let .share(url, text, to):
|
||||||
let controller = PeerSelectionController(account: account)
|
let continueWithPeer: (PeerId) -> Void = { peerId in
|
||||||
controller.peerSelected = { [weak controller] peerId in
|
let textInputState: ChatTextInputState?
|
||||||
if let strongController = controller {
|
|
||||||
strongController.dismiss()
|
|
||||||
|
|
||||||
let textInputState: ChatTextInputState
|
|
||||||
if let text = text, !text.isEmpty {
|
if let text = text, !text.isEmpty {
|
||||||
|
if let url = url, !url.isEmpty {
|
||||||
let urlString = NSMutableAttributedString(string: "\(url)\n")
|
let urlString = NSMutableAttributedString(string: "\(url)\n")
|
||||||
let textString = NSAttributedString(string: "\(text)")
|
let textString = NSAttributedString(string: "\(text)")
|
||||||
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
|
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
|
||||||
urlString.append(textString)
|
urlString.append(textString)
|
||||||
textInputState = ChatTextInputState(inputText: urlString, selectionRange: selectionRange)
|
textInputState = ChatTextInputState(inputText: urlString, selectionRange: selectionRange)
|
||||||
} else {
|
} else {
|
||||||
|
textInputState = ChatTextInputState(inputText: NSAttributedString(string: "\(text)"))
|
||||||
|
}
|
||||||
|
} else if let url = url, !url.isEmpty {
|
||||||
textInputState = ChatTextInputState(inputText: NSAttributedString(string: "\(url)"))
|
textInputState = ChatTextInputState(inputText: NSAttributedString(string: "\(url)"))
|
||||||
|
} else {
|
||||||
|
textInputState = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let textInputState = textInputState {
|
||||||
let _ = (account.postbox.transaction({ transaction -> Void in
|
let _ = (account.postbox.transaction({ transaction -> Void in
|
||||||
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
||||||
if let currentState = currentState as? ChatInterfaceState {
|
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 {
|
if let navigationController = navigationController {
|
||||||
account.telegramApplicationContext.applicationBindings.dismissNativeController()
|
account.telegramApplicationContext.applicationBindings.dismissNativeController()
|
||||||
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -218,9 +218,7 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
|
|||||||
}
|
}
|
||||||
|
|
||||||
let continueHandling: () -> Void = {
|
let continueHandling: () -> Void = {
|
||||||
let handleInternalUrl: (String) -> Void = { url in
|
let handleRevolvedUrl: (ResolvedUrl) -> Void = { resolved in
|
||||||
let _ = (resolveUrl(account: account, url: url)
|
|
||||||
|> deliverOnMainQueue).start(next: { resolved in
|
|
||||||
if case let .externalUrl(value) = resolved {
|
if case let .externalUrl(value) = resolved {
|
||||||
applicationContext.applicationBindings.openUrl(value)
|
applicationContext.applicationBindings.openUrl(value)
|
||||||
} else {
|
} else {
|
||||||
@ -259,7 +257,11 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
|
|||||||
dismissInput()
|
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 {
|
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)"
|
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" {
|
} else if parsedUrl.host == "msg_url" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var shareUrl: String?
|
var shareUrl: String?
|
||||||
|
|||||||
@ -336,7 +336,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
self.recursivelyEnsureDisplaySynchronously(true)
|
self.recursivelyEnsureDisplaySynchronously(true)
|
||||||
contactListNode.enableUpdates = true
|
contactListNode.enableUpdates = true
|
||||||
} else {
|
} 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
|
self.contactListNode = contactListNode
|
||||||
contactListNode.enableUpdates = true
|
contactListNode.enableUpdates = true
|
||||||
contactListNode.activateSearch = { [weak self] in
|
contactListNode.activateSearch = { [weak self] in
|
||||||
|
|||||||
@ -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
|
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
|
return applicationBindings.applicationInForeground
|
||||||
|> mapToSignal({ inForeground -> Signal<PresentationData, NoError> in
|
|> mapToSignal({ inForeground -> Signal<PresentationData, NoError> in
|
||||||
if inForeground {
|
if inForeground {
|
||||||
@ -389,7 +392,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
case let .builtin(reference):
|
case let .builtin(reference):
|
||||||
switch reference {
|
switch reference {
|
||||||
case .dayClassic:
|
case .dayClassic:
|
||||||
themeValue = defaultPresentationTheme
|
themeValue = makeDefaultPresentationTheme(serviceBackgroundColor: serviceBackgroundColor)
|
||||||
case .nightGrayscale:
|
case .nightGrayscale:
|
||||||
themeValue = defaultDarkPresentationTheme
|
themeValue = defaultDarkPresentationTheme
|
||||||
case .nightAccent:
|
case .nightAccent:
|
||||||
@ -424,6 +427,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func defaultPresentationData() -> PresentationData {
|
public func defaultPresentationData() -> PresentationData {
|
||||||
|
|||||||
@ -222,6 +222,18 @@ public final class PrincipalThemeAdditionalGraphics {
|
|||||||
public let chatBubbleActionButtonOutgoingBottomRightImage: UIImage
|
public let chatBubbleActionButtonOutgoingBottomRightImage: UIImage
|
||||||
public let chatBubbleActionButtonOutgoingBottomSingleImage: 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) {
|
init(_ theme: PresentationThemeChat, wallpaper: TelegramWallpaper) {
|
||||||
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
|
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
|
||||||
self.chatServiceBubbleFillImage = generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context -> Void in
|
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.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.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.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)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,21 @@ import AsyncDisplayKit
|
|||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import SwiftSignalKit
|
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 {
|
final class RadialDownloadContentNode: RadialStatusContentNode {
|
||||||
var color: UIColor {
|
var color: UIColor {
|
||||||
didSet {
|
didSet {
|
||||||
self.leftLine.fillColor = UIColor.clear.cgColor
|
|
||||||
self.leftLine.strokeColor = self.color.cgColor
|
self.leftLine.strokeColor = self.color.cgColor
|
||||||
self.rightLine.fillColor = UIColor.clear.cgColor
|
|
||||||
self.rightLine.strokeColor = self.color.cgColor
|
self.rightLine.strokeColor = self.color.cgColor
|
||||||
self.arrowBody.fillColor = UIColor.clear.cgColor
|
|
||||||
self.arrowBody.strokeColor = self.color.cgColor
|
self.arrowBody.strokeColor = self.color.cgColor
|
||||||
self.setNeedsDisplay()
|
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
|
var index: UnsafePointer<UInt8> = path.utf8Start
|
||||||
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
|
let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount)
|
||||||
let path = UIBezierPath()
|
let path = UIBezierPath()
|
||||||
@ -78,22 +85,22 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
|
|||||||
index = index.successor()
|
index = index.successor()
|
||||||
|
|
||||||
if c == 77 { // M
|
if c == 77 { // M
|
||||||
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
|
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
|
||||||
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
|
let y = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
|
||||||
|
|
||||||
path.move(to: CGPoint(x: x, y: y))
|
path.move(to: CGPoint(x: x, y: y))
|
||||||
} else if c == 76 { // L
|
} else if c == 76 { // L
|
||||||
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
|
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
|
||||||
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
|
let y = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
|
||||||
|
|
||||||
path.addLine(to: CGPoint(x: x, y: y))
|
path.addLine(to: CGPoint(x: x, y: y))
|
||||||
} else if c == 67 { // C
|
} else if c == 67 { // C
|
||||||
let x1 = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
|
let x1 = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
|
||||||
let y1 = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
|
let y1 = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
|
||||||
let x2 = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
|
let x2 = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
|
||||||
let y2 = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
|
let y2 = try readCGFloat(&index, end: end, separator: 32) * scale.y + offset.y
|
||||||
let x = try readCGFloat(&index, end: end, separator: 44) * scale + offset.x
|
let x = try readCGFloat(&index, end: end, separator: 44) * scale.x + offset.x
|
||||||
let y = try readCGFloat(&index, end: end, separator: 32) * scale + offset.y
|
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))
|
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
|
} else if c == 32 { // space
|
||||||
continue
|
continue
|
||||||
@ -107,6 +114,7 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
|
|||||||
|
|
||||||
let bounds = self.bounds
|
let bounds = self.bounds
|
||||||
let diameter = min(bounds.size.width, bounds.size.height)
|
let diameter = min(bounds.size.width, bounds.size.height)
|
||||||
|
let factor = diameter / 50.0
|
||||||
|
|
||||||
var lineWidth: CGFloat = 2.0
|
var lineWidth: CGFloat = 2.0
|
||||||
if diameter < 24.0 {
|
if diameter < 24.0 {
|
||||||
@ -117,20 +125,10 @@ final class RadialDownloadContentNode: RadialStatusContentNode {
|
|||||||
self.rightLine.lineWidth = lineWidth
|
self.rightLine.lineWidth = lineWidth
|
||||||
self.arrowBody.lineWidth = lineWidth
|
self.arrowBody.lineWidth = lineWidth
|
||||||
|
|
||||||
let factor = diameter / 50.0
|
|
||||||
|
|
||||||
let arrowHeadSize: CGFloat = 15.0 * factor
|
let arrowHeadSize: CGFloat = 15.0 * factor
|
||||||
let arrowLength: CGFloat = 18.0 * factor
|
let arrowLength: CGFloat = 18.0 * factor
|
||||||
let arrowHeadOffset: CGFloat = 1.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()
|
let leftPath = UIBezierPath()
|
||||||
leftPath.move(to: CGPoint(x: diameter / 2.0, y: diameter / 2.0 + arrowLength / 2.0 + arrowHeadOffset))
|
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))
|
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
|
private let duration: Double = 0.2
|
||||||
|
|
||||||
override func prepareAnimateOut(completion: @escaping () -> Void) {
|
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.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)
|
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)
|
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) {
|
override func animateIn(from: RadialStatusNodeState) {
|
||||||
if case .progress = from {
|
if case .progress = from {
|
||||||
var transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
self.arrowBody.animateStrokeStart(from: 0.0, to: 0.62, duration: 0.5, removeOnCompletion: false, completion: nil)
|
||||||
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.animateStrokeEnd(from: 0.0, to: 1.0, 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)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -301,7 +301,7 @@ final class RadialProgressContentNode: RadialStatusContentNode {
|
|||||||
self.cancelNode.layer.animateRotation(from: 0.0, to: CGFloat.pi / 3.0, duration: duration)
|
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.ready = true
|
||||||
self.spinnerNode.progress = self.progress
|
self.spinnerNode.progress = self.progress
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class RadialStatusContentNode: ASDisplayNode {
|
|||||||
self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
|
self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareAnimateIn() {
|
func prepareAnimateIn(from: RadialStatusNodeState?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn(from: RadialStatusNodeState) {
|
func animateIn(from: RadialStatusNodeState) {
|
||||||
|
|||||||
@ -127,7 +127,7 @@ public enum RadialStatusNodeState: Equatable {
|
|||||||
public final class RadialStatusNode: ASControlNode {
|
public final class RadialStatusNode: ASControlNode {
|
||||||
var backgroundNodeColor: UIColor {
|
var backgroundNodeColor: UIColor {
|
||||||
didSet {
|
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 {
|
if contentNode !== self.contentNode {
|
||||||
self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
|
self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion)
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
completion()
|
completion()
|
||||||
@ -177,13 +177,13 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
if let contentNode = strongSelf.contentNode {
|
if let contentNode = strongSelf.contentNode {
|
||||||
strongSelf.addSubnode(contentNode)
|
strongSelf.addSubnode(contentNode)
|
||||||
contentNode.frame = strongSelf.bounds
|
contentNode.frame = strongSelf.bounds
|
||||||
contentNode.prepareAnimateIn()
|
contentNode.prepareAnimateIn(from: fromState)
|
||||||
if strongSelf.isNodeLoaded {
|
if strongSelf.isNodeLoaded {
|
||||||
contentNode.layout()
|
contentNode.layout()
|
||||||
contentNode.animateIn(from: fromState)
|
contentNode.animateIn(from: fromState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.transitionToBackgroundColor(backgroundColor, animated: animated, completion: completion)
|
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: previousContentNode, animated: animated, completion: completion)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
previousContentNode.removeFromSupernode()
|
previousContentNode.removeFromSupernode()
|
||||||
@ -195,7 +195,7 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
contentNode.layout()
|
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
|
self.contentNode = node
|
||||||
if let contentNode = self.contentNode {
|
if let contentNode = self.contentNode {
|
||||||
contentNode.frame = self.bounds
|
contentNode.frame = self.bounds
|
||||||
contentNode.prepareAnimateIn()
|
contentNode.prepareAnimateIn(from: nil)
|
||||||
self.addSubnode(contentNode)
|
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
|
let currentColor = self.backgroundNode?.color
|
||||||
|
|
||||||
var updated = false
|
var updated = false
|
||||||
@ -235,7 +235,9 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
} else if let backgroundNode = self.backgroundNode {
|
} else if let backgroundNode = self.backgroundNode {
|
||||||
self.backgroundNode = nil
|
self.backgroundNode = nil
|
||||||
if animated {
|
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()
|
backgroundNode?.removeFromSupernode()
|
||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -208,7 +208,7 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
public override func loadDisplayNode() {
|
public override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -170,7 +170,7 @@ class SecureIdDocumentGalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -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>()
|
let cacheSettingsPromise = Promise<CacheStorageSettings>()
|
||||||
cacheSettingsPromise.set(account.postbox.preferencesView(keys: [PreferencesKeys.cacheStorageSettings])
|
cacheSettingsPromise.set(account.postbox.preferencesView(keys: [PreferencesKeys.cacheStorageSettings])
|
||||||
|> map { view -> CacheStorageSettings in
|
|> 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
|
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, cacheSettingsPromise.get(), statsPromise.get()) |> deliverOnMainQueue
|
||||||
|> map { presentationData, cacheSettings, cacheStats -> (ItemListControllerState, (ItemListNodeState<StorageUsageEntry>, StorageUsageEntry.ItemGenerationArguments)) in
|
|> 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)
|
let listState = ItemListNodeState(entries: storageUsageControllerEntries(presentationData: presentationData, cacheSettings: cacheSettings, cacheStats: cacheStats), style: .blocks, emptyStateItem: nil, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
@ -687,6 +692,8 @@ func storageUsageController(account: Account) -> ViewController {
|
|||||||
presentControllerImpl = { [weak controller] c in
|
presentControllerImpl = { [weak controller] c in
|
||||||
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class ThemeGalleryController: ViewController {
|
|||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData))
|
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
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
|
|
||||||
let initialEntries: [ThemeGalleryEntry] = wallpapers.map { ThemeGalleryEntry.wallpaper($0) }
|
let initialEntries: [ThemeGalleryEntry] = wallpapers.map { ThemeGalleryEntry.wallpaper($0) }
|
||||||
@ -152,7 +152,7 @@ class ThemeGalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||
@ -33,12 +33,8 @@ final class ThemeGridController: ViewController {
|
|||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case .wallpapers:
|
|
||||||
self.title = self.presentationData.strings.Wallpaper_Title
|
self.title = self.presentationData.strings.Wallpaper_Title
|
||||||
case .solidColors:
|
|
||||||
self.title = "Solid Colors"
|
|
||||||
}
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
self.scrollToTop = { [weak self] in
|
||||||
@ -69,12 +65,7 @@ final class ThemeGridController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateThemeAndStrings() {
|
private func updateThemeAndStrings() {
|
||||||
switch mode {
|
|
||||||
case .wallpapers:
|
|
||||||
self.title = self.presentationData.strings.Wallpaper_Title
|
self.title = self.presentationData.strings.Wallpaper_Title
|
||||||
case .solidColors:
|
|
||||||
self.title = "Solid Colors"
|
|
||||||
}
|
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
|
|||||||
@ -19,7 +19,7 @@ enum ParsedInternalUrl {
|
|||||||
case internalInstantView(url: String)
|
case internalInstantView(url: String)
|
||||||
case confirmationCode(Int)
|
case confirmationCode(Int)
|
||||||
case cancelAccountReset(phone: String, hash: String)
|
case cancelAccountReset(phone: String, hash: String)
|
||||||
case share(url: String, text: String?)
|
case share(url: String?, text: String?, to: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParsedUrl {
|
private enum ParsedUrl {
|
||||||
@ -40,7 +40,7 @@ enum ResolvedUrl {
|
|||||||
case localization(String)
|
case localization(String)
|
||||||
case confirmationCode(Int)
|
case confirmationCode(Int)
|
||||||
case cancelAccountReset(phone: String, hash: String)
|
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? {
|
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
||||||
@ -163,7 +163,7 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let url = url {
|
if let url = url {
|
||||||
return .share(url: url, text: text)
|
return .share(url: url, text: text, to: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -231,8 +231,8 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
|||||||
return .single(.confirmationCode(code))
|
return .single(.confirmationCode(code))
|
||||||
case let .cancelAccountReset(phone, hash):
|
case let .cancelAccountReset(phone, hash):
|
||||||
return .single(.cancelAccountReset(phone: phone, hash: hash))
|
return .single(.cancelAccountReset(phone: phone, hash: hash))
|
||||||
case let .share(url, text):
|
case let .share(url, text, to):
|
||||||
return .single(.share(url: url, text: text))
|
return .single(.share(url: url, text: text, to: to))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -213,7 +213,7 @@ class WebSearchGalleryController: ViewController {
|
|||||||
override func loadDisplayNode() {
|
override func loadDisplayNode() {
|
||||||
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
if let strongSelf = self {
|
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
|
}, dismissController: { [weak self] in
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
|
|||||||