Merge commit '40456cd0691db79f621f70bf57baa7ee5fbacacb'

This commit is contained in:
Peter 2018-11-27 21:31:03 +03:00
commit bfab84c9b6
65 changed files with 3989 additions and 3271 deletions

View File

@ -63,6 +63,10 @@
09B4EE4F21A7B75D00847FA6 /* PermissionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */; };
09B4EE5221A7CC3E00847FA6 /* SolidRoundedButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */; };
09B4EE5621A8149C00847FA6 /* NotificationPermissionInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */; };
09B4EE5E21AC626B00847FA6 /* PermissionContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */; };
09B4EE6021AD4A0E00847FA6 /* InstantPageContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */; };
09B4EE6221AD791600847FA6 /* InstantPageStoredState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */; };
09B4EE6421AD7B3E00847FA6 /* ItemCacheKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE6321AD7B3E00847FA6 /* ItemCacheKeys.swift */; };
09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; };
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 09C9EA31219F79F500E90146 /* ID3Artwork.m */; };
@ -1135,6 +1139,10 @@
09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionControllerNode.swift; sourceTree = "<group>"; };
09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidRoundedButtonNode.swift; sourceTree = "<group>"; };
09B4EE5521A8149C00847FA6 /* NotificationPermissionInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionInfoItem.swift; sourceTree = "<group>"; };
09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionContentNode.swift; sourceTree = "<group>"; };
09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageContentNode.swift; sourceTree = "<group>"; };
09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageStoredState.swift; sourceTree = "<group>"; };
09B4EE6321AD7B3E00847FA6 /* ItemCacheKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCacheKeys.swift; sourceTree = "<group>"; };
09C3466C2167D63A00B76780 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; };
09C9EA31219F79F500E90146 /* ID3Artwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ID3Artwork.m; sourceTree = "<group>"; };
@ -2360,6 +2368,7 @@
children = (
09B4EE4C21A7B73800847FA6 /* PermissionController.swift */,
09B4EE4E21A7B75D00847FA6 /* PermissionControllerNode.swift */,
09B4EE5D21AC626B00847FA6 /* PermissionContentNode.swift */,
09B4EE5121A7CC3E00847FA6 /* SolidRoundedButtonNode.swift */,
);
name = Permissions;
@ -3115,6 +3124,7 @@
children = (
D0215D371E040F53001A0B1E /* InstantPageNode.swift */,
D0215D391E041003001A0B1E /* InstantPageLayout.swift */,
09B4EE5F21AD4A0E00847FA6 /* InstantPageContentNode.swift */,
D0215D3B1E041014001A0B1E /* InstantPageItem.swift */,
D0215D3D1E041048001A0B1E /* InstantPageMedia.swift */,
D0215D3F1E0410D9001A0B1E /* InstantPageLinkSelectionView.swift */,
@ -3160,6 +3170,7 @@
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */,
09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */,
09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */,
09B4EE6121AD791600847FA6 /* InstantPageStoredState.swift */,
);
name = "Instant Page";
sourceTree = "<group>";
@ -4556,6 +4567,7 @@
0902838C2194AEB90067EFBD /* ImageTransparency.swift */,
09C9EA32219F79F600E90146 /* ID3Artwork.h */,
09C9EA31219F79F500E90146 /* ID3Artwork.m */,
09B4EE6321AD7B3E00847FA6 /* ItemCacheKeys.swift */,
);
name = Utils;
sourceTree = "<group>";
@ -4920,6 +4932,7 @@
D0EC6CB01EB9F58800EBF1C3 /* objects.c in Sources */,
D0EC6CB11EB9F58800EBF1C3 /* program.c in Sources */,
D0E412DA206A894800BEE4A2 /* SecureIdValueFormFileItem.swift in Sources */,
09B4EE6221AD791600847FA6 /* InstantPageStoredState.swift in Sources */,
D0EC6CB21EB9F58800EBF1C3 /* rngs.c in Sources */,
D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */,
D0EC6CB31EB9F58800EBF1C3 /* shader.c in Sources */,
@ -5005,6 +5018,7 @@
D0EC6CD81EB9F58800EBF1C3 /* ShakeAnimation.swift in Sources */,
D0EC6CD91EB9F58800EBF1C3 /* ValidateAddressNameInteractive.swift in Sources */,
D0BE30452061C09000FBE6D8 /* SecureIdAuthContentNode.swift in Sources */,
09B4EE5E21AC626B00847FA6 /* PermissionContentNode.swift in Sources */,
D0E412CC206A6B2300BEE4A2 /* FormControllerActionItem.swift in Sources */,
D0471B5A1EFE70400074D609 /* BotCheckoutInfoControllerNode.swift in Sources */,
D0EC6CDA1EB9F58800EBF1C3 /* NumericFormat.swift in Sources */,
@ -5076,6 +5090,7 @@
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */,
D01776B81F1D6FB30044446D /* RadialProgressContentNode.swift in Sources */,
D05D8B762195CD930064586F /* SetupTwoStepVerificationControllerNode.swift in Sources */,
09B4EE6421AD7B3E00847FA6 /* ItemCacheKeys.swift in Sources */,
D0EC6CFA1EB9F58800EBF1C3 /* ManagedAudioSession.swift in Sources */,
D0EB5ADF1F798033004E89B6 /* PeerMediaCollectionEmptyNode.swift in Sources */,
D0EC6CFB1EB9F58800EBF1C3 /* ManagedAudioRecorder.swift in Sources */,
@ -5645,6 +5660,7 @@
D0FC194D201F82A000FEDBB2 /* OpenResolvedUrl.swift in Sources */,
D0EC6E251EB9F58900EBF1C3 /* StickerPackPreviewGridItem.swift in Sources */,
D0EC6E261EB9F58900EBF1C3 /* StickerPreviewController.swift in Sources */,
09B4EE6021AD4A0E00847FA6 /* InstantPageContentNode.swift in Sources */,
D0EC6E271EB9F58900EBF1C3 /* StickerPreviewControllerNode.swift in Sources */,
D0EC6E281EB9F58900EBF1C3 /* ContactsController.swift in Sources */,
D0EC6E291EB9F58900EBF1C3 /* ContactsControllerNode.swift in Sources */,

View File

@ -110,7 +110,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
var updatedBackgroundImage: UIImage?
if currentTheme != item.presentationData.theme {
//let principalGraphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
//let principalGraphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
updatedBackgroundImage = PresentationResourcesChat.chatInfoItemBackgroundImage(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
}

View File

@ -71,6 +71,8 @@ let ChatControllerCount = Atomic<Int32>(value: 0)
public final class ChatController: TelegramController, KeyShortcutResponder, UIDropInteractionDelegate {
private var validLayout: ContainerViewLayout?
weak var parentController: ViewController?
public var peekActions: ChatControllerPeekActions = .standard
private var didSetup3dTouch: Bool = false
@ -4335,6 +4337,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
return nil
}
func scrollToEndOfHistory() {
self.chatDisplayNode.historyNode.scrollToEndOfHistory()
}
public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, completion: (() -> Void)? = nil) {
self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, animated: animated, completion: completion)
}
@ -4508,7 +4514,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
return
}
if case .peer(peerId) = strongSelf.chatLocation {
if case .peer(peerId) = strongSelf.chatLocation, strongSelf.parentController == nil {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(messageIds).withoutSelectionState() }) })
strongController.dismiss()
} else if peerId == strongSelf.account.peerId {
@ -4566,7 +4572,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}))
(strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)), animated: false, ready: ready)
if let parentController = strongSelf.parentController {
(parentController.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)), animated: false, ready: ready)
} else {
(strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)), animated: false, ready: ready)
}
}
})
}
@ -4783,35 +4793,43 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
private func reportPeer() {
if let peer = self.presentationInterfaceState.renderedPeer?.peer {
let title: String
var infoString: String?
if let _ = peer as? TelegramGroup {
title = self.presentationData.strings.Conversation_ReportSpam
} else if let _ = peer as? TelegramChannel {
title = self.presentationData.strings.Conversation_ReportSpam
} else {
title = self.presentationData.strings.Conversation_ReportSpam
infoString = self.presentationData.strings.Conversation_ReportSpamConfirmation
}
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
var items: [ActionSheetItem] = []
if let infoString = infoString {
items.append(ActionSheetTextItem(title: infoString))
}
items.append(ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.deleteChat(reportChatSpam: true)
}
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
self.chatDisplayNode.dismissInput()
self.present(actionSheet, in: .window(.root))
if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty {
self.present(peerReportOptionsController(account: account, subject: .peer(peer.id), present: { [weak self] c, a in
self?.present(c, in: .window(.root))
}), in: .window(.root))
} else {
let title: String
var infoString: String?
if let _ = peer as? TelegramGroup {
title = self.presentationData.strings.Conversation_ReportSpam
} else if let _ = peer as? TelegramChannel {
title = self.presentationData.strings.Conversation_ReportSpam
} else {
title = self.presentationData.strings.Conversation_ReportSpam
infoString = self.presentationData.strings.Conversation_ReportSpamConfirmation
}
let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme)
var items: [ActionSheetItem] = []
if let infoString = infoString {
items.append(ActionSheetTextItem(title: infoString))
}
items.append(ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.deleteChat(reportChatSpam: true)
}
}))
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))
}
}
}

View File

@ -189,7 +189,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNode = ChatHistoryListNode(account: account, chatLocation: chatLocation, tagMask: nil, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get())
self.historyNodeContainer = ASDisplayNode()
self.historyNodeContainer.addSubnode(self.historyNode)
self.loadingNode = ChatLoadingNode(theme: chatPresentationInterfaceState.theme)
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper)
self.inputPanelBackgroundNode = ASDisplayNode()
self.inputPanelBackgroundNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColor
@ -1323,7 +1323,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let restrictionText = restrictionText {
if self.restrictedNode == nil {
let restrictedNode = ChatRecentActionsEmptyNode(theme: chatPresentationInterfaceState.theme)
let restrictedNode = ChatRecentActionsEmptyNode(theme: chatPresentationInterfaceState.theme, chatWallpaper: chatPresentationInterfaceState.chatWallpaper)
self.historyNodeContainer.supernode?.insertSubnode(restrictedNode, aboveSubnode: self.historyNodeContainer)
self.restrictedNode = restrictedNode
}

View File

@ -30,7 +30,8 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText)
}
let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
@ -100,9 +101,11 @@ private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNode
titleString = interfaceState.strings.Conversation_EncryptedPlaceholderTitleOutgoing(title).0
}
self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EncryptedDescriptionTitle, font: messageFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EncryptedDescriptionTitle, font: messageFont, textColor: serviceColor.primaryText)
let strings: [String] = [
interfaceState.strings.Conversation_EncryptedDescription1,
@ -111,7 +114,7 @@ private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNode
interfaceState.strings.Conversation_EncryptedDescription4
]
let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor) }
let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: serviceColor.primaryText) }
let lockIcon = PresentationResourcesChat.chatEmptyItemLockIcon(interfaceState.theme)
@ -210,10 +213,12 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/Cloud"), color: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/Cloud"), color: serviceColor.primaryText)
let titleString = interfaceState.strings.Conversation_CloudStorageInfo_Title
self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
let strings: [String] = [
interfaceState.strings.Conversation_ClousStorageInfo_Description1,
@ -222,7 +227,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
interfaceState.strings.Conversation_ClousStorageInfo_Description4
]
let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor) }
let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: serviceColor.primaryText) }
for i in 0 ..< lines.count {
if i >= self.lineNodes.count {
@ -324,7 +329,8 @@ final class ChatEmptyNode: ASDisplayNode {
self.currentTheme = interfaceState.theme
self.currentStrings = interfaceState.strings
self.backgroundNode.image = PresentationResourcesChat.chatEmptyItemBackgroundImage(interfaceState.theme)
let graphics = PresentationResourcesChat.principalGraphics(interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
self.backgroundNode.image = graphics.chatEmptyItemBackgroundImage
}
let contentType: ChatEmptyNodeContentType

View File

@ -25,7 +25,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
groupBucket.removeAll()
}
if view.tagMask == nil {
entries.append(.HoleEntry(hole, presentationData.theme.theme, presentationData.strings))
entries.append(.HoleEntry(hole, presentationData))
}
case let .MessageEntry(message, read, _, monthLocation):
var isAdmin = false

View File

@ -24,7 +24,7 @@ public enum ChatHistoryMessageSelection: Equatable {
}
enum ChatHistoryEntry: Identifiable, Comparable {
case HoleEntry(MessageHistoryHole, PresentationTheme, PresentationStrings)
case HoleEntry(MessageHistoryHole, ChatPresentationData)
case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryMonthLocation?, ChatHistoryMessageSelection, Bool)
case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, Bool)], ChatPresentationData)
case UnreadEntry(MessageIndex, ChatPresentationData)
@ -33,7 +33,7 @@ enum ChatHistoryEntry: Identifiable, Comparable {
var stableId: UInt64 {
switch self {
case let .HoleEntry(hole, _, _):
case let .HoleEntry(hole, _):
return UInt64(hole.stableId) | ((UInt64(1) << 40))
case let .MessageEntry(message, _, _, _, _, _):
return UInt64(message.stableId) | ((UInt64(2) << 40))
@ -50,7 +50,7 @@ enum ChatHistoryEntry: Identifiable, Comparable {
var index: MessageIndex {
switch self {
case let .HoleEntry(hole, _, _):
case let .HoleEntry(hole, _):
return hole.maxIndex
case let .MessageEntry(message, _, _, _, _, _):
return MessageIndex(message)
@ -67,8 +67,8 @@ enum ChatHistoryEntry: Identifiable, Comparable {
static func ==(lhs: ChatHistoryEntry, rhs: ChatHistoryEntry) -> Bool {
switch lhs {
case let .HoleEntry(lhsHole, lhsTheme, lhsStrings):
if case let .HoleEntry(rhsHole, rhsTheme, rhsStrings) = rhs, lhsHole == rhsHole, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
case let .HoleEntry(lhsHole, lhsPresentationData):
if case let .HoleEntry(rhsHole, rhsPresentationData) = rhs, lhsHole == rhsHole, lhsPresentationData === rhsPresentationData {
return true
} else {
return false

View File

@ -166,11 +166,11 @@ private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, a
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .HoleEntry(_, theme, strings):
case let .HoleEntry(_, presentationData):
let item: ListViewItem
switch mode {
case .bubbles:
item = ChatHoleItem(index: entry.entry.index, theme: theme, strings: strings)
item = ChatHoleItem(index: entry.entry.index, presentationData: presentationData)
case .list:
item = ListMessageHoleItem()
}
@ -209,11 +209,11 @@ private func mappedUpdateEntries(account: Account, chatLocation: ChatLocation, a
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .HoleEntry(_, theme, strings):
case let .HoleEntry(_, presentationData):
let item: ListViewItem
switch mode {
case .bubbles:
item = ChatHoleItem(index: entry.entry.index, theme: theme, strings: strings)
item = ChatHoleItem(index: entry.entry.index, presentationData: presentationData)
case .list:
item = ListMessageHoleItem()
}

View File

@ -9,14 +9,12 @@ private let titleFont = UIFont.systemFont(ofSize: 13.0)
class ChatHoleItem: ListViewItem {
let index: MessageIndex
let theme: PresentationTheme
let strings: PresentationStrings
let presentationData: ChatPresentationData
//let header: ChatMessageDateHeader
init(index: MessageIndex, theme: PresentationTheme, strings: PresentationStrings) {
init(index: MessageIndex, presentationData: ChatPresentationData) {
self.index = index
self.theme = theme
self.strings = strings
self.presentationData = presentationData
//self.header = ChatMessageDateHeader(timestamp: index.timestamp, theme: theme, strings: strings)
}
@ -81,11 +79,14 @@ class ChatHoleItemNode: ListViewItemNode {
let currentItem = self.item
return { item, params, dateAtBottom in
var updatedBackground: UIImage?
if item.theme !== currentItem?.theme {
updatedBackground = PresentationResourcesChat.chatServiceBubbleFillImage(item.theme)
if item.presentationData.theme !== currentItem?.presentationData.theme {
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
updatedBackground = graphics.chatServiceBubbleFillImage
}
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Channel_NotificationLoading, font: titleFont, textColor: item.theme.chat.serviceMessage.serviceMessagePrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Channel_NotificationLoading, font: titleFont, textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let backgroundSize = CGSize(width: size.size.width + 8.0 + 8.0, height: 20.0)

View File

@ -375,7 +375,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
})
}
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), animated: animated, completion: { [weak self] in
var scrollToEndIfExists = false
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
scrollToEndIfExists = true
}
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), scrollToEndIfExists: scrollToEndIfExists, animated: animated, completion: { [weak self] in
self?.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
})
}
@ -385,7 +389,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
self.chatListDisplayNode.chatListNode.groupSelected = { [weak self] groupId in
if let strongSelf = self {
if let navigationController = strongSelf.navigationController as? NavigationController {
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .group(groupId))
var scrollToEndIfExists = false
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
scrollToEndIfExists = true
}
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .group(groupId), scrollToEndIfExists: scrollToEndIfExists)
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
}
}

View File

@ -1,19 +1,23 @@
import Foundation
import AsyncDisplayKit
import Display
import TelegramCore
final class ChatLoadingNode: ASDisplayNode {
private let backgroundNode: ASImageNode
private let activityIndicator: ActivityIndicator
init(theme: PresentationTheme) {
init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper) {
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = PresentationResourcesChat.chatLoadingIndicatorBackgroundImage(theme)
self.activityIndicator = ActivityIndicator(type: .custom(theme.chat.serviceMessage.serviceMessagePrimaryTextColor, 22.0, 2.0, false), speed: .regular)
let graphics = PresentationResourcesChat.principalGraphics(theme, wallpaper: chatWallpaper)
self.backgroundNode.image = graphics.chatLoadingIndicatorBackgroundImage
let serviceColor = serviceMessageColorComponents(theme: theme, wallpaper: chatWallpaper)
self.activityIndicator = ActivityIndicator(type: .custom(serviceColor.primaryText, 22.0, 2.0, false), speed: .regular)
super.init()

View File

@ -22,7 +22,7 @@ private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, P
return result
}
private func attributedServiceMessageString(theme: PresentationTheme, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
return universalServiceMessageString(theme: theme, strings: strings, message: message, accountPeerId: accountPeerId)
}
@ -30,12 +30,15 @@ func plainServiceMessageString(strings: PresentationStrings, message: Message, a
return universalServiceMessageString(theme: nil, strings: strings, message: message, accountPeerId: accountPeerId)?.string
}
private func universalServiceMessageString(theme: PresentationTheme?, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
private func universalServiceMessageString(theme: ChatPresentationThemeData?, strings: PresentationStrings, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
var attributedString: NSAttributedString?
let theme = theme?.chat.serviceMessage
let primaryTextColor = theme?.serviceMessagePrimaryTextColor ?? UIColor.black
let primaryTextColor: UIColor
if let theme = theme {
primaryTextColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper).primaryText
} else {
primaryTextColor = .black
}
let bodyAttributes = MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [:])
@ -507,7 +510,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId)
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId)
var image: TelegramMediaImage?
for media in item.message.media {
@ -549,7 +552,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
}
let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0)
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
let backgroundApply = backgroundLayout(serviceColor.fill, labelRects, 10.0, 10.0, 0.0)
var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0)
let layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0)
@ -635,7 +639,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.serviceMessage.serviceMessageLinkHighlightColor)
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
linkHighlightingNode = LinkHighlightingNode(color: serviceColor.linkHighlight)
linkHighlightingNode.inset = 2.5
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.labelNode)

View File

@ -780,7 +780,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
forwardSource = forwardInfo.author
forwardAuthorSignature = nil
}
let sizeAndApply = forwardInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
let sizeAndApply = forwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
forwardInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() })
forwardInfoOriginY = headerSize.height
@ -794,7 +794,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
} else {
headerSize.height += 2.0
}
let sizeAndApply = replyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
let sizeAndApply = replyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
replyInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() })
replyInfoOriginY = headerSize.height
@ -1101,7 +1101,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
var updatedMergedTop = mergedBottom
var updatedMergedBottom = mergedTop
@ -1903,7 +1903,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
if self.highlightedState != highlighted {
self.highlightedState = highlighted
if let backgroundType = self.backgroundType {
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
if highlighted {
self.backgroundNode.setType(type: backgroundType, highlighted: true, graphics: graphics, transition: .immediate)

View File

@ -153,7 +153,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let themeUpdated = theme != currentTheme || type != currentType
let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: !theme.wallpaper.isEmpty)
let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: theme.wallpaper)
switch type {
case .BubbleIncoming:
@ -201,7 +201,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.mediaImpressionIcon
}
case .FreeIncoming:
dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
dateColor = serviceColor.primaryText
backgroundImage = graphics.dateAndStatusFreeBackground
leftInset = 0.0
loadedCheckFullImage = graphics.checkFreeFullImage
@ -212,7 +213,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.freeImpressionIcon
}
case let .FreeOutgoing(status):
dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
dateColor = serviceColor.primaryText
outgoingStatus = status
backgroundImage = graphics.dateAndStatusFreeBackground
leftInset = 0.0

View File

@ -113,7 +113,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
self.backgroundNode.image = graphics.dateStaticBackground
self.stickBackgroundNode.image = graphics.dateFloatingBackground
@ -169,7 +169,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
}
func updatePresentationData(_ presentationData: ChatPresentationData) {
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
self.backgroundNode.image = graphics.dateStaticBackground
self.stickBackgroundNode.image = graphics.dateFloatingBackground

View File

@ -18,7 +18,7 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
super.init()
}
class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer, _ authorName: String?, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageForwardInfoNode) {
class func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer, _ authorName: String?, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageForwardInfoNode) {
let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode)
return { theme, strings, type, peer, authorName, constrainedSize in
@ -34,10 +34,11 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
switch type {
case let .bubble(incoming):
titleColor = incoming ? theme.chat.bubble.incomingAccentTextColor : theme.chat.bubble.outgoingAccentTextColor
titleColor = incoming ? theme.theme.chat.bubble.incomingAccentTextColor : theme.theme.chat.bubble.outgoingAccentTextColor
completeSourceString = strings.Message_ForwardedMessage(peerString)
case .standalone:
titleColor = theme.chat.serviceMessage.serviceMessagePrimaryTextColor
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
titleColor = serviceColor.primaryText
completeSourceString = strings.Message_ForwardedMessageShort(peerString)
}

View File

@ -141,14 +141,16 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
for attribute in item.message.attributes {
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
let availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - videoLayout.contentSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)
replyInfoApply = makeReplyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
updatedReplyBackgroundNode = currentReplyBackgroundNode
} else {
updatedReplyBackgroundNode = ASImageNode()
}
replyBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme.theme)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
replyBackgroundImage = graphics.chatServiceBubbleFillImage
break
} else if let attribute = attribute as? InlineBotMessageAttribute {
if let peerId = attribute.peerId, let bot = item.message.peers[peerId] as? TelegramUser {
@ -184,7 +186,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
forwardAuthorSignature = nil
}
let availableWidth = max(60.0, availableContentWidth - videoLayout.contentSize.width + 6.0)
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
if let currentForwardBackgroundNode = currentForwardBackgroundNode {
updatedForwardBackgroundNode = currentForwardBackgroundNode
@ -192,7 +194,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
updatedForwardBackgroundNode = ASImageNode()
}
forwardBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme.theme)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
forwardBackgroundImage = graphics.chatServiceBubbleFillImage
}
var maxContentWidth = videoLayout.contentSize.width

View File

@ -365,7 +365,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if hasThumbnail {
fileIconImage = nil
} else {
let principalGraphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)
let principalGraphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
fileIconImage = incoming ? principalGraphics.radialIndicatorFileIconIncoming : principalGraphics.radialIndicatorFileIconOutgoing
}

View File

@ -312,8 +312,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let durationFillColor: UIColor
switch statusDisplayType {
case .free:
durationTextColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor
durationFillColor = theme.theme.chat.serviceMessage.serviceMessageFillColor
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
durationTextColor = serviceColor.primaryText
durationFillColor = serviceColor.fill
case .bubble:
durationFillColor = .clear
if item.message.effectivelyIncoming(item.account.peerId) {

View File

@ -39,7 +39,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
self.contentNode.addSubnode(self.lineNode)
}
class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ account: Account, _ type: ChatMessageReplyInfoType, _ message: Message, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode) {
class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ account: Account, _ type: ChatMessageReplyInfoType, _ message: Message, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode) {
let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode)
let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode)
@ -50,23 +50,24 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let titleString = message.author?.displayTitle(strings: strings) ?? strings.User_DeletedAccount
let (textString, isMedia) = descriptionStringForMessage(message, strings: strings, accountPeerId: account.peerId)
let placeholderColor: UIColor = message.effectivelyIncoming(account.peerId) ? theme.chat.bubble.incomingMediaPlaceholderColor : theme.chat.bubble.outgoingMediaPlaceholderColor
let placeholderColor: UIColor = message.effectivelyIncoming(account.peerId) ? theme.theme.chat.bubble.incomingMediaPlaceholderColor : theme.theme.chat.bubble.outgoingMediaPlaceholderColor
let titleColor: UIColor
let lineImage: UIImage?
let textColor: UIColor
switch type {
case let .bubble(incoming):
titleColor = incoming ? theme.chat.bubble.incomingAccentTextColor : theme.chat.bubble.outgoingAccentTextColor
lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(theme)
titleColor = incoming ? theme.theme.chat.bubble.incomingAccentTextColor : theme.theme.chat.bubble.outgoingAccentTextColor
lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(theme.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(theme.theme)
if isMedia {
textColor = incoming ? theme.chat.bubble.incomingSecondaryTextColor : theme.chat.bubble.outgoingSecondaryTextColor
textColor = incoming ? theme.theme.chat.bubble.incomingSecondaryTextColor : theme.theme.chat.bubble.outgoingSecondaryTextColor
} else {
textColor = incoming ? theme.chat.bubble.incomingPrimaryTextColor : theme.chat.bubble.outgoingPrimaryTextColor
textColor = incoming ? theme.theme.chat.bubble.incomingPrimaryTextColor : theme.theme.chat.bubble.outgoingPrimaryTextColor
}
case .standalone:
titleColor = theme.chat.serviceMessage.serviceMessagePrimaryTextColor
lineImage = PresentationResourcesChat.chatServiceVerticalLineImage(theme)
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
titleColor = serviceColor.primaryText
lineImage = PresentationResourcesChat.chatServiceVerticalLineImage(theme.theme)
textColor = titleColor
}

View File

@ -234,14 +234,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
for attribute in item.message.attributes {
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
let availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - imageSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)
replyInfoApply = makeReplyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
updatedReplyBackgroundNode = currentReplyBackgroundNode
} else {
updatedReplyBackgroundNode = ASImageNode()
}
replyBackgroundImage = PresentationResourcesChat.chatFreeformContentAdditionalInfoBackgroundImage(item.presentationData.theme.theme)
let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage
break
}
}

View File

@ -202,6 +202,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
switch type {
case .twitter, .instagram:
entityTypes.insert(.mention)
entityTypes.insert(.hashtag)
entityTypes.insert(.external)
default:
break
}
@ -328,6 +330,21 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
break
}
}
case let .hashtag(_, value):
if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
var hashtag = value
if hashtag.hasPrefix("#") {
hashtag = String(hashtag[hashtag.index(after: hashtag.startIndex)...])
}
switch websiteType(of: content) {
case .twitter:
return .url(url: "https://twitter.com/hashtag/\(hashtag)", concealed: false)
case .instagram:
return .url(url: "https://instagram.com/explore/tags/\(hashtag)", concealed: false)
default:
break
}
}
default:
return result
}

View File

@ -32,6 +32,14 @@ extension TelegramWallpaper {
return true
}
}
var isBuiltin: Bool {
switch self {
case .builtin:
return true
default:
return false
}
}
}
public final class ChatPresentationThemeData: Equatable {

View File

@ -91,8 +91,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
self.loadingNode = ChatLoadingNode(theme: self.presentationData.theme)
self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme)
self.loadingNode = ChatLoadingNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper)
self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper)
self.emptyNode.alpha = 0.0
self.state = ChatRecentActionsControllerState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.fontSize)

View File

@ -1,12 +1,14 @@
import Foundation
import Display
import AsyncDisplayKit
import TelegramCore
private let titleFont = Font.medium(16.0)
private let textFont = Font.regular(15.0)
final class ChatRecentActionsEmptyNode: ASDisplayNode {
private var theme: PresentationTheme
private var chatWallpaper: TelegramWallpaper
private let backgroundNode: ASImageNode
private let titleNode: TextNode
@ -17,8 +19,9 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
private var title: String = ""
private var text: String = ""
init(theme: PresentationTheme) {
init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper) {
self.theme = theme
self.chatWallpaper = chatWallpaper
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
@ -31,7 +34,8 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
super.init()
self.backgroundNode.image = PresentationResourcesChat.chatEmptyItemBackgroundImage(self.theme)
let graphics = PresentationResourcesChat.principalGraphics(theme, wallpaper: chatWallpaper)
self.backgroundNode.image = graphics.chatEmptyItemBackgroundImage
self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode)
@ -48,9 +52,11 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.title, font: titleFont, textColor: self.theme.chat.serviceMessage.serviceMessagePrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let serviceColor = serviceMessageColorComponents(theme: self.theme, wallpaper: self.chatWallpaper)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.title, font: titleFont, textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let spacing: CGFloat = titleLayout.size.height.isZero ? 0.0 : 5.0
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.text, font: textFont, textColor: self.theme.chat.serviceMessage.serviceMessagePrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.text, font: textFont, textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: insets.top + insets.bottom + titleLayout.size.height + spacing + textLayout.size.height)
let backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize)

View File

@ -413,7 +413,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
var commonHeader: ListViewItemHeader?
switch presentation {
case .orderedByPresence:
commonHeader = ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
commonHeader = nil //ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
default:
break
}
@ -568,7 +568,7 @@ final class ContactListNode: ASDisplayNode {
private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool)>
private let authorizationNode: PermissionControllerNode
private var authorizationNode: PermissionContentNode?
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
self.account = account
@ -582,8 +582,7 @@ final class ContactListNode: ASDisplayNode {
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.nameSortOrder, self.presentationData.nameDisplayOrder, self.presentationData.disableAnimations))
self.authorizationNode = PermissionControllerNode(theme: defaultLightAuthorizationTheme, strings: self.presentationData.strings)
self.authorizationNode.updateData(subject: .contacts, currentStatus: .denied)
//self.authorizationNode = //PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
super.init()
@ -814,12 +813,12 @@ final class ContactListNode: ASDisplayNode {
fixSearchableListNodeScrolling(strongSelf.listNode)
}
self.authorizationNode.allow = { [weak self] in
self?.account.telegramApplicationContext.applicationBindings.openSettings()
}
self.authorizationNode.openPrivacyPolicy = { [weak self] in
self?.openPrivacyPolicy?()
}
// self.authorizationNode.allow = { [weak self] in
// self?.account.telegramApplicationContext.applicationBindings.openSettings()
// }
// self.authorizationNode.openPrivacyPolicy = { [weak self] in
// self?.openPrivacyPolicy?()
// }
self.enableUpdates = true
}
@ -870,9 +869,9 @@ final class ContactListNode: ASDisplayNode {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let sublayout = layout.addedInsets(insets: UIEdgeInsetsMake(0.0, 0.0, 40.0, 0.0))
transition.updateFrame(node: self.authorizationNode, frame: self.bounds)
self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition)
//let sublayout = layout.addedInsets(insets: UIEdgeInsetsMake(0.0, 0.0, 40.0, 0.0))
//transition.updateFrame(node: self.authorizationNode, frame: self.bounds)
//self.authorizationNode.containerLayoutUpdated(sublayout, navigationBarHeight: 0.0, transition: transition)
if !self.hasValidLayout {
self.hasValidLayout = true

View File

@ -65,7 +65,8 @@ public class ContactsController: ViewController {
self.authorizationDisposable = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
strongSelf.tabBarItem.badgeValue = nil
//strongSelf.tabBarItem.badgeValue = status != .allowed ? "!" : nil
}
})
}

View File

@ -26,6 +26,8 @@ private var telegramUIDeclaredEncodables: Void = {
declareEncodable(CachedChannelAdminIds.self, f: { CachedChannelAdminIds(decoder: $0) })
declareEncodable(StickerSettings.self, f: { StickerSettings(decoder: $0) })
declareEncodable(InstantPagePresentationSettings.self, f: { InstantPagePresentationSettings(decoder: $0) })
declareEncodable(InstantPageStoredState.self, f: { InstantPageStoredState(decoder: $0) })
declareEncodable(InstantPageStoredDetailsState.self, f: { InstantPageStoredDetailsState(decoder: $0) })
declareEncodable(WatchPresetSettings.self, f: { WatchPresetSettings(decoder: $0) })
return
}()

View File

@ -190,14 +190,10 @@ private let bubble = PresentationThemeChatBubble(
)
private let serviceMessage = PresentationThemeServiceMessage(
serviceMessageFillColor: UIColor(rgb: 0x18222D, alpha: 1.0),
serviceMessagePrimaryTextColor: UIColor(rgb: 0xffffff),
serviceMessageLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.12),
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x18222D, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), dateFillStatic: UIColor(rgb: 0x18222D, alpha: 1.0), dateFillFloating: UIColor(rgb: 0x18222D, alpha: 0.2)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x18222D, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), dateFillStatic: UIColor(rgb: 0x18222D, alpha: 1.0), dateFillFloating: UIColor(rgb: 0x18222D, alpha: 0.2))),
unreadBarFillColor: UIColor(rgb: 0x213040),
unreadBarStrokeColor: UIColor(rgb: 0x213040),
unreadBarTextColor: UIColor(rgb: 0xffffff),
dateFillStaticColor: UIColor(rgb: 0x18222D, alpha: 1.0),
dateFillFloatingColor: UIColor(rgb: 0x18222D, alpha: 0.2),
dateTextColor: UIColor(rgb: 0xffffff)
)

View File

@ -190,14 +190,10 @@ private let bubble = PresentationThemeChatBubble(
)
private let serviceMessage = PresentationThemeServiceMessage(
serviceMessageFillColor: UIColor(rgb: 0x1f1f1f, alpha: 1.0),
serviceMessagePrimaryTextColor: UIColor(rgb: 0xffffff),
serviceMessageLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.12), //!!!
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), dateFillStatic: UIColor(rgb: 0x1f1f1f, alpha: 1.0), dateFillFloating: UIColor(rgb: 0xffffff, alpha: 0.2)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), dateFillStatic: UIColor(rgb: 0x1f1f1f, alpha: 1.0), dateFillFloating: UIColor(rgb: 0xffffff, alpha: 0.2))),
unreadBarFillColor: UIColor(rgb: 0x1b1b1b), //!!!
unreadBarStrokeColor: UIColor(rgb: 0x000000),
unreadBarTextColor: UIColor(rgb: 0xb2b2b2), //!!!
dateFillStaticColor: UIColor(rgb: 0x1f1f1f, alpha: 1.0),
dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.2),
dateTextColor: UIColor(rgb: 0xb2b2b2)
)

View File

@ -275,26 +275,18 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
)
let serviceMessage = PresentationThemeServiceMessage(
serviceMessageFillColor: UIColor(rgb: 0x748391, alpha: 0.45),
serviceMessagePrimaryTextColor: .white,
serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25),
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))),
unreadBarFillColor: UIColor(white: 1.0, alpha: 0.9),
unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2),
unreadBarTextColor: UIColor(rgb: 0x86868d),
dateFillStaticColor: UIColor(rgb: 0x748391, alpha: 0.45),
dateFillFloatingColor: UIColor(rgb: 0x939fab, alpha: 0.5),
dateTextColor: .white
)
let serviceMessageDay = PresentationThemeServiceMessage(
serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.8),
serviceMessagePrimaryTextColor: UIColor(rgb: 0x8D8E93),
serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25),
components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0xffffff, alpha: 0.8), primaryText: UIColor(rgb: 0x8D8E93), linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: UIColor(rgb: 0xffffff, alpha: 0.8), dateFillFloating: UIColor(rgb: 0xffffff, alpha: 0.8)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0xffffff, alpha: 0.8), primaryText: UIColor(rgb: 0x8D8E93), linkHighlight: UIColor(rgb: 0x748391, alpha: 0.25), dateFillStatic: UIColor(rgb: 0xffffff, alpha: 0.8), dateFillFloating: UIColor(rgb: 0xffffff, alpha: 0.8))),
unreadBarFillColor: UIColor(rgb: 0xffffff),
unreadBarStrokeColor: UIColor(rgb: 0xffffff),
unreadBarTextColor: UIColor(rgb: 0x8D8E93),
dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.8),
dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.8),
dateTextColor: UIColor(rgb: 0x8D8E93)
)

View File

@ -21,6 +21,12 @@ private let identifierDelimiterSet: CharacterSet = {
set.formUnion(CharacterSet.whitespacesAndNewlines)
return set
}()
private let externalIdentifierDelimiterSet: CharacterSet = {
var set = CharacterSet.punctuationCharacters
set.formUnion(CharacterSet.whitespacesAndNewlines)
set.remove(".")
return set
}()
private enum CurrentEntityType {
case command
@ -51,6 +57,7 @@ public struct EnabledEntityTypes: OptionSet {
public static let hashtag = EnabledEntityTypes(rawValue: 1 << 2)
public static let url = EnabledEntityTypes(rawValue: 1 << 3)
public static let phoneNumber = EnabledEntityTypes(rawValue: 1 << 4)
public static let external = EnabledEntityTypes(rawValue: 1 << 5)
public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .url, .phoneNumber]
}
@ -113,6 +120,8 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
detector = dataDetector
}
let delimiterSet = enabledTypes.contains(.external) ? externalIdentifierDelimiterSet : identifierDelimiterSet
if let detector = detector {
detector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in
if let result = result {
@ -144,7 +153,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
if let scalar = scalar {
if scalar == "/" {
notFound = false
if previousScalar != nil && !identifierDelimiterSet.contains(previousScalar!) {
if previousScalar != nil && !delimiterSet.contains(previousScalar!) {
currentEntity = nil
} else {
if let (type, range) = currentEntity {
@ -178,7 +187,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
case .command, .mention:
if validIdentifierSet.contains(scalar) {
currentEntity = (type, range.lowerBound ..< utf16.index(after: index))
} else if identifierDelimiterSet.contains(scalar) {
} else if delimiterSet.contains(scalar) {
if let (type, range) = currentEntity {
commitEntity(utf16, type, range, enabledTypes, &entities)
}
@ -187,7 +196,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
case .hashtag:
if validHashtagSet.contains(scalar) {
currentEntity = (type, range.lowerBound ..< utf16.index(after: index))
} else if identifierDelimiterSet.contains(scalar) {
} else if delimiterSet.contains(scalar) {
if let (type, range) = currentEntity {
commitEntity(utf16, type, range, enabledTypes, &entities)
}

View File

@ -88,6 +88,9 @@ final class HashtagSearchController: TelegramController {
override func loadDisplayNode() {
self.displayNode = HashtagSearchControllerNode(account: self.account, peer: self.peer, query: self.query, theme: self.presentationData.theme, strings: self.presentationData.strings)
if let chatController = self.controllerNode.chatController {
chatController.parentController = self
}
self.displayNodeDidLoad()
}

View File

@ -10,7 +10,7 @@ final class HashtagSearchControllerNode: ASDisplayNode {
private let segmentedControl: UISegmentedControl
let listNode: ListView
private var chatController: ChatController?
var chatController: ChatController?
private let account: Account
private let query: String

View File

@ -128,7 +128,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode {
if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(to: imageReference.media) {
if let largestSize = largestRepresentationForPhoto(imageReference.media) {
let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: .black))()
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference), dispatchOnDisplayLink: false)
self.zoomableContent = (largestSize.dimensions, self.imageNode)
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: imageReference.resourceReference(largestSize.resource)).start())

View File

@ -0,0 +1,434 @@
import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
final class InstantPageContentNode : ASDisplayNode {
private let account: Account
private let strings: PresentationStrings
private let theme: InstantPageTheme
private let openMedia: (InstantPageMedia) -> Void
private let openPeer: (PeerId) -> Void
private let openUrl: (InstantPageUrlItem) -> Void
var currentLayoutTiles: [InstantPageTile] = []
var currentLayoutItemsWithNodes: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int: Int] = [:]
var visibleTiles: [Int: InstantPageTileNode] = [:]
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
var currentWebEmbedHeights: [Int : CGFloat] = [:]
var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var requestLayoutUpdate: ((Bool) -> Void)?
var currentLayout: InstantPageLayout
let contentSize: CGSize
let inOverlayPanel: Bool
private var previousVisibleBounds: CGRect?
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.account = account
self.strings = strings
self.theme = theme
self.openMedia = openMedia
self.openPeer = openPeer
self.openUrl = openUrl
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
self.contentSize = contentSize
self.inOverlayPanel = inOverlayPanel
super.init()
self.updateLayout()
}
private func updateLayout() {
for (_, tileNode) in self.visibleTiles {
tileNode.removeFromSupernode()
}
self.visibleTiles.removeAll()
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
var currentDetailsItems: [InstantPageDetailsItem] = []
var currentLayoutItemsWithViews: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int : Int] = [:]
var expandedDetails: [Int : Bool] = [:]
var detailsIndex = -1
for item in self.currentLayout.items {
if item.wantsNode {
currentLayoutItemsWithViews.append(item)
if let group = item.distanceThresholdGroup() {
let count: Int
if let currentCount = distanceThresholdGroupCount[Int(group)] {
count = currentCount
} else {
count = 0
}
distanceThresholdGroupCount[Int(group)] = count + 1
}
if let detailsItem = item as? InstantPageDetailsItem {
detailsIndex += 1
expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
currentDetailsItems.append(detailsItem)
}
}
}
if self.currentExpandedDetails == nil {
self.currentExpandedDetails = expandedDetails
}
self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
self.currentDetailsItems = currentDetailsItems
self.distanceThresholdGroupCount = distanceThresholdGroupCount
}
var effectiveContentSize: CGSize {
var contentSize = self.contentSize
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
contentSize.height += -item.frame.height + (expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight)
}
return contentSize
}
func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
var visibleTileIndices = Set<Int>()
var visibleItemIndices = Set<Int>()
self.previousVisibleBounds = visibleBounds
var topNode: ASDisplayNode?
let topTileNode = topNode
if let scrollSubnodes = self.subnodes {
for node in scrollSubnodes.reversed() {
if let node = node as? InstantPageTileNode {
topNode = node
break
}
}
}
var collapseOffset: CGFloat = 0.0
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .spring)
} else {
transition = .immediate
}
var itemIndex = -1
var embedIndex = -1
var detailsIndex = -1
for item in self.currentLayoutItemsWithNodes {
itemIndex += 1
if item is InstantPageWebEmbedItem {
embedIndex += 1
}
if item is InstantPageDetailsItem {
detailsIndex += 1
}
var itemThreshold: CGFloat = 0.0
if let group = item.distanceThresholdGroup() {
var count: Int = 0
if let currentCount = self.distanceThresholdGroupCount[group] {
count = currentCount
}
itemThreshold = item.distanceThresholdWithGroupCount(count)
}
var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
var thresholdedItemFrame = itemFrame
thresholdedItemFrame.origin.y -= itemThreshold
thresholdedItemFrame.size.height += itemThreshold * 2.0
if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] {
let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight
collapseOffset += itemFrame.height - height
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height))
}
if visibleBounds.intersects(thresholdedItemFrame) {
visibleItemIndices.insert(itemIndex)
var itemNode = self.visibleItemsWithNodes[itemIndex]
if let currentItemNode = itemNode {
if !item.matchesNode(currentItemNode) {
(currentItemNode as! ASDisplayNode).removeFromSupernode()
self.visibleItemsWithNodes.removeValue(forKey: itemIndex)
itemNode = nil
}
}
if itemNode == nil {
let itemIndex = itemIndex
let detailsIndex = detailsIndex
if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
self?.openMedia(media)
}, openPeer: { [weak self] peerId in
self?.openPeer(peerId)
}, openUrl: { [weak self] url in
self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] height in
}, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails) {
newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode {
self.insertSubnode(newNode, aboveSubnode: topNode)
} else {
self.insertSubnode(newNode, at: 0)
}
topNode = newNode
self.visibleItemsWithNodes[itemIndex] = newNode
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] animated in
self?.requestLayoutUpdate?(animated)
}
}
}
} else {
if (itemNode as! ASDisplayNode).frame != itemFrame {
transition.updateFrame(node: (itemNode as! ASDisplayNode), frame: itemFrame)
itemNode?.updateLayout(size: itemFrame.size, transition: transition)
}
}
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated)
}
}
}
topNode = topTileNode
var tileIndex = -1
for tile in self.currentLayoutTiles {
tileIndex += 1
let tileFrame = effectiveFrameForTile(tile)
var tileVisibleFrame = tileFrame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if self.visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: self.inOverlayPanel ? self.theme.overlayPanelColor : self.theme.pageBackgroundColor)
tileNode.frame = tileFrame
if let topNode = topNode {
self.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
} else {
if visibleTiles[tileIndex]!.frame != tileFrame {
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
}
}
}
}
var removeTileIndices: [Int] = []
for (index, tileNode) in self.visibleTiles {
if !visibleTileIndices.contains(index) {
removeTileIndices.append(index)
tileNode.removeFromSupernode()
}
}
for index in removeTileIndices {
self.visibleTiles.removeValue(forKey: index)
}
var removeItemIndices: [Int] = []
for (index, itemNode) in self.visibleItemsWithNodes {
if !visibleItemIndices.contains(index) {
removeItemIndices.append(index)
(itemNode as! ASDisplayNode).removeFromSupernode()
} else {
var itemFrame = (itemNode as! ASDisplayNode).frame
let itemThreshold: CGFloat = 200.0
itemFrame.origin.y -= itemThreshold
itemFrame.size.height += itemThreshold * 2.0
itemNode.updateIsVisible(visibleBounds.intersects(itemFrame))
}
}
for index in removeItemIndices {
self.visibleItemsWithNodes.removeValue(forKey: index)
}
}
private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) {
// let currentHeight = self.currentWebEmbedHeights[index]
// if height != currentHeight {
// if let currentHeight = currentHeight, currentHeight > height {
// return
// }
// self.currentWebEmbedHeights[index] = height
//
// let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
// if let strongSelf = self {
// strongSelf.updateLayout()
// strongSelf.updateVisibleItems()
// }
// }))
// }
}
func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) {
if var currentExpandedDetails = self.currentExpandedDetails {
currentExpandedDetails[index] = expanded
self.currentExpandedDetails = currentExpandedDetails
}
self.requestLayoutUpdate?(animated)
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let transitionNode = itemNode.transitionNode(media: media) {
return transitionNode
}
}
return nil
}
func updateHiddenMedia(media: InstantPageMedia?) {
for (_, itemNode) in self.visibleItemsWithNodes {
itemNode.updateHiddenMedia(media: media)
}
}
func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
contentOffset = itemNode.contentOffset
break
}
}
return contentOffset
}
func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
return detailsNode
}
}
return nil
}
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
if let node = nodeForDetailsItem(item) {
return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
} else {
return item.frame.size
}
}
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
let layoutOrigin = tile.frame.origin
var origin = layoutOrigin
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
if layoutOrigin.y >= item.frame.maxY {
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
origin.y += height - item.frame.height
}
}
return CGRect(origin: origin, size: tile.frame.size)
}
func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
let layoutOrigin = item.frame.origin
var origin = layoutOrigin
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
if layoutOrigin.y >= item.frame.maxY {
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
origin.y += height - item.frame.height
}
}
if let item = item as? InstantPageDetailsItem {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height))
} else {
return CGRect(origin: origin, size: item.frame.size)
}
}
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
for item in self.currentLayout.items {
let itemFrame = self.effectiveFrameForItem(item)
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageScrollableItem {
let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
}
} else if let item = item as? InstantPageDetailsItem {
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y))
}
}
}
}
}
}
return nil
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
for item in self.currentLayout.items {
let frame = self.effectiveFrameForItem(item)
if frame.contains(point) {
if item is InstantPagePeerReferenceItem {
return .fail
} else if item is InstantPageAudioItem {
return .fail
} else if item is InstantPageArticleItem {
return .fail
} else if item is InstantPageFeedbackItem {
return .fail
} else if let item = item as? InstantPageDetailsItem {
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY))
}
}
}
break
}
}
return .waitForSingleTap
}
}

View File

@ -71,6 +71,10 @@ final class InstantPageController: ViewController {
self.settingsDisposable?.dispose()
}
override func viewWillDisappear(_ animated: Bool) {
let _ = updateInstantPageStoredStateInteractively(postbox: self.account.postbox, webPage: self.webPage, state: self.controllerNode.currentState).start()
}
override public func loadDisplayNode() {
self.displayNode = InstantPageControllerNode(account: self.account, settings: self.settings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, statusBar: self.statusBar, getNavigationController: { [weak self] in
return self?.navigationController as? NavigationController
@ -96,7 +100,12 @@ final class InstantPageController: ViewController {
self.displayNodeDidLoad()
self.controllerNode.updateWebPage(self.webPage, anchor: self.anchor)
let _ = (instantPageStoredState(postbox: self.account.postbox, webPage: self.webPage)
|> deliverOnMainQueue).start(next: { [weak self] state in
if let strongSelf = self {
strongSelf.controllerNode.updateWebPage(strongSelf.webPage, anchor: strongSelf.anchor, state: state)
}
})
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View File

@ -22,6 +22,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var webPage: TelegramMediaWebpage?
private var initialAnchor: String?
private var pendingAnchor: String?
private var initialState: InstantPageStoredState?
private var containerLayout: ContainerViewLayout?
private var setupScrollOffsetOnLayout: Bool = false
@ -62,6 +63,16 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var themeReferenceDate: Date?
var currentState: InstantPageStoredState {
var details: [InstantPageStoredDetailsState] = []
if let currentExpandedDetails = self.currentExpandedDetails {
for (index, expanded) in currentExpandedDetails {
details.append(InstantPageStoredDetailsState(index: Int32(clamping: index), expanded: expanded, details: []))
}
}
return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details)
}
init(account: Account, settings: InstantPagePresentationSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusBar: StatusBar, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) {
self.account = account
self.presentationTheme = presentationTheme
@ -246,7 +257,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.view.addGestureRecognizer(recognizer)
}
func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?) {
func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?, state: InstantPageStoredState? = nil) {
if self.webPage != webPage {
if self.webPage != nil && self.currentLayout != nil {
if let snaphotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
@ -259,8 +270,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.setupScrollOffsetOnLayout = self.webPage == nil
self.webPage = webPage
self.initialAnchor = anchor?.removingPercentEncoding
if let anchor = anchor {
self.initialAnchor = anchor.removingPercentEncoding
} else if let state = state {
self.initialState = state
if !state.details.isEmpty {
var storedExpandedDetails: [Int: Bool] = [:]
for state in state.details {
storedExpandedDetails[Int(clamping: state.index)] = state.expanded
}
self.currentExpandedDetails = storedExpandedDetails
}
}
self.currentLayout = nil
self.updateLayout()
@ -317,14 +338,15 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
if resetOffset {
var contentOffset = CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top)
if let anchor = self.initialAnchor, !anchor.isEmpty {
if let state = self.initialState {
self.setupScrollOffsetOnLayout = false
contentOffset = CGPoint(x: 0.0, y: CGFloat(state.contentOffset))
}
else if let anchor = self.initialAnchor, !anchor.isEmpty {
if let items = self.currentLayout?.items {
self.setupScrollOffsetOnLayout = false
outer: for item in items {
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
contentOffset = CGPoint(x: 0.0, y: item.frame.origin.y - self.scrollNode.view.contentInset.top)
break outer
}
if let (item, lineOffset, detailsItems) = self.findAnchorItem(anchor, items: items) {
contentOffset = CGPoint(x: 0.0, y: item.frame.minY - self.scrollNode.view.contentInset.top)
}
}
} else {
@ -702,11 +724,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var rects: [CGRect]?
if let location = location, let currentLayout = self.currentLayout {
for item in currentLayout.items {
let itemFrame = effectiveFrameForItem(item)
let itemFrame = self.effectiveFrameForItem(item)
if itemFrame.contains(location) {
var contentOffset = CGPoint()
if let item = item as? InstantPageScrollableItem {
contentOffset = scrollableContentOffset(item: item)
contentOffset = self.scrollableContentOffset(item: item)
}
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY))
@ -848,10 +870,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.openUrl(url)
}
case .longTap:
if let url = self.urlForTapLocation(location) {
if let theme = self.theme, let url = self.urlForTapLocation(location) {
let canOpenIn = availableOpenInOptions(applicationContext: self.account.telegramApplicationContext, item: .url(url: url.url)).count > 1
let openText = canOpenIn ? self.strings.Conversation_FileOpenIn : self.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(presentationTheme: self.presentationTheme)
let actionSheet = ActionSheetController(instantPageTheme: theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: url.url),
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
@ -968,7 +990,16 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
private func presentReferenceView(item: InstantPageTextItem) {
let controller = InstantPageReferenceController(account: self.account, item: item)
guard let theme = self.theme, let webPage = self.webPage else {
return
}
let controller = InstantPageReferenceController(account: self.account, theme: theme, webPage: webPage, item: item, openUrl: { [weak self] url in
self?.openUrl(url)
}, openUrlIn: { [weak self] url in
self?.openUrlIn(url)
}, present: { [weak self] c, a in
self?.present(c, a)
})
self.present(controller, nil)
}
@ -979,40 +1010,44 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if !anchor.isEmpty {
if let (item, lineOffset, detailsItems) = findAnchorItem(String(anchor), items: items) {
var previousDetailsNode: InstantPageDetailsNode?
var containerOffset: CGFloat = 0.0
for detailsItem in detailsItems {
if let previousNode = previousDetailsNode {
previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false)
let frame = previousNode.effectiveFrameForItem(detailsItem)
containerOffset += frame.minY
previousDetailsNode = previousNode.contentNode.nodeForDetailsItem(detailsItem)
previousDetailsNode?.setExpanded(true, animated: false)
} else {
self.updateDetailsExpanded(detailsItem.index, true, animated: false)
let frame = self.effectiveFrameForItem(detailsItem)
containerOffset += frame.minY
previousDetailsNode = self.nodeForDetailsItem(detailsItem)
previousDetailsNode?.setExpanded(true, animated: false)
if let item = item as? InstantPageTextItem, item.plainText().count > 1 {
self.presentReferenceView(item: item)
} else {
var previousDetailsNode: InstantPageDetailsNode?
var containerOffset: CGFloat = 0.0
for detailsItem in detailsItems {
if let previousNode = previousDetailsNode {
previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false)
let frame = previousNode.effectiveFrameForItem(detailsItem)
containerOffset += frame.minY
previousDetailsNode = previousNode.contentNode.nodeForDetailsItem(detailsItem)
previousDetailsNode?.setExpanded(true, animated: false)
} else {
self.updateDetailsExpanded(detailsItem.index, true, animated: false)
let frame = self.effectiveFrameForItem(detailsItem)
containerOffset += frame.minY
previousDetailsNode = self.nodeForDetailsItem(detailsItem)
previousDetailsNode?.setExpanded(true, animated: false)
}
}
let frame: CGRect
if let previousDetailsNode = previousDetailsNode {
frame = previousDetailsNode.effectiveFrameForItem(item)
} else {
frame = self.effectiveFrameForItem(item)
}
var targetY = min(containerOffset + frame.minY + lineOffset, self.scrollNode.view.contentSize.height - self.scrollNode.frame.height)
if targetY < self.scrollNode.view.contentOffset.y {
targetY -= self.scrollNode.view.contentInset.top
} else {
targetY -= self.containerLayout?.statusBarHeight ?? 20.0
}
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
}
let frame: CGRect
if let previousDetailsNode = previousDetailsNode {
frame = previousDetailsNode.effectiveFrameForItem(item)
} else {
frame = self.effectiveFrameForItem(item)
}
var targetY = min(containerOffset + frame.minY + lineOffset, self.scrollNode.view.contentSize.height - self.scrollNode.frame.height)
if targetY < self.scrollNode.view.contentOffset.y {
targetY -= self.scrollNode.view.contentInset.top
} else {
targetY -= self.containerLayout?.statusBarHeight ?? 20.0
}
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
} else if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, !instantPage.isComplete {
self.loadProgress.set(0.5)
self.pendingAnchor = anchor
@ -1037,14 +1072,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.loadProgress.set(0.0)
self.loadProgress.set(0.02)
let resolveSignal = resolveUrl(account: self.account, url: url.url)
|> afterCompleted { [weak self] in
self?.loadProgress.set(0.07)
}
self.loadWebpageDisposable.set(nil)
self.resolveUrlDisposable.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] result in
self.resolveUrlDisposable.set((resolveUrl(account: self.account, url: url.url) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
strongSelf.loadProgress.set(0.07)
switch result {
case let .externalUrl(externalUrl):
if let webpageId = url.webpageId {
@ -1068,11 +1100,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}))
} else {
strongSelf.loadProgress.set(1.0)
openExternalUrl(account: strongSelf.account, url: externalUrl, presentationData: strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: strongSelf.getNavigationController(), dismissInput: {
self?.view.endEditing(true)
})
}
default:
strongSelf.loadProgress.set(1.0)
openResolvedUrl(result, account: strongSelf.account, navigationController: strongSelf.getNavigationController(), openPeer: { peerId, navigation in
switch navigation {
case let .chat(_, messageId):

View File

@ -8,432 +8,6 @@ import SwiftSignalKit
private let detailsInset: CGFloat = 17.0
private let titleInset: CGFloat = 22.0
final class InstantPageDetailsContentNode : ASDisplayNode {
private let account: Account
private let strings: PresentationStrings
private let theme: InstantPageTheme
private let openMedia: (InstantPageMedia) -> Void
private let openPeer: (PeerId) -> Void
private let openUrl: (InstantPageUrlItem) -> Void
var currentLayoutTiles: [InstantPageTile] = []
var currentLayoutItemsWithNodes: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int: Int] = [:]
var visibleTiles: [Int: InstantPageTileNode] = [:]
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
var currentWebEmbedHeights: [Int : CGFloat] = [:]
var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var requestLayoutUpdate: ((Bool) -> Void)?
var currentLayout: InstantPageLayout
let contentSize: CGSize
private var previousVisibleBounds: CGRect?
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.account = account
self.strings = strings
self.theme = theme
self.openMedia = openMedia
self.openPeer = openPeer
self.openUrl = openUrl
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
self.contentSize = contentSize
super.init()
self.updateLayout()
}
private func updateLayout() {
for (_, tileNode) in self.visibleTiles {
tileNode.removeFromSupernode()
}
self.visibleTiles.removeAll()
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
var currentDetailsItems: [InstantPageDetailsItem] = []
var currentLayoutItemsWithViews: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int : Int] = [:]
var expandedDetails: [Int : Bool] = [:]
var detailsIndex = -1
for item in self.currentLayout.items {
if item.wantsNode {
currentLayoutItemsWithViews.append(item)
if let group = item.distanceThresholdGroup() {
let count: Int
if let currentCount = distanceThresholdGroupCount[Int(group)] {
count = currentCount
} else {
count = 0
}
distanceThresholdGroupCount[Int(group)] = count + 1
}
if let detailsItem = item as? InstantPageDetailsItem {
detailsIndex += 1
expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
currentDetailsItems.append(detailsItem)
}
}
}
if self.currentExpandedDetails == nil {
self.currentExpandedDetails = expandedDetails
}
self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
self.currentDetailsItems = currentDetailsItems
self.distanceThresholdGroupCount = distanceThresholdGroupCount
}
var effectiveContentSize: CGSize {
var contentSize = self.contentSize
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
contentSize.height += -item.frame.height + (expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight)
}
return contentSize
}
func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
var visibleTileIndices = Set<Int>()
var visibleItemIndices = Set<Int>()
self.previousVisibleBounds = visibleBounds
var topNode: ASDisplayNode?
let topTileNode = topNode
if let scrollSubnodes = self.subnodes {
for node in scrollSubnodes.reversed() {
if let node = node as? InstantPageTileNode {
topNode = node
break
}
}
}
var collapseOffset: CGFloat = 0.0
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .spring)
} else {
transition = .immediate
}
var itemIndex = -1
var embedIndex = -1
var detailsIndex = -1
for item in self.currentLayoutItemsWithNodes {
itemIndex += 1
if item is InstantPageWebEmbedItem {
embedIndex += 1
}
if item is InstantPageDetailsItem {
detailsIndex += 1
}
var itemThreshold: CGFloat = 0.0
if let group = item.distanceThresholdGroup() {
var count: Int = 0
if let currentCount = self.distanceThresholdGroupCount[group] {
count = currentCount
}
itemThreshold = item.distanceThresholdWithGroupCount(count)
}
var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
var thresholdedItemFrame = itemFrame
thresholdedItemFrame.origin.y -= itemThreshold
thresholdedItemFrame.size.height += itemThreshold * 2.0
if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] {
let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight
collapseOffset += itemFrame.height - height
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height))
}
if visibleBounds.intersects(thresholdedItemFrame) {
visibleItemIndices.insert(itemIndex)
var itemNode = self.visibleItemsWithNodes[itemIndex]
if let currentItemNode = itemNode {
if !item.matchesNode(currentItemNode) {
(currentItemNode as! ASDisplayNode).removeFromSupernode()
self.visibleItemsWithNodes.removeValue(forKey: itemIndex)
itemNode = nil
}
}
if itemNode == nil {
let itemIndex = itemIndex
let detailsIndex = detailsIndex
if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
self?.openMedia(media)
}, openPeer: { [weak self] peerId in
self?.openPeer(peerId)
}, openUrl: { [weak self] url in
self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] height in
}, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails) {
newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode {
self.insertSubnode(newNode, aboveSubnode: topNode)
} else {
self.insertSubnode(newNode, at: 0)
}
topNode = newNode
self.visibleItemsWithNodes[itemIndex] = newNode
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] animated in
self?.requestLayoutUpdate?(animated)
}
}
}
} else {
if (itemNode as! ASDisplayNode).frame != itemFrame {
transition.updateFrame(node: (itemNode as! ASDisplayNode), frame: itemFrame)
itemNode?.updateLayout(size: itemFrame.size, transition: transition)
}
}
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated)
}
}
}
topNode = topTileNode
var tileIndex = -1
for tile in self.currentLayoutTiles {
tileIndex += 1
let tileFrame = effectiveFrameForTile(tile)
var tileVisibleFrame = tileFrame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if self.visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
tileNode.frame = tileFrame
if let topNode = topNode {
self.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
} else {
if visibleTiles[tileIndex]!.frame != tileFrame {
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
}
}
}
}
var removeTileIndices: [Int] = []
for (index, tileNode) in self.visibleTiles {
if !visibleTileIndices.contains(index) {
removeTileIndices.append(index)
tileNode.removeFromSupernode()
}
}
for index in removeTileIndices {
self.visibleTiles.removeValue(forKey: index)
}
var removeItemIndices: [Int] = []
for (index, itemNode) in self.visibleItemsWithNodes {
if !visibleItemIndices.contains(index) {
removeItemIndices.append(index)
(itemNode as! ASDisplayNode).removeFromSupernode()
} else {
var itemFrame = (itemNode as! ASDisplayNode).frame
let itemThreshold: CGFloat = 200.0
itemFrame.origin.y -= itemThreshold
itemFrame.size.height += itemThreshold * 2.0
itemNode.updateIsVisible(visibleBounds.intersects(itemFrame))
}
}
for index in removeItemIndices {
self.visibleItemsWithNodes.removeValue(forKey: index)
}
}
private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) {
// let currentHeight = self.currentWebEmbedHeights[index]
// if height != currentHeight {
// if let currentHeight = currentHeight, currentHeight > height {
// return
// }
// self.currentWebEmbedHeights[index] = height
//
// let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
// if let strongSelf = self {
// strongSelf.updateLayout()
// strongSelf.updateVisibleItems()
// }
// }))
// }
}
func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) {
if var currentExpandedDetails = self.currentExpandedDetails {
currentExpandedDetails[index] = expanded
self.currentExpandedDetails = currentExpandedDetails
}
self.requestLayoutUpdate?(animated)
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let transitionNode = itemNode.transitionNode(media: media) {
return transitionNode
}
}
return nil
}
func updateHiddenMedia(media: InstantPageMedia?) {
for (_, itemNode) in self.visibleItemsWithNodes {
itemNode.updateHiddenMedia(media: media)
}
}
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
contentOffset = itemNode.contentOffset
break
}
}
return contentOffset
}
func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
return detailsNode
}
}
return nil
}
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
if let node = nodeForDetailsItem(item) {
return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
} else {
return item.frame.size
}
}
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
let layoutOrigin = tile.frame.origin
var origin = layoutOrigin
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
if layoutOrigin.y >= item.frame.maxY {
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
origin.y += height - item.frame.height
}
}
return CGRect(origin: origin, size: tile.frame.size)
}
fileprivate func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
let layoutOrigin = item.frame.origin
var origin = layoutOrigin
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
if layoutOrigin.y >= item.frame.maxY {
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
origin.y += height - item.frame.height
}
}
if let item = item as? InstantPageDetailsItem {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height))
} else {
return CGRect(origin: origin, size: item.frame.size)
}
}
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
for item in self.currentLayout.items {
let itemFrame = self.effectiveFrameForItem(item)
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageScrollableItem {
let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
}
} else if let item = item as? InstantPageDetailsItem {
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y))
}
}
}
}
}
}
return nil
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
for item in self.currentLayout.items {
let frame = self.effectiveFrameForItem(item)
if frame.contains(point) {
if item is InstantPagePeerReferenceItem {
return .fail
} else if item is InstantPageAudioItem {
return .fail
} else if item is InstantPageArticleItem {
return .fail
} else if item is InstantPageFeedbackItem {
return .fail
} else if let item = item as? InstantPageDetailsItem {
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY))
}
}
}
break
}
}
return .waitForSingleTap
}
}
final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
private let account: Account
private let strings: PresentationStrings
@ -447,7 +21,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
private let buttonNode: HighlightableButtonNode
private let arrowNode: InstantPageDetailsArrowNode
let separatorNode: ASDisplayNode
let contentNode: InstantPageDetailsContentNode
let contentNode: InstantPageContentNode
private let updateExpanded: (Bool) -> Void
var expanded: Bool
@ -485,7 +59,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
self.separatorNode = ASDisplayNode()
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, openPeer: openPeer, openUrl: openUrl)
self.contentNode = InstantPageContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, openPeer: openPeer, openUrl: openUrl)
super.init()

View File

@ -23,7 +23,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private var currentSize: CGSize?
private var fetchStatus: MediaResourceStatus?
private var fetchedDisposable = MetaDisposable()
private var statusDisposable = MetaDisposable()
private var themeUpdated: Bool = false
@ -47,15 +49,26 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.addSubnode(self.imageNode)
if let image = media.media as? TelegramMediaImage {
if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference))
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start())
self.statusDisposable.set((account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in
displayLinkDispatcher.dispatch {
if let strongSelf = self {
strongSelf.fetchStatus = status
strongSelf.updateFetchStatus()
}
}
}))
if media.url != nil {
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
self.addSubnode(self.linkIconNode)
}
self.addSubnode(self.statusNode)
} else if let file = media.media as? TelegramMediaFile {
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
if file.mimeType.hasPrefix("image/") {
@ -68,7 +81,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.addSubnode(self.pinNode)
var zoom: Int32 = 12
var dimensions = CGSize(width: 200, height: 100)
var dimensions = CGSize(width: 200.0, height: 100.0)
for attribute in self.attributes {
if let mapAttribute = attribute as? InstantPageMapAttribute {
zoom = mapAttribute.zoom
@ -83,12 +96,13 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference))
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start())
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
self.addSubnode(statusNode)
self.addSubnode(self.statusNode)
}
}
deinit {
self.fetchedDisposable.dispose()
self.statusDisposable.dispose()
}
override func didLoad() {
@ -115,6 +129,27 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
}
}
private func updateFetchStatus() {
var state: RadialStatusNodeState = .none
if let fetchStatus = self.fetchStatus {
switch fetchStatus {
case let .Fetching(isActive, progress):
var adjustedProgress = progress
if isActive {
adjustedProgress = max(adjustedProgress, 0.027)
}
state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: false)
default:
break
}
}
self.statusNode.transitionToState(state, completion: { [weak statusNode] in
if state == .none {
statusNode?.removeFromSupernode()
}
})
}
override func layout() {
super.layout()
@ -126,12 +161,15 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
let radialStatusSize: CGFloat = 50.0
self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize)
if let image = self.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
let imageSize = largest.dimensions.aspectFilled(size)
let boundingSize = size
let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0
let makeLayout = self.imageNode.asyncLayout()
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.pageBackgroundColor))
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.panelBackgroundColor))
apply()
self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0)
@ -168,9 +206,6 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let makeLayout = self.imageNode.asyncLayout()
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.pageBackgroundColor))
apply()
let radialStatusSize: CGFloat = 50.0
self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize)
}
}
}

View File

@ -32,7 +32,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPagePeerReferenceNode {
return self.initialPeer.id == node.initialPeer.id && self.safeInset == node.safeInset
return self.initialPeer.id == node.peer?.id && self.safeInset == node.safeInset
} else {
return false
}

View File

@ -43,7 +43,6 @@ private enum JoinState: Equatable {
final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private let account: Account
let initialPeer: Peer
let safeInset: CGFloat
private let transparent: Bool
private let rtl: Bool
@ -58,18 +57,17 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private let activityIndicator: ActivityIndicator
private let checkNode: ASImageNode
private var peer: Peer?
var peer: Peer?
private var peerDisposable: Disposable?
private let joinDisposable = MetaDisposable()
private var joinState: JoinState = .none
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (PeerId) -> Void) {
self.account = account
self.strings = strings
self.theme = theme
self.initialPeer = initialPeer
self.peer = initialPeer
self.safeInset = safeInset
self.transparent = transparent
self.rtl = rtl
@ -140,7 +138,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
self.joinNode.addTarget(self, action: #selector(self.joinPressed), forControlEvents: .touchUpInside)
let account = self.account
let signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: self.initialPeer)
let signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: initialPeer)
|> mapToSignal({ peer -> Signal<Peer, NoError> in
if let peer = peer as? TelegramChannel, let username = peer.username, peer.accessHash == nil {
return .single(peer) |> then(resolvePeerByName(account: account, name: username)
@ -160,8 +158,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
self.peerDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self {
let textColor = strongSelf.transparent ? UIColor.white : strongSelf.theme.panelPrimaryColor
strongSelf.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: textColor)
strongSelf.peer = peer
if let peer = peer as? TelegramChannel {
var joinState = strongSelf.joinState
if case .member = peer.participationStatus {
@ -178,6 +175,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
}
strongSelf.updateJoinState(joinState)
}
strongSelf.applyThemeAndStrings(themeUpdated: false)
strongSelf.setNeedsLayout()
}
})
@ -214,6 +212,11 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
let secondaryColor = self.transparent ? UIColor.white : self.theme.panelSecondaryColor
self.checkNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/PanelCheck"), color: secondaryColor)
self.activityIndicator.type = .custom(self.theme.panelAccentColor, 22.0, 2.0, false)
if !self.transparent {
self.backgroundColor = self.theme.panelBackgroundColor
self.highlightedBackgroundNode.backgroundColor = self.theme.panelHighlightedBackgroundColor
}
}
self.setNeedsLayout()
}
@ -289,13 +292,15 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
}
@objc func buttonPressed() {
self.openPeer(self.initialPeer.id)
if let peer = self.peer {
self.openPeer(peer.id)
}
}
@objc func joinPressed() {
if case .notJoined = self.joinState {
if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress)
self.joinDisposable.set((joinChannel(account: self.account, peerId: self.initialPeer.id) |> deliverOnMainQueue).start(error: { [weak self] _ in
self.joinDisposable.set((joinChannel(account: self.account, peerId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self {
if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined)

View File

@ -13,11 +13,21 @@ final class InstantPageReferenceController: ViewController {
private var animatedIn = false
private let account: Account
private let theme: InstantPageTheme
private let webPage: TelegramMediaWebpage
private let item: InstantPageTextItem
private let openUrl: (InstantPageUrlItem) -> Void
private let openUrlIn: (InstantPageUrlItem) -> Void
private let present: (ViewController, Any?) -> Void
init(account: Account, item: InstantPageTextItem) {
init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, item: InstantPageTextItem, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.theme = theme
self.webPage = webPage
self.item = item
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.present = present
super.init(navigationBarPresentationData: nil)
@ -29,7 +39,7 @@ final class InstantPageReferenceController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = InstantPageReferenceControllerNode(account: self.account, item: self.item)
self.displayNode = InstantPageReferenceControllerNode(account: self.account, theme: self.theme, webPage: self.webPage, item: self.item, openUrl: self.openUrl, openUrlIn: self.openUrlIn, present: self.present)
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}

View File

@ -2,28 +2,44 @@ import Foundation
import Display
import AsyncDisplayKit
import TelegramCore
import SafariServices
class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let account: Account
private let theme: InstantPageTheme
private var presentationData: PresentationData
private let webPage: TelegramMediaWebpage
private let item: InstantPageTextItem
private var containerLayout: (ContainerViewLayout, CGFloat)?
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
private let contentContainerNode: ASDisplayNode
private let contentBackgroundNode: ASImageNode
private var contentNode: InstantPageContentNode?
private let titleNode: ASTextNode
private let separatorNode: ASDisplayNode
private let closeButton: HighlightableButtonNode
private var linkHighlightingNode: LinkHighlightingNode?
private var textSelectionNode: LinkHighlightingNode?
private var containerLayout: (ContainerViewLayout, CGFloat)?
private var openUrl: (InstantPageUrlItem) -> Void
private var openUrlIn: (InstantPageUrlItem) -> Void
private var present: (ViewController, Any?) -> Void
var dismiss: (() -> Void)?
var close: (() -> Void)?
init(account: Account, item: InstantPageTextItem) {
init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, item: InstantPageTextItem, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.theme = theme
self.webPage = webPage
self.item = item
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.present = present
self.wrappingScrollNode = ASScrollNode()
self.wrappingScrollNode.view.alwaysBounceVertical = true
@ -33,7 +49,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.theme.overlayPanelColor)
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.isOpaque = false
@ -45,10 +61,10 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
self.contentBackgroundNode.image = roundedBackground
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ShareMenu_ShareTo, font: Font.medium(20.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InstantPage_Reference, font: Font.medium(17.0), textColor: self.theme.panelSecondaryColor)
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemSeparatorColor
self.separatorNode.backgroundColor = self.theme.controlColor
self.closeButton = HighlightableButtonNode()
@ -65,6 +81,9 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
self.wrappingScrollNode.addSubnode(self.contentBackgroundNode)
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.separatorNode)
}
override func didLoad() {
@ -73,6 +92,21 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
if #available(iOSApplicationExtension 11.0, *) {
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
}
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
recognizer.delaysTouchesBegan = false
recognizer.tapActionAtPoint = { [weak self] point in
if let strongSelf = self, let contentNode = strongSelf.contentNode {
return strongSelf.tapActionAtPoint(point.offsetBy(dx: -contentNode.frame.minX, dy: -contentNode.frame.minY))
}
return .waitForSingleTap
}
recognizer.highlight = { [weak self] point in
if let strongSelf = self, let contentNode = strongSelf.contentNode {
strongSelf.updateTouchesAtPoint(point?.offsetBy(dx: -contentNode.frame.minX, dy: -contentNode.frame.minY))
}
}
self.contentContainerNode.view.addGestureRecognizer(recognizer)
}
@objc func closeButtonPressed() {
@ -139,37 +173,47 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar, .input])
let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
if insets.bottom > 0 {
bottomInset -= 12.0
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleAreaHeight: CGFloat = 54.0
var contentHeight = titleAreaHeight + bottomInset
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
if self.contentNode == nil || self.contentNode?.frame.width != width {
self.contentNode?.removeFromSupernode()
let sideInset: CGFloat = 16.0
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: 0.0, y: sideInset))
let contentNode = InstantPageContentNode(account: self.account, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: contentSize, inOverlayPanel: true, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in })
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: sideInset, y: titleAreaHeight), size: contentSize))
self.contentContainerNode.insertSubnode(contentNode, at: 0)
self.contentNode = contentNode
contentHeight += contentSize.height + sideInset
contentNode.updateVisibleItems(visibleBounds: contentNode.bounds, animated: false)
}
let titleAreaHeight: CGFloat = 54.0
let buttonHeight: CGFloat = 57.0
let sectionSpacing: CGFloat = 8.0
let maximumContentHeight = layout.size.height - insets.top - max(bottomInset + buttonHeight, insets.bottom) - sectionSpacing
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
let sideInset = floor((layout.size.width - width) / 2.0)
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
let contentFrame = contentContainerFrame
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
let contentFrame = contentContainerFrame.insetBy(dx: 0.0, dy: 0.0)
self.containerLayout = (layout, navigationBarHeight)
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.size.width, height: contentFrame.size.height + 2000.0))
if backgroundFrame.minY < contentFrame.minY {
backgroundFrame.origin.y = contentFrame.minY
}
transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame)
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleAreaHeight))
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: 15.0), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: 17.0, y: 17.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
//transition.updateFrame(node: self.closeButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight)))
@ -177,4 +221,205 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
if let contentNode = self.contentNode {
for item in contentNode.currentLayout.items {
let frame = contentNode.effectiveFrameForItem(item)
if frame.contains(point) {
if item is InstantPagePeerReferenceItem {
return .fail
} else if item is InstantPageAudioItem {
return .fail
} else if item is InstantPageArticleItem {
return .fail
} else if item is InstantPageFeedbackItem {
return .fail
}
if !(item is InstantPageImageItem || item is InstantPagePlayableVideoItem) {
break
}
}
}
}
return .waitForSingleTap
}
private func updateTouchesAtPoint(_ location: CGPoint?) {
var rects: [CGRect]?
if let contentNode = self.contentNode, let location = location {
for item in contentNode.currentLayout.items {
let itemFrame = contentNode.effectiveFrameForItem(item)
if itemFrame.contains(location) {
var contentOffset = CGPoint()
if let item = item as? InstantPageScrollableItem {
contentOffset = contentNode.scrollableContentOffset(item: item)
}
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY))
for i in 0 ..< itemRects.count {
itemRects[i] = itemRects[i].offsetBy(dx: itemFrame.minX - contentOffset.x + contentNode.frame.minX, dy: itemFrame.minY + contentNode.frame.minY).insetBy(dx: -2.0, dy: -2.0)
}
if !itemRects.isEmpty {
rects = itemRects
break
}
}
}
}
if let rects = rects {
let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: self.theme.linkHighlightColor)
linkHighlightingNode.isUserInteractionEnabled = false
self.linkHighlightingNode = linkHighlightingNode
self.contentContainerNode.addSubnode(linkHighlightingNode)
}
linkHighlightingNode.frame = CGRect(origin: CGPoint(), size: self.contentContainerNode.bounds.size)
linkHighlightingNode.updateRects(rects)
} else if let linkHighlightingNode = self.linkHighlightingNode {
self.linkHighlightingNode = nil
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
linkHighlightingNode?.removeFromSupernode()
})
}
}
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if let contentNode = self.contentNode {
for item in contentNode.currentLayout.items {
let itemFrame = contentNode.effectiveFrameForItem(item)
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX + contentNode.frame.minX, y: itemFrame.minY - item.frame.minY + contentNode.frame.minY))
} else if let item = item as? InstantPageScrollableItem {
let contentOffset = contentNode.scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x + contentNode.frame.minX, dy: parentOffset.y + contentNode.frame.minY))
}
}
}
}
}
return nil
}
private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? {
if let contentNode = self.contentNode, let (item, parentOffset) = self.textItemAtLocation(location) {
return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX - parentOffset.x + contentNode.frame.minX, dy: -item.frame.minY - parentOffset.y + contentNode.frame.minY))
}
return nil
}
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
guard let contentNode = self.contentNode else {
return
}
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
let location = location.offsetBy(dx: -contentNode.frame.minX, dy: -contentNode.frame.minY)
switch gesture {
case .tap:
if let url = self.urlForTapLocation(location) {
self.close?()
self.openUrl(url)
}
case .longTap:
if let url = self.urlForTapLocation(location) {
let canOpenIn = availableOpenInOptions(applicationContext: self.account.telegramApplicationContext, item: .url(url: url.url)).count > 1
let openText = canOpenIn ? self.presentationData.strings.Conversation_FileOpenIn : self.presentationData.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(instantPageTheme: self.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: url.url),
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.close?()
if canOpenIn {
strongSelf.openUrlIn(url)
} else {
strongSelf.openUrl(url)
}
}
}),
ActionSheetButtonItem(title: self.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
UIPasteboard.general.string = url.url
}),
ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let link = URL(string: url.url) {
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
}
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
self.present(actionSheet, nil)
} else if let (item, parentOffset) = self.textItemAtLocation(location) {
let textFrame = item.frame
var itemRects = item.lineRects()
for i in 0 ..< itemRects.count {
itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textFrame.minX, dy: parentOffset.y + textFrame.minY).insetBy(dx: -2.0, dy: -2.0)
}
self.updateTextSelectionRects(itemRects, text: item.plainText())
}
default:
break
}
}
default:
break
}
}
private func updateTextSelectionRects(_ rects: [CGRect], text: String?) {
if let text = text, !rects.isEmpty {
let textSelectionNode: LinkHighlightingNode
if let current = self.textSelectionNode {
textSelectionNode = current
} else {
textSelectionNode = LinkHighlightingNode(color: UIColor.lightGray.withAlphaComponent(0.4))
textSelectionNode.isUserInteractionEnabled = false
self.textSelectionNode = textSelectionNode
self.contentContainerNode.addSubnode(textSelectionNode)
}
textSelectionNode.frame = CGRect(origin: CGPoint(), size: self.contentContainerNode.bounds.size)
textSelectionNode.updateRects(rects)
var coveringRect = rects[0]
for i in 1 ..< rects.count {
coveringRect = coveringRect.union(rects[i])
}
let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(self.presentationData.strings.Conversation_ContextMenuCopy), action: {
UIPasteboard.general.string = text
}), ContextMenuAction(content: .text(self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in
if let strongSelf = self, case let .Loaded(content) = strongSelf.webPage.content {
strongSelf.present(ShareController(account: strongSelf.account, subject: .quote(text: text, url: content.url)), nil)
}
})])
controller.dismissed = { [weak self] in
self?.updateTextSelectionRects([], text: nil)
}
self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (strongSelf.contentContainerNode, coveringRect.insetBy(dx: -3.0, dy: -3.0), strongSelf, strongSelf.bounds)
} else {
return nil
}
}))
textSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
} else if let textSelectionNode = self.textSelectionNode {
self.textSelectionNode = nil
textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in
textSelectionNode?.removeFromSupernode()
})
}
}
}

View File

@ -0,0 +1,75 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
final class InstantPageStoredDetailsState: PostboxCoding {
let index: Int32
let expanded: Bool
let details: [InstantPageStoredDetailsState]
init(index: Int32, expanded: Bool, details: [InstantPageStoredDetailsState]) {
self.index = index
self.expanded = expanded
self.details = details
}
init(decoder: PostboxDecoder) {
self.index = decoder.decodeInt32ForKey("index", orElse: 0)
self.expanded = decoder.decodeBoolForKey("expanded", orElse: false)
self.details = decoder.decodeObjectArrayForKey("details")
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.index, forKey: "index")
encoder.encodeBool(self.expanded, forKey: "expanded")
encoder.encodeObjectArray(self.details, forKey: "details")
}
}
final class InstantPageStoredState: PostboxCoding {
let contentOffset: Double
let details: [InstantPageStoredDetailsState]
init(contentOffset: Double, details: [InstantPageStoredDetailsState]) {
self.contentOffset = contentOffset
self.details = details
}
init(decoder: PostboxDecoder) {
self.contentOffset = decoder.decodeDoubleForKey("offset", orElse: 0.0)
self.details = decoder.decodeObjectArrayForKey("details")
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeDouble(self.contentOffset, forKey: "offset")
encoder.encodeObjectArray(self.details, forKey: "details")
}
}
func instantPageStoredState(postbox: Postbox, webPage: TelegramMediaWebpage) -> Signal<InstantPageStoredState?, NoError> {
return postbox.transaction { transaction -> InstantPageStoredState? in
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: webPage.webpageId.id)
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, key: key)) as? InstantPageStoredState {
return entry
} else {
return nil
}
}
}
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
func updateInstantPageStoredStateInteractively(postbox: Postbox, webPage: TelegramMediaWebpage, state: InstantPageStoredState?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: webPage.webpageId.id)
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, key: key)
if let state = state {
transaction.putItemCacheEntry(id: id, entry: state, collectionSpec: collectionSpec)
} else {
transaction.removeItemCacheEntry(id: id)
}
}
}

View File

@ -711,13 +711,13 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
}
var height: CGFloat = 0.0
if !lines.isEmpty && !(string.length == 1 && hasAnchors) {
if !lines.isEmpty && !(string.string == "\u{200b}" && hasAnchors) {
height = lines.last!.frame.maxY + extraDescent
}
var textWidth = boundingWidth
var requiresScroll = false
if maxLineWidth > boundingWidth + 10.0 {
if !imageItems.isEmpty && maxLineWidth > boundingWidth + 10.0 {
textWidth = maxLineWidth
requiresScroll = true
}

View File

@ -1,5 +1,6 @@
import Foundation
import Postbox
import Display
enum InstantPageFontStyle {
case sans
@ -80,6 +81,7 @@ struct InstantPageTextCategories {
}
final class InstantPageTheme {
let type: InstantPageThemeType
let pageBackgroundColor: UIColor
let textCategories: InstantPageTextCategories
@ -100,12 +102,14 @@ final class InstantPageTheme {
let tableBorderColor: UIColor
let tableHeaderColor: UIColor
let controlColor: UIColor
let imageTintColor: UIColor?
init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?) {
let overlayPanelColor: UIColor
init(type: InstantPageThemeType, pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?, overlayPanelColor: UIColor) {
self.type = type
self.pageBackgroundColor = pageBackgroundColor
self.textCategories = textCategories
self.serif = serif
@ -123,14 +127,16 @@ final class InstantPageTheme {
self.tableHeaderColor = tableHeaderColor
self.controlColor = controlColor
self.imageTintColor = imageTintColor
self.overlayPanelColor = overlayPanelColor
}
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme {
return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageTintColor: imageTintColor)
return InstantPageTheme(type: type, pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageTintColor: imageTintColor, overlayPanelColor: overlayPanelColor)
}
}
private let lightTheme = InstantPageTheme(
type: .light,
pageBackgroundColor: .white,
textCategories: InstantPageTextCategories(
kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: .black),
@ -156,10 +162,12 @@ private let lightTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0xe2e2e2),
tableHeaderColor: UIColor(rgb: 0xf4f4f4),
controlColor: UIColor(rgb: 0xc7c7cd),
imageTintColor: nil
imageTintColor: nil,
overlayPanelColor: .white
)
private let sepiaTheme = InstantPageTheme(
type: .sepia,
pageBackgroundColor: UIColor(rgb: 0xf8f1e2),
textCategories: InstantPageTextCategories(
kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0x4f321d)),
@ -185,10 +193,12 @@ private let sepiaTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0xddd1b8),
tableHeaderColor: UIColor(rgb: 0xf0e7d4),
controlColor: UIColor(rgb: 0xddd1b8),
imageTintColor: nil
imageTintColor: nil,
overlayPanelColor: UIColor(rgb: 0xf8f1e2)
)
private let grayTheme = InstantPageTheme(
type: .gray,
pageBackgroundColor: UIColor(rgb: 0x5a5a5c),
textCategories: InstantPageTextCategories(
kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xcecece)),
@ -214,10 +224,12 @@ private let grayTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x484848),
tableHeaderColor: UIColor(rgb: 0x555556),
controlColor: UIColor(rgb: 0x484848),
imageTintColor: UIColor(rgb: 0xcecece)
imageTintColor: UIColor(rgb: 0xcecece),
overlayPanelColor: UIColor(rgb: 0x5a5a5c)
)
private let darkTheme = InstantPageTheme(
type: .dark,
pageBackgroundColor: UIColor(rgb: 0x000000),
textCategories: InstantPageTextCategories(
kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xb0b0b0)),
@ -243,7 +255,8 @@ private let darkTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x303030),
tableHeaderColor: UIColor(rgb: 0x131313),
controlColor: UIColor(rgb: 0x303030),
imageTintColor: UIColor(rgb: 0xb0b0b0)
imageTintColor: UIColor(rgb: 0xb0b0b0),
overlayPanelColor: UIColor(rgb: 0x232323)
)
private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat {
@ -307,3 +320,15 @@ func instantPageThemeForType(_ type: InstantPageThemeType, settings: InstantPage
return darkTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif)
}
}
extension ActionSheetControllerTheme {
convenience init(instantPageTheme: InstantPageTheme) {
self.init(dimColor: UIColor(white: 0.0, alpha: 0.4), backgroundType: instantPageTheme.type != .dark ? .light : .dark, itemBackgroundColor: instantPageTheme.overlayPanelColor, itemHighlightedBackgroundColor: instantPageTheme.panelHighlightedBackgroundColor, standardActionTextColor: instantPageTheme.panelAccentColor, destructiveActionTextColor: instantPageTheme.panelAccentColor, disabledActionTextColor: instantPageTheme.panelAccentColor, primaryTextColor: instantPageTheme.textCategories.paragraph.color, secondaryTextColor: instantPageTheme.textCategories.caption.color, controlAccentColor: instantPageTheme.panelAccentColor)
}
}
extension ActionSheetController {
convenience init(instantPageTheme: InstantPageTheme) {
self.init(theme: ActionSheetControllerTheme(instantPageTheme: instantPageTheme))
}
}

View File

@ -0,0 +1,11 @@
import Foundation
import TelegramCore
import Postbox
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case instantPageStoredState = 0
}
public struct ApplicationSpecificItemCacheCollectionId {
public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue)
}

View File

@ -9,21 +9,22 @@ public enum NavigateToChatKeepStack {
case never
}
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, animated: Bool = true, completion: @escaping () -> Void = {}) {
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, completion: @escaping () -> Void = {}) {
var found = false
var isFirst = true
for controller in navigationController.viewControllers.reversed() {
if let controller = controller as? ChatController, controller.chatLocation == chatLocation {
if let messageId = messageId {
controller.purposefulAction = purposefulAction
controller.navigateToMessage(messageLocation: .id(messageId), animated: isFirst, completion: { [weak navigationController, weak controller] in
if let navigationController = navigationController, let controller = controller {
let _ = navigationController.popToViewController(controller, animated: animated)
}
})
} else {
let _ = navigationController.popToViewController(controller, animated: animated)
} else if scrollToEndIfExists && isFirst {
controller.scrollToEndOfHistory()
}
controller.purposefulAction = purposefulAction
let _ = navigationController.popToViewController(controller, animated: animated)
completion()
found = true
break

View File

@ -75,6 +75,33 @@ final class ApplicationSpecificCounterNotice: NoticeEntry {
}
}
final class ApplicationSpecificTimestampNotice: NoticeEntry {
let value: Int32
init(value: Int32) {
self.value = value
}
init(decoder: PostboxDecoder) {
self.value = decoder.decodeInt32ForKey("v", orElse: 0)
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.value, forKey: "v")
}
func isEqual(to: NoticeEntry) -> Bool {
if let to = to as? ApplicationSpecificTimestampNotice {
if self.value != to.value {
return false
}
return true
} else {
return false
}
}
}
private func noticeNamespace(namespace: Int32) -> ValueBoxKey {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: namespace)
@ -106,6 +133,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
private struct ApplicationSpecificNoticeKeys {
private static let botPaymentLiabilityNamespace: Int32 = 1
private static let globalNamespace: Int32 = 2
private static let permissionsNamespace: Int32 = 3
static func botPaymentLiabilityNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0))

View File

@ -572,16 +572,16 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
private func notificationsAndSoundsEntries(authorizationStatus: AccessType, globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsAndSoundsEntry] {
var entries: [NotificationsAndSoundsEntry] = []
switch authorizationStatus {
case .denied:
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings"))
case .notDetermined:
entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON"))
default:
break
}
// switch authorizationStatus {
// case .denied:
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
// entries.append(.permissionEnable(presentationData.theme, "Turn ON in Settings"))
// case .notDetermined:
// entries.append(.permissionInfo(presentationData.theme, presentationData.strings))
// entries.append(.permissionEnable(presentationData.theme, "Turn Notifications ON"))
// default:
// break
// }
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))

View File

@ -0,0 +1,96 @@
import Foundation
import Display
import AsyncDisplayKit
final class PermissionContentNode: ASDisplayNode {
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let actionButton: SolidRoundedButtonNode
private let privacyPolicyButton: HighlightableButtonNode
var kind: PermissionStateKind
private var title: String
var buttonAction: (() -> Void)?
var openPrivacyPolicy: (() -> Void)?
init(theme: PresentationTheme, strings: PresentationStrings, kind: PermissionStateKind, icon: UIImage?, title: String, text: String, buttonTitle: String, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
self.kind = kind
self.buttonAction = buttonAction
self.openPrivacyPolicy = openPrivacyPolicy
self.title = title
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.textNode = ImmediateTextNode()
self.textNode.textAlignment = .center
self.textNode.maximumNumberOfLines = 0
self.textNode.displaysAsynchronously = false
self.actionButton = SolidRoundedButtonNode(theme: theme, height: 48.0, cornerRadius: 9.0)
self.privacyPolicyButton = HighlightableButtonNode()
//self.privacyPolicyButton.setTitle(strings.Permissions_PrivacyPolicy, with: Font.regular(16.0), with: theme.list.itemAccentColor, for: .normal)
super.init()
self.iconNode.image = icon
self.title = title
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor)
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
self.actionButton.title = buttonTitle
self.privacyPolicyButton.isHidden = openPrivacyPolicy != nil
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.actionButton)
self.addSubnode(self.privacyPolicyButton)
self.actionButton.pressed = { [weak self] in
self?.buttonAction?()
}
self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
}
@objc func privacyPolicyPressed() {
self.openPrivacyPolicy?()
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
let sidePadding: CGFloat = 20.0
//let sideButtonInset: CGFloat = 16.0
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude))
let buttonHeight = self.actionButton.updateLayout(width: size.width, transition: transition)
let titleSubtitleSpacing: CGFloat = 12.0
let textHeight = titleSize.height + titleSubtitleSpacing + textSize.height
let minContentHeight = textHeight
let contentHeight = min(215.0, max(size.height - insets.top - insets.bottom - 40.0, minContentHeight))
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentOrigin), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSubtitleSpacing), size: textSize))
transition.updateFrame(node: self.actionButton, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: buttonHeight))
}
}

View File

@ -5,46 +5,89 @@ import SwiftSignalKit
import TelegramCore
final class PermissionController : ViewController {
private let account: Account
private var controllerNode: PermissionControllerNode {
return self.displayNode as! PermissionControllerNode
}
private let account: Account
private let strings: PresentationStrings
private let theme: AuthorizationTheme
private var _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private var animatedIn = false
private var didPlayPresentationAnimation = false
private var allow: (() -> Void)?
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
init(account: Account) {
self.account = account
self.strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
self.theme = defaultLightAuthorizationTheme
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: nil)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.statusBar.statusBarStyle = self.theme.statusBarStyle
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
}
})
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation {
self.didPlayPresentationAnimation = true
if case .modalSheet = presentationArguments.presentationAnimation {
self.controllerNode.animateIn()
}
}
}
override func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
self.view.endEditing(true)
}
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.title = self.presentationData.strings.Settings_AppLanguage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
//self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
self.controllerNode.updatePresentationData(self.presentationData)
}
override func loadDisplayNode() {
self.displayNode = PermissionControllerNode(theme: self.theme, strings: self.strings)
self.displayNode = PermissionControllerNode(account: self.account)
self.displayNodeDidLoad()
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.controllerNode.allow = { [weak self] in
self?.allow?()
}
self.controllerNode.next = { [weak self] in
self?.dismiss(completion: nil)
//self?.allow?()
}
self.controllerNode.openPrivacyPolicy = { [weak self] in
if let strongSelf = self {
@ -53,27 +96,13 @@ final class PermissionController : ViewController {
}
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
}
}
override func dismiss(completion: (() -> Void)?) {
self.controllerNode.animateOut(completion: completion)
}
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType, allow: @escaping () -> Void) {
self.allow = allow
self.controllerNode.updateData(subject: .notifications, currentStatus: currentStatus)
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
@objc private func nextPressed() {
//self.controllerNode.activateNextAction()
}
}

View File

@ -4,58 +4,88 @@ import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
final class PermissionControllerNode: ASDisplayNode {
private let theme: AuthorizationTheme
private let strings: PresentationStrings
enum PermissionStateKind: Int32 {
case contacts
case notifications
case siri
case cellularData
}
private enum PermissionRequestStatus {
case requestable
case denied
}
private enum PermissionNotificationsRequestStatus {
case requestable
case denied
case unreachable
}
private enum PermissionState: Equatable {
case contacts(status: PermissionRequestStatus)
case notifications(status: PermissionNotificationsRequestStatus)
case siri(status: PermissionRequestStatus)
case cellularData
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let buttonNode: SolidRoundedButtonNode
private let privacyPolicyNode: HighlightableButtonNode
private let nextNode: HighlightableButtonNode
private var layoutArguments: (ContainerViewLayout, CGFloat)?
private var title: String?
var allow: (() -> Void)?
var next: (() -> Void)? {
didSet {
self.nextNode.isHidden = self.next == nil
var kind: PermissionStateKind {
switch self {
case .contacts:
return .contacts
case .notifications:
return .notifications
case .siri:
return .siri
case .cellularData:
return .cellularData
}
}
}
private struct PermissionControllerDataState: Equatable {
var state: PermissionState?
}
private struct PermissionControllerLayoutState: Equatable {
let layout: ContainerViewLayout
let navigationHeight: CGFloat
}
private struct PermissionControllerInnerState: Equatable {
var layout: PermissionControllerLayoutState?
var data: PermissionControllerDataState
}
private struct PermissionControllerState: Equatable {
var layout: PermissionControllerLayoutState
var data: PermissionControllerDataState
}
extension PermissionControllerState {
init?(_ state: PermissionControllerInnerState) {
guard let layout = state.layout else {
return nil
}
self.init(layout: layout, data: state.data)
}
}
final class PermissionControllerNode: ASDisplayNode {
private let account: Account
private var presentationData: PresentationData
private var innerState: PermissionControllerInnerState
private var contentNode: PermissionContentNode?
var allow: (() -> Void)?
var openPrivacyPolicy: (() -> Void)?
var dismiss: (() -> Void)?
init(theme: AuthorizationTheme, strings: PresentationStrings) {
self.theme = theme
self.strings = strings
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.textNode = ImmediateTextNode()
self.textNode.textAlignment = .center
self.textNode.maximumNumberOfLines = 0
self.textNode.displaysAsynchronously = false
self.buttonNode = SolidRoundedButtonNode(theme: self.theme, height: 48.0, cornerRadius: 9.0)
self.privacyPolicyNode = HighlightableButtonNode()
self.privacyPolicyNode.setTitle("Privacy Policy", with: Font.regular(16.0), with: self.theme.accentColor, for: .normal)
self.nextNode = HighlightableButtonNode()
self.nextNode.setTitle("Skip", with: Font.regular(17.0), with: self.theme.accentColor, for: .normal)
self.nextNode.isHidden = true
init(account: Account) {
self.account = account
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.innerState = PermissionControllerInnerState(layout: nil, data: PermissionControllerDataState(state: nil))
super.init()
@ -63,119 +93,124 @@ final class PermissionControllerNode: ASDisplayNode {
return UITracingLayerView()
})
self.backgroundColor = self.theme.backgroundColor
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.privacyPolicyNode)
self.addSubnode(self.nextNode)
self.buttonNode.pressed = { [weak self] in
self?.allow?()
self?.dismiss?()
}
self.privacyPolicyNode.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
self.nextNode.addTarget(self, action: #selector(self.nextPressed), forControlEvents: .touchUpInside)
self.applyPresentationData()
}
func updateData(subject: DeviceAccessSubject, currentStatus: AccessType) {
var icon: UIImage?
var title = ""
var text = ""
var buttonTitle = ""
var hasPrivacyPolicy = false
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
switch subject {
case .contacts:
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
title = "Sync Your Contacts"
text = "See who's on Telegram and switch seamlessly, without having to \"add\" to add your friends."
if currentStatus == .denied {
buttonTitle = "Allow in Settings"
} else {
buttonTitle = "Allow Access"
}
hasPrivacyPolicy = true
case .notifications:
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
title = "Turn ON Notifications"
text = "Don't miss important messages from your friends and coworkers."
if currentStatus == .denied || currentStatus == .restricted {
buttonTitle = "Turn ON in Settings"
} else {
buttonTitle = "Turn Notifications ON"
}
case .cellularData:
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
title = "Turn ON Mobile Data"
text = "Don't worry, Telegram keeps network usage to a minimum. You can further control this in Settings > Data and Storage."
buttonTitle = "Turn ON in Settings"
case .siri:
title = "Turn ON Siri"
text = "Use Siri to send messages and make calls."
if currentStatus == .denied {
buttonTitle = "Turn ON in Settings"
} else {
buttonTitle = "Turn Siri ON"
}
default:
break
}
self.iconNode.image = icon
self.title = title
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.primaryColor)
let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.accentColor, additionalAttributes: [TelegramTextAttributes.URL: ""])
self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
self.buttonNode.title = buttonTitle
self.privacyPolicyNode.isHidden = !hasPrivacyPolicy
if let (layout, navigationHeight) = self.layoutArguments {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
self.applyPresentationData()
}
private func applyPresentationData() {
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor }
func animateIn(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func animateOut(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in
completion?()
})
}
private func updateState(_ f: (PermissionControllerInnerState) -> PermissionControllerInnerState, transition: ContainedViewLayoutTransition) {
let updatedState = f(self.innerState)
if updatedState != self.innerState {
self.innerState = updatedState
if let state = PermissionControllerState(updatedState) {
self.transition(state: state, transition: transition)
}
}
}
private func transition(state: PermissionControllerState, transition: ContainedViewLayoutTransition) {
// let insets = state.layout.layout.insets(options: [.statusBar])
// let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
//
// if state.data.state?.kind != self.contentNode?.kind {
// if let dataState = state.data.state {
// let icon: UIImage?
// let title: String
// let text: String
// let buttonTitle: String
// let hasPrivacyPolicy: Bool
//
// switch dataState {
// case let .contacts(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
// title = self.presentationData.strings.Permissions_ContactsTitle
// text = self.presentationData.strings.Permissions_ContactsText
// if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings
// } else {
// buttonTitle = self.presentationData.strings.Permissions_ContactsAllow
// }
// hasPrivacyPolicy = true
// case let .notifications(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
// title = self.presentationData.strings.Permissions_NotificationsTitle
// text = self.presentationData.strings.Permissions_NotificationsText
// if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings
// } else {
// buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow
// }
// hasPrivacyPolicy = false
// case let .siri(status):
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
// title = self.presentationData.strings.Permissions_SiriTitle
// text = self.presentationData.strings.Permissions_SiriText
// if status == .denied {
// buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings
// } else {
// buttonTitle = self.presentationData.strings.Permissions_SiriAllow
// }
// hasPrivacyPolicy = false
// case .cellularData:
// icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
// title = self.presentationData.strings.Permissions_CellularDataTitle
// text = self.presentationData.strings.Permissions_CellularDataText
// buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings
// hasPrivacyPolicy = false
// }
//
// let contentNode = PermissionContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind, icon: icon, title: title, text: text, buttonTitle: buttonTitle, buttonAction: {}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
// self.insertSubnode(contentNode, at: 0)
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
// contentNode.frame = contentFrame
// if let currentContentNode = self.contentNode {
// transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
// currentContentNode?.removeFromSupernode()
// })
// transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
// } else if transition.isAnimated {
// contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
// }
// self.contentNode = contentNode
// } else if let currentContentNode = self.contentNode {
// transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
// currentContentNode?.removeFromSupernode()
// })
// self.contentNode = nil
// }
// } else if let contentNode = self.contentNode {
// transition.updateFrame(node: contentNode, frame: contentFrame)
// contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
// }
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.layoutArguments = (layout, navigationBarHeight)
self.updateState({ state in
var state = state
state.layout = PermissionControllerLayoutState(layout: layout, navigationHeight: navigationBarHeight)
return state
}, transition: transition)
}
func activateNextAction() {
let insets = layout.insets(options: [.statusBar])
let fontSize: CGFloat
let sideInset: CGFloat
if layout.size.width > 330.0 {
fontSize = 22.0
sideInset = 38.0
} else {
fontSize = 18.0
sideInset = 20.0
}
let nextSize = self.nextNode.measure(layout.size)
transition.updateFrame(node: self.nextNode, frame: CGRect(x: layout.size.width - insets.right - nextSize.width - 16.0, y: insets.top + 10.0, width: nextSize.width, height: nextSize.height))
self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.semibold(fontSize), textColor: self.theme.primaryColor)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
let buttonHeight = self.buttonNode.updateLayout(width: layout.size.width, transition: transition)
var items: [AuthorizationLayoutItem] = []
if let icon = self.iconNode.image {
items.append(AuthorizationLayoutItem(node: self.iconNode, size: icon.size, spacingBefore: AuthorizationLayoutItemSpacing(weight: 122.0, maxValue: 122.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 15.0, maxValue: 15.0)))
}
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.textNode, size: textSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 5.0, maxValue: 5.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0)))
items.append(AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width, height: buttonHeight), spacingBefore: AuthorizationLayoutItemSpacing(weight: 35.0, maxValue: 35.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 50.0)))
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 20.0)), items: items, transition: transition, failIfDoesNotFit: false)
let privacyPolicySize = self.privacyPolicyNode.measure(layout.size)
transition.updateFrame(node: self.privacyPolicyNode, frame: CGRect(x: (layout.size.width - privacyPolicySize.width) / 2.0, y: self.buttonNode.frame.maxY + (layout.size.height - self.buttonNode.frame.maxY - insets.bottom - privacyPolicySize.height) / 2.0, width: privacyPolicySize.width, height: privacyPolicySize.height))
}
@objc func allowPressed() {
@ -185,21 +220,4 @@ final class PermissionControllerNode: ASDisplayNode {
@objc func privacyPolicyPressed() {
self.openPrivacyPolicy?()
}
@objc func nextPressed() {
self.next?()
}
func animateIn(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func animateOut(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.dismiss?()
}
completion?()
})
}
}

View File

@ -90,14 +90,9 @@ enum PresentationResourceKey: Int32 {
case chatBubbleMediaOverlayControlSecret
case chatLoadingIndicatorBackgroundImage
case chatServiceBubbleFillImage
case chatBubbleSecretMediaIcon
case chatBubbleSecretMediaCompactIcon
case chatFreeformContentAdditionalInfoBackgroundImage
case chatInstantVideoWithWallpaperBackgroundImage
case chatInstantVideoWithoutWallpaperBackgroundImage
@ -126,7 +121,6 @@ enum PresentationResourceKey: Int32 {
case chatInfoItemBackgroundImageWithWallpaper
case chatInfoItemBackgroundImageWithoutWallpaper
case chatEmptyItemBackgroundImage
case chatEmptyItemIconImage
case chatInputPanelCloseIconImage

View File

@ -65,8 +65,9 @@ struct PresentationResourcesChat {
})
}
static func principalGraphics(_ theme: PresentationTheme, wallpaper: Bool) -> PrincipalThemeEssentialGraphics {
let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithoutWallpaper : PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithWallpaper
static func principalGraphics(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> PrincipalThemeEssentialGraphics {
let hasWallpaper = !wallpaper.isEmpty && !wallpaper.isBuiltin
let key: PresentationResourceKey = !hasWallpaper ? PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithoutWallpaper : PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithWallpaper
return theme.object(key.rawValue, { theme in
return PrincipalThemeEssentialGraphics(theme.chat, wallpaper: wallpaper)
}) as! PrincipalThemeEssentialGraphics
@ -86,7 +87,7 @@ struct PresentationResourcesChat {
static func chatServiceVerticalLineImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatServiceVerticalLineImage.rawValue, { theme in
return generateLineImage(color: theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
return generateLineImage(color: theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText)
})
}
@ -168,16 +169,6 @@ struct PresentationResourcesChat {
})
}
static func chatServiceBubbleFillImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatServiceBubbleFillImage.rawValue, { theme in
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chat.serviceMessage.serviceMessageFillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
})
}
static func chatBubbleSecretMediaIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleSecretMediaIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SecretMediaIcon"), color: theme.chat.bubble.mediaOverlayControlForegroundColor)
@ -198,12 +189,6 @@ struct PresentationResourcesChat {
})
}
static func chatFreeformContentAdditionalInfoBackgroundImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeformContentAdditionalInfoBackgroundImage.rawValue, { theme in
return generateStretchableFilledCircleImage(radius: 4.0, color: theme.chat.serviceMessage.serviceMessageFillColor)
})
}
static func chatInstantVideoBackgroundImage(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? {
let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatInstantVideoWithoutWallpaperBackgroundImage : PresentationResourceKey.chatInstantVideoWithWallpaperBackgroundImage
return theme.image(key.rawValue, { theme in
@ -286,15 +271,9 @@ struct PresentationResourcesChat {
})
}
static func chatEmptyItemBackgroundImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatEmptyItemBackgroundImage.rawValue, { theme in
return generateStretchableFilledCircleImage(radius: 14.0, color: theme.chat.serviceMessage.serviceMessageFillColor)
})
}
static func chatEmptyItemIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatEmptyItemIconImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/Chat"), color: theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/Chat"), color: theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText)
})
}
@ -900,12 +879,6 @@ struct PresentationResourcesChat {
})
}
static func chatLoadingIndicatorBackgroundImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatLoadingIndicatorBackgroundImage.rawValue, { theme in
return generateStretchableFilledCircleImage(diameter: 30.0, color: theme.chat.serviceMessage.serviceMessageFillColor)
})
}
static func chatBubbleReplyThumbnailPlayImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleReplyThumbnailPlayImage.rawValue, { theme in
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
@ -1002,8 +975,8 @@ struct PresentationResourcesChat {
context.translateBy(x: 0.0, y: 1.0)
context.setFillColor(theme.chat.serviceMessage.serviceMessagePrimaryTextColor.cgColor)
context.setStrokeColor(theme.chat.serviceMessage.serviceMessagePrimaryTextColor.cgColor)
context.setFillColor(theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText.cgColor)
context.setStrokeColor(theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText.cgColor)
context.setLineWidth(1.32)
let _ = try? drawSvgPath(context, path: "M4.5,0.600000024 C5.88071187,0.600000024 7,1.88484952 7,3.46979169 L7,7.39687502 C7,8.9818172 5.88071187,10.2666667 4.5,10.2666667 C3.11928813,10.2666667 2,8.9818172 2,7.39687502 L2,3.46979169 C2,1.88484952 3.11928813,0.600000024 4.5,0.600000024 S ")

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import TelegramCore
public enum PresentationThemeParsingError: Error {
case generic
@ -563,28 +564,59 @@ public final class PresentationThemeChatBubble {
}
}
public final class PresentationThemeServiceMessage {
public let serviceMessageFillColor: UIColor
public let serviceMessagePrimaryTextColor: UIColor
public let serviceMessageLinkHighlightColor: UIColor
public final class PresentationThemeServiceMessageColorComponents {
public let fill: UIColor
public let primaryText: UIColor
public let linkHighlight: UIColor
public let dateFillStatic: UIColor
public let dateFillFloating: UIColor
public init(fill: UIColor, primaryText: UIColor, linkHighlight: UIColor, dateFillStatic: UIColor, dateFillFloating: UIColor) {
self.fill = fill
self.primaryText = primaryText
self.linkHighlight = linkHighlight
self.dateFillStatic = dateFillStatic
self.dateFillFloating = dateFillFloating
}
}
public final class PresentationThemeServiceMessageColor {
public let withDefaultWallpaper: PresentationThemeServiceMessageColorComponents
public let withCustomWallpaper: PresentationThemeServiceMessageColorComponents
public init(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents, withCustomWallpaper: PresentationThemeServiceMessageColorComponents) {
self.withDefaultWallpaper = withDefaultWallpaper
self.withCustomWallpaper = withCustomWallpaper
}
}
public func serviceMessageColorComponents(theme: PresentationTheme, wallpaper: TelegramWallpaper) -> PresentationThemeServiceMessageColorComponents {
return serviceMessageColorComponents(chatTheme: theme.chat, wallpaper: wallpaper)
}
public func serviceMessageColorComponents(chatTheme: PresentationThemeChat, wallpaper: TelegramWallpaper) -> PresentationThemeServiceMessageColorComponents {
if wallpaper != .builtin {
return chatTheme.serviceMessage.components.withCustomWallpaper
} else {
return chatTheme.serviceMessage.components.withDefaultWallpaper
}
}
public final class PresentationThemeServiceMessage {
public let components: PresentationThemeServiceMessageColor
public let unreadBarFillColor: UIColor
public let unreadBarStrokeColor: UIColor
public let unreadBarTextColor: UIColor
public let dateFillStaticColor: UIColor
public let dateFillFloatingColor: UIColor
public let dateTextColor: UIColor
public init(serviceMessageFillColor: UIColor, serviceMessagePrimaryTextColor: UIColor, serviceMessageLinkHighlightColor: UIColor, unreadBarFillColor: UIColor, unreadBarStrokeColor: UIColor, unreadBarTextColor: UIColor, dateFillStaticColor: UIColor, dateFillFloatingColor: UIColor, dateTextColor: UIColor) {
self.serviceMessageFillColor = serviceMessageFillColor
self.serviceMessagePrimaryTextColor = serviceMessagePrimaryTextColor
self.serviceMessageLinkHighlightColor = serviceMessageLinkHighlightColor
public init(components: PresentationThemeServiceMessageColor, unreadBarFillColor: UIColor, unreadBarStrokeColor: UIColor, unreadBarTextColor: UIColor, dateTextColor: UIColor) {
self.components = components
self.unreadBarFillColor = unreadBarFillColor
self.unreadBarStrokeColor = unreadBarStrokeColor
self.unreadBarTextColor = unreadBarTextColor
self.dateFillStaticColor = dateFillStaticColor
self.dateFillFloatingColor = dateFillFloatingColor
self.dateTextColor = dateTextColor
}
}

View File

@ -76,6 +76,11 @@ public final class PrincipalThemeEssentialGraphics {
public let checkFreeFullImage: UIImage
public let checkFreePartialImage: UIImage
public let chatServiceBubbleFillImage: UIImage
public let chatFreeformContentAdditionalInfoBackgroundImage: UIImage
public let chatEmptyItemBackgroundImage: UIImage
public let chatLoadingIndicatorBackgroundImage: UIImage
public let clockBubbleIncomingFrameImage: UIImage
public let clockBubbleIncomingMinImage: UIImage
public let clockBubbleOutgoingFrameImage: UIImage
@ -98,9 +103,9 @@ public final class PrincipalThemeEssentialGraphics {
public let radialIndicatorFileIconIncoming: UIImage
public let radialIndicatorFileIconOutgoing: UIImage
init(_ theme: PresentationThemeChat, wallpaper: Bool) {
let incoming: PresentationThemeBubbleColorComponents = !wallpaper ? theme.bubble.incoming.withoutWallpaper : theme.bubble.incoming.withWallpaper
let outgoing: PresentationThemeBubbleColorComponents = !wallpaper ? theme.bubble.outgoing.withoutWallpaper : theme.bubble.outgoing.withWallpaper
init(_ theme: PresentationThemeChat, wallpaper: TelegramWallpaper) {
let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.bubble.incoming.withoutWallpaper : theme.bubble.incoming.withWallpaper
let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.bubble.outgoing.withoutWallpaper : theme.bubble.outgoing.withWallpaper
self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none)
self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none)
@ -135,8 +140,18 @@ public final class PrincipalThemeEssentialGraphics {
self.checkMediaFullImage = generateCheckImage(partial: false, color: .white)!
self.checkMediaPartialImage = generateCheckImage(partial: true, color: .white)!
self.checkFreeFullImage = generateCheckImage(partial: false, color: theme.serviceMessage.serviceMessagePrimaryTextColor)!
self.checkFreePartialImage = generateCheckImage(partial: true, color: theme.serviceMessage.serviceMessagePrimaryTextColor)!
let serviceColor = serviceMessageColorComponents(chatTheme: theme, wallpaper: wallpaper)
self.checkFreeFullImage = generateCheckImage(partial: false, color: serviceColor.primaryText)!
self.checkFreePartialImage = generateCheckImage(partial: true, color: serviceColor.primaryText)!
self.chatServiceBubbleFillImage = generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(serviceColor.fill.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
self.chatFreeformContentAdditionalInfoBackgroundImage = generateStretchableFilledCircleImage(radius: 4.0, color: serviceColor.fill)!
self.chatEmptyItemBackgroundImage = generateStretchableFilledCircleImage(radius: 14.0, color: serviceColor.fill)!
self.chatLoadingIndicatorBackgroundImage = generateStretchableFilledCircleImage(diameter: 30.0, color: serviceColor.fill)!
self.clockBubbleIncomingFrameImage = generateClockFrameImage(color: theme.bubble.incomingPendingActivityColor)!
self.clockBubbleIncomingMinImage = generateClockMinImage(color: theme.bubble.incomingPendingActivityColor)!
@ -146,28 +161,28 @@ public final class PrincipalThemeEssentialGraphics {
self.clockMediaFrameImage = generateClockFrameImage(color: .white)!
self.clockMediaMinImage = generateClockMinImage(color: .white)!
self.clockFreeFrameImage = generateClockFrameImage(color: theme.serviceMessage.serviceMessagePrimaryTextColor)!
self.clockFreeMinImage = generateClockMinImage(color: theme.serviceMessage.serviceMessagePrimaryTextColor)!
self.clockFreeFrameImage = generateClockFrameImage(color: serviceColor.primaryText)!
self.clockFreeMinImage = generateClockMinImage(color: serviceColor.primaryText)!
self.dateAndStatusMediaBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.bubble.mediaDateAndStatusFillColor)!
self.dateAndStatusFreeBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.serviceMessage.serviceMessageFillColor)!
self.dateAndStatusFreeBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: serviceColor.primaryText)!
let impressionCountImage = UIImage(bundleImageName: "Chat/Message/ImpressionCount")!
self.incomingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.bubble.incomingSecondaryTextColor)!
self.outgoingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.bubble.outgoingSecondaryTextColor)!
self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)!
self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.serviceMessage.serviceMessagePrimaryTextColor)!
self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)!
let chatDateSize: CGFloat = 20.0
self.dateStaticBackground = generateImage(CGSize(width: chatDateSize, height: chatDateSize), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.serviceMessage.dateFillStaticColor.cgColor)
context.setFillColor(serviceColor.dateFillStatic.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: Int(chatDateSize) / 2, topCapHeight: Int(chatDateSize) / 2)
self.dateFloatingBackground = generateImage(CGSize(width: chatDateSize, height: chatDateSize), contextGenerator: { size, context -> Void in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.serviceMessage.dateFillFloatingColor.cgColor)
context.setFillColor(serviceColor.dateFillFloating.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})!.stretchableImage(withLeftCapWidth: Int(chatDateSize) / 2, topCapHeight: Int(chatDateSize) / 2)

View File

@ -720,7 +720,8 @@ public func settingsController(account: Account, accountManager: AccountManager)
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")
let controller = ItemListController(account: account, state: signal, tabBarItem: combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, notificationAuthorizationStatus.get()) |> map { presentationData, status in
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: nil)
//return ItemListControllerTabBarItem(title: presentationData.strings.Settings_Title, image: icon, selectedImage: icon, badgeValue: status != .allowed ? "!" : nil)
})
pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)

View File

@ -71,7 +71,6 @@ final class SetupTwoStepVerificationContentNode: ASDisplayNode, UITextFieldDeleg
self.inputNode.textField.textContentType = .oneTimeCode
}
case .email:
self.inputNode.textField.autocapitalizationType = .none
self.inputNode.textField.autocorrectionType = .no
self.inputNode.textField.keyboardType = .emailAddress

View File

@ -268,7 +268,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
insets.bottom += inputHeight
}
}
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
if state.data.state?.kind != self.contentNode?.kind {
if let dataState = state.data.state {
let title: String

View File

@ -5,7 +5,7 @@ import Display
private let textFont: UIFont = Font.regular(16.0)
final class SolidRoundedButtonNode: ASDisplayNode {
private var theme: AuthorizationTheme
private var theme: PresentationTheme
private let buttonBackgroundNode: ASImageNode
private let buttonNode: HighlightTrackingButtonNode
@ -25,7 +25,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
}
}
init(title: String? = nil, theme: AuthorizationTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0) {
init(title: String? = nil, theme: PresentationTheme, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0) {
self.theme = theme
self.buttonHeight = height
self.buttonCornerRadius = cornerRadius
@ -35,7 +35,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
self.buttonBackgroundNode.isLayerBacked = true
self.buttonBackgroundNode.displayWithoutProcessing = true
self.buttonBackgroundNode.displaysAsynchronously = false
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.accentColor)
self.buttonBackgroundNode.image = generateStretchableFilledCircleImage(radius: cornerRadius, color: theme.list.itemCheckColors.fillColor)
self.buttonNode = HighlightTrackingButtonNode()
@ -72,7 +72,7 @@ final class SolidRoundedButtonNode: ASDisplayNode {
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
if self.title != self.labelNode.attributedText?.string {
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: self.theme.backgroundColor)
self.labelNode.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(17.0), textColor: self.theme.list.itemCheckColors.foregroundColor)
}
let labelSize = self.labelNode.updateLayout(buttonSize)

View File

@ -116,30 +116,4 @@ public final class TelegramRootController: NavigationController {
}
presentedLegacyShortcutCamera(account: self.account, saveCapturedMedia: false, saveEditedPhotos: false, mediaGrouping: true, parentController: controller)
}
public func requestPermissions() {
guard let parentController = self.viewControllers.last as? ViewController else {
return
}
let account = self.account
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .notifications)
|> take(1)
|> deliverOnMainQueue).start(next: { status in
if status != .allowed {
let controller = PermissionController(account: self.account)
controller.updateData(subject: .notifications, currentStatus: status, allow: {
switch status {
case .notDetermined:
account.telegramApplicationContext.applicationBindings.registerForNotifications()
case .denied:
account.telegramApplicationContext.applicationBindings.openSettings()
default:
break
}
})
parentController.present(controller, in: .window(.root))
}
})
}
}